From bfc1718230a38fe02b6109abc16877057b7f86a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E9=A3=9E=E8=99=B9?= Date: Fri, 9 Oct 2026 06:15:10 +0800 Subject: [PATCH 1/9] feat(lifecycle): separate runtime and history retention Give runtime resources, conversations, message bodies, delivery evidence, audit facts, media, and traces independent configurable deadlines. Preserve existing data behind an explicit preview/apply migration and make deletion cleanup durable and observable. --- cmd/hotplex/gateway_run.go | 259 +++++- cmd/hotplex/lifecycle_migration_adapter.go | 70 ++ cmd/hotplex/messaging_init.go | 1 + cmd/hotplex/routes.go | 38 +- configs/README.md | 7 +- configs/config.yaml | 44 +- docs/assets/data-lifecycle.webp | Bin 0 -> 986372 bytes docs/explanation/session-lifecycle.md | 18 +- docs/guides/developer/session-management.md | 24 +- docs/guides/enterprise/audit.md | 79 +- docs/guides/enterprise/disaster-recovery.md | 2 + docs/guides/enterprise/resource-limits.md | 14 +- .../D01-session-history-retention.md | 53 ++ .../D02-delete-cache-dependent-semantics.md | 43 + .../D02-idempotency-fact-retention.md | 43 + .../D03-audit-facts-retention.md | 45 + ...04-execution-time-window-test-precision.md | 37 + ...ecycle-retirement-placeholder-rebinding.md | 44 + ...-excludes-backups-and-platform-messages.md | 40 + docs/reference/admin-api.md | 39 +- docs/reference/configuration.md | 55 +- docs/reference/metrics.md | 20 +- ...10-08-hotplex-data-lifecycle-luna-guide.md | 628 +++++++++++++ docs/swagger/swagger.json | 117 ++- internal/admin/admin.go | 137 +-- internal/admin/audit_service.go | 3 +- internal/admin/audit_service_test.go | 27 +- internal/admin/lifecycle_migration.go | 139 +++ internal/admin/lifecycle_migration_test.go | 135 +++ internal/admin/models.go | 23 + internal/admin/ratelimit_test.go | 5 +- internal/audit/collector.go | 18 + internal/audit/combined_store.go | 171 ++++ internal/audit/gc.go | 16 +- internal/audit/hash.go | 6 + internal/audit/hash_test.go | 27 + internal/audit/lifecycle_store_pg_test.go | 74 ++ internal/audit/lifecycle_store_test.go | 194 ++++ internal/audit/store.go | 240 +++-- internal/audit/types.go | 34 +- internal/config/config.go | 56 ++ internal/config/config_defaults.go | 35 +- internal/config/config_loader.go | 46 + internal/config/config_types.go | 98 +- internal/config/lifecycle_config_test.go | 235 +++++ internal/cron/executor.go | 10 +- internal/cron/executor_test.go | 17 + internal/effect/attempt_gc.go | 90 ++ internal/effect/attempt_gc_pg_test.go | 37 + internal/effect/attempt_gc_test.go | 95 ++ internal/effect/effect.go | 65 +- internal/effect/effect_test.go | 1 + internal/effect/lease.go | 7 +- internal/effect/operator.go | 5 +- internal/effect/payload_gc.go | 92 ++ internal/effect/payload_gc_test.go | 121 +++ internal/effect/reconcile.go | 6 +- internal/effect/store.go | 12 +- internal/eventstore/pg_store.go | 125 ++- internal/eventstore/pg_store_test.go | 134 ++- internal/eventstore/query_shared.go | 99 +- .../sql/queries/events.delete_expired.sql | 4 +- .../sql/queries/events.has_older.sql | 6 +- .../eventstore/sql/queries/events.insert.sql | 4 +- .../sql/queries/events.query_after.sql | 3 +- .../sql/queries/events.query_before.sql | 3 +- .../sql/queries/events.query_latest.sql | 3 +- .../queries/lifecycle.get_content_policy.sql | 3 + ...ecycle.update_session_content_deadline.sql | 13 + .../sql/queries/turns.delete_by_session.sql | 1 + .../sql/queries/turns.delete_expired.sql | 4 +- .../eventstore/sql/queries/turns.insert.sql | 4 +- .../sql/queries/turns.query_before.sql | 1 + .../sql/queries/turns.query_latest.sql | 1 + .../sql/queries/turns.query_with_gen.sql | 1 + .../sql/queries/turns.stats_with_gen.sql | 1 + internal/eventstore/store.go | 178 +++- internal/eventstore/store_test.go | 141 ++- internal/eventstore/turn_write.go | 1 + internal/eventstore/turns_table_test.go | 127 ++- internal/execution/lifecycle_gc.go | 89 ++ internal/execution/lifecycle_gc_pg_test.go | 36 + internal/execution/lifecycle_gc_test.go | 122 +++ internal/execution/queue_test.go | 15 + internal/execution/sql_console_test.go | 10 +- internal/execution/sql_queue.go | 48 +- internal/execution/sql_store.go | 148 ++- internal/execution/sql_store_test.go | 132 +++ internal/execution/store.go | 16 + internal/gateway/api.go | 147 +-- internal/gateway/api_test.go | 168 +++- internal/gateway/bridge.go | 78 +- internal/gateway/bridge_forward.go | 199 ++-- internal/gateway/bridge_forward_seq_test.go | 72 +- internal/gateway/bridge_worker.go | 153 ++- .../gateway/eventstore_test_helpers_test.go | 23 + internal/gateway/handler.go | 172 ++-- internal/gateway/handler_test.go | 19 +- internal/gateway/input_execution_test.go | 108 ++- internal/gateway/queue.go | 6 +- internal/gateway/queue_test.go | 20 + internal/gateway/system_input.go | 19 +- internal/gateway/tool_audit.go | 99 +- internal/gateway/tool_audit_test.go | 86 +- internal/gateway/turn_deadline_test.go | 80 ++ internal/lifecycle/migration.go | 869 ++++++++++++++++++ internal/lifecycle/migration_test.go | 341 +++++++ internal/messaging/feishu/adapter.go | 12 +- internal/messaging/feishu/adapter_test.go | 24 + internal/messaging/slack/adapter.go | 12 +- internal/messaging/slack/adapter_test.go | 18 + internal/observability/AGENTS.md | 9 +- internal/observability/instruments.go | 163 +++- internal/session/cleanup_outbox.go | 517 ++++++++++- internal/session/cleanup_outbox_test.go | 308 ++++++- internal/session/coverage_test.go | 3 +- internal/session/delete_audit_context.go | 24 + internal/session/lifecycle_metrics_test.go | 67 ++ internal/session/lifecycle_policy.go | 99 ++ internal/session/lifecycle_policy_test.go | 49 + internal/session/lifecycle_projection_test.go | 134 +++ .../session/lifecycle_retirement_pg_test.go | 186 ++++ internal/session/lifecycle_retirement_test.go | 243 +++++ internal/session/manager.go | 451 +++++++-- internal/session/manager_test.go | 106 ++- internal/session/migration_041_test.go | 56 ++ internal/session/multitenancy_store_test.go | 3 +- internal/session/pg_store.go | 86 ++ internal/session/pg_store_test.go | 134 ++- internal/session/purge.go | 86 ++ internal/session/purge_runner.go | 86 ++ internal/session/purge_store.go | 269 ++++++ internal/session/sql/migrate_pg_test.go | 28 +- internal/session/sql/migrate_test.go | 53 +- .../040_session_lifecycle.pg.sql | 35 + .../041_content_expiry.pg.sql | 14 + .../042_session_purge_jobs.pg.sql | 44 + .../043_effect_payload_retention.pg.sql | 9 + .../044_audit_lifecycle_v2.pg.sql | 70 ++ .../sql/migrations/040_session_lifecycle.sql | 35 + .../sql/migrations/041_content_expiry.sql | 14 + .../sql/migrations/042_session_purge_jobs.sql | 44 + .../043_effect_payload_retention.sql | 9 + .../sql/migrations/044_audit_lifecycle_v2.sql | 59 ++ .../sessions.advance_lifecycle_deadlines.sql | 12 + .../sql/queries/sessions.upsert_session.sql | 10 +- .../queries/store.delete_terminated_by_id.sql | 22 +- .../session/sql/queries/store.get_session.sql | 2 +- .../sql/queries/store.lifecycle_gc_status.sql | 51 + .../queries/store.lifecycle_gc_status_pg.sql | 51 + .../sql/queries/store.list_sessions.sql | 2 +- .../store.select_expired_lifecycle_ids.sql | 27 + .../queries/store.select_terminated_ids.sql | 17 +- internal/session/store.go | 159 +++- internal/session/store_test.go | 70 ++ internal/session/upgrade_fixture_test.go | 23 + internal/worker/acp/trace.go | 118 ++- internal/worker/acp/worker_phase3_test.go | 37 + webchat/app/admin/activity/page.tsx | 11 +- webchat/app/admin/page.tsx | 2 +- webchat/app/admin/sessions/detail/page.tsx | 2 +- webchat/app/admin/sessions/page.tsx | 2 +- webchat/app/components/chat/SessionPanel.tsx | 5 + webchat/lib/api/sessions.ts | 3 + webchat/lib/types/admin.ts | 1 + webchat/locales/en/admin.json | 1 + webchat/locales/en/chat.json | 1 + webchat/locales/zh-CN/admin.json | 1 + webchat/locales/zh-CN/chat.json | 1 + 169 files changed, 11340 insertions(+), 979 deletions(-) create mode 100644 cmd/hotplex/lifecycle_migration_adapter.go create mode 100644 docs/assets/data-lifecycle.webp create mode 100644 docs/issues/2026-10-data-lifecycle/D01-session-history-retention.md create mode 100644 docs/issues/2026-10-data-lifecycle/D02-delete-cache-dependent-semantics.md create mode 100644 docs/issues/2026-10-data-lifecycle/D02-idempotency-fact-retention.md create mode 100644 docs/issues/2026-10-data-lifecycle/D03-audit-facts-retention.md create mode 100644 docs/issues/2026-10-data-lifecycle/D04-execution-time-window-test-precision.md create mode 100644 docs/issues/2026-10-data-lifecycle/D05-pg-lifecycle-retirement-placeholder-rebinding.md create mode 100644 docs/issues/2026-10-data-lifecycle/D06-deletion-scope-excludes-backups-and-platform-messages.md create mode 100644 docs/superpowers/plans/2026-10-08-hotplex-data-lifecycle-luna-guide.md create mode 100644 internal/admin/lifecycle_migration.go create mode 100644 internal/admin/lifecycle_migration_test.go create mode 100644 internal/audit/combined_store.go create mode 100644 internal/audit/lifecycle_store_pg_test.go create mode 100644 internal/audit/lifecycle_store_test.go create mode 100644 internal/config/lifecycle_config_test.go create mode 100644 internal/effect/attempt_gc.go create mode 100644 internal/effect/attempt_gc_pg_test.go create mode 100644 internal/effect/attempt_gc_test.go create mode 100644 internal/effect/payload_gc.go create mode 100644 internal/effect/payload_gc_test.go create mode 100644 internal/eventstore/sql/queries/lifecycle.get_content_policy.sql create mode 100644 internal/eventstore/sql/queries/lifecycle.update_session_content_deadline.sql create mode 100644 internal/eventstore/sql/queries/turns.delete_by_session.sql create mode 100644 internal/execution/lifecycle_gc.go create mode 100644 internal/execution/lifecycle_gc_pg_test.go create mode 100644 internal/execution/lifecycle_gc_test.go create mode 100644 internal/gateway/eventstore_test_helpers_test.go create mode 100644 internal/gateway/turn_deadline_test.go create mode 100644 internal/lifecycle/migration.go create mode 100644 internal/lifecycle/migration_test.go create mode 100644 internal/session/delete_audit_context.go create mode 100644 internal/session/lifecycle_metrics_test.go create mode 100644 internal/session/lifecycle_policy.go create mode 100644 internal/session/lifecycle_policy_test.go create mode 100644 internal/session/lifecycle_projection_test.go create mode 100644 internal/session/lifecycle_retirement_pg_test.go create mode 100644 internal/session/lifecycle_retirement_test.go create mode 100644 internal/session/migration_041_test.go create mode 100644 internal/session/purge.go create mode 100644 internal/session/purge_runner.go create mode 100644 internal/session/purge_store.go create mode 100644 internal/session/sql/migrations-postgres/040_session_lifecycle.pg.sql create mode 100644 internal/session/sql/migrations-postgres/041_content_expiry.pg.sql create mode 100644 internal/session/sql/migrations-postgres/042_session_purge_jobs.pg.sql create mode 100644 internal/session/sql/migrations-postgres/043_effect_payload_retention.pg.sql create mode 100644 internal/session/sql/migrations-postgres/044_audit_lifecycle_v2.pg.sql create mode 100644 internal/session/sql/migrations/040_session_lifecycle.sql create mode 100644 internal/session/sql/migrations/041_content_expiry.sql create mode 100644 internal/session/sql/migrations/042_session_purge_jobs.sql create mode 100644 internal/session/sql/migrations/043_effect_payload_retention.sql create mode 100644 internal/session/sql/migrations/044_audit_lifecycle_v2.sql create mode 100644 internal/session/sql/queries/sessions.advance_lifecycle_deadlines.sql create mode 100644 internal/session/sql/queries/store.lifecycle_gc_status.sql create mode 100644 internal/session/sql/queries/store.lifecycle_gc_status_pg.sql create mode 100644 internal/session/sql/queries/store.select_expired_lifecycle_ids.sql diff --git a/cmd/hotplex/gateway_run.go b/cmd/hotplex/gateway_run.go index 34e5ae6d0..a76169c4b 100644 --- a/cmd/hotplex/gateway_run.go +++ b/cmd/hotplex/gateway_run.go @@ -65,6 +65,7 @@ type eventStoreProvider interface { eventstore.TurnQuerier QueryBySession(ctx context.Context, sessionID string, cursor int64, dir eventstore.CursorDirection, limit int) (*eventstore.EventPage, error) DeleteExpired(ctx context.Context, cutoff time.Time) (int64, error) + DeleteConversationBySession(ctx context.Context, sessionID string) error Close() error } @@ -276,14 +277,24 @@ func runGateway(configPath string, devMode bool, stopCh <-chan struct{}) (err er // Audit subsystem (issue #833 P1): construct Store + Collector + GC + Verifier // when audit.enabled=true. The collector batches events and fans out to sinks. - // GC prunes old rows; Verifier checks hash chain integrity. + // New writes use the lifecycle-v2 chain; the legacy chain retains its own + // retention and verification window. var auditCollector *audit.Collector var auditStore audit.Store var auditGC *audit.GC + var lifecycleAuditGC *audit.GC if cfg.Audit.Enabled { - auditStore, err = audit.NewStore(stores.sqlDB, stores.dialect, stores.writeMu, log) + legacyAuditStore, storeErr := audit.NewStore(stores.sqlDB, stores.dialect, stores.writeMu, log) + if storeErr != nil { + return fmt.Errorf("legacy audit store init: %w", storeErr) + } + lifecycleAuditStore, storeErr := audit.NewLifecycleStore(stores.sqlDB, stores.dialect, stores.writeMu, log) + if storeErr != nil { + return fmt.Errorf("lifecycle audit store init: %w", storeErr) + } + auditStore, err = audit.NewCombinedStore(stores.sqlDB, stores.dialect, legacyAuditStore, lifecycleAuditStore) if err != nil { - return fmt.Errorf("audit store init: %w", err) + return fmt.Errorf("combined audit store init: %w", err) } var auditSinks []audit.AlertSink @@ -312,27 +323,38 @@ func runGateway(configPath string, devMode bool, stopCh <-chan struct{}) (err er return fmt.Errorf("audit spill open: %w", spillErr) } - auditCollector = audit.NewCollector(auditStore, spill, auditSinks, log, audit.CollectorConfig{ + auditCollector = audit.NewCollector(lifecycleAuditStore, spill, auditSinks, log, audit.CollectorConfig{ ChannelCap: cfg.Audit.Collector.ChannelCap, BatchSize: cfg.Audit.Collector.BatchSize, BatchInterval: cfg.Audit.Collector.BatchInterval, SinkTimeout: 5 * time.Second, SpillBlockTimeout: 5 * time.Second, + FactsRetention: cfg.Lifecycle.Audit.FactsRetention, }) auditCollector.Start(ctx) - auditGC = audit.NewGC(auditStore, audit.GCConfig{ + auditGC = audit.NewGC(legacyAuditStore, audit.GCConfig{ Retention: cfg.Audit.Retention, Interval: 1 * time.Hour, }, log) - auditVerifier := audit.NewVerifier(auditStore, audit.VerifierConfig{ + lifecycleAuditGC = audit.NewGC(lifecycleAuditStore, audit.GCConfig{ + Retention: cfg.Lifecycle.Audit.FactsRetention, + Interval: cfg.Lifecycle.GC.Interval, + }, log) + legacyAuditVerifier := audit.NewVerifier(legacyAuditStore, audit.VerifierConfig{ + Interval: 1 * time.Hour, + }, log) + lifecycleAuditVerifier := audit.NewVerifier(lifecycleAuditStore, audit.VerifierConfig{ Interval: 1 * time.Hour, }, log) go auditGC.Run(ctx) - go auditVerifier.Run(ctx) + go lifecycleAuditGC.Run(ctx) + go legacyAuditVerifier.Run(ctx) + go lifecycleAuditVerifier.Run(ctx) log.Info("audit: subsystem initialized", - "retention", cfg.Audit.Retention, + "legacy_retention", cfg.Audit.Retention, + "lifecycle_facts_retention", cfg.Lifecycle.Audit.FactsRetention, "channel_cap", cfg.Audit.Collector.ChannelCap, "batch_size", cfg.Audit.Collector.BatchSize, "sinks", len(auditSinks), @@ -392,6 +414,11 @@ func runGateway(configPath string, devMode bool, stopCh <-chan struct{}) (err er } else { log.Warn("gateway: durable session cleanup unavailable for session store") } + if purgeStore, ok := stores.session.(session.PurgeItemStore); ok { + go session.NewPurgeItemRunner(log, purgeStore, stores.event.DeleteConversationBySession).Run(ctx) + } else { + log.Warn("gateway: durable conversation-content cleanup unavailable for session store") + } repaired, repairErr := sm.RepairRunningSessions(ctx) if repairErr != nil { @@ -566,7 +593,7 @@ func runGateway(configPath string, devMode bool, stopCh <-chan struct{}) (err er TurnsQuerier: stores.event, // SQLiteStore implements TurnQuerier RetryCtrl: retryCtrl, AgentConfigDir: agentConfigDir, - TurnTimeout: cfg.Worker.TurnTimeout, + TurnTimeout: config.EffectiveTurnTimeout(cfg), WorkerEnv: buildWorkerEnv(cfg), WorkerEnvBlocklist: cfg.Worker.EnvBlocklist, CronEnv: buildCronEnv(cfg), @@ -891,11 +918,33 @@ func runGateway(configPath string, devMode bool, stopCh <-chan struct{}) (err er log.Warn("Brain initialization failed (fail-open)", "err", err) } - // Events and turns retain only their configured events.retention window. - // Audit plaintext has an independent retention policy. The GC captures this - // value at startup, so changing events.retention requires a gateway restart. + // New event and turn rows carry lifecycle.content.retention deadlines. + // events.retention is the fallback for legacy rows without per-record + // deadlines. Audit plaintext has an independent retention policy. The GC + // captures the legacy cutoff at startup, so changing events.retention + // requires a gateway restart. eventsRetention := config.EffectiveEventsRetention(cfg) go runEventsGC(ctx, stores, log, eventsRetention) + if cfg.Lifecycle.Policy == config.LifecyclePolicyV2 { + effects := effectStoreFor(log, stores) + if payloadGC, ok := effects.(effect.PayloadGCStore); ok { + go runEffectPayloadGC(ctx, payloadGC, + cfg.Lifecycle.EffectPayload.RetentionAfterSettlement, + cfg.Lifecycle.GC.Interval, cfg.Lifecycle.GC.BatchSize, log) + } + if attemptGC, ok := effects.(effect.AttemptGCStore); ok { + go runEffectAttemptGC(ctx, attemptGC, + cfg.Lifecycle.Facts.RetentionAfterSettlement, + cfg.Lifecycle.GC.Interval, cfg.Lifecycle.GC.BatchSize, log) + } + if executionGC, ok := stores.execution.(execution.LifecycleFactsGCStore); ok { + go runExecutionFactGC(ctx, executionGC, + cfg.Lifecycle.Facts.RetentionAfterSettlement, + cfg.Lifecycle.GC.Interval, cfg.Lifecycle.GC.BatchSize, log) + } + go runACPTraceGC(ctx, filepath.Join(config.HotplexHome(), "logs"), + cfg.Lifecycle.Trace.Retention, cfg.Lifecycle.GC.Interval, cfg.Lifecycle.GC.BatchSize, log) + } leaseMgr.Start(ctx) log.Info("gateway: lease manager started", "owner_instance_id", ownerInstanceID) @@ -1344,7 +1393,10 @@ func initSQLiteStores(ctx context.Context, cfg *config.Config, log *slog.Logger) } // EventStore shares the session store's *sql.DB (schema managed by goose migration 002). - eventStore := eventstore.NewSQLiteStore(sessionStore.DB(), writeMu) + eventStore := eventstore.NewSQLiteStoreWithRetention(sessionStore.DB(), writeMu, eventstore.RetentionPolicy{ + Content: cfg.Lifecycle.Content.Retention, + Legacy: config.EffectiveEventsRetention(cfg), + }) executionStore, err := execution.NewSQLStore(ctx, sessionStore.DB(), dbutil.DialectSQLite, writeMu, log) if err != nil { _ = sessionStore.Close() @@ -1379,7 +1431,10 @@ func initPGStores(ctx context.Context, cfg *config.Config, log *slog.Logger) (*g return nil, fmt.Errorf("pg: session store: %w", err) } - eventStore := eventstore.NewPGStore(db, log) + eventStore := eventstore.NewPGStoreWithRetention(db, log, eventstore.RetentionPolicy{ + Content: cfg.Lifecycle.Content.Retention, + Legacy: config.EffectiveEventsRetention(cfg), + }) executionStore, err := execution.NewSQLStore(ctx, db.DB, dbutil.DialectPostgres, nil, log) if err != nil { _ = db.Close() @@ -1454,14 +1509,190 @@ func runEventsGC(ctx context.Context, stores *gatewayStores, log *slog.Logger, r cutoff := time.Now().Add(-retention) if n, err := stores.event.DeleteExpired(ctx, cutoff); err == nil && n > 0 { log.Info("events gc: deleted expired events", "count", n) + observability.RecordLifecycleGCProcessed(ctx, observability.LifecycleGCProcessedEvent, n) } if n, err := stores.turnQuerier.DeleteExpiredTurns(ctx, cutoff); err == nil && n > 0 { log.Info("events gc: deleted expired turns", "count", n) + observability.RecordLifecycleGCProcessed(ctx, observability.LifecycleGCProcessedTurn, n) } } } } +// runEffectPayloadGC blanks delivery bodies only after every effect that +// references a snapshot has reached a settled terminal state. Identity rows +// remain to prevent late retries from restoring expired content. +func runEffectPayloadGC( + ctx context.Context, + store effect.PayloadGCStore, + retention, interval time.Duration, + batchSize int, + log *slog.Logger, +) { + if store == nil || retention <= 0 { + return + } + if interval <= 0 { + interval = time.Hour + } + if batchSize <= 0 { + batchSize = 100 + } + + runOnce := func() { + cutoff := time.Now().Add(-retention) + n, err := store.DeleteExpiredPayloads(ctx, cutoff, batchSize) + if err != nil { + log.Warn("effect payload gc: cleanup failed", "err", err) + return + } + if n > 0 { + log.Info("effect payload gc: expired delivery bodies", "count", n) + observability.RecordLifecycleGCProcessed(ctx, observability.LifecycleGCProcessedDeliveryPayload, n) + } + } + + runOnce() + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + runOnce() + } + } +} + +// runEffectAttemptGC removes detailed send-attempt evidence after settlement. +// The terminal effect row remains as the idempotency marker; unresolved effects +// and records without a reliable settlement clock are protected by the store. +func runEffectAttemptGC( + ctx context.Context, + store effect.AttemptGCStore, + retention, interval time.Duration, + batchSize int, + log *slog.Logger, +) { + if store == nil || retention <= 0 { + return + } + if interval <= 0 { + interval = time.Hour + } + if batchSize <= 0 { + batchSize = 100 + } + + runOnce := func() { + cutoff := time.Now().Add(-retention) + n, err := store.DeleteSettledAttempts(ctx, cutoff, batchSize) + if err != nil { + log.Warn("effect attempt gc: cleanup failed", "err", err) + return + } + if n > 0 { + log.Info("effect attempt gc: expired settled attempt facts", "count", n) + observability.RecordLifecycleGCProcessed(ctx, observability.LifecycleGCProcessedDeliveryAttempt, n) + } + } + + runOnce() + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + runOnce() + } + } +} + +// runExecutionFactGC compacts old settled execution rows while preserving the +// client idempotency key, payload fingerprint and terminal outcomes. +func runExecutionFactGC( + ctx context.Context, + store execution.LifecycleFactsGCStore, + retention, interval time.Duration, + batchSize int, + log *slog.Logger, +) { + if store == nil || retention <= 0 { + return + } + if interval <= 0 { + interval = time.Hour + } + if batchSize <= 0 { + batchSize = 100 + } + + runOnce := func() { + cutoff := time.Now().Add(-retention) + n, err := store.CompactSettledFacts(ctx, cutoff, batchSize) + if err != nil { + log.Warn("execution fact gc: compaction failed", "err", err) + return + } + if n > 0 { + log.Info("execution fact gc: compacted settled execution details", "count", n) + observability.RecordLifecycleGCProcessed(ctx, observability.LifecycleGCProcessedExecutionDetail, n) + } + } + + runOnce() + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + runOnce() + } + } +} + +func runACPTraceGC( + ctx context.Context, + dir string, + retention, interval time.Duration, + batchSize int, + log *slog.Logger, +) { + if interval <= 0 || batchSize <= 0 || retention <= 0 { + log.Warn("gateway: ACP trace GC disabled by invalid lifecycle settings", + "retention", retention, "interval", interval, "batch_size", batchSize) + return + } + runOnce := func() { + deleted, err := acp.PruneExpiredTraceFiles(dir, time.Now(), retention, batchSize) + if err != nil { + log.Warn("gateway: ACP trace GC failed", "err", err) + return + } + if deleted > 0 { + log.Debug("gateway: ACP trace GC removed expired files", "deleted", deleted) + observability.RecordLifecycleGCProcessed(ctx, observability.LifecycleGCProcessedACPTraceFile, int64(deleted)) + } + } + runOnce() + + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + runOnce() + } + } +} + func shutdownGateway( _ context.Context, log *slog.Logger, diff --git a/cmd/hotplex/lifecycle_migration_adapter.go b/cmd/hotplex/lifecycle_migration_adapter.go new file mode 100644 index 000000000..e0c5dfc1c --- /dev/null +++ b/cmd/hotplex/lifecycle_migration_adapter.go @@ -0,0 +1,70 @@ +package main + +import ( + "context" + "errors" + + "github.com/hrygo/hotplex/internal/admin" + "github.com/hrygo/hotplex/internal/lifecycle" +) + +type lifecycleMigrationAdapter struct { + service *lifecycle.MigrationService +} + +func (a *lifecycleMigrationAdapter) Preview(ctx context.Context) (*admin.LifecycleMigrationPreview, error) { + preview, err := a.service.Preview(ctx) + if err != nil { + return nil, err + } + return &admin.LifecycleMigrationPreview{ + PlanID: preview.PlanID, + CreatedAt: preview.CreatedAt, + ExpiresAt: preview.ExpiresAt, + PolicyRevision: preview.PolicyRevision, + BatchSize: preview.BatchSize, + Sessions: toAdminLifecycleSummary(preview.Sessions), + Events: toAdminLifecycleSummary(preview.Events), + Turns: toAdminLifecycleSummary(preview.Turns), + Blocked: admin.LifecycleMigrationBlocked{ + LegacySessionsMissingInputClock: preview.Blocked.LegacySessionsMissingInputClock, + OrphanedEvents: preview.Blocked.OrphanedEvents, + OrphanedTurns: preview.Blocked.OrphanedTurns, + ContentOnSessionsMissingClock: preview.Blocked.ContentOnSessionsMissingInputClock, + UnknownExecutionRecords: preview.Blocked.UnknownExecutionRecords, + ActiveExecutionRecords: preview.Blocked.ActiveExecutionRecords, + }, + }, nil +} + +func (a *lifecycleMigrationAdapter) Apply( + ctx context.Context, + planID string, + confirmPlanID string, +) (*admin.LifecycleMigrationApplyResult, error) { + result, err := a.service.Apply(ctx, planID, confirmPlanID) + switch { + case errors.Is(err, lifecycle.ErrConfirmationRequired): + return nil, admin.ErrLifecycleMigrationConfirmationRequired + case errors.Is(err, lifecycle.ErrPreviewExpired): + return nil, admin.ErrLifecycleMigrationPlanExpired + case errors.Is(err, lifecycle.ErrStalePreview): + return nil, admin.ErrLifecycleMigrationPlanStale + case err != nil: + return nil, err + default: + return &admin.LifecycleMigrationApplyResult{ + Sessions: result.Sessions, + Events: result.Events, + Turns: result.Turns, + }, nil + } +} + +func toAdminLifecycleSummary(summary lifecycle.MigrationSummary) admin.LifecycleMigrationSummary { + return admin.LifecycleMigrationSummary{ + Count: summary.Count, + Bytes: summary.Bytes, + OldestDeadline: summary.OldestDeadline, + } +} diff --git a/cmd/hotplex/messaging_init.go b/cmd/hotplex/messaging_init.go index e15729a4f..bc614602c 100644 --- a/cmd/hotplex/messaging_init.go +++ b/cmd/hotplex/messaging_init.go @@ -215,6 +215,7 @@ func startMessagingAdapters(ctx context.Context, deps *GatewayDeps) ([]messaging Extras: make(map[string]any), } acfg.Extras["turn_summary_enabled"] = appCfg.Messaging.TurnSummaryEnabled + acfg.Extras["media_retention"] = appCfg.Lifecycle.Media.Retention // Chat access store (welcome card + analytics). if deps.ChatAccessStore != nil { diff --git a/cmd/hotplex/routes.go b/cmd/hotplex/routes.go index 821a52305..abd1c3531 100644 --- a/cmd/hotplex/routes.go +++ b/cmd/hotplex/routes.go @@ -11,8 +11,10 @@ import ( "github.com/hrygo/hotplex/internal/admin" "github.com/hrygo/hotplex/internal/config" + "github.com/hrygo/hotplex/internal/dbutil" "github.com/hrygo/hotplex/internal/docs" "github.com/hrygo/hotplex/internal/gateway" + "github.com/hrygo/hotplex/internal/lifecycle" "github.com/hrygo/hotplex/internal/messaging" "github.com/hrygo/hotplex/internal/observability" "github.com/hrygo/hotplex/internal/security" @@ -38,7 +40,6 @@ func setupRoutes( gatewayAPI := gateway.NewGatewayAPI(log, auth, sm, bridge, deps.ConfigStore, deps.EventStore, deps.EventStore, deps.WorkspaceStore) if deps.AuditCollector != nil { - gatewayAPI.SetAuditCollector(deps.AuditCollector) sm.SetAuditCollector(deps.AuditCollector) } @@ -56,6 +57,7 @@ func setupRoutes( mux.Handle("POST /api/sessions", corsMw(http.HandlerFunc(gatewayAPI.CreateSession))) mux.Handle("GET /api/sessions/{id}", corsMw(http.HandlerFunc(gatewayAPI.GetSession))) mux.Handle("DELETE /api/sessions/{id}", corsMw(http.HandlerFunc(gatewayAPI.DeleteSession))) + mux.Handle("GET /api/sessions/{id}/cleanup", corsMw(http.HandlerFunc(gatewayAPI.GetSessionCleanupStatus))) mux.Handle("POST /api/sessions/{id}/cd", corsMw(http.HandlerFunc(gatewayAPI.SwitchWorkDir))) mux.Handle("GET /api/sessions/{id}/history", corsMw(http.HandlerFunc(gatewayAPI.GetHistory))) mux.Handle("GET /api/sessions/{id}/events", corsMw(http.HandlerFunc(gatewayAPI.GetEvents))) @@ -63,6 +65,7 @@ func setupRoutes( mux.Handle("OPTIONS /api/sessions", corsMw(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))) mux.Handle("OPTIONS /api/sessions/", corsMw(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))) mux.Handle("OPTIONS /api/sessions/{id}", corsMw(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))) + mux.Handle("OPTIONS /api/sessions/{id}/cleanup", corsMw(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))) mux.Handle("OPTIONS /api/sessions/{id}/history", corsMw(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))) mux.Handle("OPTIONS /api/sessions/{id}/events", corsMw(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))) mux.Handle("OPTIONS /api/workers", corsMw(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))) @@ -81,6 +84,26 @@ func setupRoutes( cronProvider = &cronAdminAdapter{scheduler: deps.CronScheduler, turnsStore: deps.EventStore, sessionMgr: sm} } + var lifecycleMigrationProvider admin.LifecycleMigrationProvider + if deps.DB != nil { + migrationService := lifecycle.NewMigrationService( + deps.DB, + dbutil.ParseDialect(cfg.DB.Driver), + func() lifecycle.RetentionPolicy { + current := deps.ConfigStore.Load() + return lifecycle.RetentionPolicy{ + ArchiveAfter: current.Lifecycle.Conversation.ArchiveAfter, + ConversationRetention: current.Lifecycle.Conversation.RetentionAfterLastInput, + ContentRetention: current.Lifecycle.Content.Retention, + LegacyContentRetention: config.EffectiveEventsRetention(current), + BatchSize: current.Lifecycle.GC.BatchSize, + } + }, + nil, + ) + lifecycleMigrationProvider = &lifecycleMigrationAdapter{service: migrationService} + } + adminAPI := admin.New(admin.Deps{ Log: log, Config: configAdapter, @@ -124,11 +147,12 @@ func setupRoutes( } return func() error { return coordinator.Commit(ticket) }, func() error { return coordinator.Abort(ticket) }, nil }, - DB: deps.DB, - DBResolver: deps.DBResolver, - KeyValidator: deps.Auth, - APIKeyStore: deps.APIKeyStore, - WriteMu: deps.WriteMu, + DB: deps.DB, + DBResolver: deps.DBResolver, + KeyValidator: deps.Auth, + APIKeyStore: deps.APIKeyStore, + WriteMu: deps.WriteMu, + LifecycleMigration: lifecycleMigrationProvider, }) // Wire audit subsystem (issue #833) - activity endpoints + meta-audit emission @@ -220,6 +244,8 @@ func setupRoutes( adminMux.HandleFunc("GET /admin/logs", adminAPI.HandleLogs) adminMux.HandleFunc("POST /admin/config/validate", adminAPI.HandleConfigValidate) adminMux.HandleFunc("POST /admin/config/rollback", adminAPI.HandleConfigRollback) + adminMux.HandleFunc("POST /admin/lifecycle/migration/preview", adminAPI.HandleLifecycleMigrationPreview) + adminMux.HandleFunc("POST /admin/lifecycle/migration/apply", adminAPI.HandleLifecycleMigrationApply) adminMux.HandleFunc("GET /admin/debug/sessions/{id}", adminAPI.HandleDebugSession) adminMux.HandleFunc("POST /admin/restart", adminAPI.HandleRestart) diff --git a/configs/README.md b/configs/README.md index 9e8f1803e..4e9882444 100644 --- a/configs/README.md +++ b/configs/README.md @@ -141,12 +141,14 @@ hotplex admin create --config "$(./scripts/dev.sh config)" --username admin | 字段 | 类型 | 默认值 | 热重载 | 说明 | |:-----|:-----|:-------|:------:|:-----| -| `retention_period` | duration | `168h` (7天) | — | TERMINATED 会话在数据库中的保留时长。过期后 GC 扫描将其标记为 DELETED 并从数据库物理删除。较长的保留期支持会话历史回溯和调试 | -| `gc_scan_interval` | duration | `1m` | ✅ | GC 后台扫描间隔。每次扫描检查:① IDLE 会话的 idle_expires_at 是否到期 → TERMINATED ② 会话的 max_lifetime 是否到期 → TERMINATED ③ TERMINATED 会话的 retention_period 是否到期 → DELETE。热重载通过 channel 信号重置 ticker,不中断正在执行的 GC 周期 | +| `retention_period` | duration | `168h` (7天) | — | 兼容的 Session 运行到期时长;到期后终止运行并释放 Worker。它不是聊天正文或历史保留期 | +| `gc_scan_interval` | duration | `1m` | ✅ | GC 后台扫描间隔。检查僵死 Worker、`expires_at` 运行到期、IDLE 空闲期限和 Legacy TERMINATED 记录期限。v2 归档及数据保留使用独立生命周期时钟 | | `max_concurrent` | int | `1000` | — | 全局最大并发活跃会话数(CREATED + RUNNING + IDLE)。超出时新会话创建请求被拒绝并返回 `POOL_EXHAUSTED` 错误。根据服务器内存和 Worker 资源需求调整。实际配额由 `pool.max_size` 控制 | | `event_store_enabled` | bool | `true` | — | 启用事件持久化。Bridge 在每个 `done` 事件时将完整 Envelope 写入 MessageStore,用于会话回放、调试和审计。关闭后不写事件日志,减少 I/O | | `event_store_type` | string | `sqlite` (代码: `""`) | — | 事件存储后端类型。目前仅支持 `"sqlite"`。空字符串表示未指定,依赖 config.yaml 设置 | +新会话默认使用 `lifecycle.policy: v2`:7 天无输入显示归档标记;会话期限默认 180 天并由有效用户输入续期;每条正文从创建时间起默认保留 180 天。查看历史不会续期,输入也不会延长旧正文期限。逐条正文到期过滤、v2 会话自动退役和持久异步清理均已接线;存量期限变更须先预览,再单独确认 apply。 + ### pool — 会话池 PoolManager 在内存中追踪全局和每用户的会话配额。每个 Worker 进程按 512MB 估算内存占用。 @@ -179,6 +181,7 @@ Worker 进程启动时的工作目录遵循以下优先级覆盖逻辑: | `max_lifetime` | duration | `24h` | — | **绝对生存周期**。Worker 进程从启动开始计算的强制最大寿命。到期后无论是否活跃均会被终止。旨在通过定期刷新来清除潜在的内存泄漏或内部状态退化。Worker 可通过 resume 机制在下次请求时无缝重启。 | | `idle_timeout` | duration | `60m` | — | **闲置回收周期**(Idle Recycle)。Worker 在等待新输入(IDLE 状态)时的最大允许时长。每次接收到用户输入后该计时器都会重置。旨在及时释放不活跃会话占用的资源。较短的超时释放资源更快,但可能导致用户体感的冷启动增加。 | | `execution_timeout` | duration | `30m` | — | **僵死检测周期**(Zombie IO Timeout)。Worker 在执行中(RUNNING 状态)无任何 I/O 输出反馈的最大时长。用于检测并强制清理卡死、陷入无限循环或无响应的 Worker 进程,防止会话永久阻塞。 | +| `turn_timeout` | duration | `30m` | — | 单轮输入到终结的绝对上限。流式输出不会重置期限;v2 的显式 `0` 仍使用 30 分钟默认值 | | `default_work_dir` | string | `~/.hotplex/workspace` | — | Worker 进程的默认工作目录。当 session 或 platform 未指定 `work_dir` 时使用。`~` 自动展开为用户主目录。目录不存在时自动创建(`mkdir -p`) | | `pid_dir` | string | `~/.hotplex/.pids/` | — | PID 文件目录。proc.Manager 在启动 Worker 时写入 PID 文件用于孤儿进程清理。网关重启时自动扫描此目录,杀死不再有父进程的孤儿 Worker | | `env_blocklist` | []string | `[]` | — | 环境变量黑名单。默认所有 `os.Environ()` 变量透传给 Worker,仅黑名单中的变量被过滤。条目以 `_` 结尾时按前缀匹配(如 `HOTPLEX_` 阻止所有 `HOTPLEX_*` 变量)。`HOTPLEX_WORKER_` 前缀的变量会被剥离前缀后注入(如 `HOTPLEX_WORKER_GITHUB_TOKEN` → `GITHUB_TOKEN`) | diff --git a/configs/config.yaml b/configs/config.yaml index 4fd65560c..23c547385 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -164,10 +164,50 @@ security: # ───────────────────────────────────────────────────────────────────────────── session: - retention_period: 168h # Data retention for events and logs + retention_period: 168h # Legacy session expiry; new lifecycle policy uses lifecycle.conversation.* gc_scan_interval: 1m max_concurrent: 1000 +events: + retention: 720h # Fallback TTL for legacy event/turn rows without per-record expiry. + +execution: + queue: + enabled: false + per_session: 20 + global: 1000 + max_payload_bytes: 65536 + interactive_ttl: 15m + ttl: 24h + sweep_interval: 1m + sweep_batch: 100 + +# Retention defaults for newly written lifecycle records. Existing records keep +# their assigned policy and deadlines until an explicit preview/apply migration. +lifecycle: + policy: v2 + conversation: + archive_after: 168h # No valid user input; still viewable and resumable. + retention_after_last_input: 4320h # 180 days from the last valid user input. + content: + retention: 4320h # Each message expires from its own creation time. + effect_payload: + retention_after_settlement: 168h # Keep retry/delivery bodies briefly after settlement. + facts: + retention_after_settlement: 2160h # 90 days; retain status and dedupe evidence separately. + audit: + capture_content: false # Reserved; true is rejected until separate payload storage exists. + facts_retention: 4320h + content_retention: 4320h # Reserved with capture_content; facts remain bodyless. + media: + retention: 24h + trace: + retention: 48h + gc: + batch_size: 100 + interval: 1m + max_lag: 1h + pool: min_size: 0 max_size: 100 @@ -182,7 +222,7 @@ worker: max_lifetime: 24h idle_timeout: 60m execution_timeout: 30m - turn_timeout: 0 # per-turn kill timer; 0=disabled (execution_timeout catches zombies) + turn_timeout: 30m # Absolute maximum duration for one accepted turn. default_work_dir: ~/.hotplex/workspace pid_dir: ~/.hotplex/.pids # Default permission mode tier (read-only, workspace, auto-edit, bypass) diff --git a/docs/assets/data-lifecycle.webp b/docs/assets/data-lifecycle.webp new file mode 100644 index 0000000000000000000000000000000000000000..5931c4105b54ec4dbcf073b11bc646f78c2444e6 GIT binary patch literal 986372 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 r`g1c)>JH!KYx)L_<_<&pK49D9 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
}Cy4ij&Qd&Z7~w~2{)JslOn(2- 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%0DR`#*VV7{^@ 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κg&0|(r`FJ}?3lsT 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

n^76VtqeU$vC~71kUiNmt z)qpc-ou|IU_U8CNoDf`xfSHP!rEO$7!?P}P?I-WA|2yuUNjg2-4?WElOQR99P?0{2 zTr(YGjwFZ^shvb#6i^aYKn*doc(|K6PWz_ZH-~dmo}HlAS!fgGkVqyt75JtKO4!<# zn?3}grpH;ymO;e;=$m_jQ|81}^T>;5Rd!v|kleWwjY0>NA_I_7huzif^gep>}0Wlr??+5RDv2GXkg+fyfvjbnSrEpyK(-#Ib{OX@{$ncJXd4=XPUPZfrq z!9mdst06rM3uYEHR#vEirO|(Z2E_LAC6}i*oCbGCrsCyNe$z`iHL8vY)>HRbiV6#r za|Rk`7lOh#LJ^vHacU4=ssO}TWzfwExl7EM0M{T4*)YXN4|BmtN@fWKTVzvQ9!AA| z#~JVzjp<#PJghFkD-D9Db!Wq*y*aJ9-+(Fz;)7sP(Bt0w64*2Cl!{hB159~!zu=a! z(^@^2=NNk$h2FP>MGN5!8v~>s?*l+92Yjp^C>QzdZhpDE?DdtK9)J0i?MI&z9qV74 zrxhY1BW9dqq1&gox&P=xhvSI82Wm&m=r~25=0?#P+TU~Qanz|v(~Wha@ln_|YGOv} zO51@vE`R*o^~KQ0MJAqa{?m%M3D=FXYn~^RY(y}OAW@JIsIsfhvpLRjdU!6o+2Fbt zv{FV?TROZuFA2K?eE?_5CdC3!xDciQ%ckMv4J`p!bT6m-zNI6Ej0+8BJ;8 z9Jzz534VCfw*TFI%^zyOb|jGI5)s}#XsZI-(p8jCi6y4Js!PHO%&oiwWeI5xVCCEtT{kn3{KV%(v4K*cef=gV_Rdv4XZb>IMUN| zhq|pCJZ%A##@45_-!;ACp#HSd_AXt_u&<9)<@Cr5H4w>azGjC_n3$#o*E#2qj^1wp z?2PR?Dd}jrFGPR36oJ)Hi_~&dBNJtdY!YJW5XCDLF)OY>QBXjeyY0fB#cl%~r7g)H zqBtWIE?+qoRY##ypfE2UwCO$GuHEL;yxTms&3)+gdgOE*v#FBlPkV+a%qwZ?Wxal4 zfS1i=91pd`2)Uh3kpLmzr5tLzUuW1qD*Rlb1u;8z@2KF0b&}##C^uj*76WQ7!gJ8U+b{P+tOjnyNIPIRH$y}tK1f% zS;PWuyUN@5NUh4C&e-g0FL3L`PA{Tl3DG4$j4DAX)W79d(M9F!*tsHW;|+aK%tQ=u zU(uVrR$Y%U+LH@_$Xu=w+2=Dx)4ZBM6&MoKD4pYtUG{EuIA)gHv*3nrHEID31tc-W zZsl>gLSK!(fb+#b5G|C4vNtNYSf~mZSQrg-R4i;snu8Nadxv?Bu$yvSbt`LiYgW=U zf`|Z$XFfJ{VEdA^AG2F$w!tYBjGZ>cDakAG4ks?Ku%d(yq&iKPb!$L$bEX7oupg=r zK8dKJ(ib2Bj9HU(j;~%X_OdT>>~IHr3+QJvp>#O&CIe=NGa)q*G4pV{i!WGdR?dcQuY8OnPnJcsxtW5SF5v@)0Pbi5sIQG}LxZ;w%F3Gi&tB^@_>k3#_+kcC>&Ck5X7f#&FU8S5$9>V4 zd3n8jjXdJ3h$+r5dZ(rCoDp2I? z^~mpiT1t1~^h!Gr+ y?P|a9bI-^`4zWI759M=;@}o7oGf&$D7TbQ_zE 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

Hz_!eU1zYTUGYrF_5-nJ1cJ)r zVZZjgw2Hq$WSKs|gtJl0sg$pFJQ+@-FZu1Cj_05BC5M>$QZV^-2h!ZTl z*!(UjQ@24^t+Vm2_@Mi^w|;?P-c#D+{TrtL-CdW?W%kNF54Df~XLOjpwt|aFZg`Nt zH*S9SryTyzD`*fXXLlx|qH5`67Ac?Z*O~&Q z01-*k3p!($J==7i{`Ggh&Rg%2`$V1|jn&cB6XsQn#7uR+s%qyRkzDe~UO7%kx;o+T zxeIVB*vi>KzuK?hc(9#$AC~F&IqVx3R%#$E04TBWF-k$Gl9Ee_)i|k5Gu*>fa#)6d zmI(_$3P3gmF)EDg;C-rz9sTxcJ@e+;D>t%hPPb;LPQ0%Vr5bg!e^BSnJGa^K%ZoV| zXvin{z~gF!j$`BVZ9e9*^}6P}IsMN=gMJCiyqyo~by)9S<>V_KH}om0TJ3f3lom3u zbXe2Sb3D6ISD(0ie96o18(dU*r}FMq;`cAn|KPIQ&$w-rMI6+;8*%z~+VIc%ivQU+ zBP$g`5{hM?_22oePJeI{m5d^~X{!Cuu-Vker5U|^WA$G4TZLEo1Lq&to=?<0WzNzq zj-fzZxvqM3)k>(rU%Kts&IoV|DpHAnp&3Ol=yT(ud7QoecRyiIJ~V1DiYv$2R)JUF zxY6CObJN$vAI&kSZ^Y9gVw7PLAf<+VP7b&fQv2Th6&AkasqLmc5lW~g1=bwx#$I9R z%UEsf-R|Y%x=+sLuukZ(5hem91Uy*4Fa|s3aGxA|Fz3xPZX>9;iGcYrfJr+>x^gS& z2?HFsGWQ`9H_wyS&X+RCgpB_`8{^|=`OWW$mCIJSPYJ;&)BM+>trt~ zNSIM|m5wSec&f_0Me2pqEyZy$W)ca0=w&^udH^XP?jjTCL@jYKLL-BQvW$yt`;v23 zOk;+}JWZV~#&%d??aw9T92NymTjC6#UReye?hqF#uUzi!yK_6r0g|v3Y0JY`9{;?J z=TlWST{G5wf7w$%oKAnyVPq|g^sxMwT;{G9d#l2y=coPgA-kQ&yKM9K17%QN$lKl^ z@`;D^?S2OpJ-s^P&(C&#gWFwvkLKJ0g0a6(anzcN_qfU1&i`lI)j6y#pVr8_ped*VC}etKc`4jA);nF!Pk!=obG?@R zr+>hd=N4m$0X~VOR@5x)T)TGe`TIG4p7Yjo9-eddbC$f$R6XzRR`u|_c;%M0BkB&x z+UwXScW$)qu95rsh@CnY;sDMjkgy;V#Er2a;3Xf}18xdtEn#PiUsT4+JIjz37n;;0 zsR>~KZ-we=-RD&KoU3!qeW%L16hVwiZmE5F zz48#hNJg!7qiaKu#1t-O>?9cQObYcJ=W$x-(|t%?o|$Kg!2h!SI`hmG#?n#ixKKoY zwa?(DN|z?077~?Wn5dPl=zMErVOqp!4(5!AMu4pud&wHnhUgkBdhWdA17Fdn>79rW zHQTf%`Xs`zujME4r46_|P$5NyD1=B0?onP;I>ZhvnxZ$|SG3Bvr4g~65>hl4^X(`f zUSL^1Mavh30yo0=R<>{NTjcR?jkvLV=bE5%LffH+$oV8Njz!HudwJ_OeW#~={NujC!#-kNJzu@?IOnoS*3b5r`%UEf zB*xzO`Ye^jiwR+rq(+LHc<_}=%{z7KkgSM_o077sq+;x642Z4mv}`IT z=XTKG3$a`(Q%M4en!i9zw=Tf0Q)1vdYN9k$rJ)7x4tNRyo*9=+R z=sRomKCtf5VOQd~roDZV!t$gKAPMfO0DGpDKoR^i(_k+3HjQDTk0i)SDc^BmtHNdK z7FR4$5%Be)O!o(u;W*St6O67)rR>_YX`6zh_q*}CC3?TrYK~Hi6)Rb1XvYPl96AE- zu&}7_6zH+Mu{_mz**4zexjK76c8>W=X$ZSKQBlY!t*~&U>U5GYcZS=dkXTSfXheDR z8xhKNfgH9UWYQSG0!)MUZ;s4(c)3jd@Lc;7D=bQDmTt$?$iZ&98s+HGpDeHCS`|0G z={~`BKSwg}$Vx{0pJL`e{&(-D_B`GdS3}4hPtz6yLwvWGGZ}fI<*?Gvmd)|_!^iU;pi<_*IHV}4j zHRIq(B}D~Y{9+`X8VbX}%w?ICMsA8@TxwVTur2-Smn^ZpmL2SQP0efYFNzMA2wg)W zK0>ULy5TeNt%9sS3PpwTZ zFYR|4C5VwmQxDbCuffiTQlcm`cjK1~1_{-x_C`DN$__`hI}K$sALP$c16@jX-)Esg zLz;32)Oj?Ck;7rP$v#Y!qE<-76P#UkC3CRMm2x;qsE>*VItt*(@>CLYb>A*xy0g35 z7ntddzFa?fSfS?ejc+fAl`lMDQ?UqX+YXvAF_(_Iuf6~3#-cb8Jq~V1KJwONF?3|? zo)_}A_35gG#u%mmK;jvluA&Jy@i9zW@%tgEL%EJ*Xutq?V3NsU3vNaL<0u&OMCO|h zb1%PPJ{P6M8I!foHWtH!=V2fkjxQE70%AkX|+kP1l zmmyPF-||L&-0<6<`NE_HH&VryjCS3roiDSmwvW%x_`_|N*YE{6VuTn|^GP)ez0${8 zVD~gM4EAs8+F)Nw+QcL?$oOp1uUovF7WR86kp zLOoCNV9k_gr&1}~=9H7OB)v;aV@$*0?HwvQA%`(IRy-D0mgw`+lbNmEqN*r~BScI! zm&v;_-%0hJxCzV7HHHc;aTj)ZePTZnAk9W6I;JyQOBaraZPA*?2N6IZI6VI13zq2# z#f~a~`P_wYApq1v0G>2xNsI>YEmfp0ebk+B?D3@2zlynbT>%u3a;WamtjA6cPV?La zXV|{E5^|Z`&UgCOCHCK+Yiuo{gFA(Y&4Oc3JN2-c%xxrsBS_8MTVwx@W_0XBkniBj znEGL~&V8Y6J9*EA{Zh{EBlzfjIguLvLHDR$?v1DVg33C-{s+CCvc>&P!SgLE0qUdZ zd}`T7gi9}=0-=(Wq|%6-jkzm>A~#^?v^i)t(>Wekq$o0uDyj%=xUTBKyCVoPn--@_ zhc(l(of78`LxuYcq`v;s#(C`1VT*_n$iKQ`mcs~R6?_5&Pg)U}h|#Zfn7djz6OcQz zLP0G-1};9paeDv;j;mMT%lKtng)ysCO-z~^ZH$2tW?(=(#Q8qdX&>_OKJ3jt!pkFV-BXjSB#?|w$_LuiL$1OQQ?UPcdC>yiJGsh~Mwh2Hy0QLZIFU1-YuaSMX zyW-XhKIN(P_`>z5+;NWB4wt&jbjexbd_TT+m3MC+!DVUS&QzpE26~?9)j29+@_x_z z8G|j$tWZRzlyCDr6Byi7AMt$vS*Otp|Y=g$kOVO#$l?sktT{XLUIHgC165 z{k?Ld7d__GP)YY;OZ;@Ty7Vj-%ZIXwb0>yoyawZlkzTIlK_B&ZFTi4vN2<>w)F@*J zJ`%o7KooLc$$Q&&?c(sRcDQX{>~N>px6cePK_ZjH^XiRzpA+r8RR6$}uGbqwt_!}# zKG=Vso;+t+>>m5@Od))Q&nXr zR<&7UYPm`S@5j}iN{;B*1PM*S9SOiQ3waS-DD&bo4K2n7%v@eEXx~_9hR|@$zhJ4c-+x8URKsh-y&MfeNFZ3PK|10XxI#2MPfWVn9_}Z?_LvL*uqB(N+~dFs7!1q z3@d0N!kk6(H7q-g9+iQm%MP($^{MfM7{{R2VUz=;(mB<_^AFn$ez(*U zuz1_f_33vyOU0*%j>svU6KsxG*V(#(n7Z6tPI0y$#z+Rfj52(Pr-Hc{#c5*tpLPQeDhOuGGDol8+g zZqL$#b4@g$b`ti-E?_B;^IVnvshPoVCgxqJSthq@)1Th!{K++$dUlW_H&rH-O+Nin zr`r9hB9jxGk#LgDkdzoTikTJi`AKS;@;0jX^G79pf(rdcd_BkQXx7oR!ysv-h%2e2 zG*?^p`KYU{yyIRg-|gqDU%yWK#RB&0)z)WwGk(LlXP1q6)rt#-=oXjRZC%_2tvhXb zO4s~#MYt)3Ar)gBbhiravc~BF&kIV@G=d{0Y(+RVfkJh4aR4TQx3(UO3`GS1vqKC;TgydaJv{gJbz`d-I`U;6OmNQarlixT1$@`Dyfc zWglABjibVzwUnx-1-hv;w~w+FT+gBsmjYr%zg9D7a-;YBzL>q&_%Jpn6mgg^O@EkH zhc{Nhq3Yna_O&1wf{CD1MP+gj2szqv91{%8IQ!-_8D^h{A5Z~irq8&|9>4@=}-UNzR{b7@C%bC1Yzuq-M_Mh+A^O z0LG2u;J8#OWdnpr*CLQ)V0TV{rPhgbc|H2tE(J@>Gnu7_qRf;i|<qy)fafzblO1UT%*8UjRl@CqkgNB90q_PSZeKAnzg7^cEd7;m6EcaHn0 z(!jJgK8NG%`R#7vLx8itynfOB_ZOMqg%duQQ5qEi6m?n8z9m^c2yecpTv6t7bzSMZ zcXLV-Ms$c=@M!D#SlZ{drKtKQ8I)^y(z_Xu!wtkxBv0zIYagxw^BfP=?yR5l-mJC9D`)4(lq?Oy4uZjG6zZLO_hv@P5Sk43`obMvegWZa+hvqpl>4K<60(0>7XK^3Q`$%w*Z3}|NWi`Ns@QGY9 z(!Yy+_RKEVj9cbiCkfSZv#?TDW9F=#_^@x_V_x6;JwY5=E<0WaqCh!w72ue4j+6D` z2FMA=VbJ=k@3mX4?q4c?4x{4xKXXQ~;lVe$R;~k_&o}!Ezqg-rmQKi{%Eaqo-S@2v z793+6XD1`QBPI^DM?LOSq=`u* z{=TY6?X&rRaHhImK&YDZ2q&2aAw(h}q#77HlVP?OE`5{lv|)1j)n#+Dm%YtBXUkSV z?s~=Sqo+D)y-}f^R!Nqu$-AP)PAg|LXAFV^2ApTeO%s(bS+aH{2rg4o8VZf>N)q6P zuRI@kWRv7()D0UZnzOu~B>G>Ky1Z)-hhZ|IqpIY~Fd}bzN=$%LVNHzPN># zF+Fb=LRV)7Kbes%!HmJWlx?VW2H>n-nEfP7q6#Y2+X?RBi*8gY!y*bv!^wocc?!h! zY!j~E<7RitQT1qbo8!lBRF3ts{1Fl32W>RvW!T^K@P^jS^R75Oa=*j-A=_R(>M?|@ zP*f_v;txKKffEt5TH~CXxV6%460IGYrIF$(9(pe-RCbgl3_~#SjqB0`A00-9Gryq6 zvcsM2`}L@vh6al3-8%8`;ud`lD87K0)UME=ZaYm=<{@rAEPFf)U#YO|A>T$kFZskM zSvk>9&Na@0oA*PPU9DZJq+Q>4BQlRQ&D95epH7M?Z`l*o0Hb1riWWJlO4KV090`a zndKfp(e=PI{2+>DXj2&>03Q35QxtWML=Jk=<9A2E0859V!gAsM($z z^g}1F5IoJh&z?h{yXq4Pg3TGtp0k;NYyB6dP*hTBe&6)+ucDDCX#*>zdLx z;Dj;e)gm^MX8ZPCc4BxL)T+i1Z#FCAbfxMGfYk9Y$0*+qW=qB&CY$p$0vCO7zg#JWh+;WF zMxi)&92Pv?4=gF3x%Kp!vx05upsCR4GvJ9@6>%Le$FXXo-{qT6`TJKxvvz8VbFvwz zBt!IWf3#k}_11Y6?>|GEW3=D2*0qVR1zwbA+uh#cQt*)y=NR9bW@IJtkrwC{*~L^H z(+J>LvB5dt>be&J%sE7xLFhhP(SpL+xUadAW5^RsK|*+-cSH?RV^X7yHlY!2Uw!wB zf4O?JaNJ^?Pi7msg(=kOwi4d9>1^DUyT99^6E<8E8E2X~S`b_kARKZzQBM|UNZZ)# zHM>*zj(aJwh+Bsz51a6@@qtt<3qzwdG!3T24FVVgZA@B8`wie40R)ai8{t7f_gCN_ zb^o?_#_Sg6NY|;`!zWauvN~8DF&b|eD4rncXY zG*9t#KCI0p)GX+4AnM-KSsAB#h#L8f%RH?BA*FliAaRPguNTDfN%b*t*xjJqDQ$Vc0-j`*s?9nqlQ^}ob zUW=ZL3PAyCTZ+4WeY0-$4tbp$0_!3mZD5tm_FuZygNtVbqd_5y(>qX!(D4j0?%*z3%Av{ud+|VoO9S29=0bk&F$J zfHqSC60%76!>t{*xnUv*MFOeMsYBftTg+WHxA@VmiJAJcB;0$sv&)k-4;v>jG9oyj zZDTcR@!ERQyj=e`so zmXT+|f@OIaI&^?k!H{&-Bl3=)nCo|6?l5g@k4N{hk9{0%ysth*nQ(v1BYDgKbH$*5 z5DA7S1)=%C_U3ZvQ)Xk;Iv`$Ts8^ObBd2i|nUom8O%Z~r8R(Y*o5>5vEt-CAG_u2S zp1Br@@3nqI_6DghX;!wO|PBdP}uLlq^gq&Jd)12t4(p~i0x_OanQ zd%(nd58xfv;HUL0;Bl|L2o^>T^?N&fodWt8b540bVwO30t#;6U+o{yBxc8@QOZM)5 z$tSyB9_!R9D~GZjz9e%GWV9_C8&DXlyvL=MXV7}K|p`hnl z>#|pW+|H_uo8j;%M5XlB0_N@xcM&KjU1;;3+XKb6nX^8f;Yv~ybuj}?aVlXzL9VWG zx^GnhxDZzxc&~{u)w+B*>!nXQ^*xW#)$3NT*z4mG7hZ>)sJSdZ~(t)1F5p#-K5w?RiEIK-1&;NlBH(d7WA zbAv%JMTzv@DxGntC_)%rnHS2Rb&;y5RD>zc?%tQ>E6rINE)+7InuH&-V3=&!IAlP-W?IpZ;cArLt&& zZu#~$2|drlXQHYg5CcJu7dkr{aC?RI8nxb9A{|}!HR!>I?Y1hE3Lp}vNp>6O9l!qj z--G|&ZS}rb-97B%Jj^aEVwNI84@GhqMeTF6$FyEOM>|~O-NI^!m>%C83N5j zU8N*#IPT|+FQ`!O8V=lNS2}sPBLijB4%Ib z%5QyV?I#$XbgK{joMG2=d~&?T^Xn39eBTJAf{Zi_loo@iL5Gso<`W4?z)9&07x%Ok zGm|Y!GQo0U#F(`)aU#;`8gmPSZGwfOMgGQvDsu-%B0ph-)0U-kvtTN93q`7ht2=kmy>&;_^!0x$5%W8viU=s5{9DzVxj=fkg8aqJL7ZH#kM&zZ(^iEE9R#5YYtezKkB2bMr}QcJapbWEUjFfv=<w!7T0V) zIUV*ZoFy zbU{X6_j_O>g&=iuTTe9zj^DBkN`-O&a62>t;zS^~J zcO}mF(@W>S`r+JDo>*B15AK6b=HO9tz^3I2pejTu3CJPCcoBmUoZPJ59X2~F2$~pb zfI*<9t@j~>mAfA`lMAA@WOH3>{HNcNf=nGf8HKGH-9in`~k>8=E(lns%F#F(nNeN)kv+7y_fo zGE-DWzy%=S2fh&srjHwgK344IbK2sT#i+N~?7Lsc-=uuN0}@#tG8%0vav~N~#?k^) zJXJTm*g*TjcPFnjf9Tg9H6Qxx&G|+dg?Y%miOWw0(;9J^KX~($fAZ?>e*Hl!cJ@UI zdXhmAV-j$&g92&=)1US;)|$B-TKYp!BT2cIE<_Dcz_3_<#SJ#D`l`prG8`m>c3ZH5=KXNQz>?0C} zn^2+>4v*%P3Mz&M0a-P|aXz>LrD{mYLTK-jWR7v6i5LCDx6^~h_6-NEDtDXD)vB0G zew5Wu^Z{`1(2d0ONCcq-V-m4AVsZ&&A|ym~0<^8s8dgCeNKr&?QPhdJ;zORX`FTxy z++jo{LgxKU4}Hmg+lSZOd*kQNg;wxQXz<}PFJ(({PuI+t@|`%6U`UX zlf?)a(FDd6L*kCr&6!o7E$pM?mcEDfE;)>n0K5UeBXcwy}L7qbMm2S3(0=urkDGr3TeE?E1L7zwpw}=}&jIyGQ>ZW=&7C%&}wY#c}rPmIbwa;esMyoq)m{27^ zu{Kj*+svjf@**Af^yWP`r|`b^tH;yg^QZjgr*Bnvsa5f_7Q0hU^2F`5i0XjD5|Jw7Tc}DQ}&(aX-_Ub-r2$dpR&PShjj`Y1W?0w_g;-zb12!&J>cKr)A;iF^1fT19Wr*A?Q!d%r)5PT0?R z^Od;I{xVh9LhE_;(aXG^muH`)V87F~f~lEPDK7BfgqT0N4|uz~j#dV?)(S=xoLms4 zD^}a*jo#vyZvJ=uE!w_VNwC7LsPQG)*r>4y4tSyA(JdVYBEm8jMnIX7VZ2_lE^&k` zrgt)>OqCHnxUwI9yLa^0Pt*VCL%QlR8f7~WC=GRLLvqx6jagg!pe@!O+d;N-h6(C4 zC4*rD9k`j_6W&yE@GaOvSt28M&jaoKJk`tIB}qCfdrlt_90AJqbX-;-uAV z2|Ft?I%H9V#O?}FAuMj)>ke(7kBZTwX~r(3^%B)Pse#PfGLSG>K<(rO^-omO^fYcZ z_@;0Fz|V{BY!2CWSY8hNX}kN|>FZknrS#C$j1_B!We`YmlQd!59E9+8qt=9N#nNf{ zt{HROI5#sc1wz^5Yh|rbMPi)m>KC(I-G{baWBP5bw?6x@1wWkvAlI2c)@-)gPIqFv z!0Y+I4<9*wey(g)ona!7B~bmg6Lx&R+HHb%tQzjS+PBX9wOgFg_IavOkHUS`sLW0u zCEwTgIj45zC}X_Qpcf))JL|At$PtkmkrCBzV(f0ntX~yNA}MTckq`_>54SNk@f_PW zMtaO990swETfzehV28*;WM*CDRi7Iy;rcS7U|mH~ij8Pwl>Welg0n)?k@LEi2?b5Q zW|h(Cy1DTPe9~BkD>h97K1J;jq)Eq$zy+o}) z;UoNjYD(sW9{H&wv|h+zM{?mQ-0|tuL?7_(*;g323Hg=lWgQi_yalD^ z87E%$`!~<|{sI8)nqs4aZvkG$)w&B^L?T5SexGeOxZsglwdlb}-4K#0Hufic=3O^Av)Nk9ye zLa2%Yv@~hz9(4bm$jf$ozE zdQ@8S04li5IDD{Wn09uz;A&3Q=98t;EiGf_QcWv5tJz#u+nCHFeam#m;`y7Wx?2SEHEp``Kyxr0RkP zG$SN}2oyK~H+3dv`;Y)Qyfq#JkbF~$Lh3^37aw-l%sT`(CsEmf9k6z3EIOHPb2Mt| zdLs!J6)tPd87L7Yg9Js10<0tnyPBk3dW(JD@u4p?dcvGoT^g*p&C;3@gVClhH2Y=b zjBQWR^T@MXY!{BoJ1#(_(Q|Q+%gYz(w9cuYv=?T-4S>{KtyxjlllO5}@}9Y8HIK)~ zN$TE_c*R4R!%KQeFIm^9_;VeN5W5*gO?X?I37hyRMch|2IFC@wu(8t82N5{iiBx$Q<@2mqH5Mj z&ZgE+nRh^^M|PQ-St)h0$lUXFVRZo12!kFTF)mouYC%bo`Jy;N`)l#J7DRl$kmxqL z27H9N&$?;lh&D&xV_7$9eZo;K42T3C)apjVR3GoO{b+GoM+&tBDgZyoKm|pxlz@_i zNy$27SrW{l!2ex;lTm_@CBX4s<&kIJG_c1ey8 zp2s>z*Aa&qFf;K{A@DB9`?60Y9Ml<>!2yn*{EP^) z0dPR8VKgc&Qqx!1KJWLE$EWzh0De{TleFHF^)f09@a+O z?u|pG7X;c2@BbdDMzqwa`)M8Yngb4zJxi^ql36!$nt1yXLG^m;=@R|HLy8N}qjWxw zxab1@gsLiYYvK2I(6bkQ1=3tP#XLuzwe^C0G9Z$%7d8K3401$WDoC;Hg0q(p`&w#< zP2t5OqM2&fJ}OJ8>$jaH5yRX5!}?~ z!r8aFX#Vsri7U_f2W)FR8~Wmj+uY=Ow>aN)UUb`zQ)3}JatR})JGV$?i7+y1L(_759t<|#ulMPn8~z*6sA zB8Ta*Mz%29I9(EX3&#qWg2bJS5CJ46A(ag=Z%AJOG9V>wD{@7dbYKcZF?UgvH9&_F zRzt?j05G`>w>q8l0F2r*Ie`dpV$RbIcHU6a$S?T9lt`z&O!1`^C`JjA$V@N@t#bFo z`^S78tG)4^n8zQV_Qcs|t)~L?@i1=lq(b%y`o0*w^v(ra>2#cs%)ITmDb?uLt`;w# zP_+e^95A4?W2e(FZ9AQE#??~EV5kADxQp%G@`08PGusW9`z>|h66O2y^+QSYx%GvA zygA*?QzC`P)^^{yLWf@@5>%8i5Gq|wY2w&Xwc?GdiDgFBq?!J(ofrj*uB7f@po~_w zHWP5+eO>wMAARO>M<1qEn|I%{$BJX*L@UUWdCb_oAMX~$O5@2N<3b;AFVb$OcU8o$ zn$u|zV;T_Y<`F(~CkWd2)Lb8F9}5x`=L|gi7)zKvAgSCkxl^)*)J<89%2JyK;7ZKJ zJUhI}AC}f-h3ka*1CEIu(Z%D9=Cf6GMhgWD3H!g&1)gp%IKO_#<$Q&^-_0AWMIY6-`)?+`Xu-&JBGyw!iPnaB z5CtZmVp5yrQ3e}xo#m#k`pC<1o^IR@w*v>}+Qt0QU3$$T&B8H5Ki!qO+uNRc^|{&) z7ak0Jh)7$Y%Uwb9H*kOQb?;BX0O zh`~WmM%bC~seE}03dULyz(Fm5Kmf?dKxw0mHf6019F+|xu4bZBCS^V>zg=5t54ppW zW6lGofb%#-WdbmcX}iwhOHv}0?u=NmTQeK~y(cmzld-KOH_>GG9>>_bN+_%WkxC_{ zFf@S%QdN8wmT1pTY;GD5E|>=ojoL)L^K4CXZu1 z_+szEU~X^~XZh*VN4n(9(7Nb5?Xy(%FU-w2<-+Mr5mF=D6(9AuF7V~cA650b_yLDI zaW>=qSr4n6@xx0S&06&9Hub(H>H)P7O!VTM%qE*h28}&aSIxcS4`b%vzTWNHM*?yH zjShluPbiwLP~lLY(qH)7$T7TRkR_7l6DedvF)P;o4WVt_fJdm-?cVk*FR`*>zS5P@ zE)1N=qMJEjSr45M5uZQ-&rx#;LmF^6QgcqNB;WwdYXERLB1KjdQZVbdAL3olJj>A? zOkBjn7wh_TzUqq~k^g3@F4= zl#-^u^1tE$%&Kh-YnF+|g=to+0AUaUwcMD5LkTOkbrYd= zVj9-m>cy_CcKX*0*PM14^J2YAY`lm{GttwXx#CO~@OSmR>ABl!2G;PD3VRI^s%7m{ zT~6mczgBTpfp9gh{fdkFXhE!mdAov0(wDKxn|Yec?II>kc-m8_pulQa?ZFUck|qf0 zSq>EEHlLAFQIR^0t`9g}-A|VW2l)ltPT0(%CJNc$B5YJwnZnUAsOr_Qhm!WRm}JQ2 zNEw_uOiP@ad0+KW{jEi-vt8rkB0e3b$8pr0;ot?rIV1j;r7jrMo)xGR{3;)O!8w zJBBri^KyfHbouW|w$_HqUGi8PkJe^KpDObGiJAmBVL%F7PY?BXb{?P&AaP99)!94b zr6x3mO5~U;YqB(vQV}V)Tt0Qktu8fFcYzhLc-cz1EoFW{>5Z=8Yt`m??XSgQsfCJX z8V}v9g#25TAyv+E7GEd!o9x}U`d&Bl4En?oxgU?1*(NDt6fLIK+)a!YOBzYl96jr9 z#JX6%t^eP%5+UtF>ir23a0h&Lwd`s5gXARx7EfpH$L#hJYB5^(u%n!JTaPQ@w`<+5 zypv}PofSP>eSZ`1KO2Uk^>TE(p7uVkf8A@|@M3;{#Pxji64UymP!vN~Xbjz0KF3T| zml*%BtvxBp+dx^0`a0|Fg{SK@XP#h}r+oiG?}vxYdF5`M?a`U?Q!5NmfYk7mElf!1 z$@{aF?p_p4J6csT7ZP$90%v9Cu5zD-C>iSlDWoN-MPgrv*_m&bvHN1#XeR(DF$=gF zcyXQLt|kTvNVk9;;G??2k-EU(kU{{!U{u|NTXLo0p_9`|Rid4hyQlhZbZ!PE6NjBr z4a{diGX7R>%(LK02mPgl?SaX};BlzduA><{HBicqKQqW~iT<(}NT4BIUhV|RYCd3{s>E?sk!a*4Cd7sqSx5d)~QfY z2D9*cGbov~2=T-TX%ImS7S>KV-rf5%>qwJ*!PkaIOjpGFwn?R79+}n(+g0Xxgf`|* zDjYG@ZWh-guxsXV_Mcz5!L+L&LzG-|e>+}kew!V5-!C^^rkri>Bi~$~7cnRf>V9s9 z6NF5sQCmAFbIyHkHC;QjxNVKGXO5dg#0V{|_l&yWRnOdgez+~XY>ZSPVsr$E0tc5< z2fA#n6KQX*&9}~SpirSK(Yi9OlC`8+8bbi3kR-*8g>|5c2o2sUefaXmjl;GhuJl47 zGXVie2p8bu_VqmNQivc!;PJkVtM~U7nVnY=O1<$3Z;K`d>#$OE>*u)CN$c#_fy&Lr zu79tntl@*9r7A-{?~@&LE&Ou4n|#QSOC$e}MG#Y& z{*CL2D~4t-9)uEbrPOMY=3Qjnk;doi7QBLV&kP#-C@_~=;O#cYX$}U zm#Do|Wey`7iTX@8diC}NzPz%0nXOObwt9UA`Tq6&@y(t1ASBq2r*=2ZsefAhQ(171(0|AR(cOP#Q)XLsV^2ThhUlvQBVDxg=LQ zy0b`bhKDX5$pR8F#43k*M6*@KxG`gZNOV9fXsy7CMmZCwFPhqrA(PbR?1W~rKd+}b z(gr0Z5GhmwC_uw#B3&5*oI+qwCMVcYBeThB-*h1J zwiT~twV?rW2(l4>dXr%s-<;swx;qvg`f7jW2a$>>n3b|>lVKB?nAw-Zc=+2N% zk|>r=?Cl2>LnIXuROurbc1*jCv)eWmt1`ndjefaPy_Hd8vk$*?%bQwBMdHno2nH#I z!jMQHMN->y3NNVaI7Ob1^SN*S{qU`q-iLYhUtFX0tOQ$pJrs=S)#=4Y1vduWXg(KmVJ$~kgd6~0vOv1fQ3Cp;LH#Fs~UQ)d-8Xb@uO05ER#jBCf% z@rRGh_UEs!_o@#4HTK^l7A6V1d+;Oq01m8R=_7J3{gPrQF%DoNVw=ZZ7iCguGXJ>v zy6$*=jf}0tROgCVSSf(h+*^rj?EoZ6f|Ql_Q4kVZMg#?nh!UZxNFBRED9Z%J08b6b z0EJ`+3dv?yKJaNS^Hi(b@aT=xcB-ASqg_rS8&~iDB}IiXlcHedGIhYjdnJ&%fC^-3 z&%158_TOo@ztcKIL}!XqAVU}cW6FYdB+BW!XIpoR(fTSgOo-!}m)9T{FLou3%h0Ai zX=L>>SCX5naYWFWJLn#=dC3jx+B&um&u!Zy3N)}x4_=*_Or|}nMP*B5uP09beGKr+ zJWC%=n_HR~Bo7g>;19Q*zdZ_bZ`I?&-1_={ZFL^=@H!n5u8b8er%&>!_e+R?v6pc7 z;+zbh`Cy1jaEg;ZY(RqmY#V>P z#oVx=7d>Mv5_6D11iT0EAwW?^Fpi_{H0}AiVSK}HK3-cqs;`?M*B_o~E=?Qbn6;Q% z=aF|Ju_KF!t z-f-?RS-pli-i#}FC>mF82p!mr~iKlcs;>q56J^jWg7%5A6hd_<*3r|! zf*hsQ>;5;LD}p6Y-9G&#x;huz9?e|JXJpQp0qHRB+ry*vSbxd2x0{ECN(*w& z%(u)XbYHAGuFwiv0SP0pT8q5eb6)Q5ul&uq7tYzY^G7aHYlD9K5wsdAhrXTZTBod+ z+vV*w%h7jUH++4W?`eD8-Ji~c`(Altg!B-Rbw?CYA$F(Awv$>AuMDe6B|}g5TGjRV zuqKbyJLXC4SsF6v%_@GLbTwPSXH0| zPigJMs`g;Y$}tLswtyf`p(x5Na1m*Px!}?aH9!Dx4YW=Tx!z6gIHTsY{*2eB(i&#< zTtlCoPS`g?T%n;|c!H4mL^WZd(ae|)fTScc<1#YagbbB+OU3&q7iwYdni_9-kIi#T zJxD9bxxJUp@q~|>Fnna1U&086-j#`pg_>l%y0va{|pmh%_V;Y1m`&#+g`0XZ~S#X`yI5>3xB!GcOG?z!|(K6v8r8PkG>rbf4aTX zwr<_GYkf1X(0bM5o?m*qQ6F#E$bpV6rRx?lugjv)r#aY1+QqBpz#JHiv9_{~X8&_2 zoiIbSjAH@#t{*$1*NOMB6Vm0=o`wFcdhgBK_^@oJYHK+yL{bJCAPbCL6eeDQi1=nY zDpbD6M^w5o#SBTQ2L#|*E=0YUBDlQI<_&u1Vc*WL<-L7iDwN!5a9CYv(##{3ATxd) z;Z!KWM4^yAC+|?eLmYbBXojWGGt|`-9&OOMV!6-H`BDW?0Tn`y7<8VQX8t(}ql2Xn+r}A(n965F_M1V;I+no{U{p9U z%dT(o-P<=m$1QnzLA`pO+Yg=rpBFjzO(=s=gc*j*Sh0a7Go~?;>FbLe-t-8O>dr6< z&6G4lpaW8cb^vKbVos%Dw5v_(&^CGUQ#ajo*n3mvwuV%5Roj#h4f!Cs<)L*Vx;hCs z2HhMWF2-1Yb@jLP$F?*KStnqkT}#U;83$~}8zMn)6dnO6z);Xy8JYT8dM>1J3&Iyb zHBj(VU;Fcx*^4WzzTJ!UEI!IEh({x9F2oQkMOy~Vd*3#IYZgL?Bu#y2nxTWGu6-1? zVT^qh#t1UcubWfCyB=d{YBp&dT-V$$U)y7O2ruC?tZ^B4S@+y<7qJ9EF#NIWhQ4$( zGE!k=lVob5kq(=pVYQ+c{C@s6R@=54KRk+Xwbbe8?>=l%S6Cv6=M6C}?xKZTs#X{O z8~);|oVU5@lF_Y?NpJ+CJl59uH59AYg|0%afovmo zOHK3HoqqdjV1?f~V+rgyEDd`L9cZXu;=C4*4sM9J11;6E=3MeL_2D8`6&7zk-1e+z zQ0N1Pj}OHVO_*WrB|#XB&6S7&98nB|1hnALBxMW;2qFh0<-QB)DW11mv%7P3d+1;+ zdnC4?U5P&Rbs}$nW;1Cq(ql@y9>}kbW)h=@&n`x>gvz0oL{ma^BFSxF=*W#OPNcUY=KrE|B0YlUQ!-x>WdDYL}KwKZ&D@GOv z?@RA}2{`a}M^cz3OQf1Q79VP#h71J`d$5QPiO+^P-G|aLu<0ZsBUiX=xA!W5k3=5W zr;&KFWpcPdH{B($y2}?a|6?C2bsxsbfBr;)-&AHZEEZIKTm$oNTD|u5dCP{FJ#gvo zf9;>D?a29h++QdG0?LOtm-W+J&23A&)n+1LT4~d%N~Wo}Fx|pgqEJl!Vb0}xb0|iY zjOh?J97rr4R)wVOl`qVf;b-hrN2Tl-yf#9d_WFDGKI{6bb!xQ=y+2qMH5VC=fYa$c z^2`FPZH z{v-@vV|i+`+pXh@N?xN7K2ez}FQM2~GQkudp7wZag&0BrKh{RuqbqKDs54~F&D64$ zu};-=T7N&xjO5N>4osjS4WY}e?CutN?#g#A!3-p#l@S{`7oB?>M=QUMdPH4$dEGdk zU1IMD^7rT)aEjCU)VdbbC!6+10fs0my3E*L(&xb-Ze>{V^lzD2GdedoT>(RUl=%v$ zyQsH%p4JBj5ACO!hYwcN#k-1i|G&sPd(cZcsQ_eJ@mORU)-!lE7H>?S*@R>ZM@xsTGlOW z44Ej*v-$|Zt=~PBO4Va?mx$(cbK6m-(M{gGj~8|+GGmenlsU%E-I_HkJ4FY*aImv+ zF>$ja4o_P8p|4`(!*}3(DhYL%Af))2P{di#0*~v6t?=BdwYWL~&ZUqu^)*9R6*;Af zPDuIx-~7_^ZJdhOv-$OMXx%)gs5^CKhvReI(~{G$6kND8!d0YoDY_Oi7Tk;nNWjbx zA>HUnq<+M_bM1DU(~nM{qM{)4CD$Kx_r{Z5Jy~ExepA_$N{~2mJ3gSWA=k5j_|{r@ z7n_RcJo2B`#*}M$g>u~Qdgh&3zK@_d=W`YbQO0Y%XL(qPg zo?oMD&z@}rYx!Y1z!yw&w(c;@S~W*oG#!I%ZXT#uuM*um=_9rZ7v`TdTWW)x-P1q5 zOn5+W#dO8r@Nz-wtMzeiFm3C<-`2DslJ4TPt+Rq_mG|v(55M!E+tp&LIex=cvhUO3 z!}OQ_#EhiIQ5ow6DO(Uw*%944Cq4AOJz@zkt70Zyz17xJkOd_%3I($g z6#Uo`huVrQ?+|m&E!kdY(?ORKk83hJCzyl_=@)!=j&)dv^|=QU&?1RYS*F{nk2>g( zqk@4Hz-VLv4+8ZNqA zu%w7Y@B&`Qj8%qrbU+k{lCW!LWzOB>w%27~g|XJG-ApKI8SD%V&c!&?y0@pJ;Cr-i z&eAmdk#nb8M0Jdq1u=`cV?mBJ-Yyb;B3I>G4I^p2XdsoD7;$S@k+Gsionc4hPJ>Rr zjdT3)l5clE#kSZAtrcZLS`Y!pl@8)yDH)lNf^l9e5 zaozFS85hB3-ZW9-z%lj-Ks*-kjg(CoCGmMtpW!gjWKdi^EfGXPAp^G*SXlv8fZ`%B z8Zbe(O|#WUkW-YL&@1NCj_PI=BwC0~&tQULX^;#gf|eNq&~O-*5Um9k*&E-WZpEt| z#m8+Qw?6)<@;#(8Ko$c6N&z8=)W#&b?1m`#z#zn+f+%q-M5Tn-7;S!UJIv<_MLp@I z33FwPeD&8~=23a~X^C*CMIp;Tfl=;M~wqXw!u>AkqmskXK}b)#YNx!umU{| zp^y>b=eC^1onG~~K}!{!>pMH^F=hgwnF8h;J)W;}GkzH1k!qhx#JXdhVf?J_#j*#Y zs)i-h5Nfo7z?L}+TRvh1i;Lr7XkyNCx@UcN!JDTBK@xQ=X{Nd3>1+@W_`NS^|K`XRZw62JxDI*|*P}Ial#y+jb`Z4Zj-XPbbf{0lxW<4CJ=*ly`rGelE z4#ZNA+=9?5c%~^oGBvoBQ#3k_D$%?YQ^pbkb<_We#`9yh9JqW7M4L z6rx}P5Sp>4hAfo{PyrT(k5%n1b2_$T<_Ol{N8Sj5GJt|A_-nw3n-6d57M%et>a&M; zI8?%iib0^5mY^`aaGyrxHJ8%kS07uDbQaPAGqVP>KtaHPG)+?&gf2?IRfQ?oo0wv{ zY1d@q)5BsVXu@ znSOx)``>Su$9tiKHpGI1c#t^QXd_AMI@8;CRy;wE4p%ToRMp!3Q71I|$c^LlKxrN8 z5|^1TPX+BSIj3I-M<~U(UBGps-Oaie8>Lzn%{Y=IcC#FqmgPl@&6BNazLywVnvU<> zxiTXOnmRE~p{Uzf%?s!d56pencDu{7d0JC3 zRjIh-tw;MiAFIeI|JFRQ!927QYJyl1nKAr*X)Z2s&;Ur9By67KqKnwW&2%*PH1j^a zKBPmhG`hJgX<=+yv*`2GFB#Q9Y*5k?GvCrwzQq*|K&TyM%206(K@;q`9pLJitzGI| zO~lG@#!xW;q%7O36Xu@t)RVJq^Ew*G5+BFp+wQ}9Q)3>lnpGL(4)O;!6!OZ{( z={W#lCp>8qvsdr44z_ilkDt1Kzn|vI*NdOCeCB8Lr+XnT(QrEOXoJq!VqIN3QUBYi zDqB~zD99NYWcj(iIXNk%Lk6k%oF6QC&1pX70q${lYzGeHl3@&%NO1&ZGb1&9xwM!{ zmc&Ha2nYy|81{CDB)p7e#>_ZGN{CumJ3H1WE-SPeL!VDZ-L(B#o6!0T3ebTvmw7o~ z_{-N;o|(TEL%&i-=Ph18;}qK$r!O(@9CJDksi!n$%!*vae^c92+nHG#rU9u~ng9(7 zN?T*)RAoScm{2~TW6L(F)ohiFR3%D8tXpiJb6+c+#sjYu@vGABIQI4*?`P2Ru|q1u zY|vh`cFQVXN5LXJgZ26v?#i|DwiTBvPhvWuM{5?Te^ z0V3Cp0A!-alr>V!TfxG{siTi|Jl8pFpWNFU6apFmIGzcK1|lLMga^l$@JuH~jS5-y zDnzBIIXp!KN;%I@??{uDH|(jVNP&UGX_$9>c4fNafM_xA4JL*&ioMU=_bbKMA3k1L ziH0|&7M{WiQpfJ6qq}1Yh&GP1xA;^u`8^qcu{yX#h0zC4!6#A&nS7|&_Lu1E*vsJJ zRjop+ZkJo_R&>|lS$bgL6JiIYF89lKe9)l_WY{pyMdjvj;79T#qepj*}KfLMd%Rt&tcFRq!Dp;0g%1YB@?3acluv z9Gqc4`9+nPBa>!&Y=l%%H>vxv>--Xy$UJz;ObS}s`7s~Mh&H~(qfG*y&;p_B_BW`7 z%_f)X8g*q0`Y4brG>C?3ylu-eX7D-F#@E&Fz^?p$KA{+9$JyNKXuj<&pUX@%kOA1u|QVG@#58@0|14H@$Ml0jEF4H1mvw zp&)I5nbrjz1KnSs-2}}QuIBEey!2VF_GyOo1`3e|2kJRkrzeITdcPlG^MjmcTOOXE zss)d7Q)N)Sj7;%>>R_ z#GdTyZPhN#iD4(4*J~3Saq+Eh+ei+Zh~3OpLJ(jK6T;XbDdH5sgY~dCppUhAT6GtF zpQDjRpj0a>Ujd+L6_8a=Vg-^1E-a#4?Htl#xfJlK5DEua33g#5)OWgH-^US;B3-PY zO;wF5UyV!$fCmVX5R#iyd%UejGmOE}D*q9rz;t{ll?0U#$qvC}oUK_|k%VHjBdHF) zjsmP5T;nD#bMxCH*%J*KvpbQJj#Enj%d59Mo4OaZ0)S@h%s^q=!BU4D*A7*Qw|cf$ z=XlYsRQi;=MGmD&YZkGriVU)Xw0-9;iGR z-)4&7Lhu2`l}l2UGJzD^TrPk(@JPV zYZLuuZv+*QsnB`e;V&|QF!14Xd&Ocpw-DfFxK6Rvxe+cQRG#Un85S{zqd&+IQonPwknBG zBW%4h;A^g`nI2EHTmn|soZIPy+-DeEu23>qi-cLL$$GUuSN9Up#v|>_HFUa{-{jRv z4a}*K+LYf_(!pIyF)7i*9?tFeXB*}#O8WjTuOz@811b-*qiVt27{e#)g?k$Sfovtg zNKOp!n<^U~R2h=8J5eB?*&DszwDid#57=1x0T4M`=5MA*|PX;D>u zo$gCi@zAlo12{-o(PKL0n7CpnIz2|nggp))(jGhv$7&a_2^MFY(5BK#!YUQ@+r`~Z z#By&@TP&AeE8CX z{WsHnetDLkMxXssI3|nS4z22{^8S`_x`P&;?{fCXr+#)s`9@KPVScZn?`4EYDv|X| z2cpmU*S7`41CV|k;TX|OCQL|v&=6#HqNqWMw6qAt(Gx{lAoKC4Q9SzPK05L4cFc^q zt`M*>aU!GZshPNa-e=!E>edbw`0|MI-8;v){Nsb$y;Qf6#pfohDn(+6ls^z2bLD|I zV*8qBJZe_2X-HP0$-o3}IW?XyrsR&jGh#7zah9(!6bxg(ihfgk9a;KIjtcrP; zg^`{)oS)s}#>sI`)reH;z2rrF+za`>PPuYT(PI>-LQv|Mtm9W(YpSM$Y~?(guBbOr zK0+$9>_CI>`jP93%0k-ZBMew4d${upUf^3i+rN7+{q?frE}>4M!xNK$2>7l`nMPp_ zO-329+FI~4;e$SL`3gPqF@m9#hS7j$0v;>@xI$!H$MlkDjCqZ!0W%Id;t(aP58^Y2 z2BVUKLmxCj*sg#`var%X9QE^zI{t62({gKDG@OZqQHh+TuB-i(w>M?>p+Ent8lD$$ zb|eC|3_6yHWu_kwZXI^%!uE-Yd;3HMGUS6rp7-OHXLYdRSq%m`Z1`I|eE0-G(a_hRLr(!D?HOf#j!zw!9Yb4l{EmA$(2v2eQc$E zq6K^f?9(7kvn&B1e z)SOgNromSliMQH)CwolRgM$$;<0+{*>xLS#PU{w}>jA|yE|I9& ze)sJ~Ga;cXDuilcN>GN9R)FO8smEKy2n>q~k4jr{0tE@5wIPmghJZEMY#J67rybUz z4_04df5XB@jVeChmW&jB!|{bflI5*`wkpwr?l_p44PX}_#S%;!$AOoIgY60~GX1ny z#KQY7bC+1CNrj@F31{DwpWGjHrjKj@Za|U0!<=3+QXPg|OiZ1)hO`<;T`eMjB&-*U zS&)Q!1W7{1hLxHKaYB>L++zBacsw4{^ByD6uA=A2uec5x37vM} zF6`guiI}txUURh1mtK8kBrTb=gbCLah&f|NywZ?58V-9x@I04&KcJEvCeT*SP~>S0B^)cAXp}ms32)(A@1X(?>JW1bqEa%P@jKumq$+8jc@o z8qUY6awMLj5jtcOLhe3s*vF+5hVN<+5pg0Ke1HQaJSnhOkJ0NGqr)6l7{(y&DU>l7 zKuJ$Ga$auzGheKO)LG8>yt^8RPTnVsD2nQc^#>f3 zu#;~R=61;a(=!75FPe4y(_ZaS?|k4>r&7rYvElac#-`f$QREiozO$Zv2(~$jrpXVY zmb;P2=WE&&u2cF4dS&mtRS2s|V^G$K{5KJ7(HL70BZei#2G9jxv8uHYUVd|fa=TnAN$e)cAjTj9*S3m z3no_RG~@ns9(;^!Yvy4uvj-7x-N}-HvJ8dcK`jpGIPagT6)@AlN}YE74clScB#}l~ zpY0R29OKkBHr;xjT_0$=DCoh&G1AJUwlJVA?4M_;ZkFsfnQ&Tu7XJdOaS<5}^N zVQ9r9N_T$FR$UTgj736bL}E^aQI}h)EwaK5T5dKt2F2vc)IeN8O0Hqni zFp%O52jU8R$@K&*n&;Lxn=gJnPRHGEDQV?OXQe6Bt1~ROl#P)9URio3;94#$NQ|@K zxiMHq#*a0cy8hQsuDk!`4EJvoecgK9IM66KVk~F-SLCl+a;j6tCuf)Doj6lL$BJTC zzB|ZPimM=o>V(F43%39|j!93fE&LK#M_n7we9WgjozjWa9-L zR=Y~*@vn9ih_Q1Qk0cfi8y;|^Q1#9H=T z>v>RPb=4P~r)XXDT0{FfloYp&RvdibTBq%Hv-IL$cKMDuZJm-> zx@TK+Prp8FVCOlXYCR})ul1KucWP;}@lmQ%I z_Uqn$4}uFuf{8-Zr7hSD5VokDNKO0d?h_zcVhaO|C|C;V_pa_(X112Ql65=8HL}4n zAwNphhxRKHo1*~(sbPP%&ORP^@826xH(s_CPE74a61vEf`iQu158D zh#4R~8lTY7>qzrGH`&_{jY7MaBBpw6ISw9xqz3>=5AXoM8JkE9a@4S_V$4ujk%#x#3A_g}|?fza?8Ct=p{prSYg$K}3x*Dx7!)VKgE?;~PR zSmBg_fKyCO9ae!2NvQ(jC5QxQGBU-(%N~6AE#`G+p5rmsBuKqcCrqKD)+80%oIy+g zAqOgeI7-5@XP3HV_WCQ2zg?#nJJl|oHIb|;Yf7&YRRjekeN+QwqyS)~4@f{5B@7@G z8DRL03!|Lg>JwycI9OJrWOMD|n0hGj;CTQ{n4FYmB4Lh+6wPjZiUbC|Iw-y}T?=o^ zeDA|UK+|5kn3hSC%>#5}yRsZRX624$M_s0X>N=fsdmvb# zFgeHpB#@fGz>k{}0vUMKO!P?^r14r}0A!7Lw^U1H3Dm)U3VxwR9h*KIN5_+`g(q*AKW!du;WYOLQ|h4bwqm-uo+>N#Z5c6PqQM=5p7QzLZw{| zp=}-ulqZdE#Kox7K)lG@17PW}a(dvnrI|a7tr8o{paAwz0`EuD`_t2N_}!Z6|qhtG)^e4F$%__XCcUtbi>s7Pt z;D&&d*YQvA8&!Gia4u_ZjsB=1qlS(Y@5R7ZMu(A?$tyG^%a(<#2GuQ)f@`n(Llpq<#&tie>j&}IWdgf$1(zb`8%X|G;y>NBGi8GKZmZOiDzt9&fA7c8CoawUPrqw#u zEsixdRBQgWP38LK=a-=_8WVS5Vv(opLXtKD;X`rVdbg06GR5EeY z+&5;vq?YXOU$3sPY_TkHSM*7#V1lbe!L_DsI}aft7r$tH$m<_&6+P9%0ndu{uslf1 zga>$WyAFT_NYnFVeN;%*Qi_RUcVX87fUpGsWVtayaDqzP)S1#!fX99mX#)@TC}C5v`H0K&Ka-&o%eYq?Md*8o|y?&pmG8 z^@e3yrxhXv6d2l)W7xFy5%sWzV}GKi#7jpf0GOZ=kZ9z^dde`vBao70U}88i*xLy= zh5jdP3*!KQ$TqU+tU_nA4r-q`rP>5c2_OvzfP@EuM_YI)I0bvKlwP84fXx!kVj$#} z2KoyS#;@`PJE#BzSN@|0$_*qk}fP08agDSGyvMAAk=0V(jzx;hg`!m?oQUN=0hK@o#gX&;yr z9)PC$s4-?b!|Lk1IyAXSO-w)E8)^XDWI2wtX9z??GLaC{sU9mn6_eh~ajZ-x>o^uf zv-FZ_LL~vD009$8lS%+QSh6<00~}B&)&Rgn%x(g*Ks_4a2@b#nOkt(60&!}F2_OwX zW=ssOL%*Uq0S}{)q7(oRLdMfpU5z=W(LGqUv97-|Iw~!5oGF`oKqOGs9^5P!@0v|9 zytPd4Ug~F8F{sw%Jn^LB9#NBQuJlbWfC^`)m|n7al5Q%~X8KX7RLV5twy=fR5#WSu zYIYgvSf+rGBUm~$IN>#V3j53GE}$uCiZNg!Y9@#YCSj0-*gl;Kal8_#onN?{Qv*2J zbXArZC-79#OKwrh(<{~RE@2EEF=CVhk$I|aQEE4hR$56Yvbjes(j-BQKPKG}8@&rn zK?u<_JXE%Ax7c0D<}MOw24H5v*s)SO*^2dUV@G8?%YzLeQe3FS_*4{X7-@O{s^N1F z6FWB;1E#}-stW7T4%9R#ds(Vxe7duM3+NJBgPU^2wMBi zrX0;&5u$gZ^oo4@#)4Ic+#0a?9aq|Bpo~kC&<{Xi-P@PVdwd3LTOC~UD zVjllbrIETHDNA$tw5Y|l&t`5?urnU%gRgRWat}U6P#B)=H0@llIrf1@xy=u}Zk*`E{*$ zW(A|>UviHh($#CKBMd05K>wxVA_I635=h&C>6v0ef~7zNZwl%l zU?(L!t1$rpEU9F`tTinY-bjM^l&DaRv@H+a_Wr($WGV1`xc}YF5M4C6k*YW<{29WW`ZQ zyYFshhG+BhPx-7UB``V&f}pJSK?t`19Vux-lgd=WLx)Se^(&XTyk7F$2|4*8u}-Bs z?QxfvK7qW^>GMuMjVjA`W zoqPrhH<_r*FmiO#LmYKnN+%?M^c2gc5Cb7Ol@JgBFib&sX-piA44@Dx3M>ahfpR6l z65!L4fF}tn!J0ZXptvj>t&d7(k(udiHWSqh@T?dx0W!d$3?wL$#>j`bPWd`HI~R7v zMdbVrY$pd}>J0-3pfCpzC;-xzrXnD!fO#O)Szfvt&T(>|YT3l%b%pJRvx_XkS^3c1 z3~&Iu4zL{4vjE3&94RaYCH5TD1d)cBG#ez4NZ6)@M1rP9r*~|wd+8J;xd&$uNdXfO zX+qS(G3#D9y1{WQix`JHUK*kqrfJ%;4rmtWua)!w4`@!>5XlrEA&@p<17;RTKngG3 zoP>Z0iE0~|n8xc;k>j4DF;9e(q`I8r-n~S!b_%E$k&0H5O=4$B~7 zYl>q^>Nqxxv2G0oa6~3?ryUZ>ghjj1E|NBPcGWiC3bV38iZNEsB)Og`;Y5#DiycTi zOB+xRz~M3^KptOr)b&scq&u(>zy($XYSe$l$6#n|d%HO?kgP&H{ z>&iQVsX5c2YrSzS>2FO@F~iK+V>!!kKY6ziAIW?ki8ONaFWm2OYh{-W_`u7HQHJ1Ke0rhk|8J zlhR8UTs)C`?`9A>Ljx>EHjb}2WZveJ_B{Pn7uBl{Ui9wm&dcqC&g1@DFYncRD4wMR z>LNpbDC(8dj@HL(V9ky0)O)bA#a9yOUUuUqurF*8C^BeTJ3XKu8*H7wQk9;WvqZ~O zm&v`W-#F0Kd(TB8rrySqei(+lXsvWTO%5b*o=B7xJRNKt3&trVl#UUuY>No`7L8cXj^kmjBoKXtD2kwhR%sn=OdF%!yw<+2|8P3P9`S~aTp~BU2TVW$_2{3M zh7cZLrvM~&x3MFTYHS3|G?H*2&@7Tn1kwPdVFD8H>fpuEYylEYaGJTP7O1I10!;## zSPK(qej+T`5VmDk+g1(g05nVPss(1!&>Wf3%uGuV8Uh}Y=saMU+Tn1WN5;GJgHeeI zk_dR@kqkRF7YUECeBvI;UU;;M)n>B5mPRfdEXR z96{|kX-{)R5BQGT-_GvnZJ(KXdgUlhOqyzK5C$+%h#(@}DWGHugHUQv*FXG*_0y|f zpXXN_0<;dV?8)njH%N9D`-YnjOREeepR4)?4Di(`HBo0t$FX5%%ov0;C1An>JPQ^a zcnOO_-Xlms*tPw5{lH2gE(9C^Q@mQ35;gElNPti^fY(d~Oo1if!31LCq%~!@XAK&O zCSbgf$+X$XK=hjuwgiB<^h`JcBWx2w0zeu-+D|EJa7I;RBOvAMI#Snwhb#f@0D!Wx zvmC*^FQ?9_dJifN1tEYV3sf06@CpG*P!-lYaQ@hpn>F9T^A?b*^1hlfD`8GQK-r%| z!!;yYSwuk04r@;j%7Jc9HxM1 zMw2HX2PJC(&$eO3kTx+W0k4)qz?3mlBm|_1TGKI;Nyo7=mStJo1HcY|S!*Yo1)8M| z6BYo^0$|GK<|IT6X0rrf13;|>Z+CBpxQ8i88~sfKX_(dN7^(r&j-wGoN}>rsItiE( z-~iy6(ljNqurc;z!~xwkoTH0sY1p*700E^+Qd*n41c6azVx=jpp=qEFatZ)x?kINv zq*ZT1*r}wAF%U3Cvyc#`orDJqKqKNY$4dc_!bE>fNXSGp;*ip-HX(o-O4X)O0|`^6 zC0wLY2Ow#uYwdKcU2EI56Xf#%)PfTNW-TTHX{4oqgfJ7x3R{Y1AppzFCcFum_p>}m z&lW%e7Gg*P@LuAm-eaUzpK94%kpQGxmtqf)FdzH z{Q22a{?)gRw)@Og9%}J&t=KVe!I!YS=9s6Im^@y0>W(VI)yz$VB2aqF%c;VTnA_t$ zvI^a}1L9mF=4~25B2tc#%EsbpZ?EEtxegz>UHRjJDBVr1r8$On(tax4uZ0hvX)*Al zk_1nY&I|*n6z*~4>6vMkv2m`inLDZ{vLp*}F=A{EGb)Er%;C{tBTxR} z+Clo9(Sq!Mf_oHiJlY&r-2ch(n{wciy;@#)#!)kKJ41f<;q$@AWt7Dwwxxy}e^Wc; zXX%@osay}&Pl1SH@FL5{ZmgdztS-e)G)PZFBh~1|BoStkuS7$iY3!H?w88|E0KuUk zB9W1L2i($qPR?=r_D3QaBl8dkZM063P-`QVF*uuYNzT59vq0N zSPn%R2l8g#_|XZrMLe+3d6YA@F1Iav(GUMGR-P%Rlk>GwCbTBf6#W23LAXK)R|0?m z(y9j-;w9CFMj}6EPNDEk|gbkzt6Vj?ZScX?PqD}#EYHk$=XlgQ5 z@Hk)$V}EwQM5i;mxM`Xxf&)`QlIUzKBf2C+?~58uDqFFfwP>h*k9!a zrTLQ5dw@W{UOH9>Q<{)K8e^J)=A^V8K;ZQ&;}Zb^Zju7903@fREqH0bt2L1}C!+rz z`ZZx%0FuH(zosdhlYoeGSF-tu%>WQj%UV;$k(NlpvH@6t4M4^$D~@ymGMz*-c1N@H zOe9S}3L73^d5xwiZNO5c`=GIiq#Bho)-glyL1;1{KbEB>fMFPehVUd{N+i%62ZRSR zl@uunX#<{s2>}V%fC(=N04AnL+e@Mu5++bcm^D*?Fag`jKmdR( z9XV7gWlm#^adq-ujdIi#N-4I(kG3=nRp)+xLbOXUd>Z_wA6~}VY@F=_XJ{3m@U;U$ zhmN#$6ws8WUI~=iI&%dHSGaG4)2}R**Ll&`_mlG&UvIK|)uCe@p6IZ*dH8tF_de{r zwo9U>!jIzQ)jI4wyVzG)4(ns{P;0HZ&}ur^n@&tu=+pH5_q)$*|FJLRvw!>!AHU0I zc>c;y&-IUerS`Tmo=zVAqI~-D^Z)qIzWDt=U+L=oJ>$GmIM-1<`KO=k+mpi^`Esrg zcCWXWn)~y}zj&`lUYmW{E@}?HvGJ(5jU4S+?#E)=$L#%)=qfIn^M~t|^f&PZ{q%1? z)2BcBl)0D7^Q{ikF+EP6b-3QkyC3#By!jLF>hSi?>7uNU9I4-brTcQ}XXV4Uf8LJY z@zjUo(JSM+cg#J57eDp#*}LI~d_>2e`O#kgviEfJUEtz zp6T-acin&bsqz!;uU)VsnT zd5<6bT=#N)4*H+I&cko{-0_D#SNxr~sUMtj{K1=@|AbGI|H4NcAC@|AYx|kc+{=Ie zQ6IkhXTAUG%W!-qx8M2s;XgiQeLbqzH(1U$uXy|ANBHz3KAU&n@$PQ&{^#-1@QOeB z>KFB2{)_kan|{!T_1k{vt9^K0**qFamPmr4(~QI)T1H^1f-hBVS$~G%gMy2QP%kas zv(Ni^b6z%=U{Ty(2d_pCuPR?vP>!^!8o|#nQrs*<9WpB$1#oheR?Wg>_nDjhDc<6L z`+f0-mnvz13}E%$vC+0+?tV`2BC;dhzi2|+Uhi$q%3UkTqM=BrWLyky9@Zb(YPvpe ze&=%bEl1JSrEO2+YDP`dn?l2?^o`M1?yL(OFpPM}4r9cTt79)rZ7|S;2hY{ELz`go za874klW7-cY$eHMIN93|V_8UdZu!NY)yBm#nz?UK(_>EHJQn99?RWVB=eBL-8?P$l ziI6;x0vejQ{yc8e8f54Y}(FkjzC+{D_t*_aol)uv!?!Nx_t4I;l`hS zhms2>t?<>)PW4W)Zu)ljfZ^jCZM$rDz7L}OW<8lBn2Qo--F7Kt)%9xH`C9Pjep8Ka)sCBIzm3lL2 zy9!_A+C5j&)Hag#Mr2#ba`kTB^xXBl8g{k`^N~k=x7hS7ZS{(D5QQa|218f zF}a?uy${(w?A;Y|m{&P4ra2-kk{nle*XQwiAoYGz#0aRjG#eXE!H|k^g{lLbFB}kr zqJlZPMj@3s^Q2e1a}xIIukH^KWv}=0JmKd&>Noec%T~vBuRiaSFP`PS@7(UmV`01| ztEhV4wjC1J>F8V<=3}Rn%#t(J&BqnWG%lQ{kuO63kI&-%M?cNW&-wHne%h-Qmt2?a zVsx^n(}&;3g^&IG|399~*WORRdv!iMGv{4A@6L74xQ=sOv1j|SjbWKA$>}2fy}!$R z{@Hx?cb{I5W7o2J^Kevr$PaOx{jBA9R)2A?^XyZ8?tik^U0~lct-@`^`<YbInR&O`>&)g8NN6V-}tnS-}!@j z+Wy(o_Lq0y7w?kq^P$%d{;;lp%6mTkfTtb2$NBC||G<@Swt_`mH2dT9m}wWW)orHl zY5QyM-hcX;@)z!O_~!OczW&sn{|i5n`rQd;IlP>9;z!-e?ykq@%764^{@~-SpT0r9 zS{S_rzWsF0|MzF)pZT<&{#V}k_*?I^?4mtR^F_bgf3A0P`+VuTKG)ypv$_5IyOs}k z%ysgKeQ5Lhd?vp50Uzq^el7VPyve1?Q*S(Op8Ys)`?fJhF2M=Znf91*Ik`|J-o*QE zw+L1#3`7d199r7E`H8NUK63ZDukz_}M{)1ci)$AbzVcY5I71^^sb)BvQO;K7nd2*;~V+k8p)((#}S+Ux>vSPyB?!Kv6wtH3zS#!vH ze}A0Tv#-l^xV)NuZcCFYXsg%D4tptz-UauTWOw6rJ>y1~_uzC8%aAp2% z-*7PGplNv#2V*Ms7ls=aCiF^W(ZV`qo!&bbq`(++64a!4T9^~UTDy-9y~{1Pdqbq{ zgQ(Fsi7=hmC&$%126A1Uo~-UUuS$bi3P)yA!ZHRhwtS#ANB3Vr{zmMS*TAro1EAD z>$P!P(<|KA_qvue_B8h#iX0;u8K1nquhehE@z#}HdwH?!S(F@7c|ik+-UX{}1|~^2 zgtawxrLu`zH;?c#ei63$7^ZJ|?flVpHSDhxA7U$J$8e{++*x2^wm0lVrX>wfYN`&g zP!(JqBOA-r6V3M8yC};SyQ&t4^0aHu7`X|33p_VE+`Dd1r_4p$sm+bKKmzze;*Ixj zBINXqdGn=xzWOWGv})_Mi>##&YcJ7xV(e-1<|O!q=gq^ctd{j7^WnD@e4y7l<)T-* zEqD2#Pxs~aq7dXKql2G5)%0syoz)6;q0ugM#%hF*XLsa~z?jGy7P@C`Q8g4aWgXSE zU$LI= zsz1LZ|E5|6vb@W2C|co}l`qwu4=4K9~I^r|=c z5TEpy`_umH?fclCm*I*Y<4Z5=9S#1AM{NPbKhcl={C{3!zPqjVrSf);r`G+Y>$7*b zeE!mlKlD4>Im=M%Baiu|uuMKLm%n$MR%=>YDJ4d0Sfa&U+mbpM$Ca2de;Oi-BKz?6FH~uYT9pJo8JqcK;)DH5!ga zBfR5sWn`3_ZW9GpIMf87bTeUA)aa`&bapTM7XK@G?7#8aOro~!+|>dYSNExC+oS1x_{QfGa${_y_A03{lcI1rltn^1#tQP3g6zDbSH1Rba|ilm zR`M#NVuS;Z`zILQF-sf z`n(_Jea(3a-m|cra>d?bp5g5gRjYHsy5`T- zIb4q$UN83A>I=}Pr?&tP{^oSj@xdh-CvOb<`mSG}e}_U2W*$QT&2KlDp#7oiCh&xQ zb2FN1N@IH(0qtrrG&1rH*01#Jg&Rrvt!_U0+lbYy7Ulbwb-MgzDUvoQ=y9X8%e&Qz zL1=>VT5e40jT{zS!^ov=clE-Y1wlwf^N_4vAXZG69g;U3@^cryzQ}&-3dc4d%&p5d zE2sU!!HKwW#OLdj4y}yK+&Q!IK7`5%yFyGXVa@)Vdo!;X1n|By(U?Sl!s=@1vIMDrgC=Sd2#$NY;GpTCLmqqBr@3(+(`!vNRv* z={3)f;dHul-rrnE&+XLRvGBr)8t4D5AIYvW| zS&2q3;~0r?A%ukU{=b~xQq0`D?gT5Va+nNaZE79LOKb)2>nk`XPU@(6c=9XRm+2jK z9*_Db^}6@4?#+Jg8!ZV5Tmx3n`7A4muJBe@!V()_f~P*~tilYSJtdU(Gy%U>z0Qx7 z*StUP_IcSJGRvvV(BTYCm~y2}QX=XEn~9BJ4N{~y;Cf?XZ?@Q;Ie4S(>-*1ayjGib z4pRui920!0sqnsWrpqR=+RY%S(@L&9Bd6?hi=Oo%BWHF34?KB>GuF55<3-ha_}Sh| zdSscMke|7p1Rgwn_5Q(!eACA&humEpzNH2?>*8!9JB+kxBYR8>JM&|`2oBRsDruL` zR>@<$j{ApzW@b&r!c=FWd0%gAjQb+VrcrbHw%^T8(H~seiL2eXU_RWd|5tD7Y^zuW8CGdd-KA^M*X3_I;`77nH$&UplolgF#5VY+a#~r*K4@7pcYRX`Ys8E zLC|n~sU+d{#lm03Pd(*>{VBVRaO-YzbeJ^=M{^{eAAA_$$+n*D?I+; zeB3kdMLfgu`erO5ome>)H_Hd?kcox+rDw5^`tW+P@AOgE?JkFQ$$7!u_q_N*BHHP~ zZ_Cc&_1x#pXcw;U4|v$;0rgDqS)dXAuyTBfc|~*Zs&C8T28*+O-Uy$`3J#aA56|ZC z^V4nkkExgSuNSZS`K$6TH{&ngw_<07-{pVzv3#1J^RZg1Z?D;d09os|=gsB(e4Ov^ zjCADTQSr3o{!NnPwtDp=Y=<{{-Yq9XdXfIbL_?$xsSC%3T-E8=tgI>V`^sTd^^JL( z4%vT>L}u!zC#I8f1bm=+%^5OC*AM{DDKwYMg<88splh=Ao{2L+f+n7Br=ACI9(6vM z*VrFdidBs+!iY-}M)Pzgf;DC(41h7Hl;B{N*Zq+M6V0=@dD_+e`r{kDdUg(#YHr>D zgAMapU(p;7)1_3myhQ$Y<}pR0OYmp_?spI+LqLNa^!;pJi*{Ia`q+#VdFl_OeN zTIJ;C!?-gZQHjQzzAw_{;&zxD(NMl|gAoR7+K1i;yC~9i%I;#)cZ?uB+)Z?C%UA4% z8=3x{cRo!xXsxP?jw zVZ@@XKXL9pP}3I`X}esXP*U(U#Nq|t7e7UgBZQ&w~Jx|!*z~I1B$GxQhg5VC2EikL^V*o^So7U!w zFUOB#^NU3PvoF)Uht7Lpzs%ajn8?huw{XJM{YJ*Qb$l<{=g(Qr#4_&A%6jGs<{aqY z6qUT@5_iY-d9wH4uK#Es)}!ZKY29wD?3#JO>9ZBLsU+fcY`)&%j>D^mcu^1_kO5Y? zY^b5D{q)sc!MKc824c`#9WXkBm5Kv9tlPbfbxY`Pb@P|+S_yI5ek{IQi~MxAprL|3 zTo#Pox{bS~DSc6`L~k~MsLd858q;;LT<7eN1b`A?1rOLJ=?y?joz;<3Iv!LSY;w^h zOyT&pbH{mdA`*hNka z3tjoe;v$!eFwV}(=yF5zl(I6>OhR>qb#acGT+tDc{c4hV9CiQKWMR%XZ@QCtF0s}ij& zbh~`i65qX6SBXsoFcgzyK1)0ghePJ+wiFK@J>ESIZ}KKGlLMq#Q6|b%VdMbr1-eF3 zSsNHRfEh>Y_qVv^)B~?C_Zz*L1#<7XpU)$-c5^UwqAcrsIIrEsjh;jMj~By)#Z*Uf zRanW(=HaXu4|cq`1-IDe=0zn-y|?4_RkQ)yKvUUM}vjcvr(mx#{t9e07m- zrma8Qth@*Z+nKYYqp4Y3NyZ*?pTYUJA%>kBw=VK{+0?4-hHYo0!*Fd0x5p49&qH=q z5-*96kMQ1?M`B0q+;f}DAO4DG*`4TO=J?(3?}vNY^uE>TT<>SL_SIihgmXXS1*h%v z?C_a%-v!%|eYDT*V|&$Jv9z$PmEOqtGk$xQy~5+d3*zlXCx_pr=SPgn65~6bR?;zV zt%N_(;DbNxouIj=)x6w3%lgwi=+#dSdhx1L>FQu8y`+;M7SCRSX$!$zSm{iUVDToh z$?AcMHHOI6To+rFr*XB-NL-vQW7%_gQheZ@HI+>8kTl`7(FCT=tw5%Ms7>G;U{usXk-v;Y!iAm5r{7#@=MXTg+%O*KaGD|tJF z7Eev&r|Q7TY}(brXywM^W|C)kNpCkomuc$bd*?fB7lG5lUwoJ4>Sr!r5G8cDdK5Yg z1x=U{M0r7J0(ZP-RnDsCOK@MQFeo3Hu>PmeR`SBiS*IzMrYJ|86ZCdV>!>giSp91Ul*U_0M;|QObOh`2^)@>$!YT?*k=M=GY z-U(J@)o7oB7c((xw*f;G)HC#9+IU=w;VLRnoH;O8^8l_UU(MALZj9ttH*WRH(KNgI z1o)Teds-F4U*AXg@W5tQ&OrCxx|Z?haVh@ z?B`86ZcRZP?Lb(S$d}D3(c?+*xH~K#ydK)+a4QzKgC>sh^$llzFzU|Hkq!q$RXT9= zJnVCe+YYyWMlaX47Q|{@$BnG>6g>_kT&{#m&U@ucoNac+g6keqUvTJ>zS?Jxe56y- zJDD?(xF3tkHk?0cBoo`0SH?)jG^U@==aVpJ&ADB8-tau=?QuTPqEDieu&d|xbEaAg5qM>UBydyG01r zpiR(Q-UnuWYH1`S2B@)i*ekFt(%!n+`OFhnO6eSH%R{<6*pfUFJ&tqdnL{&XMO#?M zb&&$Z%+lURd_rg7ijUeSk%5}a)?(Qq)<=sAPzSKVpoevyzJQ}P+U4mzd}Cc)7Qgtc z{Je75uJA=l-j-%bMay}G^~=?<$&VlDciaB>_V#Dr;6!kx9WF15_UX1NJ3rU0R^Rfo zxH#M#zdS+KEI3s_Ib~}j<}M)ti_VR}J(At#X7%yPgjv5>N?LomJXcTLr5If+yqizk zm785t(L=GwC0$=gi+sy*`I+#`@3kkCUU7lxJTPK>jIhmvWXY8*&U|n))t}-hr*ki8 zC?B$56v_2wh3m^_<{&JiBpP9ox`-Jcce*V68%&MJueoGKIPox;p%_8! zPzPG#P^{V{1*B4LDD7~Q9mAqZy6md~C{3BVSYaVCKS);F4tJNKduFgMiM_OKmd$2D z<}C39IG#zNh*RvoIz7k0S*-W3=i5A?lpU4SIrxXulvMppkT2U@$lAd}Hpf4LcNr+B)>>{BtBQ(DY9^)<*| z0(v=VICS16Vpe>R;}^D6mpF?s%4P|Z_R&5#uYRr5ythSfjr&gaE}rXRP8_|K&ldHt z9FCiv$SIxTHtL`6_M>Ofuyqo{szO$yoW8omz1QcHzI-3_Lv8&Ol`S9J-ihXGt@G^7 z*&M!aR$@wfOSyU68Cr>T;E9F@b-H+r-e@M3+j4!lSznS>sRyB-cnV@oGcE?TX+}7o zn)`Bf-s4S}Ox+H%s9ukB^*Py#m3)-^4DZVsa$vcd$^<96Dx_eLn4L6);)%-$0}LQ= zb_&R5RUEka{JxiMnvaC{7AH(E>5ItilhH*5l{sauZqn6s7tss$U3G{&I|nzLCc{ow z8O^-G`WzHO1{sIa)I}faBD|z0zQmPz<<|9?y?7lQKCT=U&?h&2TXW~2cJ||YF9-`@>?$|1Gz5*VJ6>4h7>z81i$uS${O2{^ItgZ^esI2Y5R~(=cG%b5)q31vLKZR8me!{dGaJ%j)?MwSz-7>m0jpab9!r=xp2RGtd*{=H;BYv> zR7}Bxl@n)amde$&o`W_;E`7EG?X23Htvuc#J=C<@pXXa%M1D|m~mp#rEKq8^|qjmA+pKoxol z%?k=ni#JAghDP3lz)ZMo-<%0gH4q~u2cp=i3A&X&;_a?bf63#%MLb<>-RLuE)PzG) zAf=07M6pt>^e__!8>h^!?!r#zv;!xCHWhtQiJ%g(6u4}Kz)$V*)*^h>To|9;++4j;${(tF(*HH%F7zx4AMME0(Ngth1M3rcA<`o!!_>~iWhHg+#d5;xAxYza&h|b>F5kp zXv)0-3hF1L5sp>}90F%jEH8elzwCFHFJD;~Pwf`0vm)19J?_22xr}HIQ4F=@+R2Ol zk?ZV8hO+~ntX*%O7oX+C5@cH@oX7aU;kRT-3%T3|7JZ6AIy#70*9Q-0HF7%$=ZlH5 zjTd%wM~gWi<|0^NF6MMd73RqSI#Qe zwmxaa6DB7>K?=Fs(n4{{2gYY=>}M}X47ZRN8G#qPVA<`ulWhP-K)JtvvfuCb<8i1T z$v5|Qz@g52anE}i!X1aE1uv(Jc8hP;NPeXmC<#c4!>G=-}7N%=m(YP*8eOrgB-k}b7>aX-t z>9WN-D1)GLYU|D5<}kN)JBprr_}1n=9nCH0^ik!Q9*fP9Z$!~V4%CnswU%eH3k z0Rbb=ctN1fxjzEAx{wZs9&P5c_M(%W+p#@t*A{)YFE+1^)bHxPn2BlESi2??(`@6L34x)^qjeP53ZuWM(0HGv!Tp{=_yYMeTM$NR1(Z= z_3m;OAy0C<7@w?X+df@4Yv{#Q9pq~D%Q!+Y!LD9NJ^4%97UtKN#pt(M8GkB`r5v`4 z(hA*8hJ6|AnR#I1iNP;^ho`M4<1uk^Z8jBep--TJFI#Ox5HDA>rzU^0i?5dcq9 zzPjEimydKAYS6^MY?kok=uPQD^b-#2*5#|M(2Qt=_f4E?ePOljAZQdcs+G$egqD`e za=Cuxr;ApYlt%NW8Dn4@6P<(xXhcMF8X(|~1b8CNHo3z6Ma*_F`YvWgva(_6sc?Dm zwa}EaLoWw!J4G&sf@XywAK~e8Kb=CeIpFHerHkHt6AO1=j*5pIme6;mc(_e0I#0~A zayUi~PIs@C17bXk_QZsRXDs3ohFo=DD}}XQhZEiBX;VyvLvw3mW-=B;z>G12hAP9B zmiwN`JptK5!&PAw2bEQ{5kduO4)=h3x_W4njfC_wua$VttFLa4S6|oZ@w$(_-tu`D zCU(v%TQ^d`!G(eCv_&+*k{c+#k=hw487`gV)5X~@Bb4Fl>MlN7*>jY5l1d{a9N zHmB%>TCx=sBjBXSu60XL93Tb|N(FEVn@F|72B&#Kg^HvRz}}@DNQGml;sJrUFYNZ^ z>gj+QTLpN*UZJ&q+4fy{uyda~fK$}S+hHiYs^ER&z4r#?Or$7^%Wxfd!LoAJ$PH$m zZ1r)At;641WGhCn;gH%>)2@(zShajhUnnoOvb&F%U|3HCJT zg=KWDkZt>TQggs-*W>2E^VZ`^!<)R=EgWU;t1ozN;xCtfv@th={2o~C>A>IdLpl;%?jmWX zt{ipDC{9Ktk#e^O9jxra5_d^o6yeKQz%Dx7IXmGJ9*^k9V-1cQ$8Q>kq}aMQZ-eF6 z^1;BsH2AOc!Q@K0FWVECR(v)Jhm&M51^H(;$sU z*hBk<#k+F3`royFQjd(IU)h^h1l8fE*+b$=F#USk8g>>vGy#W*QlIG9w5F)I=v5U<#Nf5KK=ihgHylT$5UbtA9kh zu4kJM7r!eONn;;7&G4hEl_MTc3bFDb*C6*YL|=2`5>Jj709Oc$?tG1XF zR!e1SUtTJ=PN%7u*>}D7;!b&$*SsG|&8kXi=cwwmeB%mwhs;k)4WSJ!xXp(zYr9PD z0cvU|F5v2%7AcHjOH0ENxC?qb+!hnkXZye@Efygcs8!H`oy4N*LrKsc+YevkT^rgi z+zS7uCom_-9pX569CQ8U+-Ujy#r;sDWRn_86eOhz^dtg`C2Iw)-@@78806*!8=y-T z+JQhCw18)!0J)*5lY`y#4TjK^6o91j7Fs|^b4sH?Qbzbfpl~CH0u*k9!IX|^s|)}N zmJIAgfkHwLGnhYhqkMm_KkU{u%vgv4O0%P*+sD3bX;EdnMW)mL-q!M?pR9fd6Q0Fx zhqi?)V^OvC(AFD9S=2>H7&`EOVO9DEHWRg6 zO$`P4#ctJZ>sra-Y|DcI#f0IoK1}nEb$8I`I;j)aWmog?O7xIkdL>(|4@6x?sOuW3 z&8+hK_U$X^7Z-HN_GHm>7xpgIK6ZhPWsD@1X+dgsL5Cik&tp(hQ8=ot%oo0Mxs&BQ zcJCW9HzNzhK1yuHY||KRO&&bR`?z7)gL55*F86qN;LbF!UV?(_dTO4ownMnCCp{}5 z-(0>g#fyMfdR%RbPzLRo9}X486lB*0_Ou7Cd6xvqp=@70TK^(BF zJ|KE5+a}h)$G2^aZMod3aqiZ8O@O!|P@X)~$li4Q~cx;3;#F z2Fe&<#=>E-6BD5Twt&;pIS`ufEzjH*uB)oXRnEo@2vf-XBwQ1wEYEBC;*J1$LOe za{g?7VXSnbJ}8e7OThT0l61Kmaeap_Vz!Fo@HkG76O|KrouC|797neE-lnsty9=rq z>0^5Gr7*Oy3n?Zmpe7E6o~}w!*xEsirnD8<_y)HPzS)XGf}s=8nWGdW1%+V)8(e87 zf`PQ#g0KMvPozcK6hmk50)+$Yawq@?BbY%7BND+3-XfHSv^k_yB-VKDyE#wYU9q8U z_s_kFQx>2G1lyf?khfa1s+7iW$T)yM! z=2Lck3GCFx@MRrYW^-<#5W|s*HRqvDtSi+PGw5XR`pDsO5(GQjSc{_0!se8+GV{JH zXCpdv3pr_UTyoaJX>u3p+ip1b@UTy^reiwJrSo>)Ne@O9AK|?GujY(}?Y^Gq{Cx6U zk2`uo?IP^B-FDuZ$7b6u2hX?B>5XWZ49Cwj5AKAEO4BTE%k8|dc;U*87m-#mZMjKm z*1sM*lqM*eL9+siQ!tCR!g%}j@S_X99snrybOQN_ELuORq8zyMT@arDT`*3&pfsMIc%w0Zs^_2=EyfA6Kri};n zou?@JV5eMDhsG!G+7Pg`kDC#2T9lx#{BD;w9ybo2+?Ms3d0U!OxGjy77R7+Ojmt1L zq><@q2{jYIF&xdN=q_cPsfi|na=d?mE}*>UpHI^l}R|JlW0T zbzV*GpV8;-_j~B&NwI%cCqHC=(l_UlR_BW8k7*7jp^k5VT}u6vpn9~om^k?!V)3NM zR_PLJ8=W2Vb?O|J6oLE_(=C@m)xEj|8=_p|jeyHm%-#yDe+?DtH zY;3rYL0#bLu?k+j_jyhp(cD~UShvNnjc$isW^dXL{TR-2k>v6orVqrH8;mQc?p%B;$>_kOx-74Fml7`YD%V1 zR}3n8gRKsK>W9W==8upJmWs4^cA~BqJk^=u?Vql;ZJWvuu1eTl9;OvHdmXN>mz0*0 z_2K#>IksBq#m&Ng-5K7F@_OjqsElPH_9{Mo3jqv)OXuE)|z3{@eExo40`ozdT$aRmgaK_t_m8J-WcA2Ys zd)nE~9IHkgAQV4S7`$}NLJoosgE zQu#N1MAJuihiyn7n+iq_(!sgr^vmAx!L@bBSP^ECaWiNDT&X||ngC;mhY1|~vf#nb_o0lnq?=v| zw^e#5FJ8s;79}R+bhL-N@Ei)_ehB6WkO@T~p@829v-8{x2J^I zZ==gZR_b!5$Ia>|F)?9b$R!RaOL}C{HjKN6T^(Zhv@qJr@LnxgzC6WPQZ#3nRoPgP z1D~&~rG;~hmBE!eYnPYyp4gtT)pu8y7`ale#}qzuQ}fh9ilYn9%z4z~UtipM{?#N+ z*!6K1#D;YM+N$+!+kx7)6Yh^o`>;p$_R_xhmG04Dw#qMKEB)>MO?QCf2FF*ACtuTF z;>W*3Y*)8)-}fuD5%aLk5D0Y?siOpnl7iTztiq5mR2LXoJFJocq0`LtKM<5d0Z3f+V0f9-$DNkj?b(ow1ShPA(SvmKHu@ z-EpE&MUhsv9|J&Zco(|WSGy#Gb3(A!alrISC~@FfT}tqyK3kF)J;i!*61 zbr~M+r(Gnj4vQVBaPSMiDZ*$?F+303VoZk0XfefF*Ep(jpRY!*T7vh!GQrOm%M*&f z$&EeNPPXN-rRVFm%exz__SJIVyzMGm;dbW4?sd)I_H4n;#hoEi1vg%$HnErX=x6pFpHB<)-X@Z7XlE73E4{9P4i&QQ6-lybt=~T&_l1;BuGj|?bFR8X`1^AZNR7b8i3EM zB^ZDJ?i-RXwyCY_BUGYGUWkO;J3u%)R6U<#WQX0C#77)YA30Tr6243GheSOcf1 z$04K`IY7z9WH2kiTTE%qNqP=j3Ir(RAgpZf-l|j;ZN6KNgKR8|y@Eqhf;; z`J*ikm#=d1xclnk4vmwzxX;5%Zp&^IgO&(kRC9+o_`aQO^fnN4*=NjqhX;d#f=VCc zQb-ub-Sy-Cn!3_)oLHE_0%PLyCn7 zmbPTkYOdZ~qRj=`)Gy^S`NM=(BP^amFWWX^@m(MJ;TuJVQ6xPWuO4w{gb!JzeB8BW zbW5g{qhNny0$MpJwC(6b@1~|K0DDsXpQx})f;I+#F##z6w?MYZEpmHH*c6c5TMya3 z@Wzq8??lq;Sk>a6t^fLofs{c!lYk zI8=KI%_~}&i*G%eSKk=E!;~BLr_vYGMz|Pj%%?Zc1NL#XeD#I;MY;Toi@Q%Zx5sB% zH)ATa>P@nz!@ON~MK%ei7$^(i4yF;&0&1q=qXOI?E^{no%tDHl7d>@#v3hBE4?3+f zjg=Pm<8@Y$^YNsbkzu@1ao5x(#}-`7_1RcuGT+T-DIaa;ATx1DS&x1XNdjnFfXVD8A5{Qt%I@19@wTZK?U}b;(!4P zEj|n0LP8=WBvc}R4lkrYLjj>FtQTNk+&a+7X)3@7=jjRv4S+HCJ<{ zvkec`A>VEPvdLBUA+5v*YQB)X6BNgX?dftm9L2-sZL#<+?ne1A5In*p2d|$3T`Ut_ zJH>(Rb#XRxJjGU0@7s}cTSjOO7u)CJnEsjr@rhZz?_K1wZ2#)#ol_dJ#!RE)V1))+ zAeaYQ_0zy$KSXBops%!%q*>m#x3|~L!b3kucbUc2eYi{Bue?l$m?C4ujRk0L4m;dM z2P+1XL61q=Fkh+fY!|u?WnIPXNn?4@5bsc$-}w6b}Ut2YFkdOK`MASXZ> z|G0Msl@!INEiKpq95Cnt#W4c903)NC+GJr--|yI`7lkqSNb;cjU!Js(!#WI&v3hET z1ZV?zrmz82>MuS#S-&QfUYwEeJ}dE~!<5UIz7pj-2cggwWI0yf-g0ifJ^=wE3@n1v z_Jb!{!Bay_TPjGOIxKft4?L(uQ*`dm=0v@Ao+cXhzUq1c$D1u%t)Kv7Fjq)0aM<35 z@Tqc%cYRuwddr@b6=ubVPVcm^CeB~7%eNIZ;cB7ugjt+H_9-6t#asH6Ryl;Wd7n3_ z`pO7JuIb>(b#+bt#ezllIz=5;wlLJE{mD%ZUW;*;wcI6S^|d7>Z7HUT)NBum$Lfzt zYTX}L+)f~f6N1Pj@6GKh{dqs#*7tn*!7cI4ck@L$^qvdMg0e+y!)&eDq1jQ{R@tt6 zfmLN)XVMifC$B8TB^>Hh8M7&|p0$2o{S7;!x z29SkBf&moHL8}2OBtRf3^qy4VssgY#97q9NF=$w%B3w1s0Q-Toj&4IkK@uduXefJZ zfxwv72Y|p31;+pdl9VYWa|BQn2rGKu``FVei|k70aI((-+;XwQ@S=3Lu4bUQg(;l` zoWgwF^DrB?ee~R>WgnE*PyNQjkV}UWWDM$XYRqvUCKkg9gMRE$Iibb653F}{Cl>VS zhhA!~ZbggR{5_4~hA22$Ul(pmIeMeM!NN*z7XGkzvRHz-vKn`N;7v3I^Fd|DTdRY! z*B>*_8Qr~p^>A3GX6lIX&-)VsouMU0K4=?&(iI%^sJc21`rSqL-2%BTTLBNIk6%dltqp_N|B4E?1kC)FF6O`g$9m1522| zI7E+&ho^6y^wnOxx;(5MrE)bM?nu3;bY?P=kEfX9i(eHncgF)nm`@KvE;X&qvoLe<#qA#~q{c1m-E!7Y7fj-bz`1y6=r5#Hg%VOg`8yhYV zigd$`t5D5NpcN&E!vF%{4aI4TA%Q`k)hPf9#;8zf_K-*hP{dL8AVv>yA^=-#Ve!s@ z1QfyG0PF$5H(-Nk01Qbpz~L-=KnpvJW&KaX^>8Ula&JkgIY2f`>`oFTClbe#tvacS3`?ngc2aQ z3WK)dj>ny2(V-aWYP&faR@*Kc%f$@CZd;R<7Lbd+bBe=iQGF$s#>7H$yjtjT!~-MX zvl5M95G$Sx1pR8X&E{j)p_cA`G5Y)zU9(rBJxx7uWm;jLR7omX++J_DO~SwaJM85w z-II+l4bEA2Rv88!6vnNCoUxO@!*D@NIk#0i7H}v=&j-VR&F~UbQsSb0y+xfIg#-e%YFjt^>t1kxQEdz15a7@Xhk^Nw zwbEb=E>L+>AF1Pm8VFnz5En7VkijwdX;7czf@>9ZVFg7TgX60^+td53c*`rUJ<8=l zZzzt7pv4o1M_Bhsu^`&vxG2Fo(8F32m$dE54^PiWeC0c}T{EICM$!sJZ>@~*B5U_1 zoVRco*uLuhS-SB^n-?q+5pa` z%zaQ)wD-3*uiF~AB1`Of=hYjD~5#R=5N1iS@loPf4Wm@{V`0(g)%Ku|Dr z>MVB+F*q2ZY!eA*t)gjaMRvjZ*bhL03K*JTrGxZyAZR<*9I&ehCm^j0MI8(n132?Y z%?hpTfgui$FR77=T{S@r^T~$oHBwuT}R^x1AhS$bq(ED6$8EH1NRFyi*qd z1fvX0@-_ehEloQHrw$l9Kmm`rFal%>I8Q|n7sMHJFs}e%2sx}n$WR0Vu4=)V zLaVEu!B8Q0ZsT<9{AL{K8IJIf6Dtme;@PLG<|A1U3%+ zv{`iq$r7FG$;;ni0($Nnt=Qf#D(12C(&FT<+FU<7`+e`qVSKvm;!eN15=%cXuy;5i zB!psAv*h{Dble(N0ThE+gSt_CFOUKP1jK>1XzNUIAT?{DK_(s?I)>JbsUYBd1L^{J zgM8rbFCOk!Ry^g)=1pEnL;Ur6(M1Tzc$hb?}ZYEwxp$R%d=y`w;yosl_N((AtFUi;qe&efr+Sj#yAM#X>ba`c^W(c!2$Rh z@H``WK#fC#z9C!~Bg(WUg$oe$qQ^jK?%il=gA)M~Au4GdT|g!J1Uwv+evd(`TVt1m zxdhf}S#eCOehM`RfPu3VOfovsB}3>Fs?glS`FdnyA)@nTxy*C%s`h@$(VN102gtoR zBNP(BF+U9;0f%qU3<4%NQ7{jLmc~zn6&(u5L>Y8|IyTCnU48A%kml7_Z#%~N;iwIo>LO>1#-Nhbba`rDvk${r$IJ1mUILbRz3H+# zu{k`u2Xr7o6tIN4U5=M_f-P$Wb{XKbf?GNTR3MNN8D4^$!`ub-EC2xzhamE2(4~4^U<6By%VC^{ z?Ww-gKcLI8(TY|E0(vO!KD1LVEV%afXxixqx5nO_9qkwv*R`jRNkc-7 z_G*ak$qn-XPwvL#W0%`BpQi0WGqafSz$zj&QVY3B!VF8Bpb5d_#@eFZg$#m93t+69 zm&&L?oZ&nn2cmA($%UC8s(4`_*eV?=F-ZDp}=)4 z0Sy65K+U0=;A#+$LBrTj18Xow0a{nLFef-v98TLHXJZ(AZgR2YtXo@hf)$J_H)BbE znVXk*i)LbeBrT<+rl0G&+4=O9-Z(jQZa#0^S@fpHh}Y47gW`}#~g@7AltJ*OPCk;T|C zpx)dVCQuE8V+t87Dl)+`s0cK8gJJL!_02k*r|;%Uc2G^?JNcURonT|!>GI*A4zXmt zwJ#oAc|yh%)*Xj6Yas!nZl7Ip7Z_|6*kTHYU^pR4!)P@=y6?g@(kOkkc768>Q4?eJ&pBiuSMO_58c5rWRJo}sO ze{r9<^B^8j2lvq~zBS+z@0(!!NW4@m;zb?%QWJRaBz#NUJ$C2agO578x?-B@EMbiA z_Sw5MiaBx*!)nFT9WY4&UJtM*NHN&Z?c{WIGr_wvRz9Tq^x-|YPOm2&*{i(!%%yp` z3|i=7{PLMwE|T%O{9-nq3|nV3=Ss^jYSw4EJBhS-O2KaIu8TG9Y%ejX(2HZMes0<3 zje#CpSH($KZ|%ugxI$WGyDWwtNfcZ^4KE){@)JRcU zNr3|h;88rrBzHk64RFvn6v6NkfR-_P$7m|17zCo22UPGVn5IQIU9n3(`B|{2(Hv+n}Es$h^v9PO2fG2C!$qgfUbTa1ARDQ35yxA6%=noZXy> z0F4tSpqQ{3?uMAPyTc=0ZI^G@y*xZ#o>yTEH*XzW>m1?X<%t{ZzO~2Q{UQ2nOugq4+ykSTx~c>H9Ac|aVvfHG)!z)K3SPAsj^oK3@; zZljmGd0q}ae^I{rK~3T%%iC`0P567WAQc=~7lcP}!F?{S>q&2FaFxeXkW+iiUJJGUGS{9N(EF*1^^WYxbfVv5rdo!R;SX+HhE!R z?l3`&BQB0>^}-zvn|2wVE)SOrvzZ{49zVKJE)Q&0Cv{LheB_}GWc*4u;k}W<}J|JCgrnIE5XJr_ai;0gcL-mctPaev5m&LGz@8VqUPnU(^sR}uW zPcTb-{MHz`%k6)+Eh zk*(8=9X=0m3^k`?hj|cCz)-<q_(km>OA}|!sUeZt`1!+SCbEZ&=U@x zytR0!Lq}P9a$Q7PVUmNbG-vQ3&#Txkmvhg$`_fN8dRwmDYsbxD@bni&@8&CvZHZ)r z7!O4bPG$Ri|8VRt1^&)w&Ni+Eld$iDkIHc-5Ue|F`0u8xPZU0 z@RXDfhfS8l4~NIbohKhW#N0VpNN)6nEM6D#cw>98s789*dBhOo+rx_~JP0Gcre=j+ zOr%X8D32J#$|Hg%7o}fOhQtE`5;-6SWJhsa18Wc(#?G@&gF}>cAf_-U3{4?(G*H1rSOqE&mnKD&IBRH)EfhPnwE2yikF zik;le8*{TSNAaahvx_-S4;Ifat`u{)wtQixaUx`xiuLl77k+coR(ZE}o}8jr^_2Ix z_{a(G?BTnA&DY_@gDFlneXDsy&CbD-{gbV(jn{W|`F@cR&*eAGW*bA4G{akjC)O>a zvY=gEU1_|RmiPGpH9X`K!VoG$U5dk}K1G4DfU;g;P|Xu?f+rXtG-T8u-dtN}1i&*` zdw>L2Nx()F=t&Kma@*aOXbsLb!HHDeV3bH@k~rxKIsC!_w$lIbwOU zp$ppef-YzUkGtzx4-ZHR9_FmMI z&8REMo#UO?<;Co?A0pM{83{)p3e|KWoJ`R6CmtY>;6#t%nFs2X{2I?ZyuLy`7a^EQ zg!Q510D%Kl1waD9k)aG4Lcn8qf;U$7putJ->@hg?0oEPvdNHt5TDg1Z@~!Q1$qS40 z^)p>YFWjZ&Tf^SVq}|_}y~<@{!JTT{;bC>wxWV$x52;|9wVCM4XHVY05lrW-b!2sP z`tid@@fbM55gr)Sl<&vk6g@(}jxMF$mWI*vU1Tqt5zGYku<{`_H38; zD)Nr>qtB=1sxdMcn>eguus)W0sMY-D5dy}M2B0VbbmGadS{x0Om z&%hf2)Q+6c(eUQfgTgwvfF48ftOJ3HDT-Jkd(1VfkOOjt0iv1qdbh5P8h8(N?#J7V zgp>cs9S6S!X8`jFn6oDb<}(nmgmoMqg~mY^P`m|cVZP2_MPSe~0RhFv4yOR(Oj2## z$yp|2@j))05H7APq+P5uN_NqfAKk62N#gbf(&b9mm#ev;QZ8Lrx?Hxh`tBQhUEF!_ z5IL38gc=AsyhlH{`_ebH?vMMpKo8ep&FX&@I*8M8I?fn8h%tNmMZQj%EGd2bjo+UZ1nNrm_4d8K zMJWM6kR&~%%x_cObq`QE9kk7vVmoKoR1G&S16-@?F5Q_s0@kdsTzK3s;^FGu<+WZ{kghLCa?H zHz};ctRRhkT`f5s98RokiycX2vs$*xRSC0NAdT0a_)VVf#$DfEzV9!0A`Hap){eGH zLF!L+W$6*B&(Np!?$Q+T7y%rNEcL0+odI=asfPemoQ>L8!v#-3)I5+;kii)93Swi| z(lRCh70tvYfRFYlVvRpRmL6%&bm3pDq8+I#&@zo5Lf8Zp` z(Gcd2UOu?_?7Q#v#1x}H#MO(1EZP#ez7jo{uUkfe2dEMM*5`D<8#DzV!JGo%>1v!H z1jHK>a9|1!l=ljUu7CoLJys{>`szBcb@fHoH>`F&ed8isU2(&d%P zh004>y80k}LoDv&t1;KS4w_fIu(&&1t_QdHCw}RNx9@^8MLOJpmHVGR(jK>afB~2s zAV|PTFvUKCy=GoJP%l<~gCHF|!x!A?@u0=>I)ll*dV4=b=iB}n2PRywB9+fRgfhM1 z%7J4#Hnt~4n9tR>CyO|gw#ZWuMx)ST>O1wB&PR+l-_+>kX}Y_X#jGQ=kyQc}&4k1Aa!1F-*-%flwyO3K%70@Jzr1N?4)s>#V4>jDzziFrH$gH3tDB z@D#;BA&@kLL=R#!&=NlFjKGWU_dW*moRB{K(R_TKcGn4u1z|e zMj;^m{duu4$rk3y!HU4&lJ(E%b8s^*f3%SoYp1k)>wuDf(_1iztE{j2?A4#`8#=Gc zT<3g!eu`Tj+}LCDtu(V_C{F3RO0#pZEL#p+=k^Uh%SN{USOx=w2Q$(0;LGyiYe0(B+Z%x5EBNK44f%oVA6Q-WWi7zfa1pN zQC6BFEp|+~zQa(7`EEmLX?)SubNkc(nf>%HpJYxo#4_GJ40|NJ$`9V8uRQ77rsVw* zKw2OPiKJ=G&KX^^Ll}rtp9C!0Vuik1Q)n#x#9O=koh#OZcKK?U=<{L-F)dD`qsC5{ zn%ov0EErc$>{2W}@w8kvXW&J+eAVi(Mzj?2)14pg+L18Qt2>WNtfH-li~pj!8nvX$ zw&+wSD;b$LK>-I?4Gj#99O@GG0C)&^N5Dgl1t0?-R`M2v2_6+DhE>u~APi6F)<~qJ z7$hJlFgl7GLomq+n2@j=!AAxXXdR&$tasQ0$V>nTItRe^V4Q&^)jHuAl%a9p3FNjC zbCAILAOR8~0TM_g+2yz3aW8m%H>iUld9+wDJI!1W&ID@hc!>zS#WAFWQU-3QPTm00*SAKVi5wiunA_Rx%m;uHz z;MI*G93)5pK`aY-eKn#!fI~e55~3+a{se=I4m8okTAi7^ycP5O^+Bh zAvs;PSTT;cGMjJ+-(tcjL;OIFGUrvoBE4xX3y+JW429;aYlhGNUf#d^hF9*@D(=JF zZ@3bs#s6E8-_kb)XkftLIRnQHSy^g%CuBJqy(THp2bz)$0XqcJ8^K^WqzLQe!5wnr zhpRUT2ns&nkh8;p8^{M7<7=<{&DUzL8DxAzn8f6|D7|pyvOI93e_!$SYSxAP@cs8S znj_yvXvL^r62fHti|7OCUoMWq_~i}ON1kIozsPskD?@s0&w+efklyZ} ze{+43{Z~jb1p{9Jpd@ZYeSTB=#=5e;_W$WaG?s1G=7h|JOqof$Oyw)vvz zM%IXxn%E4os79{nDVNv94S#(_n99LNm;N3-zv(yr`}`mh#?!z!@9xqXF;`6tV%;LK z9`wbE_d-l>)&1^T)JZV)FaZw=saT4~Lpp*2DQyofcmh>Gz=UZTKuu@~AUJ%gARz;* zz`%hs`Z!P#43v=yFb;5_4`qf>ZGB<~frWsub~JUd79nvGNI;aBNnlhaCvgRO&_-hT z9{R)waA*k*4Z{N2lT|QAq5^|M&`h8{HdX;akeHSvtKnsSSIrz|=DeS$;l%#p+U4Ot z=xF%Z&^X&OU?6CKiF3jj6bTuK^rH}b24k-f5D63U<^y4%Bmhd~$RG?nIoW+i6E%<~ zYz}Lbt2#L@QJbWsMWezB67>U4vsP|#fkVmh5tf{yH#sF&TH4Bs?IM@&u@rQ<`cly& zoH^B2SBH4f77!E=ScfqL#D^6C5gJb*YXHOr0Td5R)B^-kq4V0YGG#%?M&vU0A+(DX z(|3eNy1pWJNm*n~yPTDkvgB-XG4U4xxhAb7Wby68#R*0*F<<{+bDda8v#9EKm(^hX_K*L{gD)hMH%?NdunX!2t?5P=iAOhqk#n*2$ID z)P7VGUs*LtW07U0)zpO@cg(t>Icl(Oy`7oXE|>gTp5OfKmYX77tWB!?-Y>#(D7)2_ zrrOo8IHgD047rG>!}8&IrIpJ^(Oa+D1%Xz)5hn0t2PUuw zTJeITA=a1*-au;b*cfwib`mgyF`bbF(h-+PcRLWh`}+OS`Hz1`oI?s!9xmC^m9tqF z<-tj31V}&_t7Y+QPjr<{%{0+D3DQ{wN3T2icZ>op8KrTVL=9`)}4X_P&#F zc%T|karhmN!EBfCmLZbDJci|sR{teJr z|Iq!zM&8k3O_se{uR7E}uKUG98Bz=L_KqJW4;!^JDEg}|Z6D5ae@nqOjC{&-9V^FI z_t)-h^}D~KAN9^P-%yf}HNZK6VZCn)5|t-y`Fi@Ya1Eo@Ms67YF_z1@l9su)LWgfm zmr42u``RqZ+xhYD%0#0lm&>g6Ysn#&7!3WM{fZyrfBY#m=biV=bh)FWKL8S=>xf=b zv#O;L(0DFK**HD}RS<9D0Mr$LV^jdfQ1i_aW?~w}r@=F3 z&_Ili^+qd*nx+HQMq&p&K!9GyaMdujtzy~(Bq63l%ASylflh2S_F9TUrw~0ETnEEx z@jV1CM)N{I9U2@oKHw*yl6vs0n51xFBm`S@7n9>;?2&>bz-Kt(W|6AAm4QW(5l(Klh=*omik zU9uYcBktuEnJzc|B+_w0o$-y+loWJ#Z+$S-X=!VJy_`8}B#1EyctOnrOjMi=D1(*> z=9LL34iBI>3{=3}L8X#@gO@Cyexy9Di*=D#maZU&U}Zs;tW;Uaj)WRtjU`92zP5Jg zGPa2ij7T*UX(5po#6tE`eK*9&AsLvLAuspa{5YGKscW@J9;mNeJvP@HfG`_Oo^08I z18(N#A0PqXtPOD>M-sr(jTAB*q5zW>6da0qAXAJL9RWW8XJsDJg}u#wmt~I$aqBk+e-PvB^~Ndf85Sq`RT??AfHm->rU?fWAB5u zWT-^1)Xg*+c`yJ)>)M0cxZA{8hL|o=yMI71a{r9+mz~nCFmH+o3M28|$;2h{x$>_- z1dfbWaYipZ6ut}K4ct6V5Ij5De>Q;#TlpsQh#s&on2D_bK*f7tLkv1qas%YP$2q5?5NPqC_)PtVWD=phlO9TI00VR;pfZ?23gd)JYz1n0dwOl+T|xcgoCw3EmDZUPN+9 zk!i)&Tv+?W`nkp6x*rwF!5-(Z)Bg_d*>lI}NJ1s0{G6dN$(NH@exPDr;jM*o514rX zU6+c|H6F*v;e%!x5yK8jYif!|u zwHeHTDi@Ked_1<2r`LbBk=S()+Y-(6Vg1-5a zkK)aN0m5U?sxp@b&-`EhfVgTxf84U)t83$^nP|!NGD5QySsL9lcbQAj#pZ4dYH29( zWGgW9kL`qW$^5=NS$B~9 z#sJQhCito1`_fe?OOI7OJTox#GxOzQM)@6AT@BX*T zS(hSjQ@o!XU60)dCrg-K&YVQAN&gkIuU$?j5QZb8E9dwM`f)+UCXAtr8joM>aV~DH zO!4a|n3%Q$U^j!Z8;DvW?2@iu@nFTuh{hE5QMnqJ^{1T_E~Z284c>o2|q zv~nh%sPcdiT@#=uGq&R0^9l4RMF`{{nZy)LuzOqMz-Ue`kr0L1EVm#w2h#VDgo`=t zF9ixFiTb-IrWH|iC3lzdBTKEw0xPYc!;)T{gq?A@?rouc?&$dv=L%?mg=u1NM5|iM z#Z%LnWTIPA;tQr}`+z89GZVlnB?aVjy_;5ce}2-y_T^pYdPok{H;+~XkD@Uu9K?U^ zE_wO15a6{ixOX64ZBco6^?$bb2iIEqA3yLp!C!g(?h1MUkzHCF{qS4-dG#mf=#)qs zeAn(JQLvZQ>pq|2xwaWXq7oD$u_IbEovh1eVc~QL`95P#0P?T(h6&OeNtXDygmY7W zVu0k8qvfV|`JUhK=iP*Z8pz2fQ^WmX9SAjao-fgti>CdXE>{nj+$7X{fn(ZD$Z_!6 ztukBCB?-7@S+6%A{G%<}yOFyra>-CVB%ft{W~B3pu84L0&}N6higg)sb}vV0F|^hg z-m}G2`*(ige-Lhve<^Dhj@+KW`EeLkHB;pA4Wa?3o%pS$HU^y-1O53=C#$%Q(8?2Z zsD|#+s^;(0Cv+D$6sI1Xi{ZSmJucIXaCi7m5XoxOY4bz-c`AH98-9>u1pO%jYfW>; zcT|rb*bA5J+L7KTmUWa+uzW=?&35wF0~`^e1a8ipe`b_2a=UVYpleIvhBU6Vk(ASv zdA!h%k#QCixy`CAtdLwbJ89uE@;`)@*~Vj|KEh@@E|(o~?Yir2G83pJ!G#}+Ou6R2 zswIAeKveJVFSwK={bOX91))d9L9&#`6CS&ZXHUq-`j`FLPV{1?pq(@dhYEamgoO;t zL;BdRGd4y`=JSt)N;0+kRAa)|>z7Y9sS}I!2L;!s?A1fSYN5BvY3y7UJ?H)8YJOJ* zrBZ(csqzOw)~|wsRdB-7&%heqS_I3*(p=xe0sIp=M|lJ}Q$-Vk2A!(+-Jtoh*0SSQ zqzJRaN`>E0(awh4zm1wLde6waG}zoY2027LhD}+ zpN-TYk#uy+k2Ru{?RACQ`thYUtu)2BKA>oxDt}y6=TG^!FmW?Q=_3n`v(vg{um4X3 zS~uw}pc(HOT%FgV>!T>-r!cqRd1*#;vv^j4%dT=}OIzIZ@(V88Bu;qzU@ zHP2;sy?&eq$J$TM2HaTZQrq=USsBe>7`5;8``HY7f)4K$q(!*l)>uhNuIvr%bpOP} zs^TyZ<}bMG)W}z1nb8F~nfe+l%h;(gV9JuP_)qyuZqZx#q0}PKDu~6^;B6(Mqgpn! zdAyzuPJZ0jkn?M)6%xNv)=x?DDVXMGiY?y`0Aso^&}{>Ayr}V6q>Z{w;s404F)oFX z=UN4EEhb*PZ0n$gi|kf?3YKgh@1R4CkAf8GQvAqU0wL zTV*z<U|EOb<*;I*QDKh50?^M;s2uPo0ix;M<_*5~5yo+Nwp5693AmOT&}G z><_mEYG5zKepIY=d|$i;3DDJf8&JD3(baV}E+KsS8@PBft})7YnjQn3DK9XtcQQDP z1I&q~Co(ENTv1DsyyMEU>Zv7jiy0{6X{i(4{>jRmO&q#rY(lU#&&|!zBHGAJ@x)csuHV^4c`%&_ zs$_bhBwr_Y;$R%hQjIy}S!n9^TcIq!BN~si33xkB+M6sazVa#GPad89q{aHXqYgjV zk+j5I|YlXUmRs3-RZ)SqjyBZ3>TxiJK7af>XkbdhHs)IuW6bnPR#aAQ(p%=NHCSwzNAd*v1Wln< z)^e^Zl4H9JoP;nSFgNT$=m(l}iWREY6PcpsQJbg&S))00pYjPFJ)D=g%NaoOlE{ zlv4|K5wVEQfcvdIDclaM;Gx3;`_cMw_uU(&=;P2 z?tate!+|~m@CJ;R$?uf~U}RdQlvq|iBuA6b+_W5e&i*tL)PmJk7@J*X;}%)Kzu2EP$P3wrXYB|ZLTLM;t@G8Yne##cnsM-9TkEOoZQojLJ~_8 z2g19|h6|-zb+fh&$KAM_j>L zs*o8i0qWl44EE`gZPLewxP~M}Tv_ST@$zz_S76(t?MC$k_M@lKs;oX6?mkiZ z5az^Rl1i6Ms#=k=SVo@RMQu?7HDv6elSiFjUb<*ng4wuu#1v9I2)&7UtsP`0w)SCY zldZZkXRW-`g`xiCw)Gp4&z$-{zlLk08te=YsR_p~)iEK|fw7Dc=O%X^NKQTgETdB9~<-wMr`dNoA%Z`P{VS-LM zh+k9q>`i6#aYmK=r(QHwZ1oFcIsCw*X-*^y5_Pw$P{{30YHKNTZ`0b_oXNUD55KjL z%QuwaFh^xhhM~4k=YOS3?r#!sH^?!LGR*VgHNq)Afz|J3`MRd7osuV3^-0UeH6Q<) z!e~Ti83<*O;Iq!*U$)|OnaW5MZ%HmfAv3b~rk!-NCO^RPF9Jb$`JIj&vd1{O7kDT3 zSVNmc8|zC|(I*{{RLyl{MQZq0Tt>EE{!`H+PqKFM$*V723$mql4||(F5$*1N$;lw2 zxoBOx?ulJ))sCni*Ss4L{(6Ra%vvVNlhhlg+ypKx=b}T0p2o@_+>W4zI4dWtka%#%jJvKeY269?&MVi1Lu}_URmwboS}0@MUB#eXkF`LR&12>L!g}tC z$b=ptAHR*Xe$2@o_c?d+Gp7ev@7r!hp!K8aT%Dx47}9@0XT@l&&4T)2oRjCjM1e5) zuCl1uuo$GHepfDW=wM76KDJo%LNl|i`qeG3AQ&+P&-Ha@xFwz56$&-p87ZPSPlFl| z9yPJKb)gSxii#G7D(pcn36UEwmr8lj$s+A%b0SA$LH>JO_g$bJ;?e5JhNrML{sqJ<+@ z(@@w@iSUpiiEEcAVeXO*aEXjfgyjOB|3&ChWKj)APQ8{A0gk#Sf1H1Va}x&;oio3? zzX+%%Vho#sFhg`Lif5LFT<^QT4PE;oKDAiSw0K@WCouLGC;3)gR3`CmOeV zi3cav-ZqK$Fe$Lj_((&c3tOqfHApg|V$V4FiruWUeW6(SGAui-b{sE#QF-X%@Hdvx zG?-E#6+715hmCIxwC6#{475cAARdLWwaq49!LBc;)SNpM+k~=m==!Lx+WyhY36i3~ z>T8#W1XF8U^9dbV&W^6g)%n?udEP1H`PTYMiKJ)1-5;0iQSF_NBpW)q>wd!L*6?NL zOdf~HA9xTg7G$l|hU%Pn!VS)&W!sSMBQYk&z^|MDzh2GTd6h428{>nCS78(=Y1@*z zb|i4xE(cU6TG#gg(nyjM)HD6bTJ6cm!GD4H`RYsoQQOw`_`bg%SKc)FPWJ8Phi%67 zCN6!HpH=>fA~gYC9t-&7dwm|n0R@1TsBU@ zXAV{r>CP$9)qDH&S59xySQ~s84f1;q(p)r#Cz@LAgiW7uj(^~6%usG;JX~p3w0mh# z%fr811d{ncSQpjI0v0DA-sN=&-sez6IQ?d-&lJ6j(`r@&lI88ad&~PuIy};&Jx2C3a#b|429{y|P;O5jHsD zt>Oq>YBk?VlpFm{)0w>M4^vt4+d{eQUaI@Q*en@CRP?L*{NCJmm>qV(o?`!_m{CWH z{-NXaafN8NxI!lRUcHRZ$Y+hOP_T9ZN@9-PY~NO~q9Na3u!3#-0MxP%a`Sv?@tF5W zviHFNdoov_E|Ti*b@>}@HjHa}JYHWJ!D0m&y;06XViQDdj_w|?2FAQ5h_E(?smpHePAZwX>5fi2BBaN)6y zstPzussoLpFbmm0jAWflapx1WE_RZyE7egxVeGDxXk=#13g8VuSL`;y7Ch7DY;Bhs zO*}z8uF4V%TC>zIM9;b6c$1EJmNBcXNnv9M7Hl@K_*u#(d3pp@$!Fv^liipkCtPvR zIk|J2A!zrE(c}gsEHhR%&$4Jf%mCPiBKJH2C_k8k8AR=`bv5J;gvY8%b#wu{TTWhn z^{`&2HyUhN`tzFTT`eJ9V0G)myIcs3?$ZN$Rg9eI8+pafkMR$k#~wEHz+bH_k6!Kd z*DO)?(p;p%sv#bD%fHy@9+zI9ty)Yy^r1ZLWI3<`{}Z}9I39h;X1>nIHBq@ZjDUaF z_~dNrtT`A3AOI9M9dL5zYiHlg5SXoD1oKWVR_;a`7<(bqF_iEk49hp|;^R&jq;b?@ zL>#Xzu(-cLv6x;udHWb#?+i37ySh@ouQUCP)xt9F1!UsWSKELI3HUxl5`V6hUotP> zc1@&XqZ}&hWaykGg+l4j_;6-fX9E&p(1`-2HG3zX%N`VP{ff>>oNY2=D}E)^c1$mE zo6lQ1mEn}tXOH^?57-9Oje~^rd&Per4bKj*3?V?np@xul$eno9c{zG#mXD9KE>Y?-2;~TV#4t`y<6fF>RWV%WhkA*$gvMg3%4E z4QQe|b}=g&R5Jw*m+Ver`Fo49e&T}L4SXRof>iXD?d@bbIwKl9aKhvV6RcQ}?Jv7X zZQVS%)F5s>r7vN{CAkL;b=`!r5-39nd5cQ2o6R9mM{uXmNf-b0jfq;=c6nEL zCUx-Lv0UQaplkv4AK1y5cif>d9VqgVYK_ikxCD^l2WP6=t~HR)Z1EeHXsWM**1Va| z#e$+(ANSss=v-FIwmMYbyeR@B>LXGvbBe#l&pcc*cvMB3i9enE<_ z#}1smo`XwZt>+ua&xB!uLmr13G4X^1xKj0<+fTlxwu^4!$Xk+skGS3Ty{*B#_}IJa zG@}f}8dl0<;n==?QYW4oGhsc)FU*i#4|}5=2;D1126|U7OiU7gWfoYw)@RU2-bXX; z;-)_rhDnY_HY)xF`|4LrL}l530!;!HS6(1`>YujDu|UZwC%^TKGXLr#{&>WC4l920 zRD02eCu}}D|F z9}CIap0#nM@3u>-4DD!-Oc^g2vMmgSvobgZr)X&fA~5ZYNfZQqiJM+JrmE+702D!! z+tWN%b`&H6SEO`%rl*wxthC8Q1pO)O%=YQ(;a*MCADzU#NJXQbqSVspN5zS+Ms;Ub zP~Q9Gogf_iaxuvK4ZYQ<@JjjI&2u^9!}NP+^skuaw577vqUN1iIZ(VSF>P_LU>-6& zC#F})M5hMnRyv;RcGdY0IikI$681ZqlU=RaL*+>iPo4zFEG@Wd{d6djRd9z18UxJd z=K}wsXZ^s{f3&aRgoSfXE~Y4nUA5vqX#Fk6d3S~;ocSb}G?E@kX{Do@9tW_v%Mh?<`7_pJTmw`EDp z(!Rt^PMq1H^a7vPy54rGyH|dICOAIt=pC=F?h}`%k0d~cD&PhU;WmRAWAL~`DV(du z`Dnqymaz$6R)6F!6Z!r$bBUy}d{1cgL$7Dk_f-&co8Y9Q?uYgPCc+Cnk;!)ZUYU5dcz}=$Nj|9F}iSL&DqN}y&3V-16t!_rQ=ME z7i-S@gCSe33{gX6_8aCOS_>v}UX_x3Oiu_H98siNKTHKCC-8|7X3U{gjtNX)@QqIuZIabe2MNXU_^ z)nA}(_J>jhz0M&O=(?N=sg?D;lxXSg;SBZ5pO5^K>wo>9K^a)fKktCBI{>wkqB|_f z?F3D(H`6g(J+%MorkTXyoJ*WJ$w|#sF)1=m%0lkQaRoG&J90FfQ`bl$x^q0vdiAvL z_Fv~5Yh#)J(22#N~`IkpIZ9$9%E)F`(3d6*v( z?DABgv^ppFtQg4-bq3+ZtEB@iC!p_1B7ZGzwABCipmU~o3}N{5-3MOyjU%vUIG(K} z{k^l4G)s;P#MB~)FLyGwylX2M4jgWq%NMycSX^2Xe5*ZDLSt*L=ckyI_Lw1J}kEc0OpXsjsE7KBFbG3GW&9g^4M;UI8T7D9%{trsHw=9&mEavb%A0A@FQjnk%rM+Tl3I~eFGoWVUSV{+Ahcj zDcFdg?H2E|a#mXZ5XSNr?ZA@gmHk(<$S$%; z1@YMA_mP1f<7Oz?p(!4TIf}c2T2-A}RkGM^cFv;QSDrXImlO+Dj)KXFSIi=o+b#A# zL5@DJ=L|&0N4hAG%VKNsT~6On9{|A;&vuqK=1+8ytC*<+`&L^7fj_)|M`3 z#+MXZg5%KXe-?gWFEki3k&%(H_#OOHEup;g+(qi_@-Fw@~W7DT9syh$3f`a#NL4|Crn|vV)az0sL(-t&5 zTcHbH<(gPlIj@22noT{2gHgPw5hjJ~W7Ycu0vtAxW6O*XIpYq^Hkeg*6bO3Rp1+&B z{QCM3CO9E`uAkLxl=W))3e#t1CoPsJuXJrDmms~W5mc9xE`MSo$64|y10w1hq4)+np*mnk6$`*OSCmiRc+KKmT=am>5Uj1V= zW4F9hn_AFven=EkKKcS^niDu4Mv3m^?s8c$NCy#j5_`~x17H2GV-gAn3Wk5Qwe`|2 zDJr$K_?evhrNaa)6o{%#1&hkfz?`)d!0nxBh6b{r4c?H%TRz_O1#R-OKBYpZ)q`uq zZ~bxVWwh9y9*}qKS$&sgZA{8IIvPLgIYt9@Y@h#&@0z%$SQ$gSzsTh^8x9`zne}?_ znn{1@0^$(Go~9Nf;OCe!yg{%*u=)QO&JS?07D1TZ17q0_56V{BYIS#nBF#V12RgK2e5pm@O2ntA|4y9|Mx%~QrLYz2VF4m63pfu`kJSc!caTl?8J_9PRtG~c^B<^E|6OWmCGv3zGLy4)ERz1}<3bS&O4vBu5ZxJRvf0Bzih{OiYh z&1!Gv1ktUijI~A%#A7q?2f&L*$B)W>DPVM)E2oFoJt2^bQP3JVxS!m7!=vbT;n1AP z-SFSgdrWX`gxC9WUcR6rDOHP!I|@T1DS{_p^;aj#t1Y${w#IY4!|*C!{N9svB5^$9 zWo36twh|`4!cJRx({@&CZ?*FQp{}JhnWhKGSY9$M{mmIYy~{h(OS<~P<=>>#({?l9 zU^SFJ1_gp3eqoxoZ2JgM^fpLks)?>iMHtpHka+jtLSP0l<$t$}LCVcpKrP=Z?GT9^ zR+fyU97-sy4rUInj_`1LTD#^}!(81|hT>t5f+04}`z+Mic*~6!zkPQv(X%a1xf218 zfzXD1vUR&Ifo~~k-n)b=zsAO$jq=bN;yebiFjf*(NY7@W_ng2vwphI?-(Ha2XjYo` zyZFBfUW9^d%2OMZB)X$7qiuh7E2w$>SY}vu{l&8ET+OdZOxa(F5pH>&m{#<}T4Upb zf>xm=hnt5zsro^BTbr{V-^X&<=Ugk(gp7)U1XfjRG$dqGYNsF%WyIA0VoI0BkmreU+vS#BO(goh`^y&fBQ08 zpdM6-h=Moi{o-Y1@NkP$EMqQvnA4(vkGT7GXN(8JbRjNCS5XgDC{vx0b0c8t$>>6t z)k_xZkB$rsA7xh(4p|~gMk;grD5KJsR^5R=hCOFi_;zP}aY%&NkqmHd_V^j8dAt8! zL<(!C1|^_eB7HB?MZ#*rF)*j;jc-*#!-i#K**E0T$beS;YN$J<^>*m1Q{(I7>8CV5 zMuRMc#+Hr%Wtm2;L2hYe{~(gZncy0q5?tiOk-$%ogyh%sWHI|dTUkU1;AZ$#LXZF5 zqjktj0Zvjm`F0|BNy}tYKG744B(udjn^U&Yw%y(s)2tUWH89@l=eB=hGJN~2sak`y zH45!?rg8X8pDe{3z^CiQF#Nl0u~(vbxR=Ac+^z+B%#$6An^%{Ntb;)oADi+Qrybk`-G7RUA`t*kuw?#b40dpQi>$7iC8 z=K}Fb=%qXg4Wt2eRlAnMPLANeGW#U`RC>ghLP2T3bAK1<%mOWMxb+-r#pfn(7Kdh_ zk%FCHOkFdI*^YQuOuHhK;OKlou1>ypJp@qjyvOM$kQj&eb6d{9``5@N1WAUF8X3v0 zY4!}(r^C$~;TiY4x~WsK(c9r-!%}mj4JpW{0R8&SyAhGBU*Zqc#x^g^90Z{ay>-z~ zw=mgEN8Ji;`CTJ@-+b{Qx^D2F?;rLab-dgi9lbpItFGIbRirjpd@a%2?FfQR7@ofQ zp}SylzCF{hAy@LhUOCAgC{iOm4%@zTh$kq2q>G2EHB1cS0qu42tC{00#N#U>-(SP;{~ zO7w@*=gzG*ecbdjbrbA1{>ZtKXg>zx%8+u3O`d*xf6+Fxg(iX!4Op!&`vF~J z#_Y;Oz0f~2OiVth8^l+4=F3T2Wh+$pkM;#BxS(T*2CX2GK_IF-g)xF9BooN1){4qb z2@hRUC=4-I=W6%3kAw z`Qs8AKrw)JeJN6xGiY{g0;?A!QPkzml}O;52Qj)dan5!22O!fD92Jdy0B)eo*43x9 z0H64yr}FI=IyYTXQ65-lH`#xR16Y5UW*brE4;QMlHo#otv|~CW{B3)-t;is^q|%9n z03KwA{q4!_FO%dx2MYuy)<;1>&+OOi`rB-X!$MPpQDKLZUswlXe{V-*_4`(NYF)nJ zQ=TU})iI$TE2@_L-!~Obqym+4vRrJWcf^!M1E19EY6U7%M-V*gF=n{;2Tzsbj@9f8f!?_*)|`?GAC&*Ke1g^b3@Y6sifYrV3be*W`Q`-!m^->>xtvB*y#v_X0B515cz`}jal z^2kE>X^R(w%XGDxJgI~)pctvW1 zA!aq$(Tj-6)ik)@#CF`5y5`_An!8&dp0ahwCkoQ3@m!JM6V?^@sNf1hgh&4El@tN$ z!#Rxy!)k@Id%eb)<&0)nTt)l#wr*+f?<}wL)Zay}Wp zyjT>kV%G*--`K6k@-D9oxAtP_ivm@vaCifvXd=^GhTOR_jfpN1#5y5N`fMk9Hg1Mq+>M5q|i@qHkm9RmkE3tO$9gjq?b#( z1H@L(oT-=p)6^A0o~!LB8X3iE|6r8d_Ec#-?9XV>r@R8#7~*pV>!UJjBmT5H0$$!( z%GPzxFfr9ZXJV!yLu{w;NFCpiP!r4df0%L)++C4BBJFaat z#3*0NH!ND+)=_$f#M!8r+aM-XZDzEdXEbOAlfy}vjDhApXc6K~sDdIpt6ga2Cycv! z^E80yBEy98Jm(@#dP$0M%`YdLO^I8(Y)1Eww2|B`#{AVyu9@hX&H|kapw(PHSO*|b zAmX4i_2iBPA}WA`dxc)U7ptlOb6<@?0c7tMqki}xw<{_?-lmKu5{z#lRO(lQOpS_4#l z40^p4W(L=@+k~PiJ}=K7W)6i|ld8H|ha4}&Wb*;4iY|**TWV zuJT?{JZ;ea2^ZB51DtJg=g9etsZZt;-rAHmnk0SE7E^_FB>hI_kB$9`Eg_QoN0P0q zUGk(uy`&@}{$1em?!CjZOlnJ65uFQ2~fQk9=!EWZst@Xw^NRm{BeD0m5LYK6&;o(d@ zEm@A~>bCPr7Hja%Bhn7l*4AL1!xXWE`=YnXxF7Mhf12faJfSIh&HVV5GM!2lJl>yI zo1(#2Dz@aA%@KeT_kgdGt5m*kWR{O92L)h6CqXU$kv-zasmn2 z$f}o104>Ya@)KKiy>fc%1t48dMmSRIYk*z(Fq7zPk!;)G`uD#T6D?4`KG5P^F_>j> z{lOd`2@M(k&<*Q=UT4B) z^S0bAG9bsutER393BFiipG9{$l`6289C98t--qnJeq*{__mKr$@kSrG?nHb(X>7W_ zX%c;WAE2k^Ei%rTX4INNanW_APsppE5SsWEqx$^@8Wxe{qNq9cZRC;XmjXUWzct2` zkEM94P&sMu?*s_a;3z-UO6#^B8`^E*TKow>@q~C(evb_sb1tk!xu=;Ix~PXU_W1sj zR{TYVg^U)iUHqqn=@k4}a&&hrX8w3GRiJC)eM6&>UHh&3Shr=n##d`myr1G`>~1tk<@{AE@ zdbmNAm#sj5Aj&(S8eA;sT5fFZ+xYWnvk+)7Q~6Tts_u;BiJ;kXO@k!UjlVZZDr%Me zjQyiGrsR^5dqcA~nMLzQUT9%lPx3~LRl=ONXjbxk43G}hy63G3D_FUIt)-A9)K8x! zqc5S+&+9z%h8H=pWl#wqc7J}=Du{aL`0?A9O;U!&@IB$eIhbJynIR3_H$L%Q6}x3N zoJ}`qAd?TV*pv@fUbe(}e$yt!v)fLKeKvaC+(P!^F#6fyDOnNwN;v9c(wA~00NudI zFDtCG+F-t1-!1HB;7`wP{t(z}Ag`bo(KGzjRNW?u=xJw}z$3p3noJU#=J>lDYOuEz z9wU0z2mm(KkA^fB4_hm^rq3sW#QHN5ERfLwG)W&C2U=?uY1@|#&`ze4BzHsztx{C~ zrG%EsrCa^h%h}vd@Xw{6v&;UYq!549hl8y2d5y zbTwAV-N(D+mBjmT;TtRD*mDfHaRPXf-4s4YZPh?GYUo?JKi1r7kiO`Ot`<=@u@0My zH~62#s;R)3M_$Xf{UwX2qHLp|V`;_6BAV0%RNR+aU6z+mk(t#|q^YVuehSLOXLcLI z!9<$b^-+hO$J^nh47PvChPU6u-R(GCV?Amgs~t(IWi%r_6On0=qR*%Cd467#GM7;U z{#0fXxoQCj(E0{!95Y6heHqR-89wlvim3X?8=JZs1AP)V_`>*1i>Xn!f*oT9Y|N{Q z*5pe+6u}BQl9H3WeitQ5sb%$Y`jv)eiT`ADo;|m5t znl@ha>47>|;kLobzk1&3xr1f8+C%VCdta}au9b<^M-j?9nm$+d4eL>%Yad^uZ_N+- zZk{i`B%Uu?Hu8a8WR>2W)H_*Zht!3wwdhwg#X2owB>rwgc0PRHP#+|JhfGbw*S;BB zKT`=4{rw?`PNF1S)}!i{UF99lkYDQWJ-_|BfBki~-L8fi@_FCY=Bt#|i_WZfqSC?5 zCWV80`6}Vs(+ltZVj7Vj`A!0kq`2I z;O5+x`NRp-X1&~c$Wm)9j&-KDFO5&UvQ(ODya-bA_)*E+)3|2RQOG2mu`h=$083?Y z`!04eScN<)Fiw2ijQMUHoBDNoZYzIL`^%mf3@4(n^6DQx&rjDK7xJp|0%{|kvOTy$ z6VSZnMevzW57FS^jki-hn?=H&;9X})C}cb9Q7-%-HL({|?WU40aGL4W-Dl;65#|NW zm!eV1-ZfK;q`;=EdsfNWyrZSz0A$O8`;-YTnk!_Qplv>L zX%V;^MtQtaEC?24QHkrbbaVq|%pa^_vR)Fc`x@`m)Hf%>|FmItYISfVvW||Q%e@kd zOC`^GWs5QciL{*4+3VGI1>Ly+UO*rN4TX{EXY2|C4N@Ltej~MvkO^yWi{5eFt8V!R z9U-360$jdA8&zW(_zyaT z#%AlEe6)Q1Son>DP+&M~$IwnkxYrBlI$L*ia-F-P!aKI>oyDWH$`W+Xj7|5m#~?i# z-6Xz&$Ayp|L3o1%~j|8?+{Yr zZxk(pIh##&;v~vwR@H0841OTnfmcJw_DE69Ta?65r-|K6|qsXLcy%N?waj`ViH&p4fn+2KjH*VHUa^ZtyCuZ~nx1->TDi{2-KexMtgvc3gRmZ)!d)qtEJ7^IRv6zYO4- z)^qcIND00g93Ad>hk+r;+xl(+OO`D-db&GIy0)hm%wNT>Z3VBtMS7ig<+*s9Jbp5- zs$=^p(5*68`d~_?=U<{{z|Efa{l+i9!%dC5F{g|#`(83WUfLGUsnjelFN*YW^zJV* zY-)LJsb4=}BK%snzeu|J!hy7J>ta?^AJ8myTl_*XPw`we?mkLeNwoj;(ev###Gf_~ z$Ychtyr}i;LSAxk{9Gs&+%BB0nO0B?8lIfp&pr18J-4rcujC7?#uR!Jy9Hh~nzWvM z`^cyxZQ#nWI~LHwH78#9zKM6WL2ESiPr0FSgWP z^_@wWqwTXLd$m^gmxsZzR)_m|#}ai-c=3yzc!P&n#_X;cH*GyRY{w>Id#qx&%vjs& zIW%k87oRxlqFzMUj4XE~Mkl<`{nH!Q}^*VjtVa~tY^O#=vrIm0P|-nM;L3yo+-g3q_hk>^ci>6rLS_u9a^$T;UFaXPl)tA1m; z&HTJ)`#<+*D5dD{o>M@!)G8C?E8)6ZR=%^TK;PG9K;}xNcm9W+Q^wG!*Y|E$2YA7v zX-#BsJx{N@!;eDuwM$ob1;1xy$Guux{|0$yPfz|b@VW_fGXirbH>uKa+hmaLOK(NXia&nIoB>;vE)*&QlrY|HdYSB#$dI3Y4w|hL7xT=| zy28>G{-)V(bBK!aSjjr^gt+$gyE_)Tk8*eSn{+~+F#P^f3zxi70Z`GL%WfVj=-QPn z01(-tBBTUW4~o-_H8@QOS*%yLZT?H7>6R-?{p?7GmGZLiw!E{}MoxFr;9Wgacj76G4%q79rY% zr7Ydf&I4ub-Y^!xMsg`|W}@c6+WL(sp+M{(&89g^jfh+PDCqS}2#+n~r98b`J7#)E zjh2nB1yzo|eIast>Fi(k8j0u3jF&Oknkk6lH16NUX#5|ka*-zzS5HEGpY%oR8rbXz zA7$yiOk)L`;B_=hPjF%73>vk*Nx=*u(Vu}x^!3RMZ7@Sy)0i>xqid~|!2a?FnU=P? zEBk+Z)9)yzu5Ui9fVa+$HZ1Oz;4C$|tg>egx*(n%@8V3~kBh5XT6>;6oN>h7<=vug zFz3zL-N$TSCPcO}theGBu_N;D{lR{@=-&s$tEnvnIdJS#YnFc6vYm7Ek!`F`FZrWYfQ5cFSeuzR z{6-_nP*Lsol-<|YYA*)q(VvJk3Nc(Y4ry@($dHDHj*DmXfC=`?1701ckG*ffHeXtN+ZTGQ=T+&Ln=`heWM&qZ2y+!?ytPn~mkV!aD%p9|%A&6g%IYu2$n?_M2XYCRZoS)}8t)Xgu9wGAW} z>1;YVpoFc!H2FFjX6g!o);Dj5U?<;wQ_#<5Clpv8sQ?og&YRL~^9cxVlrZmYv5#;S zg@g~4b0%jzy34O+x~(3Gy*vNvbg@5L%dI@3!DVf&bQJiET1zb$rMw@5ylGri|L|kT4=~GH`VllN|e{;&x_>NqVTQ#L)iT z^T0Z^8f(DwKt><#TLp#532_O0nh7p@YB`5j54uL_2*eY`Vs>|))q2dgG{S7cTko$5@RhY9;mH_@xl0Pl>c>nW9iWMwxoMUDHSn6%5 z0D(wL`TX#PD|#T_sQ0E{3aunCNLx>(GTtEYhadS1IyM%9>(43?+H4yf9A8F8r*4C9 z9LF+sjn#nDRNXOJFFuL^`Fx7v`m7pXf<*c_2Bp>x5w)u&>*lS?&CdJYlmcA>EmCRm z(o%>H35Jd{ksuO8e7LY1gr)xD|IC~dhkULjG){|2ZbXk22oL($9X#^neINIu-kVgX)toQamRVb^AT_nIe0Gc{{VnMf4{ouE9Koa6191|k}yzh zZMI>A)v0)9+`LqCbGa!GZd7}w%aKqcR_^0+NM*R~@w{#(R}AUH)k1#qktgelFm_L3 z^vbyHm<*&(ov*%e*3IRpeF%=2SQzmU+3PEZE658a-LM-zj7ZxNaN+6&<%a81*NC*8 z3qc_l&U(1I8y&Db9oCNRfO5U9R3ltHh$S9}Co9m_MbX$e!qqT#MoeY@^O|7EFd2FU zN3o3SV>@4-Vr5PA=baOWeU+7?gR}~*Xe+uBgMC*>8=7L7G@*v^^~G)eL02~>QJNPo z|GotM+6YMBxX^SVjTYbK>z>Ij^Sn7$SgDnv;Lqjlr|Zg(4n}gL&#vNZOnr8=g}BXi zKH@y;;KSaywfm);G4I^OLvQt+C2L%keNC#iI?L4+Bh5WnHt9gZVc^qPfqiZlc4^sm z@sLZGvu=o&v{;`~Oc|HM`ciIodbvD7LLrOuAPafttQ7vv)x;pa%R0m5)yr954(P=q zAq;klrzOcx7*f1*sSE0RLV zwmr<1Ie5`e4jGL8sju92b8wu_K+S3%uGK&F&8K&KbU9PS_+ms+f z)>v(&D5sLk)x?vwTnI0ys@)`& zR#1myg^h#fuJ7D;T{)~2)MxG^`h+59qg{S3f2&z+IJ-o-D zSwWWFZCt$RA{fC~(a3l9T&h|5IjSApUP_|HtPE3hiC%Zkj`3maK_0U+y5vHZ{l>%Y zVM-h*4-$0|UDG`7+--xVSN#8-U!hBW6Dz0M_G&8mIF~&SZoB;tgy~9Fr{amL?NhGk zyik^|>uZTydvi1IuO=;BFoGBpz3%Sw0kb6HN*#PampmV|TI8nhjukdzmtn#@+yyKnRnv!btz*B5nQl$okWfA5~k(T18l zpr=F|WnJYGuSJU?Oa}wz4IasH5RZtl5(1&-%eGUfY=}b;Egp=kPl}yBFZR>rBA9d^ zaYc@l4oqNde;~%5Vp&a39qHE8S=>~+g;_-Q@;SB6w{oez(dF$Cn~!mMI+nNB39-;` zdGNB0?|aXRuVQt2uBKPG{FQ*|5T-^9d3unOsyVME=9o7Yu3VP`kNbpP)PbX75o_K_ z75W=jcljfFd{Xb2&Y;Et31xdJ+;C6&sd+wx^i*eBk-C0N4eKgPNy3Qt>~eMC*%pzW zT`L`5&&E(!@kc-Lfn1+MNz0XeSX_CA?OX_(AD!UgESv|9w(XI{L_94$tt-SUsm8nw z^8CucO6i=Bxrk9_aWjgp)pzcnEj;64C9h2wWkkCEbg5#jbPH4uGz+dzyIZ^XTy1;3 zz@AIm?#1D`^|hK`_Pbp4-MGBa=-=;!sBze6iX)5t#yagOR!#{Q_c0kPxcStpLFbnF_prec)zyA)cQryp2&0e9>&BznXsZ`(3K@7xD_X97 z1XeqLjqB=Eh^3P=jIXy9M@?_;+&%Dkx*E8f7+a&u^C5alj97h_i>*sG?B0AYZ7Ugl zPHuJ=_h`k#s~*l=)lTE|aQXU@ig8&j)<}xkZB)J>4ALsI``By0^Ii+v`&3(=lW(n< zu*jibxmcvPG`>MUc|OESUH{zM{>SJoeLV=W*Ps%k37h`PKaH!+(LxuK6|@n}1a;#k zYP3MFBM*iz<5RSwF}CL=u9Xg-DMc0wJQdEbBO6DS5enQ+ICaEEaDwZWbsVlVqYM8 zvCx~v&htvflx+%wmwL!zzZ%$7Fec3PQr{GgM@(9-cg@|y<(lz{##icw`ME zwXt9Vnx}O|TmJruwt5+L^~AN8%g^ftr`4<=rqHTm<u3?VhYw>GBcJn7gIp0Wbfg zz2tK7uG-~Ev?Y~?%a8+`quS*z#w#zv@{Oa2WS=EhBY3);t6?`qkhZrjt5 z{edncP4~-0?3Es9wdHg)h==?5YOEYAiA6F9EZ1`gEKdZI-Ev*%u`NHCP;cZCdejea zgB{n^PvUmL+DBVl*FMC}Ane0+^^?eD2t9~JuhBt`ZQQ(YT|anN5s9gi_N^O_y^K|QFxKrHknu}&@}^unSq?JfvC-rN_GrRk+Gh{O}FE>|z)1sAUC z*%#=)uiYgqyB{7GBb9&Bv|TRmE~m0hZx^J(!7P+1raiA~*pBUM+ORKYw#r%IabEOb zL7q!BCtsm)`qof`gz}?_`k~xV7zl%)7q^|hC(_f!fNEDahp*QA#g2=wM;*Riz1UrR zD^)KZJuY3odTQ(Ma9yKITYl_z!qHEcAN%IIaMBjmB|>XF;o0&a)_796T0E{?-UnNMh0TRg<%}9Q2I)bgiXhUri0yr`JQb$<(sw8?2Ji?75vN+=;26=?z!wfPs@2x>;?+XQhT zzGGs-Ge)F~T@x!0XBqMcFM3@VAJXMPs2>?`W)w-+`v@^HLelTHVdf*~MbdY^13R}j z62<-W(hfq)8K*lPPHM)y>a~}gRdTeFE? zn5(6!adD!pUV(&i@%tH~^E(=L}G#WhM^kVtYc&u=3w% z&q*snGaFAAA0~_r@Be1*ETcowR2r7hD3h*kaJag{-!A?chs#YX5~Y20MQTf=%ifHu zfuuYn#=H*3%$wIv){H-=hSDo%Rj%Fq`;)(gFu}xq(ViFKq3^q>W=Y;g_Pl<^K)U?g z)$Q}b;I&7t3|D7ftyW5BtE=H-GSSd0iKF z6E|-+3w>d^*xp=dZ!WrcaAMIMoW!y{IdJ-W58D%q;B@gY5VpB-vd|nqbh`X4q0lVS z*Bdvsbsy>QyxKcQ`ayR~m#fv9OxBfEJ2H}~GU4A4l(b)}-M zzC*j26?2ERQ%wCD8;OON*PLyH;(OI@Vu+NXZnHbB5=r_v@Gr3fCil?@{=+7yrq7F#9v+ zdaJGp@{j0$=06$#jDL3!Q~57pT7=`+oVmuX{&C`8_M_JLN4BB8Nn793<+AeNKWu-p z@vZA3w-26ajc-isF1JTovJzk5LI@Mf<(Tz^#Z)f2x^hk4tQF~imGHUk=RZ#Eb4{(h z7>ea_^Db6mYc)^n5ry%~Z*=wR{Nt8u9dfD#-f|&(IXfU(IdbV0W9=#bIr^U$;))J= zvKzYkncPQHofKm+8Bxr({w%CBkc+@@9?zTzBV=cfOD_{*KXadR$2*dPb+s4)TnIa>r68cu-?Gg*+o#m}b zBwg0UL-41=^7Ul3JY$i`(+{Q((&ZXSx#B)LOji0ZF>b1br>n{O7)fzWKX@p=x#P=J z$0AY8g>UiC>mo5r2FoP=j0ekC%fjyxvXG|wNUT&e|HzxH$Hi=EF=A5S!Jq7cJbeMI z4r+3_|0l~*NtZ;bgOgsiHQ}b8wm~laB$bOcDxWujIwL{cc;zD`jkbKfKQTJqVVL!USqsXynG!Ji_b2T7p3G*(PA>pvI9|(0frL_ z5Zw?YNab}djif&yU}JYY(7YW6(qRsjw#{O?BCGyI{_U#NKHf8i*nC$^NQ3baOfo(w zS#lHU$x5_PIm*Qho?PEr9Mi&C966j0ME?uR=2W)%AN||^#DD+%*RS}4eNvq+S1y&y zHkmg0xKqOK+Cl#UXy+#?JW08;^@hD_0J&NDMEX6nipN($zTu zi-(dme~ezy#@7SaN~3ZzDrXuPHOaBV!#+6jMX&rAI#*aG=E^h1)jwgZHVR20=~eU_ zORuI^z@VGE|k(M9_D)-}}5>ZAs5 zX?3Zdj;`v+Vfx00yKk(%sHT@+(BFKxc;gjwU4FDQsTeUX#>5Z{_TfrR+Ag6h%b80o zNg<)=xpZA##7&p)`hkiWa@pa-*ye{Rfk6xeLtGjHWBVj<#jJrTsX?NT9KXU_i>||! z2}7zeT_(e&obH(wj+`uJIJvD>$-@oCx8AX?gF;p$H~!HExv5li@;9iYlFGAeW1+H= z6st}g`E37`myYyOkcyHl+n3Y+@36|c+7|hV^1h{E%^%RWw`ui-~wq3l{24c0jP&%IHy`J}RwJCH3gJjG~=Gli06FXw{l+>#tEvG`FwAB`Q zDR>vn_~FgxO55T(E-&rJk7)$c7Q*EbFy1Ul88l0vjkOtVMN-J6y;*HZC?u4vT$nJAlnF?}IHg7L&yIQXw8o@&2TH{A+1dCQ9T`Y11 zjj_GEdUNw;VA_vglNg2Ur3K9f)y29$kLTI+J@=k1^0FW^@jB=LYKl_m91=EL15%E+ z+;i^n-s?x0pfZ)PkT#6V-PKc5faDYMRSL|&8Ea>v;Q%2}9HG4Q1;XNbko0D48WyyF z(p%V0J4j3kyxfA>HxE2}b9j4pc;Ln32YxV7e)zNkP72Cn+g^{Q(7qn*%hG)CD!dro9 zJmBTwaxdHc=5k3CiKiW&ZhHCFa=1RiJW`mjRbV#NRHu5BtG#K;!A94S4|_Au3`xC& z@plHy=4!h(4T}uCU~T%1AAX`Qc_53Yt2dYJ*Mg#axH`D|z=f-a6}GdxLD4R-Tbpux zdTE&lbue%cE*I^3*)jDktK(Z?Jt*^7>5IA`FGv_ZNPq9cj~9uOIBNw?I56!84>uk8Jh^~Ggr+@0qiH6pEnurO(Bq&J5X&`|(7umc8a4I$A!6ui*I8c&lw z3@Mrt3>-e@?r?}s2GW?2zJi)W7F+G58cR0C6P81>U}a-p&xiWY3kcTei86 zSZGU9i5h|3+-Mi2$9Leed$9qg}oZ zi=9XCWCE_s!8n$BiK{c6h0sfNPHke*Nm-qhugH~Z6W{DvvCP#Q*XN?IN>s&!lo*)fk4+qg`+xL!28KE8Hbyft<;9FZ=kkNWm;L&o#1Fltlb8!xfY zgNKr@AIL+|X-aIq*QUdAaTAI5+;pxA&Z~97OXRA2`n1dsb*09BmEG~s2M?nhrrkQ$r_12rW^NOPT*BqV zy43Z}RmsCpzJ`yX!h33Zu{!3$w&bSzx~!%;i$yAfRLEE-l~&u+`oOq8!x}{=1!=J} z!bsGHu(*1@ky^zc9h2dE=Jbe1{@2MLS=eFBK)z}k_-3B?=& zoPgUL=2;qU80Lj)0ZTg%CRYDQeXxn6^f@r;JMQlS?fy2!=(!9?HG@>SB4{l<~1omg(Fy zk52z`F*hB{cyog5Q!lZ}HC;`u#0zeb#oXpb9Q8ePFn3tV$s^=7?^8{#%P8cmxto{K zIpphVBdC{rV_$x)Ck(X@DkfSDmkDPZS`^I3`WCyJ=}KKIRdy}cv4mzBf8J9UERilg z+v6GEiV0U{U#4TiyIsheB9do&x~|AmS^9dPPm6Wx^1cEN3Xg9nR-%H_b|DL?P_9Bt z?yZoXvc_dTk*$Q$1<|}^=OWfAI!C+U()SXR^ENU(p-AY>6HS~ zHXR>1N8PN3^dUPceY!PM`{8okFv-`_x4-6o9amIiO)t`iiHT8du!3Hs>(dmpaC33D z5=Dxqq-&AoM^m)}x2F;YeN!1zz3F@}B_mbOLdHHTENy65S*=!lBP-5hv`A#}XRTh7 z^lEP24(twdzV@v6!N0gWUdwGRXQoi-D1aDyi#os2sSJg7lBvMZ*1QuZp--0|xYMkf zNJB?F%~Jw60|y8oZL0toTPF1YaGJqPvy0kL>3njHTDM`B#=ML&xdq&I%}Q^c%db?q zzh}5UZG$E!-ov&>6swX><1inbD=>S^^?Aj6%oRIIB9~@9m`DR3-anUK4E=O`S$`Ib zj-gfN@#b=+%dmQ0M;b0|<&4g9HN!Dyda)X2#S87~C)2S+vKosXi_h_)R~DxwgJ*m1 zuw0p)B@aEZw23ZhvDzE`yaf+kmmXf|rI+2!nIBSCXCc z%!;8Ololv!e7I@rL{quqWD#qE+*#u*)_&8);%YnPi&}~K_!4WHpVxij+g;by%A#H0 z%WkebM%xIe{w&xrF=?x`Uf4LO^k8DOO9c;uc6Zgv3{4a!bk?xam0sS+E1UNfjK5=DA?tBHE>6n!AE^-;`@AK%sCM_W58UtVv{r8)Sfmtfapp{r>mvizdsF! zlG9i|HWQ?-$rWuev@k)<=GyGCBs^`eRwYB1NNZ@@MaMIV8ZM0cUw`ZYwZ0}yEZbgq zVY!}bqxh6-1nteMaD1%P=4Py@99s;{M2?ND&+xta$>#cDRpGT_9<1ETbhxu#>{MFg z7sYgX%a6Gg7M(oTM|N7hO2e#AZo1_A2QR+gC=c?<%cF;|h@o9v5LORud9yZ2uM%l$ z)z>}Jxge%EeXR7|Wf3mU6;p9T^@Y_i_24`76Y<_$C3ZyKziD*#>ToyG%*&g*%k%PC za(NHbK|8Jiy$BJG>9%hnro~{PC|)z$k~!aEyH8Bqr*Zml*|HWoa1tP$NfgEk1kTz3 zC%_5thKB8Iq_TEIm&53o!(yW5$imchVYLa;^Fm-U7WQ;2PghhY>+`)Ytah=uk<}|z zcqWuYH7jkKyQEn(%g32kNL~~x9eGxB?_3>IHx!;nvwkhJC;m!m-|p2 zvKot|Fc}g-kDNv687p#>!wP6evC)dI*8H&t%dg*lv>%02a+LJ!)p>7WO=yFK_;#^; ziDGOH?ZisEde4AAjdF2qnp{!C*HSd^ap4S$_)=rL^he&m-gU`k&$2X2+vZ(cKF5&4 z;pr=CM2lBk-yD1yH;f#w??d=Zm)m4@wae}4a#H42ti-(G=FwD+@1E%8{_E@u9kuvQ z$(7pevlv!e?3Rvq=_{UAhgn6xLyMKRXR!j#AdO<_EJrE`-*`D#ZNhOHk*jDwBP*tt z>s>w@<+m_qMXufMhITM6C#9DxZ}tl6Yc?0Nt*CSL(AVjB*W1GB9`noJLRB4ELu*t! zv%!JV0eD%uras*VRprKwMbeM$PFf@9Em!(r{o={uV}N;2I-|iE&e}*frHca4K_W0W zI0FOHg}|BK$pgcwV)DkT$S0*m2ch0+Y5U%>_VrbggXZ>hvAN67o%?`bAk_2jnP!eP}rk8mH)wSV07 zL)<4hc+{3LFNV^qo5Snh^1@f~vO@OiV|U}`V%HE^u$=nI2MXLr-yCVx8<6 z!)gcL%Z}#WvF0=1#f^PHKj2TAAlUpSqU)sDKAYt+Zk~!{$Cb7>1jb zM>o6UIc9#!Sy8t0rQ9?hzCYG9sUG-~pV{xtQI7Poy)Mr`dGS+UZRb$G>H8JaER`$t z@|#-Mwq4H-+0^ET9s)d`;KWCy~K3yfioA$ z3TKD>;9R?-H2czZ>E+1E?b1GGcG$S_$ae6w!sWbE`gLiPwz>b7zc`wHuB=GY2gOM& z@y+Qt@8QcrkGEhT^rqZ+Y?hPtSyPMa#uHCh7whKA#PmUpuDOL&&dP(UX7zJ5K5mLU z>!$Ma$?z+6N!$pW3{eigKdpZ63s-#&7P|6L&NDmBWpLNj&==;_(J_{&ocMg3&Bn2- zMK3(^=ytI4bQ#S->5Vf?wxc|ru3Vd+v=!Bc{N-@n$2E7yhFyz6<<-1iH?>Fo#ZTkml?T}~Sd7K%NnBzr&-{%CFXC7COx`0Rp8 z%!9P#XNVq^7r>2V1ZCtZng+1<38yLS2eKiN4u%E}rXEonJZ>uHxjLthr(TS02ILOM zFrRWook-M54NFXOkFTZGq=(DZ&zp?Wa6fPGk^YF?&T57mpUkocH+tN^IBe(Bsxucs z&#GP;i%IAn(!t`jG34Aom}Fh!=I|=dIoy26oGX-sv(jol+z`wK_PANi*cQ@bcCTBw z`0(<=1703b^s?=ngVdDW#Xr0`l@(dGZISCKMkI`nxl*QiXiwWk(3nl6GA`Dvt1G9=wMsTDjk$}Y z{b<~n^6b?|qE1A@&4;H~en@;Qqlus1tkdV*1WVuO8nImQvPiP7f~m0QYBdX>>1iffp~!r-$+2Me!&(;j}$+<7M+8hL=3}bwWbP!+8rY zJ}GIIle^3P(3Yp`dTOpLya*ngn1YZF({)HMPL5L^mA~lZkEoLuN6Rw1dEB@U?8ur{ z!OoyDV&TRt9%=6ID97d2^1QG}ES@A+9vA(_>0EiW-L+MVC)5;XcWEkec9*K9T7HW; z`n~kkd)N)hk#!`^i=X0)EQ)2m4smX-_ium6zMEUjC--w*k?qN^@h7bu9cp5+(RA@7 zC&o%0sgfrJhcC-0`pQ*jNTX}G7#{37*1VS|wY48OT0*V&6($|FFiLhJQ?B5 z>nfV8vCx*MZ5X)nIi6fC*Y>35_-?}3*421W)Nc+x_K}q3b0z&#KV_Gjy3scu!zWkE z%X=``gSYyTk2g2tLJqE+mLnULi(KYCKC?b5M;yyxT%78yH`%UOvqx|iRLmtX6O^)a0&r-QVO zQN3swS3fUwqU7Dh_P}C?mBQ(~bF?I$aA4Z`fS1EI;c#xY4_|%UUGHKL9_}uiaFx|8 zj1_$_IXe(bdotl0mvmx|b#Ebs!^6cFq(co&~E|#YZT%We7G}cwwPCs2Oo-Q`7EZZj?>EfE*#btZd%=)lxxu|BjK51C( z>1zArrf;RKUZ1wBEh~0L#*-A#MxvOZsb&QfzHbi7W%eqVQW>*MxXPl8d2qA)YAfGe zJCkT;Q%;`Y=ph{^EPOBt=e^TM-jk>5t69DH3I?^<9f|2>8@BVw@}n<2-0WT+tm1UI zp$o%b{s?1V8gng+Q@9BOd+?sH-vmRPt4l&^tU$(DzA(kc%~aD}#N~p%-YQ)#V0F@6 zZI6t&8>C-(d|Y{YH5CbEs`ISM!B#%fTXS--M74>Rc*gwb3oK<14)fU>qgi!iZ3qnP zNrxG9)X_W*&cJoUd9px{NjPXcRy6DUFk+GINpSow(sACig*8h0NKPN$2gz9!H-fX| zy*>2z$3*=RXFTI(^v^%lAx~xbD(lMZa`|LF7Gz_!`3OVo@D_nIGb4 zNBJQ4a_wl|xGv^>uWr;SA5lk+Y$Gtldg}uZR@Uia*mKF{#d*l$B`H~#JY0OaE`<45 z`Qo>OY4y-W4$HP&`{}wc(esh>L0X)CUFdW?^&A?$#S+!uE+0K5dA#s!Qi%n5TB_fu zCcNC$(XsFhf4RJK-gv~-O)Sj5PmFX%yR2r(liOVRaCxqnk|%H4+p#>!7Gv!@oeb&X z!#=Ky*=--5?a|MT!`HAX=1*LW@`FqJDU#23Ui@i(jn!9sdd4Pa_0PWE!G8?!ueAGthk<=|5=wU5Y&rR!1{;S`JOR3{&{BOpy!PyJjOU9RNB=1Gk! zt{3pMo_&Qvs_%+f*WN7cd8vQ4>#V4u5ptoYHrFMK$_|U?U(!#We`0yb62ld1)7KHK zUY(_eM|;>VvuB0sgU5~zZXxxUcMsL+a*Ez2QR2k}KX_nMJ>DwP^=wnjGug1C+LL*& z=n}jv?sDPqlCMMBR*ZgkU3vvC!-I*+zrOh?dO6FD7Fe8hd>Qz#oGDUFvAzyGd2Lq5 z#LdN*JnqMtOg)IR&T{m39XXgi7LrfHlZWjt9ZXj~9SPBV-`OXU@KxXP(7!n8^meeZ zbZBp#PINK1EiVGhPb~7+6g#7WfYR;2%ZifvCzg8urvgZW@ixGcp*Gqwhyzr#kAr^Fdb%PFgw)B z3#jE!vc|<&4Ama<;R`?Y^?CIkPL(TqK1{~XkeWe-B;A$Xbne_F{igA1dmqW7qntvs z7&9ubHQg{Nj;xTwUjq-@8baq114H1iTn45OIE$=Q{+=H!+w)<29=5yNxVW?M#Zi(T z1rOU2hV;uApR!`>0Y#qE3`;sCy7^~ zpufhvx;(xCBk6d$%Xu@h36FNths?un@L+a4yx{8Dmg7wL#qYrSDkI)%&xdW}Nu>Gs zzUwQxF1#zg?i{}Vx2NC9>vbLNesTFa?Z)yAqGt(2K5fILM&OU&*?wLd zl{{?gdtX|u)a$BEv7WvH*fbz`7+Ck46>}y?=4sZT_RZ&zqtjJ_Kw&r zStof=bKYLbt{^kd7YKmzsEU^gPy9{F5M_OH$5+=pO&+wt(gi&xxYoW+C3OXY;_(1s!p&~H-H4k?Pha;Wev0wM z3d~n==Toi=FWibNPr@lx%ca6lnJmW@vO?@tI{oeR+ z(<~*?%M({VzG_%#o-XELA6FhP{Zr&9j%>1S!qfB0pC%k^F-LScjL|uX%~0%Xl2<(x zwpL!U8aAea7OFX$l1#?hsUc4*yqO%Pp!3oX7fc?T={jtu9h&8v zs3Gc`$ac4FW?dC+HGaAx%NcjK@UEY+d{vM8mEpxLc85t7r`3sqNZ;a)n&joWn!|}( z>_)t6KGWhQq|3E5#oDs>TUEj3Jw6+SIKoF^U=k$k+DEROA78~%27_LK3h0BZym%a; zUR=p@ce(OHEk#*tI)Z07;eeMAT7c~ya4^&usnijPlnXGt)i{$y*9@)}JU08-9|V8= zz->1I57qCvkpagWQD>^F9zRmaKQYxksGOh<1X9qw)jH@hFA zE!S1Q+0()=rqh9Rilw3ZdAK}S=X+C4Wcmx_ZuF>lLO7V zN4vK6qSYanc(A9Q7ZhgRPtGKds6$3kj+dvab50)stqbKS%TBTT2lWz#)R%uUFNGnHW zub8{+B_5h}E=a_0H?BfDAJRW#nIqQwH)|XhlAYQ&#K()a^nB@FL7V7YAY=hw}*Lk=%#ea~&St@aX0TZ?PNu zd%FdXvs_&9#k0qeVpgS*c;t8Z%g){Zju;KD3!4k!1?%&sAhuLYlWQy~|* zM&G`*FdMyjGu};w^f?DcLo8YO)D$ael~!{e@Whp?=LoA|1Uzw|eJxhkkYV90A@LM9 zy?L^vpm_$o?*UIF_jcvB^OOjyVH`4^{T});H4?V7Az2tEo46PKm{6ZI0XY?3C?;G^ zZej|xnf4s(L7C@yJNUJp&~8rmmreWX?L^wvO*ITzkqS3EOon2Fn|N|6Y4N2F7r!eh z*i$ND`%`<^Hml9yrgbe=WyvDl+siFX7}J$UHNFF7ZUTSto?+Oz4$NJ&i^;7B)An0H zTk~d+^9|xkGwAqtGLU3 ze6Z5oV=i;-a~Y>MN4hRprk$0^i#R!Q^K9$dj+_enSlt%vIi}Yxycy@^IENA;$C1 zl}J)MbJkhXn4N+*qu)w67x~DI9Ir0UcW>fKXAzS-HV<3H!)4U=ARe;1!;_rbgLt~U z7?CPQ+LpXG)w9T7x@^}Q$DU7t>+)L85+mWDWUud_dfZ7_aaN=|MkG!L`*>J?a^AC6 z{FLRZJUm46qEl;#s0I5j{gqp;=HU9z|>x4uEN=?#|laCyRt6W&8fZ3M)7B5EX`!}4Y&(TdgF z7}d6s<>q2VHb1dwn{V4#ayNCgCQS2TjquyYGFdv^SD+Tdyu^J!cZYbu6Kh{Kc8A~G z<5F$m?R|gl3iJ6n(Uxgk?|iXrtJ6=7)o#sU*`4C{YPt7mJ8KC zg2RQr2F^B*+-`K>sG!B+GA4^IG^DObbJTXdt5t)9c{}+wwr0 z;xHXnPA@cJy+l^>#PLN9Tsat)eDA|F>LCKyX^?%J__l&idIX~vp%G0MR;!x zPQ=aSTt1%id98KrDNHc1TfW&5v$E@IL7;3V$dVBIq#|AK<}V~9)%o-5P`8^~Q?s_N zvUZj%;s-h^$Si1K0@~E)?l}kn+kG;?OiAq`^HW>$yYjZ?)N-KU3(&7mlUN*%1`&yEUQ}c zNA(- zKgmfIHeq|#%6P?17L{&cT47z;r8n1L zm%aAn9^7~_ArI4a{QmX*Inm4f`mR4=Vm>TBj{2@v%UJH#iJimc120}YxW)M4k4{WC z?uG5FvE{8%RX1w92ESu_H$xO%O@;SWIZhJyd^5vhKFK?I(wCZ-u7eLed5XV6`V7q7 zBg8{5jeYWdomiaZ&n18HN;qD=gGGUuEeO*f={o4 zR%r+}*GpRUsJF?6#YUE+dDFpj#q2y?UN3fRtd4GFUL0-HO_G-TL>;&0!HHl$gq+Hr zy7u_RT@L+W_VA9I>MK7tY4%jhrD{+y<84}t5P~a4RBOD1Vv?!An9@fj}H6WmbLgHZ=d2N@`Q8kzzE=$fMUuFFaff$ z1gF_LH)}eQ_#kJ@QD}4^sgT!t<`4YJE+Xp$;gb7qe9TnAUPs-R` zF2**i$-&#c?3B4og)2wvTED|~wN*V!OG=Ql1UuesKglOV;wSXgRM;+Zx~`V$ffc-P zWwC!l@!J6}4`L1V#dYz7t7lu#u~I>zcz!`Fzdvl>`LxXq-pjVdxY)7IPW2!Qzl<6; zUFO2zM-Vo*3)IHoym_Sh$~W%tq-c@jM$Cpvp(W(bFvHQ82lmMWw|M!%e&QB5zMc@g ze+gJq$W0|dS9)Q&G6gRWvLseKI~9?h^deP<=G>_XMv!CEXF7M6pIs{3p4l!{*idG%d(O5s=gscfDjcnN%WznmSz>O5sT}P*T|ZVJPaBR$xV~Bz4a=KX zs|%+GY67{m7ao?zX{iT0W82pSF9)WNT-t;uZKVqByHtu?JfU&;j)HY+%a6unU3sR9 ztG)=H+H#$xwZoKiMXJsNhE_0#bysmh7W3lDrHuaOnq7#7%1<$LIK$HA1-Jo6Gj+GSFp4Y3`8=T}Hwx4Y{;BoZdW6#flRazQour z#88lSxGuN8*U6ET(-OS+zGr{r2z6nSk}4o=mwpP7c>g$EOtCil24Bj}9)EO~!~4DW zgp0OHj-w^mXSkpOcEUJHAG+p4WcjdN+lS z&ijXq%zWAC3tNkW+uQq-@?g2{@t~yYVe`4c%7h(RjP+@?R8mUC=JuD{dFjvLq42strxvHP!#Rou;4N0;w={H{B^k$cWEFZaB58MkW*24IOtAmEa~rdk<5aj+pIO$j%rns&L& zqNx{gpDPBHwN8%on#S5xdazz^;ynqpm@A-@IR=6E(|zNnZ@VOOG@%`?Tu!7q>%r>! zW`!`eec|@u%T%O$ucVrEU|OY6e+13D{45KDu-^uCoDu^obiIGF<_qGPE+0>^S%&&d ze!5)M50=-5Sn$5o#bli>+F>#}UG~f7Xie%$X>Tuqwi?F8)qZ@@q;h!)sYd%dQ$bkc zVwUQJqWyv0P4INJHf?+?U$9)xUrscuDVLL6=%w%4#YJx}XH8{Tl3p21dB`OTUKT1{ zMjBjRBdZG?GHE0_a39sKNBK%)fy=dJg$>OcTpd4a^}70xH&-?K#;R0FSF#EXS=N{8 z3pecEOxq<~?5SMplgZZ7((bO-1+6et!7WJ2Vr87=0bCI+dIv08J%nMzE}z6 zC2y;=f<|48Z93YEn959sLg$jcT75-0^$0ABjw#y=2I7h8ToPTm!kYz?p;ECV`(P8b zv~RJhF)pVMq%wr@b}!phC<&FWo}M9*6iqG{!+H0^*HIYTM1S0ffpD=i3|AK1CKq!Q zOcyw5H=kC<+^>ZtFUD+_zGN#O_QCP2X{uMflNL5Ps(rmzxa~%y8rwtl?2B-#W=iK` zB|G~{tPRx{$V(SvsdJMSibNVVa8r$ETT=Pf%5@=3NvcP++s1BtQ{HS6ibNqp zdS;O>nV;jm)A}(wl)d zC?sWLU|o6Pfno?3lY^63>Le+&wP`nhZ~5_r8e;isV0?2sH9_8Io%{&cdPEV-Irenl z+{l4-Ctt#4CB_R>FXcyl1& zbh(jrv2fTIdHTj9(H5$uP`BS*cTw^eS_N4YJc**(-O z9!9}tj?dlarN`@$al^{}GrfMslkb}@sRll1?P4#hX>Z<<(5y&!3QJ5oS6JG`L9?0z zrY#4p&P`guqD?CjHMqLa@YA*fiKjraV$^)5OKs85BT?toJEAR%Mq!#~W$j`s zW2TWVm~Uq3A8j&<36jt7DYYo=qGwdLG+d=2OXtJ5*h;S~SJUM}`UgHx=enZKg}@7b zC0;JySdD5dBnoY;KwF!3U}(#a2gaLgg&%!+dt44bS|@GNUijl*lv_PeAU%#J8eJQyn z?8t>^YP=K8ViU_7g3uz5T_((;g3kSP- z>5I}NW=!@UNb2fh{Xn85MWXx@#)YR%7D~FD7oJJU+VsDp7WPa26ZKrauFBn@gC&y> zIV#e#_;*wdu5T5Zv63jd@J@R$UGZ<(UgzR{D>{aAUCWi2%i#4AEl-z&^K_?4_tKc$ zd_$#nwL~B4pf9aVF5^P(n&cXePFvAtiKT60yd5wQOqehTj5cBMCyMVyv%WFw*f% zj2rP54tV2rdVrEIQW%L`hH^ERok}o22#krhTbef@VSpACmI=A(6%=mLGrLXnk#?#~ zaNBQ7y)Y8{P9qr7@gaE3J|Yi{-$iiHvxysZnh@`N*)CK|+fC`XZPq965N&R?81yfy zZQt_)l4jBG6xr}0=N^+C@4L(0<@#r}eF}D}<-5<#V~3YsvkOh~UiclR;M^ zq^T=8rv5#0c)0i^!}nh6>he^^jCTIPvX#tCwclPdSaePFR$B2w(Jss34CSF6tctme z4VT8g!o(mx(_tm2WW$1If<)uK~Qt$0u{u~@&H??a}A>U?BPby&V$$hwdUvs=MbhE$2NHmP#Y zf-;Sp!1IkPR*bJ&*B@irb`iF>kCn?81(i(clIXeHCP5fXAw!}@KqZn=zD8Y_Jlgb< zR}aCh*q8lwE4fd+E}J|uCt#R~gsz$cJ3XYg$_@-P93eQ=UTl&Tg9=mW;bKe&IBdhm zOlt<%p!AxwJvq2d`rvfI6V9~b1~;;+b!ZPI#_}Ks2I6Kn!h^xNS;6VzL!B=C z!71L-n{>aFhvr>#O!4L7&}+dUEGCdztu)xJKMl&&-S{fk=Ov^!Yo1oKPvx>J&C=^u z*G2rO%k#MB+$UeiUG5T3Ii7`I>qE~!4BM$qFu@cu7~61pY{SKY@b`~x_`wNyB4Y{} zCPw(tsS6pV$nYQI6t13d-CUkvrD|^RY*vRfg$aLOvD~gr{I?#@fBvt&Pd@)P6fL~K z`m(g5*XwegUiufg&z<=*`GteuZtYNx&v+(((O-Mo%B0KYu%#|ypG{E&%pdBlZnnAv%3w&P-OeOzm7!-%v7!jX&?O?<;=r(d7<@HX$iZ_=U| zcIQ_n5m&aljJegp(_((kaD#_Y-|WBp$L&A&fBch;!56q4gG^|ZzeSf57K8Kj7?=VWqD^oQ}UP*PW zF+!29E*H~`L&i(_4i;A(6K-N%{j2}~zy7c8w#)tU9H1{f9N#kF?b}OZ07@jXY#g9P zx;?0YA`sE~EI+iz`F18=z4rKNF|H?qCbJMu?zez}0>(z+hPpxaB+!!p2Iyqk1L{U5 zsViZkkXU+#o9*u6N|-M3ipeJd#vbs;Il#Mq`!2CIn8$V-(9y>efC|AYO=zYCOnqkib>xj{&k#FRv( z!cd#t^opC%mj*^}hLmv6qK~J&U-x>TDdw2S=Y*zhUuc+P;bYx#_#vP|93yv z|F85{a^--7?b0*)mQ>^O+$xrQKU^W-8JSpUbi$(go+ZzOK%G1#-+AdF0&NZ@!_fzVMVj5|vNn$0n=BHoMl3#PrExWxSeoDgAq3^`i{e$KUQg72D&Y zpjB0=*N%@dqMvzG4fZE3%@&;X7WlY}Q;->y<#JW7TqdnxnLB6pj{uLNxP_WB(4(y1 zfZxEg3FZjKMU-W5lv|SReL~REaO|xh`ZLiD4_D{Q5O|yDox!7>^ z^k!R{Wua-7zqglWX_g;v+a+!<{qqXV;_>3;x?DXqi|cx7+tuD~mdiqmq1yG`K^bpe z=lT!*to{Fgo!8=p7R_~G!l*fGZkoGM?9PAvsDpXG5|eC~=ZzooFZZ42x8}Hb>E`&^ z;S*OixO#iF?P_+5TR#!$M;Gm%zK||I;p*Z-zjZMwZFS+m>w`v7mN$EGUH#OSACB1w zyQBU6-;>Ye_m8hqtZokTP>(zs-fyE0b}9}lk16J&@wVT*E{)6o;#2GWFMiQ|f4KRZ zm{u`2))f!4S#P)cnBp(m(lXmzTcu#8nNE z=0f}%aecQtdd5^QNpF11`@=umi`f?xy`6pYx%0Z@%c>y%5XaVD%{zlTuhYHhpf2;F z7l>zVe~|C_`Hw@mny_3?SPle9F?xlIm(oA=!VU=q>51!wA1p|_<)&>5(hna_IC>~Q zn97@}E?C(V2Ez8{g-B1trXUr*d5`=2CvRVrQ^C7^(eu*Biuv{m@y-vJf#NFMS3P4Q zoq_^)_LFx$Wr}mNq)~?ppB#u?ctGVr`UAp^4HMIbX7*Pjfuv9R?#5}(Z+N4mnrfGO z!oy&((iSbOwuod(yO{SlFI?Yk_YFMz!j1V)sysR&kmS{CgyZRdBFik%#sW(p2{_tMxck!Ef+g@7J(MdVFS|wC}vxMgb z8cWI7_5SyKEojR>>1ujy_FT3lS2fa`{YJ@UyDuc#+BBRD}EM(0aXf0%w+kvxg~0ra}iwMkFS)uOG4a*_ zvoft-#lDa7e$)jOSr7bb}*^W&A~1; z9zL`4$9*2GPY9Cb>ysCoZ{Dr6ts!uoe}ZPc)vA}|LgX9$v|Rn&#pt;GP_=FP(L(ZU zM{asuXzPdCvh?5Oxp^}#Opz3ZA2j=R`I-+-ew1;^;#0Oq_|dA%;cDAbjqGv3baX)v ziIM;MPapl^C%5G*)&GFK{w}T$|eDrf5r2E~d8BkMw%0 zm+kKFTS(l|Pb57Mueu{92w~Zq~Y4V|#{YzkPpuFWJu>LUR{U-gMJ%r0s9vb?M3%zU`YmW^Xy$To=T3 zO7DSW>Zd%_MRR!3+uyV0t8-=MlD-EoZ1>9x)Ty$wMbEsOSw_B~wf3hqqSbY|I6nA} zum675pR;^ggNEV5yD#p-w~jksiWR;){6UF*+`8h%P%I3gmg*Y^=ZBHz9-Xsu5Q}fR z#Lf6r?_TT2l_sZVH+wnuHEA<%k@WK3^8ARu+}AXPSdx zgrU_!5-ex1IE&^m(j6_ZPLUSQ%8$+|{~ESlO_#&7dWC;zxa%^~l4G~7 z7iM=04qhYnq+>&vvIt+0KGxv@Ivp!90bo6E(X>*da!d7CyO?|n1xxJ`Wj&piBn zjmgGgBUwB+iaQUNwO@GgaPcLhtT23kZO_qx83Xy)@_5Av61#x&26OQV)Abv|VA;z1s-8%;b{%_)a zw|9?+$Qu2oN|oE&ObTug=0Ihw87QVcNMhNsKB~=cxKCtIZQX~%@s=c!Zr_dj4;fvS z`Ka>v&9}XOF?g}-(av%E!9L-8CiiY$p~o|CyviIm-}5W8SDMxI*gm0;j!e10#1540 zL!Shfukws9`w~CHQ@_Y+^{xj$JNCVwQ+c!8svgQ=^A=_1Jxj?ZTpsQMr@Jr9c7)=q zxG^-x=nscHsfFx33eUa?Zy(`vNcJVu_CRs^4tM=6P*ymT61hyrbLrK(_;iptfg8Q3 zarN>G{>+2t+}bMXtHWi5G~nstRZXPRbH(!IuVuIv%tJ$&E612FQrRv|^ct&ESh|Kt zcxGTd!?tZ!Lq8Y7__Pk^CUFvK7UAMaEb9wu+Co@~uAyB#GS;bVdzQ#`ed(t#K3snP z;qv?i?@q#H8hyF_HvduI(7bjy%3%c@6<@g>?iTG|&+@!2*8I%j&-Del=D2V%?Xt$) z94!nb9iHL5E{D_WNbK)qEbU2lksLL2jO*H5ekdo;tAm<0h{UruNTwiC`K_!A0n62_ zhgPI1J~ZKk1JgCy!)oUWtKnwpiitF)lDSab&%Gb%yUKexKU0-%XWsTP}^lpEq(UIe{jAB8EG}ustZSNlubL-CSalD3B< zr^t9YZ?UP9g!v%-@DsiY!nDeGu@mY7duM7KI;3Mm*?}pvwps8`Jw}mH!qJY%}?gdbTK*V zeie^*@15K;{WE{X?{5B-|IWJ~d)Mxjv-j?NyYJq(f)9^!{^&71kCSDBDyss6$Bcz) z|Dt8{lle(L^+jIdJHEqT*LV19{Vjdz;LTUs>CEizT@LYD<=MlzWi$f|@$IYbQkdpG zU#VQqu)_Q!H-8)2#JJW9C-SFAK220E6SuMR*SPIS_cq^HQ7%qQ`?9!0kf(q+#Vr@>FY*s7o<8k?(f8eit3)zf|S)G@mYhD~_>w;5EEh$hvORRI_y2QzggGGP) zT;+D5nP#!PovTj-su;DM#_ZYkSt;?7KRgW_ageEW**!;i=cZo|{SM4a>1^tnc{g2hX@Gbs65; zru#05mXGknM++my!}cYMT;bls`V(!z*hWoOKk(=$*40`1&ieeuEOx$pEZN2#B2m*k z+T7)8y*|cLJ4~|k#!25suy)f*hIzRg9|NpCKsf!O4eH1zC zm%9hAXooiwVeu-JUZ&+!TR)>8YPjHhkB3o#qec02^$ts|tHoLR!NS;)hc;ceF!7R0 zEKVg@I<3aDSC&K{3_g5W#o91U$hEX5;--zhOM>Hsay9qP#g!McrwV^?r|AbCloKa+ zY3=!8`;BTWG0Rs;qH;p#R51>02;44Xvi-o%e)BJU1K+JDdcAg5n_{Cacc!DoTe8Ni zO`1Q=Pd|Lt2t;S$x8|;0ZfN%rYL0J8K%)QF;#-BJm=NE*m!>UkT`znG#kW}Yp}tmM zmu=Z`BPrS}eHgTZoBAD<6pm@u6+`KaHNr%{_nKWx?hvVqvgT^U=4(+smUz!DT{pKb z7xUwE3#z)8@AhIClGbw!0V~*~7 zA3xRk!!Nz`r6WhK`QrbP%NKU-E?xZd?!Iv5o!^%~&@XA}eEw%k+x{beiNEb%;-Bfg zaZUMQFsev)sG)}e3u+#G1N4b}fW%F@506K--h8~yFa1q-{^|Jxe^qyFih^8f=el>mwEz{2nkTsAO!~`VY5|ZXRyau7l1(0bc+GHG!ifzD*}}f zVVl?iQVYBtwrp2l8>;&aOOjq)Bi|>3+xTeJbPvzCrk||_28C2m=RlIc$_eK5BoatZ z_W;tv9Du>FOcfsR-teY4CE#&*G$n)JIiNmx`7=yjtVg;m*Jo(gvnj0T@%7FcXp4+# z;`OCVUtCRCucpJ$_bZ#PA93+Xry`Wg7;L5S{fDcmrfn{tbGE6{>C?Mk8*d_b&=Gxa zmZLRe*rv*HmIfUu8{#{6k00*44ln_j(1HS3k$XQ7C%E1MsP2Pj6adZuu7qds0KqdT zU;rvpC1a0MUe|+ZB#aphk|L%W!(_xszwygMnRK}VHQ9otro(~oK*>eNP&tALtTP{4 zxiit}_?(l=@ExBoyfB$tu9bS-C(PgwsvrJ=O;u%W0yh4B!@H9y9;21Q;DPaf#7=VgHS$eZiXG<)PZ3pG%g^+FV?{TC!m^0{s08UWM4H#L#qo#NV>j1WbBV?ckNw@%Dx$vY32tXnRE76~N zWB@>8pHl$<5de)j4LnH8_0V4j@L-}r0G>qR^#NpD2%yn~g9f~Q<47Y8m&^LceZDxB zZ7VL0&Z2E_?yNyNoYJe4bfXu1#fKa~aUMJcX)!8_WjR|Y7i+rM|5kL*(W|~uJ9l>7 zv-{%GncbHxU3TY%f78c0cD_UObGhn z$7D%2*-!Hfzt!ja-LrUcaY^52j+(V202cy-&S)q2eCZ2SJmkwpMcz9Xi_I7^Iug>N zLs_dv3c=)&Yzic)+@j;#5JOgM>yL_3}`?B0>)F)2ZRDt89)tES?j%7Gp|&g zHd6~8Dgxlw8GL@|;tZk`SG;8(d}#KnJ*J-o%HxVH&;b9qy~vHC)}+AdGF{@PPh z(X3V+MOzQt&ARJYIi*?d#N(Qa*J|VPO#Gg=D-3$R-NoD(!FWovy3J^e+{*QG;6D;C zlYs|ttLhtQp&&o%Nb`ZS86;H%```j4gVEzqf`o<&Bf|$!@Dl`b;AmIlvdm3@}}@oDBUPVmZyAOYl-)`Pp#W7Se~GeujVeGMqU9^Ca~Wde*Xj< zo69G$+Vv-Pm(LVYFc2UIqc*`aRR|!?qcAbRD|iA9JP;t1xE5dmh?&Bb$N;DV;KBn& z1H#&o^VBxmqenJs^0u~N#$^zlJgqA)78>m-^8H)25AsUQGryE;Tk3>sx)-$_jCknL z^HOp&?Y(%;|GD$R{upPJh}AnxcP*b-DK_WxT&sCqkiG-O$;2-j0X$OQX18ZIovAOzxwpFBiw=?mjXIqYcBgw;7teQl!(H!@tFQ|z5B zE_TJQ_D(JHn%F^6UPa`0iH2< z3skh=g9mS6JfwDdu5FyT`Rt$a?0@HU?z(W8e(02k=W`{80&nF6Fc46ef{|(KvEI&% zH7`+rmCJ_QL#p+O(3C`s6cl8F90UXnORhsukrF6AdV-Bq${8RP_CWLp0|#=1)vu$u z>qVzlG^ERWM5!TX7C{gO++Oz4Ssw*J8xR~>lh+^+R@6o(}`YFwt$i|WJG(4w@%N23DS07Fo0B3K1@TtM+M;0y+m@BlY~26zz2z)Z#j5JMag8QKWZj&2TX z>Qjlc&1wTny1cZDFRrk(Wg9+n(pd|uVVKnoXYS{wnuTl0XX<-@uNQe5=`b9hqWR&| z(T%h1(c%5unr#Z4MBjZGB^=!#W5!UVz2-=4tTDB1rLA;02bm%y*}pDkkG(BNoWWp zxZalQA`TNke@xjQ55h&A*H6C^CJ+Z~SV%u2=AqMrlci2+_3wk&;x=lf}&ineTtux*Mbj@ zF%Hm*z%nHLHgzXEv@HiYA>oYNlM$inroq(*8;}G}gl$0snE^uBfk6TU+3jHfkN{7> zP^_2&c53ue+4bV}p>(C?amerQLH9>8AEY9tU=GB;*BKXs>mEaziNj@77688v!Bg@f z4N#Ks5_m2=7h1swfHVP=wq{LL9c$aldk;)16oT_KVqin7oIq13mZaTAY2|F@o9Zj= zM7i3S^1(3*FR1lRjnZhv9eq&`*`*XpcbAVd?>mc0uNS>W;r*4KuFYKEmA+jLxBo>S zoTU~I_fO_J=-v@`eabu0=H=28-kRU>w>KdRfLPONtB_z^3sB^9%gKut3bKj-A&^u7 z34}loVgOaC)p1}>d;xeIjDd#1a)L29zvscnFxDyg{vGq^ZAZ@AZmM1!Jx`BQPOkHE{b7DzyQbBvtsjmOSEj?~(9Y0RRm-~{ zo_^Y&xCHqu`qVkxQIUd6tpbFU zY63z_1K>ohB*0X~@C0!N_#m_zK(OF?fIuSvQ^0eR5+2~fLIN%<&U!OPP;TTf$Kw7$ zY@9c4Pha8JG?B~HXTG6G#+i$6f7zK_DW2A*vRHHMxlLSb&%I^AlIilvmw#~nfB7yV zi8-=!lsNGAokU0B?B$u04nN@gjCwoCb_lc@$RK$3s5nzRAQjL931vJ94^`s<8VO#O zVrv9Io#HXpH85lzAPWQP!xRZt(%=aoOa7n1a=OON<(XS=oHNZ_+kN`XZzyaXGv*JJ+{Ytn3aYeIV1UXt4b?S>6@Ps(YjcSE;z;Ma{6%L)KciKWr$-&N+#pHcH-)BD0 zk61Lb50mG4jVTH>ZKe~j045}7YX>?A&d#y&hyoN3+_$-pFi+0c3@Hq`3cwl6K%fo= zBWn+kAXw>0nt2fRGizgSnOFJEjzbN#@8kXLjbc@;!`U-FJdNg~mm3oXzx|6BH&0Rn z-%Luh0Z2;-j6GvkER}}=gOV^%a6P!5DJ8yC9$=tSFxMJ0V9tt@IVv`GP#Itmr4KYk zx%^%<0@W!0)bvFtC)z)}l~&q6X;;fPH9?uCueGmVp|nzz#Wh#5DN8flJy_m8^Qs)Z zd@4L0B+@o!&5?+~rS-aLAFXx&wwLCj=JJbvy)JuwhMI6GE2<87pF77Uz$j5w8mfpY zJOv|ttpFf~QNt-}90U&r1k|nMk>J#+Z$9{79mbpE?Rd6{1%*X>S~B!RQA;B~eYNsh z*|e)qJ|yL(>}YCMX?I&6xE(p0#^vo8Kh)caw_p2}PYxe+wPIp!KFNh1J#tdx+D}iO zVec@D%kn(-MN>hpQ5ryV@2fOaB9rx|CWAC6?6+s!*Prf!(kGgNr;d6D(;c4G*-+En z$yCjyzwdW{QF76a3cv(lPPRhh#1#BWRS1Mt0rUXIg9Z?y#{^YCxe^y37=8dIq7zT6 z0p}S5p|S~#n0nB_peqAt3jrXGbS+{4AYi#b+7hnxOjlZfY1N_v00?Lx0wAG{64FEv zfKCLwxD6nUICetZN@FzA0ARrd>N5h6o(ot290(Vv222m|;Kl&~zV1B;^YdSrCx?BR z)mbJR#s?-D-a0Caa_$eb(X*D&>^E`;lwd-gU;5yF_2+iy=RVIL&dHyj{ykl|b3i@$C2bh}|GTQ4i(<9;7laA$S9vudK>C$ngCxB+=gI|LN;? zpRt|x(Yj6{Z~ZWjhzsj}QmT0&h#A%+U;jk$7op>@R>@^M%ANv+hy%cm8>D%o9g>#0 z3p5yL$_dTWgh19A4hUFw2v-kCa5W#8x_ODW(fqs*Oj13Z89w@nd4uF$J)EcW`afGu zJp`wz3SOYZ3|c3x25_jr1B?WGtVh*@uQ6N>0beUfqrkk@%KN6OT+tdU&Vv{eJP2|W z9)!!%7e%L8t7e}R(Jo_DQ^M8K76;XQpg162o24ZEHLLR!%0o`5E9=^2=_k#ShnUml z+Q;=*eW)0AK81_xoK**&1<$z)XPd@l#J!PtTi0uP$g|S73u#W*6z?@Ye<@)dCUZ;- z2gg`3#~lUrz<5A{B=95>00@VDseQxF}z2acW z!mIr3&{K1Gh%9U9R94)lS+7#F(wmF)_CBhw{J_uz)yc!1V(87`qT^Z*2GyCW@U0(x zr3)p|3h$woXya9>$j?`dH&3OpcNL6Jo`-#{iH8D^0FIyxw!(fJkT9pg>t_p2x+EUl zAnUKMKyA6RH208E48;^b5D;g0F;H;?Q-T~*f_MO*GAIxOs?(ML;(+H005UC*t@lt* z64a5KN!xf6cYTD0<4G^g!_!$;dRCz8g3pcnxH6%iwr_XK)MwUq*uC%F+mgsVKL0$s zhNkS*0`u+V-P^UTsmyb24mYOt?9#-u#&^DM0wW;cF{r@{6A)U|5#}oZyaE9c!1MrK zHK+p6K*@Lp$_NY&;6|8$pBPG3IYVl2C=*(wfefeyT!X90Xz&4X046L*q$OYi5-K%#tm)h!wJ+=HKv`em z6svoK9mVaw;yM2&r+woB*fRRyK3L6DiZBBQ>a+mTD%}s`;1L=R0uxa~Fo$|a1&t?U zu#RthGxskW`eBir_15S9DZB1=vFs6C?%YV7g@9_+NJj9AxDo&Ccy;-~nbLay=YD(r zH#yGr60zO3K(Da{nE{nPkbwY3rS}wIzh+390s)?=L!ezSD4P)yiqg#%s9m0nuxZBC zcg2%F;}6`P#pZ*mxG}~Mn7{o$76P6mnvRD|S{NhiSe~=tOQyh`004XecnZ=$a{{S= zlq7!YdZq?{6dS4@G;2Vs$^i-(Oi)4L#pOlOE|dErji*J9z(4U)G&`rO-znO+clhd) zrmqzpYA9i}RjzhYY-(%cvb{j@;2m!l4ql?{3#$9XbU427wO6#cZtC9r;_iMk?=0tS zp1Dt(9>?jNm z-}$?^z5TkWYT^OWX$|8QtO9{C%z=3rUY1CLPgM3z7iAqzA;rTgIiLn0-W*5`=3oxu zklOhM8d!&QD6wE3&k`^J&jjLdrLfY#Xb%n~>fym*b$AXzloTjP5cPTwW7u4@K^999`i=ioIAbT zTYcdosO#ctFvwD9i9$$NgfLNS0=BQ`7=H3pjfv&6JLW{>kuA8J{~bj1V!pVnZQuMtrJ_IYYwwR3Ct0 zz@dx{W3UZk0gnRsFn|Xw6aZg((o-(_{Y1+|;yCc$6tC2ZwlIYpNC5)nyoqU9*ym!d zjH}}^nl$(n$uwFijoM>;wa|jo;+@j09Yyk~V_rGlk_WHHx()5JU>6r#hxixFHTU${N<9D!D$5kNq3fItp7w;wRd z1jbvgfB|Tkszg|yGoJ|Lv}2q{CjbGDp?<uk+n0talqmo;SDlQoQQJRZi1pHn%Ul%Eno2QDZn9s>5PD{?;AsK~ z;P6erDP#m~DIJ781R3Hn5tAYfI1;W2=>c&7!ZU%S=K>PY0p>Ib07%4u5kL_b&`}NW z%z7yR6aBX!$H8*@V>SS$1s9NT;h^6}(N9(i*MgU5@MJ8PH*YR)zus_!>5_3c}bkrlc`10ER%OtRetLXrUI5_SJLF>Bj}k^4r|{t9)lV^xlW^ zoB}mp1LnXG!+Vtpm~uX%P}0c1r4CMc(@3s*t#k*62@Doyo3c;#GIT+O6bzkmH~FZx zRU^43RmXn6zBCM0kGg??Y}Ehp?~m$bDt~j^&SiJI1I<#M%C8-yv>e{2qNR zmenhOdCpZ>hY3{31m}#j_P_-aguYzA(F>%Cr58g&B|6bC62t1DLF#6|s`@%o$v|ic z(w>gL~Zb3Zln5Hz= zA$aCU8d-AG9Gu3T_pqYVtps~<`nS>I)xYYnDB0H0Vks!uSjEJ<@#Zov^2!h^y(k^V zSJ$h#mBYzArw_~r=&RG5%<1NMzrL*U$KLTD&cA#nV-35(R!|@9tyI9}(s;Vm+*D}5 zPdq@BIL0sr<`6>z2;${V>8*y_xEf@ST?MH+T5V_{pk(S=bpfGah?p|y0+k~u<|V8J z3cLdl01I$kWPmJy(GcnWNCD53!tG1-!DNkW9Jbp${# zFEmg)SjTejT6Ao9vHm$_M?cGNql zoNsfEdT79foG`hZrtukLT1-HUb%3BY>=VT)g5LMC%kKN&k9g|qoc4+jx8apM2NW{H z2lg0NG3Gv@48w{XVt|sMhjsTcWPqfV+zXs$K-C7ClOn@jY?p9)plLWUIxwi&ARtNe zD~xsPU@Ob&wBHxolk}y1ak$)>4x7mw(1t++0RaI42m}%djDouY7#0H3bnWkK(M&S`KS+r0Wrk8HgI!61@b4Vs8q2&{r zXFA4!Dg+=ae7WirD7@DU@*E6ckOT<~Jj^MfA>k{c2<3SOzQ51*fdeKLhG;;%i3@-S zf}^V;rwi{SlybCP?=FjV@Pj5_tUC*$E)=v>7>gYW68=)rl0oqZKEyC_d2<=}Z*t4+ z_1ny3Vq05nO{??fGhLm)XY%I0?WmEo3kJElnDD0Hf(OX=;MFQ4x169)g)qbm<5nx7 zuaH`P|NRYZDk!+zwHg42da?!x3_35VVp>izsA+LI4gk|aLx0j1T!A>!uv`Gu2{fAZ z@l?{6J@oEy5(eSo<0<^!SUzbC1C2Nu)`Z1CH{FhgB8w)A?aldJ!g$a@N-DLZGt1Kj z2J`WTOAb17@Z8fbs{x!EY|{3R)dgfSbz&i#}jEt+gs*j zc8yuS_p2 zDTdZp$tmopE7V)==6EVbtmq0z0(k&W5L#LS8LZO;zh0Xw5NGOy!llJ(N9;O#$}2m_ zJVF#L3fBe*4`W#PCU`P$+5Lz7c?y5UUorQ87tHpSTh;#0YkM~mTa@hBI0t&hQ~{Dq z#$<)NL+cyME; zoV(6E-NEmCTng-12!5Tc)yM>(&1offIjcH7R2QWhiv}Uem|qnElq~v4Mtg`06D{&; zH>QefK4h5VSa1LVD;u| zX@ehwD}!&K_HG@^nU0*oDhX)<)t>3XvaPsl0daVyo0att((y2f^Y&rpL6th6@@(GX z=jZoZj>teR-sQxZ}(N^q#j}o8-<$z{Arp#uyvzZk8}v zm8x++qxLk+q z(}mSwL3nU27eWGQSZVh(Cl3?8(cxNKY_N4EBsYgyj^}N+BL zvu6(-OOvDef&X;N9nBAEpZq|-?SFQswN1m$&#Ym$;Y`B~e+LuFIP+TH%v?5?My8=s&x$62sO?Zm%tTbqr&FJxWjy< zz&2cAi1~rUa(F-hcm~k5*taZz{S7gmtjPdI(@I!z2sMJ?kRYG9Kvp{}WJ<@&KrYqg z*my#3t{&oId}A+#AV<+qOA<_@f1rGexA&%_d*;KdF@`DzQxhDn|5d&k(3W~SIhGVM zQx4Op32(G@9z2$V1P1|J1r;PH2n=&aTHJj8viyRTep5Zn_fLIc5}Shy%!&fQJMmAkZj0({qL8#sLe{WkAg%)6r(eFd3E~4kL!HxbTDN z?Y3G&Km?k73PsZ+GoP@JxL`izx$lIdlaNr#h1w-`qgr#hJi#frSPrbp1`5uk)2fVRa9`DbmDFK@e4$&dyd2fu?~JM{qmBHs-X2~nru8aE;#oDK0nU@!w3 z4+K1eg%8;Z9_kiwAx&86xN8Bz6W}mldKO&NOIRi%U=Zm*f;kfdXheW6zy}Hd;P%t6 z@BqY-20T{)anpkd2>_k|92dz(0IuZ%5P&C5ST3|}yV=rpV7}eMR?2)kjAkn9`|xT+ zo?9xZ^ra%4z)>5AkyzICzQxK5{qxsZm+io5ef3dOoKbLD;p}LegN1{k{v!9yo>XkvF>?Ab0UBGs(*2$&fSVO<57DZv*CBZOeA zcQwN@){1!eFoOFaF(l30!fB2NB$RCiwcq=gZZtJKYV_NX#Hrz?W1{~0VFwI4W1rkV z@VJ{^l>*Mh_HajbEViEOI%aeV$}~!RB2x#*R#LDqb}#}@3+g;K^g2#TvKDB5>0X~^ z`V05|#91$y{oq@E2l?6mcFczL8`)EucK#6KA3NbGlipy$W2QVUIZvmozjUW<4Jsby z4H;ey0%X*VGHy807pr*Vv&EKf%OowxDbe-W@f9z%^+mB)j2KMp1E1pd;Go)NxolRd zwr))LjbvY)rL<(D%b|sOAl0;3aHf@fOn7;nc6?3}xy2JJ`wjDK!+hJ$+cYFDLqO~A zK0TiHp^Pz{<+xIha*P~R!k75FxcxUh>)b|V(++d;#nCcQ;I=9Q75GW0VvV@g`On_t zC3erGM~-_e;yFPAHVQ+L33x*TWTfJ$QU*QElSm)+3;+a+uYkD z>taz&yRYnO^->!SS2uZDzLxwrtj_XrX0J*o zFDd0ikGJvA%%Ke~RC!xVD1#g4Auy~69f;eDyBJFIR7tem7y7*Tb(*Wv`j|Pa$LfYl z2&}LU%0NOYblnHNv z7Rna$HRp+2Y>pRy`N47H149r@we*EjSadPkEFLIT^E|WNlbn?0M{+b}Ce71jDB6z8 z7%R^6mJAk(@#NAD(=c+JS6l-GSFce`1*?5A%#?A0?WM~7fZ5YMaeA|J<-7W$e~K^l zw(~+uX$W6tVlz=QRWsRa23trv2E8{c)mY=$pm8)_0N23WArOET8Bh$uNjpNWe-1sE6kX0JVT^!G#sK zO=$v<0ARtjAUI*ca|Pl6OvGvdPkckLyrQyn1&a_XLKutAeu)M;@?( zjDQDZIu7PZHA|(M27ylW{?$5UZ->f%{{6m;%kcnXf(K}80fAhfH{UpcmkDBka7i*~ zwj^+bAqg>PA~PY%iV9v}Wf8zB0xEU_qT#y@i46#;QX(?l1dc%&Ks>*c`+S&Q?mXND zn#aLt>KE);Wx$bgvh- zK5YAup{lG}AxZ8!OnOt=+>)NsaCZD{H+`d!CMm}c$feUkN{+Y;fbRWxYB1TSshL0@cQ3_Ky2x9 zQ~5<)ea33NrD7G|^`?~Xc2leDlh>-B`t57CQE1;{Js1wV+u@v~6<_#pb)}V8LYL}s zwfiOac`(EXv&cEncv@VjO!M_?#vASDHz(jZo2%`-k!2G0{K$X0xiM!pjmOt}*Xm2> zhjCXp#)y$I*b=NlHR6K$fZS~iJ}}2MNI>0y2LacM!?WrE0c1?Lo+CWB)&(ptG++IJ zQ>f{1DyB}%L&NKVc`J~!O8CLD8cQ$u=Z1C|Em%dHHcF?yH2JxC6@51~^YuOXl=rjR z@wZQ6JUk4|c4po=?4Czp2vDtHW5ODh$ux*beYa-a#hN29E9~6-J$E|T17@eQ$K->k zE%!YCR!h5Z&&-&{I3;dnwmIcwxa}3P8Ru*eE3IOPRd`ps2A$%?F>WwF;2IDD>&g`M z;343s9BOW?{jt!mZ~@PP>j{|B6OeesO5l3Wfr5dGFo#CJ0!9LpObqt`Gzer&E1juF zqXWPdjSvY)xQV{oi2RPHTPAZuoZFx3QF2)05&|Kb$P>>0fM9&oCJqPtcn8zQ%SRGmE2D{ zbRHq+zuJ7?xkF1Nd5@ed%edw$n(*HF^hd!awwhyD$7mLIb>H|cee=qO<8 z34lojfnkjVS4~ZAwI61yt^!Vg@anr$Hj{3yMD_F zJYep@_?E}cYu?YG%5b?jOxHJ-Sjp{Xy)Y$5Xr-F_92xbL>AL?|tk7RE99>3OYzlrnqH;WmYcjUs=MhN||@nThLc^Ci( zPH80}R%yFohRYpfj&bgoGqD`ga?+~vAUxC~4$E{Qg(u7mVj?SC&l(L_>FLAv9tVdL zW%{sJQ%IB*dKS@PvPf9|-gdFja3ZshZ0+!N=_Wiqw*|?Co-qrd^MbM6Jmx+y99|qP zBN=0lf|~h?JkQMOMa~dH@M+q$JyXi{A0P{N_l-n*v?IJ z%DTEOn>VLio@(QCvyCJ9vUh&nt6Q1FI3jatT__7*JG^56oEXeu46IX4XgpMo;GoIy zjT@^>c>PG%G8+Vtw%}$`akDY$nv=c$84+m!(Hs&6eQH&z1`Hw;5Lgy4+H)pD2q7Q= zx1UjojEjCPH(L+UC`JSQ0~(N!o+(^ZyJiMQuaj{wr|qt0UuJpflG7w}cb)qfV~n^= z5_MoxJm46Nl{h)&r+%YeOwC^qt9dayYI$0a&z*Xo7Z;?hdapRh!`xAO2{1u}V-hqA z07{Dss0k*d;E&~zdd~~y_q@QG4h6HKfMM+kiq0uR2tg5F!rbqK*~5K&#w%+b=?@2; zrTR3VjlO8ABw|$!hf^kCLiWzEvP1_1X|OjzL9=)8z*OuECmHtHC6Ww9*bnSM;Nd`qO`qZ1x$D|{AZNb$J>7uAKjV$Q?8p9jcI=;|dzL?woo!;)ZJ>?TFIq_*0 zZs@m%_Lgq!eeGp`xsUU^|6|*qey{I+ zD9s!Qe%&VYQ;#ngT!SVc80?iQ*cm^8`CwEH%ZdF>o;nXgq1iZi;)5T&kkkiHw9RE& zbXVXCO%)w!X2DlMQz1 zm}58i8;(8pSm9M!V=Wk=if-G|8oyVc_4Eg4!qbK0-u=$*^Lyg$j%oMK=PeL;fZoL+LvjoIe^2IYVK95%IUm5DEVS{iYvLkfP-o_iu{z()lR#& zHu@;8AJoi=pH6O1t``eeHLC@$Y-xQm^}RcTMpVAdj1 ze^zr*#>`w^RU;gh!*RE<*;ie%4+@6EOd=WuC^WSqzN#&fb)+`AMNK{8QcitibeXNZ zc7>k$g))0vy3<~xy#XQ(Lz*BlP$F7u6q@hXewEGNo95KX9_ zFUcjh*hz{UsJ#oyphHN#gqO@t2KM0@{jLNjxmY|mHdgv-`Q~)lrP0Kd%k#?5B|qtP z`@~(m*v?2;N0Ks<=>>uuG^XPf-S?UszQ5^B9bGT+w$Ht}FP6;OcE@v*4y*Y35)+`# zi}dr|{fg)O7xIF?>s>x!=}rC=K6OoS_3)L)?`^XGOTRxpRyh9YyHlR&ShkP&d;a#a z?eiz>&v_^4!HZv{d&PAg_EA6XWB&=ZJv6a36nMqgIVjA*TlVT5GgT`3FukB^>ddrh zM2e!!Yi~@8DtnjRo9p5`*fKWJWe_8plhWLig$V%waWmPRj4>`=dO-{ndDTK)*uTS5 zY(89GE-qB+(%7YCzg48ZXZ>amh&pmlI*im^xNWSVmKV6bRax^u9; zbLazZ3b`Aw#u|VbX$NwueLm+oySLd7xjJ5Ck~nroA-Sn;IM($Yd+e<|Q7tVFLPZ@` z4!6iwW&GYVt2dARW|44U@5n=Uy@lkn<1^xK?7vq(@t+${v=jNq$46&*#w_x+#%-)|E8+FMhVJ-&`9 zoZ-Ys5)f%uH!^{e4$QQW!l0)x&_T=rOaRS*Y_>i7DGg~1rU}6FfHBR@)Cke?0O!|J zD2KsN>t&<-YPX9cYnz=CIX`~Fo)jjeLY^B`X;&=0I=k*)!w$XM!9r0)fHtp!_qF%F z_65GFJvHYYZSOvs1~)tkxjf{uqxp=NW#rB;i#+ulB!Fk=vHSQ4Y3tGvO#7)n2s8v_ zDporvjg0}jRV|wTsTdi(zKjv2L~Li6c7{Ud;5>9|+uk_%MjS3=GXNzCXA7)>L#9_h zBEvEtS74OSMYxIquQ7By-QD?j1!w?ES#@{R*rLGCUFdeOZ?|`%HR!S8V zPor1*2=b#RZJRp!+;mP3ih4-r-@yNT9x(!p+`)5zYGwu=j~^w_0{%eoIdxrQ3^p53 z;4cG*nFI46ItD{fy|5nXFwNb39fz`F$WSZ69C_+p2+8qbB^=B8LWnTw^A%8HWnECB zgsY)~iRewq&4!KRGos|+WEwf-F6kX}sD%KX!RWQ6LZLtD-EKm%_G6Y@caJ-Ur|8qn z$cM0%F_pXz-|sqXj~IgVN^bmT~RFnY+_P$J^Moa^}Tu<)?hJD?Pl6w;}Sy>r^lnIyg*)U+QSssvSgm!3t;g z6CbQ7yg)c<9yd>XkZ07VzDPjimDho{SQ z#aT2Jq!@rsE6OH!?Tk2aIYcUnJ!*4T-+MF4b=AYh!>s2(xxjSD&yO(w?TH)zZtW4lZ9}=y$9b zzs!#b?@x@r?h!>)HPWx83yD`^9I+D&OX) z8{5WY|F}OMua9b+>FKCDHv4LiM&orfpC8_G)kJyrpFW=MU-k9<75^QzY?SJ9f`b;2 ziWJgNj~Bsd;XU8?1uuP)>pbC_7rroN0#wu<^uhz-;azy9U1Iaf;C>AIt+DUpiE$qt zR-ixiDh(T6#takYoahI5;?ps4h(UM&0pmz{$y7C|2kvvWi_U`RX_lAvF@7*@8?9Yl zDR4tO%HXS3@WRQa9*=BcJSQpj2#V?WR75;t6Wtqq3yIChQ;+h7uBlm zSwP1yXUpJD$qUIlA#`A0Lo=$NEZL*OX@auH27z?cSI4nzn~r-iB{OD`_vK^ceRZL~ z>oPQ`T^*wmO=j)TiErwIh7AoHbn;6Kv+ClGga=FRtde;BO9cP}K-||euS@wvS)4Lt zG6_IdT69nK5b_=E4a1-L5~R6H1-d6UT+ykzT|or2(hQPLU*O@1iqxFOnF=^Sh$aEc zjJck$Wz3eVvKC`M>4k<#FS6TG=^Ua9*r(P<)VQSBuYG4R=1A`u4KbA=Jdn%C6wIdB3f6J=CFzak;2` z1ofSzqRjao@w)rlFRrsYi?)h0$qiA2JX&Pr|JGOXG=)xyHJm z!mJAorqJM^6R_rr2n(_7l&rWzlXQCRH9bqlwo9vM7&1HVEU~!W34mqzetwrNmt?l> ztwo1?8{;0eLysc>%cMs;;i_VkR)!l=dm6_D5H$DgctjI9N6VWsN2eM((M8(L<%9BiZ*%+@g!H*$%evZ}D#4-hX&MJM=Pp zN%S+XDYJT6GNyGmhYySgrMRl*!Ci9Ozm;eFXZ>%!)>1gwdhh2^5?qzAd6>8)e%bHK z*cbktJR^V0`}$-2J^g6?kJbI+=xSf<=6CNh$6xnZe(KFX@zMS$`y%(8vJM_Mx5hKd zVWKlibr0*%QzxIxHqZFo`(yfS-u$T@xokTSU))>EjU%i3pk89OfxFDPX6f6%{nc52 z{^X_K{eTwx!N*19C%>vxx)yQs8=vIU?WMl^JH_r;X(F7_W@zXj1!K}of(RD2pvhf` zZPF|Vj@24`E{0xQoqtm;n2=e;zez)(q~()mvxvm-Wt@|CXJV_kt959YW2CdK%(LFk z+D?zl!C|M9yTbi_GRtPfF*tzWM@?uqphy@kM;cRihI^+Ar)L-0Zq4pNuQsV&VXVdR ztoe*>Bl(kueVj(ofUlBiyJMT=yWefIX;D5gS!TO+kwv@45$^Ka@?o#KC3*GR!Mkkz z(qpF6RHMtIAd&+*fWUqTcpxEQhwgD+2iY#!^DO&fcZ70RJS~0jS8(FuVEy9QU}F7@ ztj-*k2Yrs6!ekEEfpFeOC#ve0Y!kwcIWW@|VkFSyiYWFGt%0cpNqJ!33AV|`1)3h~ z)h9;S$xYis>!O*oHlgS;(+|9_*qitnb!DtM_tdI0ZkWSVLF^(4^+dmE@yweX+^*%Z zPRHB>{*F=w97=5rUYDa=)sfdaj8}waU^6FnRmfotc!X<4odaL#^zzJ|hj(}N8_|DV zCZIHfF@`Z`w3o+fIy$N$&UJnsq&&xF9H_F)GCCY>%8CgaMc2LbkC(_eH{Z^;7l3T+QQs?NMM3b**!(3?wLJBgWVpf|>+_j5g$W zH}?@$<`iiSNx`~L34DO-;S^`qU_uZX^pv)^V?&vM9e62&p)*voxn#|^?rC@_$2 zJ;}64l&0aJCzutvG&wG}oZs(Zr`uf2TTqXY``SF|9zMsEMW#By4+vI4fLM43S31k{DeawFzsYrCDFX$$2Aq5lhdV620;zYB|$` zq*`#43!K5qp4Szv8YBEeI0|jUWPI8>L~ORVxruI<+mLMJWsnb7hsns;j%}CN%vCFM zUBpG^fYcMuc=^hq%Ui^wh9ke{IY+ycGwwL!KFi&n_ljiQu5$V-POSY7^#-p+s%;I` z+501}^Ie*+&0tk49oA|!;wE8LlLg(CCws^7Psp$Q&6ko6v-xYT^ZkAPxnJ&Uc3>CT zMz13lm+UT*7<2m7(F_Azf<|G8V8jH(>rJH3#moycM>`na;sU+%MPGHZh?J~9)aB00 z;*A%VMN{8U9T%4$?i2pBPRfJHQf~(*#Yq&84yVIb?h{&nNS_tSnmEW!ZB@AF+%lJ0 z4x90)rQ&F7^P@tabl^Yh+5XwSc7&t{N3HkFfiT?2Q#by&hIMA1l&svgFZ2@c5B(@e zWkyf3L|&oCJjajo6o0+PJ?^hMQ9umesJo=aG47){IlPc=^aLHu;xbElbnxoTzSE6o zIL}G2mnPVYvwqU)CZng8kIKBc)$1+v-5%TtjwiocRyNgd_?Tg1j`iBY%?-5aL{g{8 zN+X{#a#(tej8)osi@sJK?hdv+I@}aK^rH!7)p-mMoC1btRy$ty;sPBL?T%T{!2~TL zVrb1|SjkaN*>`tz`cw;pIo7iFksy;Q6wj#FANT6$r0PDs<|%WOnq|O79Nfn1aZ0mb zVp7i`1eA8t0P1O+dv%$iXO<^d`@Xg?y~X9#{+e$|zjovG4{9CB*?KWmY%VuBxj9a* zj)yvNyz=jR=xoi5u;+4*)!lpcj58HF{Zub!%at1!yv#BitMC5|`}bcK`DOycWpz+R;;RK9`AE0BH^cfq}O*xM#gi}O6gUD^+}V|wFD z=b`7m;byNelLOw8T0A_oq~~wM3nW_x_ERtqp^@*nx9x!&Dd>!Rr2^ce)smTvX&Z-z zk)t`zA+$%&U7U{g_s`Hi=Eh^*O!sjGO4_p*dGJaX-#Ev#!#BJ){_UqXeT;pm-X;20 zmfq0tdjb(0gBCNLg+H&Uft@3lq#izQ1ljlTcIJM}z#^uA03b(-){{1*vOYl&0)Q9? zN}$rzz8(efj=EV*R&M8(&mU>u_ny5sX^hG$sTi9w(#|s!m<)m@4XQ9B=2~nAF-sg7 z7P{Az^lhZ0LmilHL{E(pM@+09$D$5Jny`J)XN5*)B7jqGxu?S06^YTKtW8m&;m%30|J&#odIWqfuV5HU`YC#Ku>Z6;1f9LmsbQI!=01@ zBA@MQ-WMBd%==TqARz*AtlYLJKCMZeR-M?k6uU3uyOrh4entjdk;;T=0+o8kQ>N&4 z+&Av@qN)!$oN8MPXBQNgZy~!h@8b>psq=Whk-yA_r&Go{V@t=$*ApBss`xy-3H z`ITQwvZ_ZNaiz`j>LnGsm0ioRN?-A79m-e6tL^Y2YQqJ`y5;9ZjMBnUU9^pZWthlM z!{@uN#;1hM5!)D>IKnx?VGjD4yqgG=At4P$s&$Y(ghLG|8XFaTX4DiN%Bgp}z|VR9 zpL6FoE^qtt=k<@+-`uc3n5)s!5lLIG?&BB>ty}TYB`DVN3LF$c?50&#ex*@290(gr3=brX6Z~KYc zkDhnX`wH*c{V*Y)1Sb1>U#s3P?(3*raaXyteRZ$*RgPYES&?T6W+`qdZvDbLf5T0F ziNdKz4HUpa+6V6aZlTZTzRUgGs^-mRUgv0!c6TNVYnI!$j&t)T_rkZ|*>!mDRnxwZ z(TT5fr>DN2SA2HsA9@`pAN=42_I<`K-Jr)t5kuLBCcQ15p=9cdqk*KYnj*ZvwVF!hV))T&simx@ z{cTZi)MAz|YjjOEm}Qx%rjjU9Z_ZwtJ@TNoli~Gn-R8r?J=0;DGguMSC6PvSG>c8i zy?2G)n$CJHEz?~5zga#_RIsL1$(7^FZPZi4X;!z)*4-R(bKu7FyudI?9pd@@@Vob3 zIy>Rj@LKKeOFP~=)7Zve9-g;czWkT}pFB&}um0+8p7)Ht5gxJUL4ex3O*r(<(l z-Fp$zc>ij;8?Kr8gtpVY=dm=O%-rme8BhQ4D}4F-_*QlQ{W41X$_KmhbZ>mdzsjt~ zZ^AEx1E}rPuE2_*IPLK$q+4~i-jJu7&b{TXHaOrT9}e{%n7{sw!}MR43}KK;dcE*( zN+EoVuwmvC|1}$oj3lD0H_fV03sWx8oxC2oDz{*Fq@tNyN9ZlpWVfW!#>UA@8rl`U zA^?4PR94ZC4_h5TI^`^bc&SZYoUJ1m^vk;uQm_Ew@0hoB$+9dsSY$95{-1_v^H#&hQPMoR*q-ovSIv zdTC@>C759909=4M$OP2o$0@oi^v^ZIOpwieh!Ne=0@RV&Z*}BNDR@fDEfyA9m`@$)4;=jwUWun6z4kgNi0A596YoKj!Euw`MnU zjVD)HCZA4gPk+z_=1_tpnWXIxZ=8M7`25AIGgP4ob^5uguLb9Z=d=&yEr_=eyTM`~8<&uD)H;>*{%WzrW_L%iQKPCywbTtbWH&*>l=u z`rAcA+^!YsKFxW>b21Nm-tO?`xc%$>4t>-O=2d7a+R>%92K zJ@)7S)Ek_?JnQ)ROqQ8!X7|5C=QDmlw}1NyumAC{=Zo*%Y+haQJY%2sBbV6o*1+*k zbHB8$0fmM;uVV>BaQR02&!3s&GFlQF* z`j5)T;0aeblEz}LbDiDhvB>GqY_@;5>3X)ftC?QkXWP=vQ{fOEqAb+$nl!t~i7hZQ zZRlx)@oYgy4wRQD5?jmd$PPg3f;^)W1r<|#;@%a&^3ep(&ie4NSq@iV74(%Wje1gZ z^JWaq{@bs&xYXU|(|w>&y*^QQ7Z^@<-VY9S5pFtgjklx4SC`%>Iykuxx7G^_pHZ;j zYrXGx%kW}(A_$iJHduyUB0Sw4_d~)ar18w?5>3}U(vJ2K(T>(lC%m}!a6Hk$du0>> zlr(qcMTZjfBlbfGYJGSx1Lh9zD?npH4FWaPQ(tXp?MRXWp=OPkA!pm79FCWx zRccMs%*U)~0%h4)o;dqiSlL=9S-bgA>(}&E0r)9&Pg10bznZ1_#$J-aHz)G+F2)*R z=FYD6T+k|WKktMK=7&0iq|(nm*`N1u{?yE9+7>I!@YUSm2iX@jUt;-^HEj23%E~KC zSh;oWnSC?19VjHst6ul%f^?z{Qjp7v%=C{H@1 z2d}!%>V11+Vd+b3-23V~uxju#Zn)*}*o0KWI)))5L}_^TjVLgw3HOruWkl+US1M$H^FUFfre z!&i^xtHsY&ZmtgPYT*mX3;CyiyWo5EE7P^RcVN83Nov_H0C{G*v$`?N7eNk5_ zm>r$Eli#wt+3}G6q(kS;;Rej`G7JR0TXB4Qg09o%m&x*d$}8m;{Z+h`o+p_1X*cgt zwfcJ(3(hI`e2$m<^LWhqWqj1z?0M_Axt%#-e{7OlaD8?q^Cf%+faAyMc)`==>&v|A z>jWTD^V){fw3=K{)MG)7 zDoKKU_E#NuXa<@jIoN)3F?+>>z3Qn?m)Gf6UcQNuL>``f$hPBln2<)KX&`q49+VA3 z5>nTaupjbSxA`2ez8~SIZ}iPS`KEV2a;N3>1;;WHWOIG+_VCu38@~MF#$Wz{N5gru zr@ijdo?JTfIi9x5@#bOg=i-+6d*VD}?Tj8ebiDCmGp5Aw&Lr%ZAsU z8rq(E+kG{GW zq!zw-&vcX8trII*tvWb>IPjxpk6_)@;>mk6i`uAT5i=CP^4QG_6IMcPSPhj(36%{%jRUA%z8^HyIYpAV z{Y?FNZ91QKQoSGM*?;~6$hjgIHTmB2nyMzrI3HXdW$RYGQ>Kg!QS*iq2y>{G{pPR7 zF@NS?h4+=4`P{SPw3iJL6l?&_KsRE_=H|d=s`G?_XO;q6OWbMBcwsX`%_Mpz*Z~P} zVYtyM%?kN^c!v0>A@*4x_c`L9v?X2du56Ll&-bc>QMddlh6xIIx5B(?JViC{(2hD! z*e%2Dr$@ap!v`PL8-Hd^@gn$ggP^ zaG^+FeOdOoukAZ98yvhJa>-{FYc-jznNTb3Y99$Vtf9&vC0sytcQ0C6vN$Pxgfw=X zT}IwBuXnTiW>3t|srq1k>D&_I0E8?7G)XG&9nSNIcaMQ|e2xR!4{nRz9Ezj;LL6u{ ze@cEKZ$tj=hL^aQ3RvwnPJ6{{CtC2~~@Hrj{t-25-XgzDUUPKWmKl{gF zg|TaVl~Vc256cgC#>L(=X7up9nXjmLuR#+6A!?Q?9qa-U(YSk{hsN2*KVFZt-T*Fw z(c~s8XPwTn*bmQo_Os~MEuP*dzW#2(Iof`{S%bLpn=8Tl=lS@VbGZ)xtQ&pv>s9t# zsRti&^2c7K$8GBIJDT<+ChT!y)+%kV^`lM-k|4t?0J za^BNdGhwYf8Te~@$7aq`|fFl$Q6dvo^v%`kS7zdo&R(vcDo%Da}J!h4Rh1ohh`_q z*~V_Ehn2M?BvvQ9qLo4=@G72&=R0ZGxE*@=T$b}9cpc5sumh`3c<2RPyJdI#z9%q? z^lM1ydFL>1lPIW3z>#_bp91++*TOvU`QwS_FK_(OFFi+I@2N9ew&cX6wT@YWkI!*21nkX?X7Ko@_4w?8IfGB1*f2QiHkO6C0otFZL1Pz&&kO`|S}ve0KJm>H=ND zzU-sVx$5e}&$~Q-s7^=lX*~Ru@BZc-#+YUEG48uPCiN&M0*3uEFtv2Pke?t0=bIy?v3>)>7Bb@;cPG`ifzrDjSo6IJOmjU+%5VdpjR+Uq>#i zedxm+34`}+B#1dCJ_PZh0}?WB8PN(95VIOGeBq%=m$N-rnB{7))?0j51VF*Ks=(w2 zEYfWP1iTFH?rCc5y}*!I-9VC_6FmXTfVEIjYHQ#&k+ayLwQ>ctb06Ul&LdnM;qY^1 z^MlW#*cZ}ov}rtOQ9Fx~4$*}Z*FMqp!yk3jh2)MRrSIAZX%G4iGQ6PjF_U*iZ@BfyO+L={Co~&lnG+EP zD{8yqi%{Ga!+rc(7cZ~!O@HM>&Qdzm`w;kK=gf5`(QcpqY!ljdubg`B>-OUI#^&;U zrME+GN6{{82txkh_gybZ=@s)j8H;hXeFo>X1L2D~Rb00+q4&)TlhTwUT3_TVchtBv z)BURp`II#dKBOz^ye?jP;O*?I1X>+IT@UUhYP4!vq$J*Ow{>zZVkS_^9G zXln@#4_rDnfUBz#@CYMy$skE*W_u|+%}XveANd*VbKm*c57|x5EjmmH!Eg|uZKEKS z2t5!|&D^la8`JS>$ki_UNgxOZ4t7O(@iXbg=O69&O1la@rM>-9pYOMof4OfwU2c7( z3oh`;qm7jkageE9O?{}f>pzP+`YqncdP+(^_o^?B<|UZb0tij3_}=bWcibznN#Y_^ z+Z;F2se0T7lf1p~7at?Mub-b@eF3X2?dIY?PB}*k6V7bWb9Gop zx;+yEgHx1w8QX|uOk1c#o1E05=Gp#*#q#0r5Z2do@dXV*)g zKk2HS*a_=*>&7nJ(j5E+-4J8Nfo5)vHAx5n>Rj-UdINop$65MG^NY^)H$2tt^-eC^ z&n(6nito^~IMLjNF~_~#N5A^IN22tjEn{BK;>IU-w~Y9TBJxb1=Hc3={^?VZ@$T4i z-(;jOUPeedM!l9?bno3(dC*`YK=B=%&@Q9-3VS1VNR0YFNxg%u?_Hj>FR19%hbB&Uo85&NEfIsdP9ty=>yl zAMNSrP^fm6)1G*iqkt%qjuJMnU0+Q?s2$Fn!{doD&NnpPD(-nS4;$yX1lAjR^aJAR zVL!2l^|tgo^KRK$sG_z5^U7;g5v$gN_voG;Tdv%<^wy=(1YA9m|)#Yo4}hW5#!a_c2lxU+GTfyk7Z`G=7dYe$|QVG^s-5zE!XE*zAH@I2+A)%EF*}Q=;e6az|KW%3uWA@2C<+bK zzR7NlcZ=s9a)X&yPfU^K#PhH*T&asQ7N*6mPz+mEv35uU8u?Gq<+T(NB=WRsptpYEBe3Jdy^y-)gk)`r&f>$0|;pbGNkOke7?xJ|RZ#x{!W8UwDM`Cmzv9A9u;u{=px=KIe0J zGZ$RG(fh6zCNB2she2G6tSh=(n=bSbZCh z)nu;fDdaS)ya9)=Ip@o{Q4x>yDpe;eZu~eFFa5(UxF^UP(2nStjh2)i5E^6Da#)u7 zoMZCbWN#_uz7u8rJ?&K`1pr(g_?j71%$ zW;ys*wmPbnElVHe=7&4O&vF;eiAh2`Op0*Dcn7&U>2vZsbEnJm(JniSvge+jt_;K{heYTqg)(gU2V}yaJI%3I03c=(IUZ|=yUmX*#bDsM( zb_Z`epSsj-!QaPXZ}%ma&bhkeUcS1&h1z#E==c2Cc=--2kq_X-3=!kzq%eq&w|eW> z`S4oUq-IqN4m*@Y)KG?O`7k0&UBwQiG!0M5CI53hY@fA_>wNQrR;4s>7w6S`@)>>R z^akCqy#UcZ{Csgslj5Ay7p}Q-9wvKQ>m-IekZ#23(w6&dxi(&v-M86&&oAMljjF1`$4R7K+C$^hT<^Pc<6oSU z-dQ>A%g1eh%iH~nCwbW|%P#jM$q0zrS$ zds|@8dX4^Ys%>j)rRFFRb9{K&HUrh}kK-#&v{_SXl2$68wj|5?9a3M@~O4+5I-|#{WWPHcYr2K2H;R0dSCxDw|trx{4?3{ z+x$>%DIL)G`TU{g%Yp^CurXTj4`4 ziV0h*yBN`&JOp=3TiWAzR9l){dYHKJxmRBLme?t8Z}g@sOGD!+$f@hP>bu{eQ(ybM z->}m;?&Oy~&%fYy!&{&8h!5CBJ2c8hK@bIegBw$Cu$rD{3nm}K!ra82J0Hbs z+j+&!j@NdSW1r`Oct5<|~CJNmAPJdhgcS2eaAFi7Yq|BP$!7L!_TwxZMGj@UkR4d z=00yLJd6Tv!*uog>JPK!7jt(}(G}JU@430W1Jm5)Y4r-d>6v#W9?siFD`RCxXkyp6 zd)bfol>T7+AxeZ+%b(F^9iY^va@GBVIs8;_o? zZ`Rj?{ALEI(APS7tb>t%es(3qB~)#oeAaauNFOIEae#>t8WRj42$Xi;WgF%D%s;NK ztYdrp1pdmGUVf@^8Okz~@oF)t&|%Oq)!;4HPd_0IhxNS^)?!ZeF!-X7+@o*PVUiB_ zAm;Si$7kp59GAwx%DV?;PC0I?rzx?O%PYcm|FGR(?%49({((b8d+9u%kh^?->9~VD zVtZnCXVE2{$-@^eJ%4fMV{D&$oJfwd`5=ZdRCzuTecYnDHp@Bb&FU9xzm!x!u?HRm zjg?$cSI^ev`ufC9moDzvvUSVmmc75v?w;lJru4Wq5-V-gBxq%VjOS1>0?6@+zaN;+*o8+)&$(c-W%O~8r>j?_x6dVyr?~8+pj{5{X1-?n`q0<<02lU@(77XU zV9^s=pX{5}6d@==$&SrL=NhQCZpyzt0kU;8%pGw?g%VxicVl3zRE_eX-m_Pq+%|c# z)8TclHb+*ajf;@e2D^##-MnpBB5K`r3%`xl^Uch$Yq*vs`%pz46$pbsHHO<4EV*`@ zD=6+cjN~Ro*V*r7tqY%@pYGGcqtwO{^y-Xh5&^D0!#2~0FSb1P19sdrromN? z@ouw;i!eSpoWJ9G1NAY3=MK$K%c|i8ib>g29iGUngALIkODwt~`wXMYP+z^RgKs`! zPS2=E%%*}ye5m|L*Pju^(_S!ptdfn<@;FKSwpR21~ z@6zIa^H_XQueJE#dbB@&J^CNHI@br_`TwCj+y7>lpRu^MH?vX8d2VA=yDAA7ZamlZ zV~yaH1_P+^WDv>&JMv)B4}!{N<-7<8!x>=dU{FlEqrubSa>0(n=6inc>nIahkE-)|HIjZHyXBe+{n%7HbB`h4Rol#~ z&ih9mET5i|gm4(FSXqMtR!2p}xY%~X_1|YVW?Q&$%iM0$i6eXrU&0-Bdwe?gLFfB1 z&iflq%P6%>rlkN$K*~NxSjw!IdG>$pKhgGmZ#NKmF|f+}##ecD&CC2cZ}rvON zB?-Rt738qCmFd+wGR5+4#lkktj5smvB2qo{g7uM&yOYJqSMdA`U_g?Rc_34Th-DQy zIg@qdOljw#PEc$O^L;T;gJy0JX}xu~xDUG82#D0ha`~q)VS6869XGq+q8;BDa*WL3 z!yeUsyuQ>ci_r7Nd$``oRz_xSeJWbFeR6r+DFpCC&UiCDG%79|r8RjQ!Pcwy>Or8= z{A2w+ZM0r3*{DBJtQRUlolg<0>4KR3?ffa?#W}e#2du5kLO*gps*o0=dR-IqHFV*! z=_Lf0gt**2_y7Lk6Lw70-IcSQ*Uj(dsh2A{jF!*a!>H?`&N}8s!K+SO`?d2<56TZu z%)t5}=i+q^ebaC52$rv%bI-3&`xLa#)&R!EuuCW$f{GtU&}Yf0ia7hgTt=3{CCBAT z4vV9+Ty3NIc_Hpty7Zx~qTOtl2k{S8iCr+ZLy*B>I67IwY#%b+&1J}9h_>mNmewxz zG*MGAIiJ^cBevnXuHXefCXZD360gtPwDV)_Fc;tN7x<6uw2NDhcH!L!GBntkVw?v+muUnrVpT|SqGxEmHxiR~E z@fMG!;N=?&emL*vonj~<^Mq}3D&G%}wP-TkN|ht}HMckHs`K8 zMc*GiqZDb_hcaL45Q}u}fVT|0T9ETpK=o>4liE2lN8gTKV^rXL*lh*p-Xw8jaV64Tb(>3PJzg6X4(P5tP$Mcnc@$QQi5^Cl5L5rL&bO@of7R%V@ z-UWBRL1BwSuKS{>N<)Adh8~>#5VtS6nd71V9nf@~#Qiha>c|UIY^~{D9nXW5ME1En z`5=mN4$c9q^=blSXqB(fvZe7<*gVo+9&?x5?v2;}BaiF<@$PuHNv=EpJh%H-JoOX0 zP4UVbSJZXFNH<-+^!ShG3I9WR@)nY=a!$kF$*0Mp&RK{@sY>) zqj~f{H{+$rylF4J*}t)m^B?sY|NHhk`+B(&w2r`4L^0H%^_aLsbYSAv=LVUNtulcu|75nYMQHNe7tAnCxKFwV(KH@T>R>i3108g$iY}8@elB+mp5j^z?rm+hV%Hv|*VjcNp1}>6D8DngTa{&&2O%Z8J5{5c zh}w0(%JctQ&o6xcxSL=7!3?3A(;(L4)OS7Q`8?XMp6+q#Q362b7#665qu*SQu4rqN zbQx=M6&uS5uIo4C{`(=2_P2J9k zyxk_2bJS5?MOt(Fx7=lSv0Os3PKeY4(r2zt^`oD<_Jt1IWXo~jTs6(vv8JtLo;Wn{ z;KD^mzjEP))b2Id-83wsPNnkt5@It+8_1@ZF5&V++|D4r~^mN&zhG>l%0ECa$#kxxWcAxa2ebFZoCULeGPq*=&w)C@C_e9-?_dKKbnQWXdV^6_d=aXmIeJ&W- z;187ymupOYqqYvfI2d#Fu2w;<_)F|gtZ8vhScz?YzpONrHnmY33cLjspb>3N^YK_q zt8zRoa?UsUlF_Gv7pRK4+VgTVW1LEM$UYZWh5Tf(biKzxBia)o-=CWYLoZgB%U_lY z`aEXK!kIvy+@tsIgT73U4r^kZpK0g5vFChs_MFQapEFzr4_H@?eXQBQ3jut{$IbN8 z#6G67aqmP|udcO?2VTBw=YQ~fKet2U%DsdyJOK&Pe36W1`h1?_^0`V|e+! zWA#<{e^_6o<1{1`I?8UG$E>`SbKfTSmYKJ4y`LK&+x8r9Zu-?vAL!#a(d&SQsc;K{ z-*Nv2pILV`(?t5Y2pD~m+Cv8o5)>jrQ`3Ov@AVp9%fHeOEHrn1jAX5{)|vQn>W@0v z%bvF7W?7d8u6@vhx_5i`bKaL}?ZLdTOU-)hsXSlJ=`U|_v-nx8 zR4(>fbKgH$Xxs*9o2MNj|0zO;={u8V_qb+sbm4-+bNY0Is=^ghGw_UN#6x6d9rLQs zZv^63KCSXY1%^JBbd=e`BY~@)d9LzGwDL7fwvzqRf$#!UmwT!(F-vW_?@A1mI-lK5 zq?iV?GI#~{yLKM@P$Cl&BHnx7f#3!3oj8nWN4tAJi{E)X_P_6Wvwu^gDsaX(dB*=a zxBX{{5to>$BLVm8-DmM8Pxv3^QT{FL-n#P+pZCpiKbR-^&+b$IvwPc5*iO>~UJ!=6 znw$L4NBnj6_Gj;P$tZDpd%7z({qsJ;pW%i65x2j?J%)R)Fb7d+Yl8Y|#59S|k3_4v?_K4tnRdhB#jz2^EUz41rjApa1& zrY>FP)l99*#qkOP;ysI6m8NOb8z?kT#j=Cb+~^%|@ZP<1S`nOvvmyO#paQjTvP-G_(-hX%>c%29CAKc+0(GD*phTLbczKK{bK5+fr zpW7EUx`gR^W23oc9L-xPoFCQ=XWsR9+xFD@g8L%}NFkPYI_$Gw_u6oWY0bsHYPV}Lb2{G9`Y0)?3#y0O1@cAGm|x3CQZOK} z$2^beRPXh?z1U5EmnPM)<9lIH%8a6X|**Uef-_KxAo0)Zh0oQk5FFPnbf2PXaiQgOk^I`q5gi| za9;v>9$e*HtvaMy#iwH4{yy^Y=ns$m(GPepONIMJb*O6Nq*Z@)qKW0c?}t!12XnyY zbJHY@DLI#qc(p$4Ub>jwT6vr2KIMD6?R>>~Uml_ft0%p=7UtCjm$FQ^%QHIf%J=O~ zciV@I`jetS=~x$9UOnrwU3mXim=4e?#Vp=TAqV^5AbIZ>T)kFa-*PVv%6Qil6F<8$ z#Ia`_{#?CnluHxegP&Y-)}?uSYnbOQ>F|g``-ibe=Bti|UdSlvhuiwF^yDU7?%G$e znMDo;?j@F7axif=60;`Ka^-gEekMP;zDV9Y!_?pfx$}aKVBhyTJ8XvmATS=gbGEGB z>BrubJyk4p&q+AvMM+e1`pT(FTvx0_!_Aj@8PfO*g%3{-kdYf+>TW+&v*{I<$>sju zAL-4U@6pU$W}CpM#Dv7^aN+!9xjLf5^qAAU3me@@V*AKt%gZb;IrEq&ri-f|=Fr5C zeFM4q5FDfj3lsJq(<+bDdne;0*_fA%(y}QNC3~!TF7AD{Iw#yc?s#>c^XB<@KYDI` z`+44}OE96+3M6*nPSu)=unFCSU7k-RCFuZQpaQDk_AM%oBy8MG9AVk+<1lSZeY;2I z^4sQ8n{J(+)zefT$7EL6k{e6YmOaasEi2J#*dZ?oPRMn%_+C&a4Y*mOcA2Ym-SW%Y zU}vAcblgV+9S5Fp?Wtef^;Q4Un*wOYh*BE3vmyA2nT-bUsxgq1(ahaZumlKzhTPXg zFme~GSVS%nf{-Tp-2|o3N|8mu=0~74BhPe>@t`1Y?A(iru8JVD9G+uhJSRJoSjb%t zc$^1cqmgu#ZyFimF~g4^^5K)KKJkD4y?@bs_xH2hBJ0ADn<0JeKj#y;p1a37c0cn=Kq4ze6WI1v;(%T0|tat=_c-8q& zTFYIId?*B~wrrhbTsjz!N+8xHcT_irn{jT+wc#(f^zU^od zVe4X6yQ5f9a9JLTJ`>meULW;W`AfZMi*M$nSUfpu{=i}7LI#gu-9C}z8#>n%Eo_{0 zmerEdn?s>1t){NEa;&|Y$ym>>6}^Zvt!T=jx&3x(9yc9luyo<@v>aQadOJG&IP`WQ zS>`?!=X4mx8RkAEoL>L$t0Is4~UQx89U9*?c;x<;NM!K?URC(XA zqiUr2<-f!vU?3Nngt^$dF>XQlSY(kCk8$>B2CXC{k5I*jYJNWO6gb{Z^-*wt%yF@} zX*XCpPWa_hrTWara5wjBkIRQSzO9vN4yvk1&2}8c%57`;`P%hi>*K%wpLqA>`~TMu zdtE#X6!K69idM#-y-r=tsU z(Vwe&R^xR4pd4nxo;Cs~E+;|9>{fOWG*!92q$w$fON4i`~mwoD+-L`+cna5Irb85|m1XVbOJGCF>jr`|5VO2^qHLp(50px#ylw5#21I z^PN|0gvg6{&-3QxFv#;!UJMfF& zdYv16l&x2@%k8Mc#xCs)d*M1CbK-}+-QUe|4}GZzcJ0>`FWL4eGS>DmwI)^;AMUTB z*F@F5qp;6&6ahbtlKp;oRj8d0O#}D<0#S5|_%gQ}D>ZfK6sQIeGl`6TMI%WNqvq)^ zTi+gM!gm)NPc2UeAJ#4e!MZ5+FxCZM{RfW!?qgc-9qYV|Kp;UI7$1O8kbX&qaJ2#e3pNBiST-!cGZRV3X46Z? zpMfOc0hRzD;(!?AnVy$~DFtAozcD0XS~Z9z9RSVoBvKv#)HMTAc*093(?Z%w*ht6W z!2^;ILI@LpX#$pqim6)xazKH?GkDbH5D>$zU5@g@X&-ggr_G*ci+7W`#co31lMsNx zK6|u{P$Yp0KcTofa?<+XQ4g+2l*k~9t?FD66IUTtQp455CAdNg>A3=>ETu}wg{UdS z2;st=FSRlrxC-r?G}2HW%EQp`;KX3Y0Xqp`jF%RTriZkLe$xPwHsZ-7Y*PXv1ptl& zTmVj5cu3kv0uCVRq}@+iAdR>!1#D>ork(UeGax+3WEwH_n?_myNFyodRZ%by`yioE zNFad%AaFgUfr7%L0tMg&>Z8qzeZ<@U_`3bi_S?2cC^@C+;qJnP-ij4HeiazdfHx!r z07OGIsVUG@*p6X*Iq_wemAJY4?5d6N-mqSjRFN#Jxvz%xL7gfGz1n_1LAkn~_|Oln z&#$U5^|;kV!Zm&VaUrpK+DBK{r4e>5^bl}tmr8)?;Q71LpfM6L!-8NG^@F32y5>PU zN<15hI{A3!h_zs-8O`v zAf2}0ZW#djS{Rl}21B5ckRDKz3d;t$E2S*U$yiwnfRzTU{vizjaSKSv>k(-vz(WM% z?E@EpPzC~xFfF(MKt&0-FySTok;s@Xz^5PsoTGQ400;OvHF$LmE2^WBqr;}n z&)--W?tbl#*~`>k^PqW!CNQr|8tHj!ydc`i#qXX;U)=Q?W1c`QT1Fu0NMx4695 zsr+@846JI(JF(3@q8qPUJ2E0V&~UNLyi@BQk(?RNZl-DLvZo#lXoT6$m=Ota@9)ET zY123;OXWTzyqeu)7f`^knq{dq2iRAV%q%U7!P3#~<@FyVS}fYcs&@IN z@7?=f-S)73`{D8=@ilWm0+g`fa$b{}@^)Kp-Q5l{JY$ZgkT#H%X#fxsE{8N{pzI}? z7y0yWzr}Dlj3 zf6)hZJdb-iFSNdLvGSwohNDC?SYwmL=U@NcwVO0UR22rp7SYP_CTCE{abLO*8O}x9 z&ah}Xc$r$6{)PK4h3u^?#x>O12N)iE5aR@coDpjZ1BU_Va>z*L6ytGEE{RSvZat7VA_*Y7PUws zi6%1%ZtTYcM03M<$;510G;>PE0MUdb(Cm6d8fhofh;ey9oI&p}To3{=#JR~c48lp8 zUeXgOw}C>A)m#S-cxe|R<+=j~jCb8seZA{S3C~+TRShc`st5(10z3tI?g~#1udBS) z75{Rt^o!TN(;1xVi=OkRpSq{{Pm{){?Ro-OBLL6S!yX8L&q-JpBT(*D(EO;+rz7S* zKc^F#rHLsi4Ihv+fje9(dB+S7$HTrSyB1TW+rSs08B_DQfK*5opvtjM6S6#qz^U`nR&!l{`Y+RQPsAZp5C9+=0lMjI9rn{CfBkp|!(4HpBrAXUExRUj840%8E#6mY%u97Y2LNUK1e z5C9>UKDk%N^Sr;sQ~m#w+!4tu)@_|-9Ugs`EJ?F4_I(nBAQ1!510uql22xo?ZovGJ zKEAGhb?v^}xF&I~y8CM1^pTB?#O}?6#%nu2sH9erPPA&<&_N>N>A*`!!gy5np_K`$ zSd%wz-pY16z1j9sd%XFKBQN*z=xJrr9++|#m_QP{a*y9>)I7E#$%>S?$+JQS!*n>j zhGx`1M(LOaF_~M8htF`g?mOLOtCu+B!@kKr@A~%r-nI@q9oNUtW`;Le`*m{z(g+swoA_v|yTqOq(VoEDJ~jaQm_Fhy@e|0Rw1w zFi?>26ifsNSO7f06X@3kBmh`|3o~H>Q3_!WYY-sy-~d4k0(oxW@pTK3*EpC}k2&3@$WMRy~aeW8>-o+QIF}l2p!#vhyzJF&L zstoi23NSW4ey6zYlnehRwLdH>g%;-=KulXrzDXX+QBN6%YH~+(i=a^G-~d;FH8gFSc~g^3L{0lh_(#WQquZ950Lsag&gH zzaY72u_r-O=>-x%3?hrP6o5=|dCNfE`sjR#PygndjOXHI87!jW$`Qkf^Ueff-6mNq|%;!O1!YBERH=PqFrlmYZn3@?08M$K!tQ5*PkDU$^|AO}Wwp z-*SV(jpr?&i&@cq% zk0E9^4zD7@#7q5*FF2G5lcEseagAY(Hv(r0%{~ z&AqmG-BXi2R#$Sz|I5}5V(rO=v`Rbh;Yr_zF&$s>o67I8gB=H8hJYAxG!RDy7-8;>*_jCv$JP0JELg_9;HZ9>HHpo*!cc^2<(^ks!wil?^jXGfu__Ay_ z9*;|Skcj3^!GS{C$N+O7cO*zoEMkGYrs3Q@zPB&-Z}GjUJ4xg8$?1N`^ZxYLFFy82 z0<3`>3*iY*a#hJ2mwWOsna`Wt?Kh`X7%`h`$M^(uL4vyz0uALu`omqx#-0RF$|$3B zVl<3Yehg93epfN{YBGT`LJ4SJBK2HANFZhk4?rCt-L%u}cr}~>`}JW07TkKcLc#@7 z5T0eBS)}7`*3EjT^Dwj_HG&wp=sGq{rvZ4W0S&Hj>^RM)Sq4BTV!gX%PQRump^N1;ZLe%NyK z^sgT8>VNAUKRYKr&euq`n=9(bA-tv2d&i;&K_5I1kPt$61~5PZ8i3lx$aOyCjr|+` zi?{r`J!ZMuH~pBH$a}Yb`mm(8@7`@J_+~zDT-uO2>XvFAt6CrI&?q?;nGKQr$Rtld zxYBd29O78w*p3e_$#aj&cldH2pBE4GBCQ*fF17Cw1jL8|Ivkq8PRdJmC46Av(*xy7 z2oqXz^7tg8h9FQT^{6HYnoJTX1Z^;OpX>W${d7Jtag}K!m`}*5Sac@o>vkLOaE-jn ztxn59N>J@h716pCUc%qDuY${{Phm{`=ojqCjrIr-5I`1)Ny~Oj%M<-@0N;)QK0G-G z3?iigPr}6TI6xfaL-v;2HE;<5*Ex(GyHTLzqO22O6Y?yav=!dvHfxGPCU= zxB8d4%a8XeAAdIauHzp35_!Be@A4^`Mp8(bC>Uc*Eeru5f~O25d`}3m#*Z@F&X`^` z+JlN+EVkPAVg8iUv!^_z_4?@?4B=O0n8&ecuq3v%kBpeA3%-%Sh@iQ-IB~oZBwm$_ z`;hbl@BiKQZA?`rx7gEMfJ8k`nW z8qq`K)~y~M=G*~>T)yi$n@$p22dw5!CWE3HxAM}x@8^aY=Su`u_V3+ZY`@dqyxV>M zSJ&NtZa=>#t!~K2?Wu~^^HD$-Aak-NCF4Z6;| zAC`0!ItmAuM_iSZ@)4Uk2}2wUN*Bex?KcU> zd?(}`@7C(lh2`7Q9Lk;Udsr+^dy&(hr@L^NYsOurHy^kEgY5deiyzPOJ#h*2>B7n# zf48^$S6LYGqmG)73|ev$SWVHTo&M~WLn7%LQRiU)L_&_hc&L+JZu@h#cdOS`s-`U4 zxv!N7KpZxJEC7K4#QdpyXuR(IzxQS}Tb#?V8e29YweVl6!vL21k@z1Sg&5BF*zUuerkND8gr<2X;fXsh3OznLq@$e&wlSt%L~bdT)uNC+T&zOXaVjcn+H4;&-oCEj5>2+O1=3-$vNPZD9tL66#VDAp3`_`C z?g}&sN2Z&GR(8Kf&^6KaL{pt*_ekV1a%k6aT|fYKY|nZHtn!gUCu6Z@)B!-mnP#m&qZ<@lo|Np1e&0HD08JM22fb%fiy5+aV}uu5yS{?K57 z=O0BAw}uc4kMc{YQN}ZgTl5^lLq%X8(^6nW97x0Tn#XghMmFK~Es))Xfv_xV=%6iX zQ|Q+MV8hI$2P&ikLmuEc4UU!&s?+6Z-Bj-+r(iOuSk>S@+Le1gUiGn=W|@KEf@yt5 zO6d}p-^3X&(-wt>5vmqL2c=+1%X#I`*Ah1jIXK}sxU3tB1dsv-0s)MM2BKsiz$xU8 z0`i1V3%RiW_j~duk9YaM%kH}qFZQeX4PI$n&-m+cxTGrtp*>Ovk_d=MOTi=H8H9)c za>E~Gf1RK3Q6J||!`T%Cz%T|qD<1@#U;3a96EpW}4x0Krr|I4<2XC1q54`b-Oe%{8j7SHbV@yvFD@HsI66Lr`}y(&u}Chvw^X;xt7Pd@T; zOjo<7Fo>}z9hM`#=cqpZ7w4EJD~Rm#KbE2hAlKb+_gzM6kA^|SJWkpJSb7wvUjA~+ zBA|z~WjT&z0j38*$^H;I4FGW@b3g+Fh@mm(C=dajf|h^>xac<{kRWt_1p2jHxMl_h z2KCTkppIb%-bhf(0i4TopavRO=LKl)z*B?I)8?#i=eTzu52+hZ^9}i7{x*%IZpLW} zvM1)IS{1}#P6GgD)Pn+)1P(rUxrlj{F}o$#fzOsVj>3Zf5vC5$b=+^!S*}(Z%je6O z39SY8-=FGG5{Dg`9lh$|^N8+;00Mc?g~l6G`Q9zgbGGYV?EIQ=q5UL+(yB|;^A!GX zFyvKKkOT<;n6l@|G!imZzh#w7?fYbtqd7crfV78i>`fY*TgG}m17DSn&`r~Nt1Q;s zHKNR-J<+1Um+m!=jXm%ZP7?}iSXV05j_Dh6NyB<5gX%bupxH!?Js%r&KFKd#&Facy z|G-)8JKVn2)^viOURravPy13A{`Ff&MTUUA7|^UF2Uicn2HDS==l&F(2@OrKrp|mB zfSatUX5txloPFB&EQAZ9-6Ho4FW%ervsv=X+hltts%IrzxzpySt?s|?-PMnW+3v2{ ztWsBIo4Z_mUcDM|Y=@nmGhP8^sUSvF0=HY&UF%gFBNrL!8+Envv1u7ME%QR74>mEW zo1frD2?cb_jNBCid;6N@M4YEFhgA)Bs$n4=cDQMI-ct=@o9`=?#@%4pYRXv5^^e5 z^tRtzcl7=JBV#2=eCrDJwYPobG~v+uNt(o)FZdkWF7pjPYUs3`?ZZQ;H3PT;F(7M# zWODjKl?d#7n{eYDQvTs77=qZ726_wW8+SexB=sBzHi~7?TPf9-& zklsSyt9bF+wwc+T>H{LyjZ+|uV`k%2Zj~U8{9dQ%ku>K{Bv+ zy2mE2djD5z$wzPJA``0~> zZ}_}I6^Hkna~20Eh?t}djP62Q2X0Ruw=1W*IoZtNfhu^f#W(0&lzF;c!A%VU6CAqMFI07C9d#Ym!01lbtrm$3Ww6dEm0MWO+vDyR%5XsXb9FU@QPKn8780R= zrW9md+KO-MAPJA?~XM-J9Z(QKdG zlf18a6V}1|HZQSLS|LYZD3Fd*^%HG5$VZek-FwwaS?Af#BkfOvT@!bO63~Ev5keE> zpt%5yfg%KRdZWl)<)B2Vr4Y$;ci!T`vz}e=?*BTs{_!4n!;+QJ(gyrR9+v>B45$jA z1t1j+=8Qd8W)c_fFV4cCr~r;{>owY9A#SgFfH^1IW|qC-ZSQnPqFjKs7bmCI*V49=Cn+@pj)NTzlcYxRCNVoPA0- z_|Ca-c8pc$SXoFbo5^^v9e5TvU_rq3T*xFM)UFp?prn8zlq-SxMvFNb6Mzd?00cZ2 z0st%zE@0vjdc~o^S1y1z70n3_z!-E#eO=^DElgFE+LMF%-}Xu0>zQw-wamu6>$q)a zsoF#`ilcfx!hp6~C^P2*4;r2}u?Gx8f|tS>d=X3L4zJbhb$U-+`TGUc*K()&z+{OImyL=#UQFo1oRznAET6LF$q#n&B0F#*?JwMd!IfR$a&1J};=hbVqjP zeI`Ds6Sx#!KxIWpU%>+)(y5f|s)RytRwnt(u}qK-gnBKvPqmvfTsz~L!!>js&>qn$(lReec+sdsLrH|x~Td5)IZKSfz>(L@mC4zA7tzcEL)iu+i-n|fxdD|t^|!4duhe9y;^SukFXcj`?yZrgTQ zHrv}Mw=r9iQ~Fp6QOAer+1yM$R0&nO`I*CfvY*j2aP4hfdSAT6VV`d2wbK=cIrTVC zcfP%Mx!&fRr|10q`h~8>A<-kheZxL=0CI58Js8^OC8j}5etdLwnw`#A4eKum> zddM1g?qi)-DTiQv+V>VP&tpaFy*3-FarA8_9@Ot(v)dGusGJ;@w5d zTbH-(@B0gP>({qiH$KmMSbz3thCs=*zk zlcAvwbHN;Szn)bYMlhNhMlyyN-V`kX(U>sD9b z_s@!Nte#u0FIpC15xHX5>u#=)+USyJD>BrtfwBft%49H8_*xofXTuX}qQ#s>6tu19etpD?>qyUK;6{2?^pfVYZk1wgj3$8G`T7-4}2BP2b0SUb%*%pePCz_(ek#<5T4$@A<+l-l~D}0KgMM#Fc85G<#om zhI8V_9Uayx#R!n-SZeh5XBx_6nLpXiUuEo;MsSn>YVDNk>T-a(>Cx9%LXV)?AN?Pp zyUekWHs{hE`ROhS5sGw6p7}{#0PgqaBeX?i6>!B!XycdF#uFn$7Jl)8A@)klmw&^`%%C2;}83oAHJ@CO_C5A)r~n9HiI?@dr8XY$z~!9-O`%W#Afn>O|LolY4_|$+ z@cs*rzl%cSw-u1rYkk>g{$h^d_K^nBJ+YxQ4OvL|sP&>QHDTr34=#>BIR|?1-YY{T z&2x+{c5q>6EW=D=AU4~5rIRih+j7@W-reiJ-B}>MJpi~1N$OXnUqTcC8W1AwFG?ud zxCFuF8^D2~P~`5Y2!v>`g+AYJUGL`q>z2?hUV}~7bDbtPI}l-#5TT4ADu6^nNq7cQ zX<4T5!< z;0(ZIM`R%}jnWAPEVTABaA(vsr{Ukeu)wNbgWw09Ec(p@{e<)$i?=H(KES9id#%=f zm_Q7abqYN3)w`sl1st%14(CxHr*`7D*i3_5sicRk)O@Ec-s-TU4mehXX!6pQm-ey* zG|hCj8h`}=p2=k(4FCcOY62nxjE4l!5j?3tR6s)s4{!ksfC~XwY4q0x1iT@DKn|-F znzLrw5Ik9iLrX9bjF3Y~6DQQexywV7Z}}pe+I;2{u1UfN?t1xs?iV$fkd4z=W4OHkV<+94|)2n-_LX2P9&{~ccr(2K_Cs^n& zO%h*)NoY7|8oV(db&>?Fk42eT)U*>MFGe+963j8KNFhnG(COXsJf~Xs8hM%;o~0f9 zvRhWK1?PCu%7zW{U<6P>A1X+sFpd*YXi7z7O)_9=ioy&W83HSemE`A8poX!ti^h25 z9krNoi5>?n^n(L*^1;1U5I91KwU8kX!eOLyKE08@`Yxyk^>xr8{fctns>}_vg@^lD z&gXf|+YBHyCt@ribr-kZsZM1?Gj1@ftFkI1d%y9a9(l#@el+dC2-z+DI-g#AXaq}4 zTYTZ~-#HlMgEjgyJZHNAvCIa2$Gp>5XKJf-mLB-r1o%M4z&L4aITEeT3MV-57Fd@T zM{H8|+L(vEj`h1tK6TJ~y)WN<%W=z( z^Ma`hzkjovy5xq`*Z#-Ld5TZ+)2kbgJllttPEqstLYo2a5;cz32@1V?(H@Ad&whbu zd+MvzYRs)2PVO-F8b6XT(VE$DZQ{;E?8#Ys6qSN>(xt6Yg*fhZoA{oa-<>CXoD;8H zLhftLg|X)L8@Jdvy48G)3p?pSlPzLt>$CngPxNQJ&M(+qjKwwm=9JHPmVd>a-?;r? ze|qqe4(!m#>6_2Dr##C))9ZcgBmEJNf9x0h*S_UHx2Ju1Z@K0bop;g^BpH8RoVWwd ztBV*}IJo$D=#y{-z($^=NpN|{$ziTh?{|JyTdN#yH@V%&!6RXdGB{}3GHz4ZPNv2y zX2nsW{jR6G%O6LV+nYA0rCU*T;(6oBCmyu%O7Rc0+udH~-c?*~N1!sumWU<3K&S7TX^@me3mqRQ= zAdGYfFtlR-{ce5xUBBgTY6;?a2!IXgx&uJB=yN4CZf3d6PpuP%S|f;+*T7AxGgsJ1 zUg6bWX})%pI`D){fc9?jtc%~1)p?Uy>TK9snc2?Lw31e)4chnG#nF=%GvDc@*!DH` z+uh$7C_okgaAMh}HQ2($q2ne#E0Vs>voG~I{*PxU@H^i7O|hYtLbYCAT>yUs1c=S* z2FIG2EV0*D>UuC}MyMe4*#lERNpkS9^%tM9oc9P+Yn2;JOz!OyfOU#{>Jb ztKWSK{#e9JnC%$S! z>2;o8-(>Lg%ht2(=U869@%8s+E%@q(4&#CgH^dcW1LZE(yf zp656TnhT6EsIYlN=c!QVK1wWifI2lWA&BW>o9v#K_*1+6w^N_}4LXr*&23)%7rEwF z%|u%9)`npMe;8gIMa*!28z9H#6ZUn2Cd2F@yW-<~$eaE1jSfbaL<1rfjFr6=D&8>zGw-QCHNu^4sk+AIAC0%98Asm*LZ=f|5a~z((TPzr6N<30KUYoO-Td zgY2K@atqd5(q{^rb=oi9ZBw`0+kg2)_VQe~Y2n(PI#L%FwUV>#IrRn?wkH^a2g5-H z5vRgL5u_9lb7@XZ?s8vvsL?#l-LPPFVHnTHd-=!vwEy&+ICu?;#IneICrl2iP(clm zVUi@MMKVcJzyOdDfCnoCVpd`2#jnVaAaYUmu#1u~9tSd96Ow4&_pz@W1EY#PR<(_v zgv2t5lZfS#d=)<|s&!%J#XxK|Ia>Slt1egilRluDdW8*I<~g_jGTRS`5(SL)49nLp zdi-LiAD)@_CSt1^`UVte*szh*WYB)1yt7vgUu%+!%SeBYO1Q}RlT*E=LzB$>m4-7I zIczWxbKMH4lK86Y_W&0Lxr1_v-W#rU;`Qz_@015m-|*^tN`*lTHYtIOmF9*FC9P~W z1GoSLJYjieKR|>z0|1dufCzwqDnbD$o9I^nE<8XW3#5U1U|cmUc&I&%lTrX`^FV-9 zON-J}yg(i$0&Mt3n0w#C6&P1I=X3Yfb&BPC`kP9F%t7f&kCk?R@^of<5$nCoAMhOs;B%$!@-$A$_7kch=(Y+%;9 z*@-J(2JsrW*5=?c^4GC()D_t|-X*1H?Qz`9Ee~U{(6$~@RX+B4fBgE1>@;tB(8~0! zt$HO(51juw7v)gR+a1oh=(6rySuxeQzP@}C<^rg@Yuv%4_rZC046bQwaW)kpMa(mt zdQCO4-A42^Gj3ZK*C;#G0!Lo$mp`6mh|09gH9frZ+#g?BTdA;$(5Hyg(aDZfj&+1i z4tjv0-G_aq-+iLh+d@T0z_EQK~3qe*Iboe zLiZB;yLH`nubu|^#bPNBFJlj1xEel~F$Q%9@4Mkz!g6~TNC#PP!SOV+ zU+-{1?)mVi%WW>~xYRelcY*uYH<|mAW+%vcsgkFol; zIviu-raK^R?=a{cb985mdD{~cWM+?L$4C$n0eBNxr83IyQx;Zkd6Q?|dHmJm7cNd@ z=yy)pdB)vS(}lri$6Q#^5bxPBJ@a+n^FQxRzMn_B#zSTh?~3UCxvT>!UtNaCl$2s-^&vo&@{FccL{7IUAA? z0rG}%#qclpY4(BlL0PXA@;P}<>$Qp|hP7SF*|y^%7DwEDI`fFwSnG7T+WENqPVbSw zv~H>%LZxSiMjvSL6%Rism=L}^4f1^Xpa0$09ZfFG3D7*T7*wG~gvZ%?*;?2!BSwDl zXwyqmD#L&bNaBO}>*)RXWS+Ni;pKbh9^dHFGctL`qb}ud&40_XtnH6;7{=BS<420| z%BZ>UaI@IM-C6DL(u$iT=VI!d>d>@4t=92mE9NN_HCR#zBrt%=F)yJW8N|kE-~kYM zEt7m3u}!`&>ui@12si~%nCS3F98FJo-f!n|;vTP=5Qodb6-C+O3gmpZ#KNjwKB@ib zXPX~;@j#T=^F2@aLI}Z2YteG?DkE&v*Pawccmt`5QYft$_Ag!>)@|MGL)%{+3`4-f zoDKlBLfvwqBhNP8wHpvJ>hzXLg&-cjn4A${Uiq|L{pu1>?t`HQ0G+`7&f{5ceoVf5 zIh5D?wK8ClH*U~cnmTMy$7?6rEue`!aviI#D0vhCL8L$oDgb0aKo5g-aF8M7I#)i` zS@rsTuX2_bM4xmlQ?$qjMit8qAo%Fq_h-T?m7k`NFJ-X((OAMyz9ClWdAZ{QO@5MA zKyn4hF7(>m3!leo#`8SBSYgG{6AbIelI$2WQDZCVvh^&9 zLs;e-HkK|~dYN6#dU|}w@mOW`{rBsnmp5*(`@u{FaotFWSPI#}4V{L#`tZ&?C$h{? zjZ8?7*Cb@wo$64ilTWvQ2m!QyCC2K&&uNa|og=;;-r09A( zH@n9byT{l}MuAVc@@1VYzhzvqWse@km7NBB4XEX|<@Z@yW+(V6yB}Zh^v!$jk#m{4 z7d82cbLLUpHC2(nBuV(_zHnpPfI2D-S42LX@^P=dhlDC8s*5WJh!iz~hJ<30X=_PKv2tkpEJ zjusd*PGy#qbig;MA*P#1ctyY@wILV{4k;pzdWZtKDJ);+bNiS6b0^=~EspfvbqPr~ z39J23XB(-z!i9Bw9vrsOsw0M8I}9;0AJ%Q!h`C%gwEqTm`bn<_^-6de>r?P+zjz1F zd1AL39hjkjx1J;?q(9c+YCp0PyoPb=pTC@Q{=^Y^ zIsQo;FI8ykw5+nVnIvZoQ>i@9^Fx6;3XkP#{&Jn7mNC4~A;rZM^Bc~C>`6kw`<~Kg zVrgH(3~gj5b9O52hr`ZS7_9dscRf#f00%f>5N?bS3Sp#g`Lr*wzXZ08qSc_UCuets zueYCrN^g!uzKlD4|E|^AaDjC@`-fLnEwRyF7kYXr_q0(cT32qTuk_64zW6VG)VAQ; zdUeor4(3_DzWlJtu47+#n%u?wdd&CbdLZxZhaUfL9)8 z{zVnD7rBhffA{XJ6H5Ufu#W);p`}({;Pb1i6$PRzt!G3Y+I47ps=}ai*+7pm^H0yu z^U}eQ7{CGlwy)xS*UnI~O=OU0dAat}T+O^5o5{D>HzQwS<-Kk1=IX9Jd)y|so0(+N zN!PJ?TB|faX7_n^zs@$k_fDyw`tCSf9tvzH14BoGkUg^R8RL%7!;1de?Ni6T#M!1O zEX!psy;#-uc!(pV3Y>{T&y)j5a$Ad#$_ zeX&ifr}iyZfFCtEuBsW{J;HpmKa5XelRF^aa0)|xSf~shp36@yym{YL!M62Q4#S9R zT8yw=#9cH{*OPeJyEWbR_~d$LZ}K)1?Q<4E`)Z3P?M32+m0pI4?O&5CgYKv1` z*8a{suUOs>4mFps>a0pHPUtm4tg?1j4sT)YlH!2rJl`*WHpQJuq&Py>rL2@pBt8g6 zuo`N%pp4PR!9kE*&qeEUe?&~0wffH(2!Lcd3_>t#I-2E+cYH-`x?oP~>M9Cq&KIHz z%@r5kKt|<?ufV_I?i*h-&C+7>Ikhnl0I-S)!MrPSyo0&0Ug(M5fg=m zO+L-m2d5h|b^mVgHYyW)Csj8IQ;Lbz337t{Pd8dpiSpD2s)z(o3Mg_BBLie%+6JJL zZsvn4uIqi`>)!XqZ`Yk#@Bg?T#DG;hyN_UcxuoA+cXzDTpl4Ht;o@SJnIo)&!KWXo zy4PxPr~S!SQ%~xgs_F$fODQo}_u3>WDdHTz^;IxjuJC@wmX^*e3I2mUP#0q!-(b%|mUAOMKu&Upro z3gV8Eqd<3?um)1()5g=;>zlpspYF`()`_8|Kl%0-dGWuT-M^Q*3f!ucL-CMD7x8?& zn&$bw1U*<+aOtQYst$4>E*Fs1&PY<}K<_$1kKzhkdJv@TT8gFcMd-Pn01{2SB&iWq z5hYPXD5+vfX!_#TaU0r3L`El%UBr-2ZTH-YXo2Ch>X>b1Q<>W!4cm7#v8a zg_%iCREYgJj(ki75%w@LG(gMHgPVAi#6D`Ko~`U)UWSuqb2M{A=mm$aYxF2NVd{My z^Lz;aic_Zsg%TCwYAyOo`L-6Gky9(q1^4NNtV+{x{^e|)b;U$%{h ze&<`iaaZcRo+tbIorfH@%a6L<>mTH0a*r=uiS!w7l6B95f7c0@K4x)-%WC@Z$ZH!Z zd!TZ^jV+3eL|9wYNx68Dyc(}%>Ym5vy81Eh^2H=NI=UE`5F|t>7(f|_ND6=}KoE!` z2 z>{``An%>;^&20VTd9phe8$c!t3nrX2kj|z7aM?t^7T^Ma9srOaApsB?5fJG_M}Ppd zQ2;yvxB?zvx&8E;bUhP*1ac90s?uC%PA8kiOlm{(E(}P6>PXl z{`#kXQ@(5myD3uEqn+YwG+gM(hf&q0Q@gUiHc)WvJ#$ zPq@uC-D|9;DezQy*6MV2*VV0_-Xzf674Tem5P%RndOI3`k!lUkbiT+~tla<&*pwMOX_VPF2SAbw%MVi75l>;szU&XydvBAh&Z>g=rEH@X_I~{A6 z3$K~@d?x5dKeS2HrR&8Lsde8*4#!l3g`>OSGV zgEyQ;b&TdZg;TxZ#5*$mW0e!&Aa?7%q8(<%jUlF^^;xo7VjALnj&q6jS)Z32 zlg*wS$#A@KW;rj>w*mqX5=6VgMD$m7UCkDqlhgNvZY!d4X?8igpv3EyFiy(@2~r)U z+T-^U-Mx9zrVqO%F%Y|N}Y&$1z6@g~L0Z1ico-+A8Pf@Q81NCY?Q z1V&JZN(7=)A2)8!_lN)C;BK#M2eng%Xeo5SZ*KFuzq-ot?&E)HX~sC9Mi6I@!jhL! zV5@y)aL`sHK}_D@r)P(ZQir{7)*-%M=P7gQs@Y#Tl+%D!gRgFeq=&M^7#pmk0=t{y zkSf|li0@?>eQ4FAK?^d+RuCpHxxLnVJL(b~5(xo|*8xOIj=%(=jbF@MSkle6E|(s_ zyC66xZD85G%N+|y2X+vb%A}q{3=4d~m!40AB+|-U_ru-gmp~`={vcPfeDhZe>(=MKw>SQWTc552DZCB5$rD@5&+RkA zk<4a9xl^O&VNR{luQM21a2VMnbyY^3H@|sp)bhK&M_!J(jp^JgRVcBw`~21QUR{>I zR-cjur_jU0xFyZ}>36xE_wiF{j}}wSSa62(-Q?1uN|-R$%vg^zT4q~l`p9uC*=W?F z>bKSfLpIDI_6)u)TDGrEN#_Zk2(&=qDJ=gxl@0a>ZPwq9}&QI#= z9M_%k$`}9HPWymAfA2{S;hlvGNe}?@Nj_&%g>q8SkvWkn{=w&4eu2;91c$qXBx9U- zLIlHO!@zu>J0eLyg)&r821$}6HHB&^SWqH8de|pkISi=)pa#uVk!-X1XyZq#ewkyGFXa9^l%m|3*%KyMYrY8`9 z2f#!O3l6yUn|B3lgRsha9<9In;1LUL^UC9oi^qPsX$|)9JdJ!@db2qXd9~%Ot1MWe z;`mKRo3bw^%kwR=R&8~RzK$?*9#_2Q&D>AjF1Pz$(bPZ`*ev8!5}aV5YIT%5Uu*o^ zr#O9Qx`-JHr!F|%*5`fs>#N<~Z79x`=_5Z+z_VBPoyNJ&ofiCbKKVS`dJUyiyaDmD zNjY{p$5@Nr+(*_lM={!T67?Hu7=7{Q=iTW~&pKZ=2TKJEQ7D>)5ur6s0RvFMgaQPY z7e`$!xLko28%0tvgyHO*EybNbPxF@Qmo7i(1ZUsdeVTSU7HSxe{iG5bzLcoQ937*}(QnKtdb5;&M};x9HW8v#LVM@qlPo>GSPFE|0T% zWZ1v>DCUr@12|SV#AI7lKJZ43%Ge!$er5Wfx0B3(VQ3E=;uO<7N92ZzNdX!8rbOjS z>VbOpJ(p;9PFpvDl1{7Ez5)lzyu^0`crK_0tASVARGh&>_ZLE(LN-I zEDsQH0bvr_f-BPfgNrm^r6r&MLMwv$P~$x-I#9{E!e3%&?)E<{7BM4{I!rKty4*>g zU2*h&or8|ot~Q_UFOiWFTg#}mJgk9j2y~Q;(2@+KULlL5;qBlMzy&bx>q+s$V!QeWz-~w;{!EoRGr3gD_W``lG6S+hli8p=q zZ04)|g*Vqpts!=YSU8`W-7wqO^3{wAlAz>vGnlXb-CHXlRwF`zd|-Bu*=if!AD@9D zk>F>k>JEwqXV z8o7P-dDR^*nqPupAe5eqTNg*p0<}67$vN>}L^lK3?5=z+dHXf}iERqjx|?r(gpYdi ze$Lf%`r)?aR5MviOlxZ|d`i5N`>=KTvaFS+zu7F z%R%LmaW3jWjdJ-eKg2KW>la04FJg`Z)8TMz6QilI!eYsjZ+NODChVKHVW{p*nhr%! zh;yBCIGyV+{HG&EJ)0^hgIK(ls|+dE&P#syi^bNhj5||;QwdI{0)0?hn+$W4a5(h8 zEfTqC6m6YDRPB)-ZCG*m7~h?Bv%cK#s}xpv+~*NJBnEw0qTV59(0S<9&I&d?)lV<-mE(N+^ErRwReyu= zWr#|JxQqFkU0?S_?}j{-dZg|*DU1dLpcQl&rnMBBw|jo*O=|)WaV%siHo6|q4OK3x z?LvMnbcZuj+4l&bpBBwfQGSj~zVNcIKG4zW?FryfSqLL#*G|^^$VG4D2(ZRP@j{wd zZ5W2iIBE$#3ke`yPXjirG(6Z~FfkfQ#jw&T&(_~q;x!&rQtP!S@z81|zdH42wd0!p zyesN(b+{aCxT!k{O0w( zyV5SJzwk3Imxus1aElQGqeph3T07UO{egA;rxTnwH9CEVb!e|k}hfFfaeSC zdkTO`VG?DiOcL(YYDwa2NRp&?<(iT-Y9;hADjG;*Pe$8qKG{3J)mGvZjJ)VmMtj`# zN%y;Qn=d@zy60V8E99?hC@hF($f3HU#&t3*rISj%vYfsw|8UuECsU39hB4440$d%? zgwbg+ni#oT)ADtwju0G9yt+OmGh?YqAuVg$%ptD0AiL*n7g#N_pE;JKS?`xs?y%t4 z2iV77FB~>(wVzrUtP5}F+7ucncV$#mc*R^dyv^P3D(p6caTM47m${hrtR96ClfR9; zt#dTyXmOw)bnbp$U|bxi#vTdiTr8^u>mI48M)%y>h-jW<*&7#~(4>3KwX=0uKZjx- zh$saKh!Nnl1_(q@kU~fhm-l6nV5YbngE>|;^Tr&MM_o@Y9%$XSmu}ZuO^DV$S+zvA zrfWGL(IqWJB#btI6cG{tE2sfi+o(r&E6J9b&xjE}fI$t4=mc;Hu-pd*8%k8dG$+8c zD<}H}akryzWKyjfHUsozwoG`En}7xB3d1_;v*X;{Abcf{Xf?APFwx(;=(uI6xs+1@ zV5F@8P>I~K3Utl$#Xy}r&MxkJ_4|6l$9fE&RT$FX5euG{qrmU0@8E=#4)EfG3E?h9b+HoNT4yN2N#wA;97tj=Z%Bq zp$?`8+u{PwW+{-MFc0_)9I8{n4x0{hC)9+dNs#mA*0@N8_6i_J+|h?d+;DGhNCp{d z&9bF6JuIx_$&BagCj~Jp#tcs4-NMk2Oi2RiMF5~7$0RS%T;1EPYgoscK_#3lHeEQt zln^k=APEAD{RUIb&4T6iTHXP*SF7i4o;NiFRTv$@)V49Sw8uDQ#c>XF!rmP(0Bt3J zijoQjSt4ZNRAaVzQL~BHufvQ6nnE)aAFeb2Xtetz0H}(BVy^0eCZ{(G8+T@4B0*EA zOQuzGx%n{Tagv7zx2t_b?5FK&J0La3$hQk)Dtt&6G_Pud-WTzSuvf}Hwfaa;*{cDqCwrb-_ z3T+~*a%jqJs`qx|zEfW3^Ug3?sHbi113IoUsJ)O+!l37@yyojS`$o()EzO|8yS_<0 z={FZ}72+6y;R?B#0q=j9H-7!r3O#1DioF>H>2dbzz$wffGt^LH(%AAxY`4MiYC8-r z%y4^gKpi0jnCH1x9K*tRU13FumvwP=v$wjLtPmxaRdTs)ZnApS=qR3}6su4e?i}1J za@64&bxg#Pz}C~|HB13Iv8jD^)bbFIy(Hg&oID+ETt1eZL914)7k<7m;!pFQ_|o)p zQ*>P>!=XLL;AlZiqc$^aF}kIB zy8UW3#v0Fk?)~qqzjyu#x)X@)sTKdy3yyoB!?Ja$;Jn{Khz!9LOl^T8sua5G8z1=b z@_P1nrr)u&gLx#Rfz^p;MjCGl2r^OJPRJxLLJD;Gdcd=?Bq3+_*)uPBk+N#rs#-tl z$bBV@^2%=F0UXyhbYJWjUweP}%Q=7enR;$UlERk(O?Z>Lc+Hn{iEC$XW^!)Ja&?W0 zNJt}$20~~+hbfGt+Lo6L)ew_rh%E2z5_!O$d%jpk1g%^i(B?@_uZPMEH*02EpuimQ zGzH5C_%!-*5L_amw}R^?4T1zB2x~pNuIEy(XY&4OZrDh_O=BLS-VVscv$bsW0;g=@ zw8s(i_RDJ0^#H&F0LOy`Le>+ukGl02-R9EM-)0NtLsEU_^0n9YgK-WGgIY@?&oNyK zQpz{hy_Y!u=|whaA@-<<#pum+IzW`eHxDnlN6V*ezw&JJlZ`0_+CIw-+rJ}EYRSTi z{DC_0KF>NxHD%mcadpmx!4vIbwa(W&)-=SSh8Vo;xfU0TE^qu}PW@_o$A`XLE|4$s zE4$i=VTvBdwVy)R^HTM#|NtCt*Ot2eBa(9EieU)8ljJmUEX8 z(@jX+8HTFr$x;Vv6wnRy6fvieh=uLulHP$Kr|S5~jTYbEUHtfZLFpr1$J<3$(W1>! zBP^Oo**dwUPHa~5tRPweIa)<1DF(<&fkT!8Fp_~)j^ntV#V9V@k~*2GtOw6hBLYUh z+h_Q+8Z~s_E@MqQOVqXh6>aHI5M>A{d*W2O}@`H57+MD-w%xio+E)@;NBne0p z8V>+e8o62l@|2>M)gD|@Q=Gt7HM4X!62#gX`iC8U7!+4qp#`Cdn zO^rkEWwN*Gx#?(&)9lH4Y5eIJW)S-sbfSyf^id|bAGV68KNCj_7)GR_fR8+)?Kk_? zPXIL9pSocx9~a*JNskR39it2|uT-b`;It=901g9Ae4ESMy|ay4jMsQ6#OeDn+mkzFHRt}J6p;+hu-~k>W0JtSzLBl@gkM~gntHq5LOp#5R=Hf){!Jvu& zqOKXcT!%qdYq#o0ePXPGV81n7S;Vb%bV6gJU^BOvIWEqIMf+zHmt@hL3xqDe~MyH*3fB&nR0cJO zQt9`3d1o)e>uY2=%$cYj_OaEPFebHhWnk-uj%7MCb99iDGW)W8e3b!Z&mKLIV3j)P zxHC7q{Ix0L7_lQ}6t&0KfCJo2U7r@XVww}PMLzU}AA0H>Hk1u~k$cQjs7}Xu-auVR z(n=1&RW#tK()qpTi$Uj`s(~5R_iLJzmd>VKmG0AZ;^psNzort3*fQt=Rpv937{&pD zDBXde-pn#7MIl9ae~LjM-Nt(BO*n%kXogLcgs5gV6`O{2a69+hJw4gEFM0cqF;*sY z!TIC6EARH4tq7LxJ+VQ_9-4RIH4cCKAg|cBL@?3%VGF8sA_gW9iE39{$VRQ86U3vow>|czE8uPe4EW3Ylm7V{=Rm(^s2biB`=rl z@iOyjim8reEHMOV4FwIr$k=PWmrMaI77jQNj~SGkeu>#}!v5|vJf^{w!z~bOh`MxP zFtPAyRThk%deD5wkIlshT2qrBenO0an!g)fA$HDzBHiYQMk#+b0_@50AL)0qciO zUVgrm{`}wqJ8xuGzB(?uk-k&c8N1w z9?d%O4BH@&J>$WZe!Y3JM`nAEw*9(6!Bo&0iAw1X1NJBSjrEk`K!$)!RDco8_>hH5 z?HyRDws^=A6`jrKQR5S~@?)a0Q{Im1#-CJLg*u7HM8zwM$Z+ z&YwTz_-41YAH3rC((+;)4L8J_OULuM=J(FU{-UifsVi(0LI4sC-ZfQ%jw?KuGRm`< zu}D3?X{Jr?I`9kr-6jp0By2a~sf-C$SPq{jcdflrVq>zmSdL8xaWFB=8O+Z*vqVEH z)^28RV9N!*EQv%+5ao*{(Q~mU4#%s0mDii;X zJeyNUdLrg15K?;$ZBj@TFv#aUha&=@3g$rDBHKWTVb0LxFp^+NMK(D-&Pr2lVb5qq zP9or>6r&pJJx5q3g;Vj${WS`6~uGB^(dg2__AIbdN%(q zqGXLzugb-RJ+;oZynO!~%=**KwRUgsZ+|F;ukf5dy|%c{KHoIsVIIB_whZ=Di3I8XiYwfG zKb_At-(WpnmPtV+1||_T0~`>!Z(J_Sxx?qztv^2>A*tlvyj4CH^TQoEO%Frei!b>h z`pq)u5=Srn>b~^K9wV7Fx7R6F{+fTy*-TxBcQrhdhy;+FLK%&aiht>H zuJL6ewQ-fN{M>7}x?E2x-EEM8>hQRvZ8%AY1^j5WxTl5oydRl~FuE0CUs? zA^;X#3y&}nL%&J5NW*dg(qRB;@Prk}K=3F58nsx|jnqYweiL@Tu?GVQ@Z zj8c;XC*V10(*o*Z31Rut%^a}O*gfGTO_^i~n)1E`PqeYhrZ!__vdMu1AQrP>)i$*$ z0KjPtZ2DLf8lV0<<{2XDkgVnkeUhybJCLpHhi;;>khsE%i@TMsDNNaN)*JoKycps| z*S;@p;gzm~>%3LdE)E<J445MWf^J?H z)Qbp`0&RD!WvyIp%RKl=ObuHVy*emPS^8ko@!4<1Sb2I5miV57iSs#}BKSy(#Ae!?h? z`9*Wvtyj6^+q|2qe4jbX8wb2_&fV-wa3KP{^vixw z*CheHti1KwV|OXJIS50IlM)+uD^c9a@U=UQJu;2HB5~L{=`-sdD-z3$IraJ3`KJ$& zhcVBFJ8|9v(4KBO&6Rh0F+8w)R_j_uZku4`T%Mik8G#)z(ClUCY+ms36N~JR)wT6G zc4d`D9y3E9v|&d<@;Ku@l{igV!wrO7EpW5*oZ55lnmS-gDU!opx+@j$ur7@p6j);Q zv6tUCJTr_5rp;hz&8P}%wCQKJt=9|wcAxSeX6LhgUmKQ4O^^0T5xKQ5ll3evDbb*V zA?s?+4;fpp9d_hA+pcT3jpRz-_;I;k(7s|-okaQT1D_$NYt)g$T!%+Hi8sUc`HQAq zbLy+y{sp@?`qqiw_;a^-oBA_vSBtF5tWTe-e!F|T@--F__h)*BFVXPWe19zKTifvK zT=tuDg%5@uCCR~m$nD;`=llW<&u(spnH~+A$OXe*T<%#M*MJBfMXo0Bikcb=iLzE< zuOEH@(e~5p#!Ve!*tg}4478=`2_W%RM!+}F1P#>O@5oUKvwNQ2F=V-3+^pZ@+lmpi z$Edx3o;hTT0tS51d!B|C+MZb$kWNGeyDYb_GZ2m{?)P-6WD)g_gSd&wuiYE_t!O4$o=hyyJMj87AQ++k3 zGhO-Wo%Yr3qFX(jO6NS=`L!c@oo+xYxyyLo=l$UfVh@ziT0sGTfdWdLswXeWV{dX7h#!ndS$UNuFj4^eLyS>V|n$7-Z|T(~?^F z4uEOBS|{Fvfz@>sA_n0l(XSkjL=%t{R5na+z|~4`EB-C00^q^)07}6C(nB2!rUh6I zIDo-vcxe+z0~r%(Pq=Z*^d!IqlfxFwjEK@mClL?=0*Ih}0SKT5B#49sHwh1bG^JYy zHw~{2s0aG7k#JUI0G@^n=$>fqgHh*|OCq@!5 zf5pz6#Yc@My&WPLBQdDG)iQU0R~7yWNy_E4CO6C$_DD5wYG+noslMtXRR#!U171160b5vGA70L=4j=C!z!=tvpTvj_O$45 z*bYmUovF$-A+)su$zx?0uq=8OEfUJXBpJEUtRjcf4BG?@V44?f7SL2zDQZvv9*?K? zEqDf}uSRW$sOoTO?x0OFFaD=^SZ=}P>PbCdlGA&zpAFw~;kCAK|90f0w!Wpkv=vh{ zHJ#7(@;C3YN8V-Et#m_pJPNn!5T}hQ48$WpEI|{j2~PxpObWRK!9<5D>=8RR<#sF- z4%sXl%HQRWQgH6N|Mhv7an0BNPyspwD|A#2IW?=W7x(&jVt0z<2o4My^sA*BN>a&~ zNXZd{sKhcj(&P-wFz?gUNFn*eRJF%hhLz?`ttidKaW5ybY0fKX1ScR_7@ceszJ;$Z z7V&3(p~3JPSZf$?KA;xKhbg|zmYROINu%gqR%W^7%mH3#;3kJDNY9YOc96{sPm?x@ zs6C->zg*{UEUEVQT?&#^g|fzj4yEy+0SPyDeMh-v`(2$w075K{rijPo8gKkOFLepp zMjJ$t9MMs3rMW1FA5=jfE5xO7#G^(HHZ%!WsEGC%@;=XgJK;VQux7JyvmSrWyS?67 zeXP-azd$XM)v=R0AYaFBh}zCnPrHd{deP^6b-r^x?hqrS$3fpwxfU4Iu(u78#atSt z0;jzj2fL1su8_=V2yYY+yJALmZvXV`;m&|=ZqurA*~tuLAeUD?1c4TM_~fDXMPMqa zWURovvf={mtLshO_HnBmvA^8rXDe!->)T`$H|TP19_uG=U8;26{DTPb9txNTuHSiG zba-0(Jz>6Ex^37;_gMA*e3$+G<+7){e>BtQZ*;G#3mhQxi+Y9gPPgqcS+!265H^f1 zc+Qy~w{@m#q3yp#Vc098P$ zzo&e5Z%r=16-7eQBCS&d0ga_n5Cn3KKhJl-*j9Be(q=OuP;o!>{MM-|hC_ zV4QQ}d^@|tH}+cJ&sl#M=5U30>&$h&|9Y9P9De8Tns<8g>vOrU%oRS!d4G)jx)fzX zlL`{ZAf9HNA9`yPvnJEP*d@`us-2 zp1%y;a@Xo=g7~lvd7D>lI1Rp}b19+$Q%JYqC~mFVsI++#fAb8cm!;mC<+Ezi zx6Phze}nEehNG{o%B$8HpC>^4_{urg8IMoKkb!_vSh}lKoh2Q9#MNHCcfrrE-4(gk zTi#%-4Wbt;F-}A&w|*mRXuQg(w>V?%&2`_kZx1qfvOIN< z(tB8BBqX9pdq%<_RdDx-)4a|_1PT{?9VAy~vZoXMp-x3l%e9Z$EraAD+$Gxx9B z=8}e9x<1=|x$pe>`(n>SHD+s=Up+SCFqTEEtsjdNSVd|SNrL9&1tObhhGJj|189ba zIuHGgnIoDdHzY+&B&3plRh(p}O$#QFhG$w}pqEY}nG_Om0pXdp1;8{t00{t=bWH&O z5`YJVIa4SAobBEd-7Wk}Cm7K>4;I>-m?5z*Lqq!wG%lOgXZ+>Ubkv$~ zC1k6DH1pd}G;On`-R3v_rsb1M4Isv(hZJT$JOkf&6%ZpxfwnGRU3?5wHQO{s;xKdz zLZA`?Xu)sf37f-M8tbDS+HYR)x9`P!ALTDS7a!zdOS{*^(CWO+Lj~Ux@CwzKtMHxT zOZTvpn7ryViAj&0Kf@ApboM*jwT_RBqSWbkx7rxCNfCC zOnRfzP7-FVZ0^SafQcYZmk6?15^9XqoE*VjanYn9(GTwIhXMkn6X?LK0SuE80!E7( zHQ7nT<1l0JzzqOKDnuOsDU#?{CY6$PMB#R>*|kMy%thL>*tdf{Ew_Z$-R&^?TIaX; z{I)W$TN?uDg~uO94cBN19*Bv99_$*DilZvL0)7*!84rcrHP;9Qc#6s(brgAJuELW< z8fx&gITZL&sO3Es?HEkU3`j>A9(d4pL} zO@yXu*Xu6V|Bw0SYv0qpf$`s>7KC^t z+Lbp706_1`mEYY3}^>bbSt&!2vYZpa8OQ1!29#vx$G*TV}G@v0l@qOPhs_ig874Q+q9tXHd zCL@Wy`_{Ibb*2stSL*inK@j1}&!n>g!Ter^rEW{}*4P7e0}}%pWy7!53REU@9R~0P zqQI|k%i6O)+DWx}qf3;OAP0tq+?WK%n~V52Jo{V!=#*E#$#e#oqzoZl#FZ||p2*qN zm^|4hmd#Kaua<8g=Z(sNH&lr%6Jkf;X_~bv;qVB&qS9q5o7w6>83((-_jnV$yIY_j zLiqt|_?NAhxIp{L;yZNuxOcn3)Y4h44>4cMp;vhx3^0dYVb#%}Us?BU{mAi#L9r}# zeQEIlQ}^?cheW8a+&UIP%#8Exdv}>4KREm5b>>=eEaRv-G&hiPE)U!1fCeb!doPnz z`wQJKxDr2X^Ypcxb-$+WPTICL=IL+G%>C2j*2WxZ?cl(uv4*zZ0oF*J@4hIh3HO_BsXbte3hUR zz(Q``D`a(E7iZpQ*S#Ng|Dn4wLNe|5|*6it*d-+LCI3aS+?-FrWG zmfRQN+L=kAy7f@#(16dR6y|FWY<#xkP<5mZPeW6-Ue;IiTmvnYlwo7wX3SWErTa#q zT*GCqf4&wH$yf{t~ujnE!bVzwNC8482h!F zYZ}v_rm8|AaJ6?oxa?P6exB3RRcM4Lzrr+@2h(ipJs>M(g!DrlpfAi2z9bR}h>0LL zN^kISS&%STnxm^jnGDSC367DUlFDsAQhRrmLKJo4dSq z;gGMSuN@*)%lu#$9(>@zwyI*FXau{y z2w$lX5C#a1%PEc?70D&%@*)m^!(8TY!S}&lPq#R8zhCFS<$AJzm&YCHd1I!-w7@uG zW&Nt=&(zO6|L=VLPXAYbYS&8_7iw2InxQfnb)I%p3eZcIMNxX-l*2S5q~jRjCArJw zL{lBmG{$W9@B*iqW}_KRJ3@*vL~W;uMGSS#Njqs`a2*EM!4pZ-jstEIHZTd0RPx{f zE)ogLl<Id8UXOj}k*LTFJ;WC08pRfB_9P3H1OPOqi1Z3uuJt*11_A zQ*QziEd-Qxb;AY|G8k7*1txQ{X1pPqlh{rr*}dZ*8atikT8@9ZsM}$pRxC-{D_l|F zagj5NOq$CR zz56nKd*v82Z%SrgNXcpv~&5g1f?0fY!cpaSIrP7KL-3?dC6Eo8FJvppGukbu`H?4&)6%ddTD-KWtu zzr2TD!@ksjZt~3AMn|xZpD%qrscV}z{M58($U>R3)T4nw4^NPgFqP;OC+dL-;MIc{ zm?8nZ1=(v%1i+&nKsbP);PE=!Eb)2QvB(=CPBvpFVk)cVa~B7#J*c5^r{(rudp(oq z%uEq+Eji8;BF^c=1k0utQF`_(9@*l0*K2<-`_YCt;#|2`?DBZD4s=H<@IaR279!Co z%rqlMB0VKxfRw5jFwNL>>RIpl65syqC12L4X;{dgbxJcBNS$l@hIIkE+l(>*B42f*52)|nT#57)ED>ZEz|}G;Q_uF zUOL~2v49I8X(@mX9&UIzESSOqIvo=zejSw74v3y(^Gi+H{ zcrY8U(^Q_Kj?EAhml4nY_`;I=)#g4z!`0XDS9pl^Z9{!KpT!FZ?Tro!YoJQ2)xH@r zMqq>Bt!o>@Ht)F)o$T4Gm2r~|=Y0C=n#1YHOdj`}4sjyt3fxw3bUw|fYqjj64YSc0 zO~^`WWy`>teM>(4m2=nNKJNV8yeMavvbT8I&ZqMcD{mJ(>-`tnDArGDgUjoPrec0# z@UXNEk>1Qm@Wk=IymC^0z1>M0%x_2Q{R4;1*Xm?*e^19b206f2m`a0y6nTWoJZTaq zTI{2=XbgvR2vrqmw$L>3;Bm7duGpKr;`@PTG+SkBGnXFZ5e+Q|gSEBgS4%pXlK>Yp zWULsJIqIAq=}n_bN8T+bORiaQZ!c9Hy}V^eyc#uPZRa(av#`6jEn&xDOE&2qqCIpa zM{>4)bAIYDx8P2ca6uA9V~x6n#O++KOacuBAI3tgzD2pA{H|!Vk z_SDE+tiyc%hE_cT^6>0pV_4c|!^?f&VKg82yq}%kIB>?Ff9JlxG7mb^-6+E z0CAa{pB{x(CUck+Sse3*Hu-iN=6Nz-$)|rVw_k=(LP+U?hawOHC`2_49gZ*Q*{u(9 z``_!k`fv7>pJw}`UD_Dd4ytq6y!>MJvzc2Q)Ep6V{9wNhscK9wRjj#J`HP;$)i>n=_w>`ef!jRhFkd1s+C&AB=}StTCCs?a zw;u8af5|uY@%4HA*dKkTOo%`Wep*Yf#=<^&Z1JGk6`aJD3LrcH$hsD!2`eocO|irF zNyCLP(#*hYv}+`Yfsr0y0`MFUfLDijOhCvw2uzMkx5kWlbpp021?s(Iv+30#iF6%2 z^j8B(0Fwx#0sw2OqAL6V5CKHNh6A8Zda!{V;F)RkHwniE8~`O0m6Q`vf@nvtZF3{JL9WO--(G&>yRJCYT4wI= zOZ}x@;V*?ug(oBw03U*@Z~>GA02mlzOSBVoYl;j}t#XekBS8`>8UV@9i_5t3R6pH! z_#>~ku-jaoJ>b>8%gf5OhL5|?6QtL_N%v}((QjxcY7wVvd_fw&M|fTshNY6nQr6Oi zsm0xagi$a(CO}P{v|(FTGwN(>0$>Zk5DpvwTD0LaMb1X5|h0LD{*6iK(y!YD$753(20cyM8Xy8UCvNfzWE2EJ)gJ_ zPe_A~ztW9+)Z~n%9s{?|aKqoU81)KswyT^E;3~xkk_OAL_8DIowO$07nnfp zCohU87xh|4LkabCIN^19HELY|;Byi6uyRb4!#pHyR1K)# zcH{u3eSt1NYY0|Y`fhilcR3yU=gT1vnAQgT^GM&hfmS2^wi6EcS4xq?z)CS#i zPVpk8JF~7n|IyNxH-C4Yd%^K|Klk3{eW||Br%SIPmW=IT=Ukyg?r@&H?$C_}v2#as zPU{7?`@VygA1(?#+Y6^l-gVe@Kdr}J61@_qG4?!J#mhzM2u7;`y;xOI!r?~IEHK1* z@EOzJzjBYUPTEc@zJI&taDR>GQ()7~)vRQM9f7EYPi+tix!_1dDKIhrMWxgY-c)3`vfPM<8ss#$t5!Pi0$Fm{i_FmynZT)FV&;g_<7(hk>fLNy@6A!31F|Dd8b8m43^PsJdd$)-<;5T-a-L`Ce zW21zzk}|R9;lAas|NGwb9bVwuE{-OO_pSP7n}cG9I6bo@TtvEp8|grR6rO-7VGBr@ z(%T(7NWqCZ@pLoUXeLc=05LLoumjtKG(8Lb3KtM?1z?KFn>WPGbd71b0Bj%*51v`a zjY)WQ9$vpC;}>8cBfwMuKLDT#RR~A{c<@Xiy*f{LA`QR+kV3z90-QF&pf;HTxP+xl zA%_%6B5K2k={E}}ul@a={1bb3ZaKN%9SxeIVtM7rtz!;Es*vJA)r!3}KYHg}P5YJ) zb1Pf2*16B+f7zYB8Oaw+iPZwB5(F|X8F;9`Q%V>CW{IgTH0yz56{M^xg9KGv;|m)> zWctbG;>Zg;^R55he|l{Z_8K~IePKH9DLt~4x#@u?2sc}^c;gaih}~Gk-a?_2={c5x zHHj7LK1lbiss#cDi4X0;17N|n>%16tw6BbM$iT-*3OnA*e z4V4iCV(70609G21loDwSYDLrt8b~n!L`ao~A`lrMjG*Dh;ij#$6|aS70&Xg9O3Te= zU5Jc?({QfV)A3~X8WCQd*tmwnxjc!%G-J5sGo$V(Tfgf;W_{v(?zNCd!&6A|z&am$ z1zKs50AVVgQkmmC1R)_ACVO$Idd6O`4oi+ejZzC-;btmVCF$1A4lI49o1Nw5HEtq8 z@QSt`#cMa@ujjdzj^}cHTz-dojk3}x?rFi z#);;*L5xe8O~ZyU*JD&Cmw(ZLELqyhC$<^+!Hj4k36{{J3y19zd~<3{|9}m*I3K^+ zb&8Mi=|}BI(nifFIvi_MGxum_YU0+k`0`(#`9`1` zuRJU%&FyW)@$Y84AM=zy#FTfi9!W}&;K6wOaOP!qdw#Cr;9@} z#NyfYtzWLry^YR?O~#(#@ePu|1W-l_a1sKNgRo54yZ8j1dPu9nr%$w7qW64XD3EOG zfsMg1+~)Zs8y&V)yk}~ogRv4&EGWd)lBCo=xLu~Q?b5G*xUr5?#mjkX&iIhi^$|o- zS1OvJB$%OfvfO>YeU*<=1cZrFqIKy@eE)s-M$pyY-OYU(H@}RfN0~DokC_MS7P-UV zh&k$(b6F2jUuHvPIZeA^e}z#4F-FJ$bS`8!_SD2(tWL3ht##LhU;TJ~^ih6#iF94+ z=2|)4Q|&=S>r6Ekw`s^4xh+)mal+0hv~Q0+D*1reINUGJkmqZmg8 zXdqeQEp}}kd|2^=-s|(c0BOsycc9^algp+mEWe9 z05{qsC;$@sC_s7_6aXw|%i^tmx9{P9xu^U%ok2gioYCRcWc1=Im^H4PSpsHp@=)n? z?~toQcP%9o(?Lrfoh@JQI$v-8NxqmRZkh8<>KBnlT3 zP;W_efOW|7eQz-7*Qp<{+BotYD-tF)7k5QX@uJ;R9__}Yvy5~W%PK*dffye_ovj z6ZNieWlXxJ0A$#~L7RY=o= zv;kRHmu{?U8g3XOSkE+UQ>DIZc~qXKiZ!kxlF34ur+y2j?=PE^OpQx8}Hmc(zd02GdHAW z#!Gg6zs_A-*C!M(NfLUH!_okz;u%tUsPuH82{Y2w`kSLlbR$411)z$+XaSylBMN=c zDU-h5^G{yy>PsTK02aC(n0Gp5x6@G~X1SVQckxNt$2?ujhh8S65yA&xYH@*C7GD{M zv36YiBwzss079<I1lo%m3u| zB%Si%M6xSW3{rE&$B}?Vt#Su#9vwDgTi-l|Oc8UX-0JCD{ZGacQ#G~LHNjSuYWqFKDIt>WM5mi)mojlWPAUpzvO;C>ta=B2?kKOo^hKBp6xJj7eh|3 ze!)ZA=HEWI!11YX++h$t1RmlFE~l9uQC9bdAu7U$CO`GsKB_ZmUue%N#Nxn4!yx~t=M=%T{G6#^sO=L8F;Hw?}*eAwLRnauy6c5YS2 zf!8q&xSIUX8!bIx!PSl&N<~dZ+=b;x6}<5S=KcEh2M_BEWvu@ComuNu1y=38=N7(r zH0?SMawtlU&gOeV1#o%7Mv%;kGG=z4UpM{pk0<`uok;DqbImQ^7a>GiML1u^zEcd@ z8ryK%y4iFoea%ZhcDk)wrAwj&3|ltOSUK|+=h3`8g9W^zp$rchBtn3URW0(0Qev~O z?z_i5W#N?5zD=_C{mHzoGZRM-&TZ{?(r;}mb=+xl>rlqzi_C4d*!hFzvPa&8Xiv6r zD)p+v#s|saYqSqH8~3Ma7j%UgVH}Zk`K!ZZMOx!T20&;+L!;rSAeP+WqKB>SW5y4o zP;<_G>jE`#I5yTo#05l4S1fOyDcYwj zz3FL9=T~>R5fT4x(s83`;7BnD&*(VM6 zTTK92K*0bI1}Gpw1tukiwZ84pY91n%$^e2uvAT47Z>{-?dvu|R%95i7Q5bD19eLE% zmnLM#+SSr44TF25YuQ!*udT-Boll#U?lj>1ljN(l0 z+oI@XFHA5pjGDf4!5_O1^Q-n@e&#;fuj)g6nLl?6L%g>AjPtl@E2dwYJ6f|>EeX?2 zWD*&`Brxz`pn~eL3u82tStssK`a17C9ap3>2$VvAs;4*#a`}jL!gi3jubV$RPyNbU z-uE=H&@Rku*@zS7UE87KT{2mCW9xb1tLHAY5kde&)OdxJZB47DZeexUEO)HAG)&@T z3jjy}AMgMhmgmG0-X4&3O{NDT_hZL_v~NQ+cSX_Jj=`gpsE6Z2q@wh38ORs5gDancm%u*AYcl>0$jias%;klEWk{< z_IRIpBw>s>r#Tp}O!MF{dcqjOj2`>4^ns75_XHM+^29WqKY9LK!D|jwC<#>Hk)e7p z#1Pg&r|fwh2!Ij16XxvqZUmgiMK!gYlQ2`&?6Q;g{PlX6PCVeJ9Gl_++5#UyOar1+ zn`6L`2Qf1DRy_rc>1sc)7zWzm%eaXD-b|yv`r1UAN13efam>Yp$)*j+DU=o4;8MMN7|?U6p{WJD`o($= zwWBUMT(H|xf=4Uur*UC;9dFjjRJzaml?fhmdrDU;{28!64 zNie4!2gbz#dpMKfVENU{cenrP!5Tnot)u2&*dQhdN zu~x?cN6g5Z@W^8>r{he%>9$VSzw|Cn0=j#uD$FAyH@cUWSZv9(~xs>*g5%VEdvt(q5&Fkg1^f5QPQh( z?F77Yy6@$?M{e+I=dZr&oG#a!TW_;F`LUGH#M#JiZZ03}va>Pgam?r>n0OjnOC8pM z-Dz?kKD*nzE!bf-jqT(?!zsB?fAXe8c&ZFTl-OvJq;U;~Vwi*o%}|-D$S5<{yzuK+ z%crmU7z-631P272nv@0rE6oAe1`%_I661IGq<{X>1Fsy2I(W-m=N&43t|w-lul`e_ zghLq?p{x=h@mmols(!DRbHF^6$)lsUmn6m1Nf3+Di{G2yZ(TTq(6t zJ^9zsI~vryuiATO7G(rK_dsf)Qp2`ZLQLU+8EAqShMp^araNfz^t2vvI#n zd2#c+zUt5a&mMAq&m13+*yjZ;PjGi-kD7uvJ>$YAT5hbWba&KI2-ab zJt^UUB{OOc9c^c}`8scRzArT#X&BQC!?R#oUee{R8p7s9B>T0Z*6<`AOpFtnKwF`~ z8|>iQ!TPs;gL-L@A}PPu%Qc+y^wOrvGuJ%jJ}WvDs<8=!>oQ81CB`%}kM+f+5CwakcCW@ng5Es}d4YRN~SrTMMfhY!rD-OjN|dHitR!*4r6? z0;up;w8?2udivQugC;~tE-K(t;V2{kTf#WWo)dHK*CWrru@&b=>nuR=O;x5q9;j+9 z*HThE4W)ME?(=SYmmB|yIqnI`-lUD(t2Zu5Xl4nrzrCLlvj^kMzF3_03v=4fXWJk3 zls|Mfe&gvKG_v6m5_xVP&`Tj-MC4I6ul_`c$l+dIn4>c0|b{E3B&=< zjKfBM2fz{;i;|vc$5ZiGeM*|U*_KU(nD79{aV$r=(%p8zu+do1`3MHMkQKo0TD>X<#C3Qo1G;_L?3 zt{M+yN0?v`_lWZ79%!l>FbvWp32L#Yl4ZK1+{TPcEZ*<(=k?&CTq*fVju^nx(b$8V z8%&K|{BpXBaO-U_b)b}C)@Zdyud2DWJj)F`)~*OM69*HPJu~J?P6Oj5lEBPi18#R? z1xZPZCD>k=y)IU4lZcQvjY|OC8@F8L^l#5rwa9a5Y9*%_2Dgq4dFrvf=l^}@^uIRr zNk3AYGE6kYFtkyw0sPfT>vLKj*zu+RWrubc-) zKdHSU(Blwic{N*}KD0`D37>A5_uy`9Y;_&iwGgOi8Uj@ae(Q)Ggq9 zJA8^i?uX`vva%UYsth5c)LwfwaKhOrdE}389R2dGjqmz0;}0JE-P^6omnJZnZyceiyF$rC z`m9Begdt39EYTZ-LZw(&uBs1&mOrdmxVX>DVScl^Z&o!1I27{^Z))74-D1 zH2%JetQF9D20=4)GS-}WB_7(<70kI{`Yljd!q;M5=ea)qj>^07ilq;J&S!7Yyt;2W z+h81&0)x5uNmtOg8Di$F<1z0~UWdm4Oqck>-(PUK7S_7-+xOCaA2`*ly7LF~_t#7w zK0m%PXH!1yO6Ss`04||X0}0>&6Q)qE&MPe6>lG;4=HhZz7A|%!hWvwDZ*6z5^)CNm zdp=Al6NVA$oS31vE6!JXW6wY#l5rFf0q6*T9MgC*V@1Pi*Q(+1H9|4(UG`M4C0Q8s zG;s>1J~x`&Hfoz$(%xaeBH3lSAs&E7zkGjxEpPv?&!4QML`Wjw(j=fux@soPT29@vRreT27&v`Ftwkxv z04QVSrO$Nw8{H<}y8M&#A8H!~e1gLTY6`0tThV1yP&mZSm&MuB5*n4kRP8jLhb37H2Kv``P;LD?iuYHJ2^U6Pm zq@5>Dl0=y{Vo7Lztj_gVKKGH0m`SQUk?*92A-WKA(iR|LTWPB*V>_}j;liS>fmK!D!j_FNEeH@f z?lRB;+y<2dTszkA0R}bE01QJa;mH_wc9isBVv0GQ19$+^Kmd0RuUJ+Q_F03|5GL{@ zV3`htg`}H;1mFQC>ZMn20k9zjTTRWGgzi;m&G496(3?$=b;p>4SciMq7q(dOhUOwM zbn25;e9_Txx#Y&B!QiE*r}iY;7qs!7(orgSNLW}8x^Aq3A`Azb3>>*PDnjnAP|pTz z)K`qpYf9Gx1MAARwcNCNDzdYEX^u_8>m`Nt{TRzYlu2#7;I$0XlD3~xtC*m5>3J+ zu|p=a#Ym3i)-09Tiy{nL415fSGts8ra73Ze2CO4VCJ7CvV4Om-TFJ#)>(qp&Y?Cn* z(SnLpLD#>In%|!#suC7CneZ}Rq3bK5leYt15V8%&;T9jZj1(i_?lMyyfC0|8j9DB# z{W`gM{W@Z&yQ~IJVi?c{s43TryIdBWs$&Zq8k4=%@I6=J)Y>pqk&e`U8zkl3*g&Use|tUq)9bC;w&d-TgFHSyZbQ)g__uu4hO1WFk=r`gVl(aQvXpT1Nqdsp z+TN*Uz+t;`!XboV$O3f+h$9vJm^20g03;lla_e0N7zCE-{ot7Yf_VzQ28|9Na6`wg zx##B>u?}88=+?XFIB)ff^Sw{iVy)wqERpwag1tSZ7{U+bo;zpjupBWn!%)_!o47&q zB2ixhMHq$D3{G&^01Z(ku#EYRxGxBCzpih6*5l`{aQnw|*4ALfa+PIWE0F{1#Tyk} zAHMA251H8w5)Tia`loMHmM9xaFDSfNEB;i`We>E-zwj>8>VADkm5f;&%pF15D$K{& zgR9^3ohMSt>?NM<+J*gHNA23k>F>n#$f}8ie`(QJ+^LgA}F{NQ&NLmD;M+r^LvV_=B+NTnQs-~o* zkYfpK>yySY@lqbYHnE_@6+o2%KmiJ2m;~rl9AXL#fty}@_PXbvXZYAHG-o-;Rfz$X z0Z9Om0t0kXC!VGPjJj^d#mZOJH7XC+^R45IcKt*b=1bQds`#A7)E9sAm&alM@oIcP zb|R9NGhPu(z?1&@M>nCg@mM5$>|GL+FZy&uRRWsAA zPS@+dR?(OEMYPO~U{j)&zwiM!eYv6T^7_3p&j`B}+*ULNH%nq@@+<>*j|jZ}u1gYiJ?#{vVEKdh+WJu%Gvpyep-tmXhoCkIVni_XqpTA3gWOFYoB)8MY)_WnEA) z+G7A1Dme&E-9&>VYT4kq!eQDr#pQ`8A$R+y`yuVh?*4ebZ_M0$y1D1>G0k&~&BJxL zV>AR%C`3`WF#sa1WTfUvT0%twG6oUDyj30=Gy{L9>6tkcSP4z|gUV9PH5}*nnIk3kHF*)~bYK5EeD98rKfnIrs9>d1v1Y zyx{hdu2j$!fZ}T6R&vL8pBT+`>gHqAuB5!&#O>AB85pd?hkiGiH7WJNEF*`R)Pjjh zaLVRX!5o@*rF_ORU+1P3Q z^gGLP%7yQ|)I;`Euet6u<1TjQo0x=_7-V+|ns)?Z$I$ z-|mKoxdEy}haz7+&B+hW=e%8a!Q*qccOLe!r(S)!Zk|td|NIQmv)~Ce+a`AgZ^(bs zr*rc^xbEVvoJe_K7sjB@Lq2JV3ZGON?=;(ifyL`g&{S1=^u#HwB+5$ypo zW9c-UN>B9znk=!V&yENn>$w<}m{ zEkEC^gA5_f`k)Vh=RvgaQcwzYf*{2q7s#tPrx^GnVSqUkz$CIjf)hwN%|qVt5|97((7GLYKNi{H z`-~JU*c7%rlM7C4uo=rTA*K-NEXo2KtbV|0uG{OhuWn~N!13_<Vo4KWY{6D&z7NZT_32p9kYDF73B5)jb7hT#zq zaHR>rYa9nJ4QT=OaTBhaPC~kI^jnZbToN$}XC#SbFIOeFFG)eCz+||`$^djX(W`JI ze~jC%bKCRv&?S>S;Pe9(_o^s(Jf+$&2c|*j1DIHliFrJfY64EcLk+bfhupuSyCS?| zBIr(b4;L055k_wxXYsj(xtm4o<(q&SQzK**W`Hp~jbH1S-CH}K*>H2TD$OL)Fm2ua zAqb0GR~yztcy2aTG?->%Y_cT>W{9e4JV$Y-fii2-O@!@P41U=zK3O4MyE&Qm96}jt zhe9TSt!Hu;cgOb74W`L~#P0PCAY67G5j;GDLJkczr&(GBLNuvnmRw}CT_@}@f#8Ux z)eQvR@Cq}}50I-flQ{S;)+j3$<_tLw1VD14rXT(cCtTumdwf8;sUudB1ITYhH9rPuwy`Y4D5w1Uy_Nm7$~eh=H( z^sUm$O%;-))?vFY2*b@0es#J1bf$tiW~;u|TKALY*CNAODq`iPxkjHZ`;7Omu4K@6 z?)Z4V(&6t+?WGYToY;MUjjGhXpIuIN_PiWM?#1>`#%-`UhLUX7XF3f^9YYwZB;l$W z2VfWy67T>*z_d);O=(~b5l-GGvBvF~>uGO$Eog~c;2L15pM34*{XMKj=Cfg#DK)S9 zo(q<{JKxA-;j<6#E`^6NBQ=uj3irrAie5WAT#H_qh_%?!X! zIx)dn4m``}7b}M`&C2QOgQa+V=*ZJtzHrU!KbVtTGuG=oe_^AsTWrY(_Plv`bK^Aw zr?n_G803#8*Rcs!hqH@pTd%Gh_UpcV@8GJ7WZdlPda!L@5MDj{PWucAGfsnK7H2Cc z!}%cwDTEHH#!ZAX{``eyM=lfHGW)^PyRY~9`-ZmxrX5`{WDY!d>1~X17;YW@L{3>? z`_}hll-5-Xg+-A#)aL8laB&h`4tbpz51#N>I(!=ay2f>0GHtH4kdwi>$*j$-)AMV_ z?Y0hC6Nf|Hq3^>ph}Z~%PK6lNNfIm}ajfr2wWuE-Tl(O;xRwJxMqRBTc2>|ZM{xj zsij6Ru)*=n0dLvYbgJKfYRQscLvddhIsB-&G!YCmM{K5%I$@>Im#z%-JmhkUh!Awi z`|TNBPYZ~1IC8BCG*#K!zN|J|uEufIh*i4CvZgk+g}~LiLB6x?DCCQSh7}uDyv@>^ zwSU7@g_sbYICb+8LoLyjgvMmiQI{HS4J--X{q-Pa4TPcoCw%y2_Di>I*2_LMXMoq> z1%(0Hi=FQ4j$AI02*_Zr}b)8)-Y|^%!p+n29>bBhhq9_CKf# z-_vKk^V@C(ahcs$(zIwtFRL_iOU^Du=ctxMRRxgbj8Yk|UbPsv+=iBq52kv4u-}_? z|5i((T0IQ^y8p{6P2mEi4{o7V6rPqZhmtO|3kG;hs1(Ys$ybpkt(Y?gS%Y3^;ggsI zL+Z{zV?l!gL~E26My*05bm5l5xyP5MdcFMj$=aY+4&7OImFV9OnO~b=!*Jr|g*-ZBSnJCURzR%h`#bqwZ=N(=C z*^^(*;)-1mmWyv@_EX>4N4$^E{YAq}DWXo?Il9gLXJ-ECQ}evKaw2w*j?3)SrVX|n zWz}>3_G`JP2jPk<_mz)n;P90U*Xyxxd&X-940#$YFSohp2RU{qj*?tkd%E_~UOsQ{ z$2-8DzQ1+vuN$87dd;UXe&L}rTlg;38E!WY6P2k`EwARP5+?q=3; zx=1o+=jN@`=M8tXj|N}YVUm@jmrJEdz(Pm5ZPOh*nc%4I`U*+kd@INAS-8-YAgd*j zNY6`|pu~h!qG`D5SZBd%^g8b^{Eu&seZAjoyKcBqp+`?@Y@TSOIfE8yoeFr)+kLhj zwiF|Ev1^NNjontSxnQNWHYzNbW0vQS{lBNXueNPG=!{LXjS{bVDbJ#UuQIZUn^^}pv0ec$KmMoP?3ewvk1dl~cIw#7 zJTS~7!52@R z+v#A7HD;r)W2T-Ua`$d=GXc-58${gkUY<*x zV1%$?_?=(5meP58gc**NT__lh-~*wIOimx|@9hq+^R&shzLK6Y^}Z?Rex9fJJ*z+Q zacBOSbH?9zoL*kkyy2l5j83GV0Dy?GLN0W9^S~|NK6^dy^pAY4bty%iq&o3E*S0Cb z_2VATyLI1hka6KZ_*?rk z%+mJ7<}SRPeJ{B}#)-zIf&;eUq5FCFvY6>v561lY^JPEbwSB)Xb>to`Img=attVA4 zpVN9w{U!6ka3D=2j3iw|Fp4I?OI0m>~e8UMauDzLsf z)5AFWFi=7SijJ1_sjq)~iagEBjy}`2pN##-9>in{QEKeR`o8B`9vnERStBL^mhieH z+jkgPY6wYFA-=8$foJ>t+9neN$zuwq-=8jU{QPxxr@!#3+t@UHvZpVsFftfETzWtA zeky%2thg+$=i45goDq>{FSzwLKUk-s6J0vQF+Y4Oi1U8woXV1NWi?K^@}MK`3!mWY z=erCwYxrj67mE&FPDRj@UbK}DTDAF94_{h!=)5fhy|1(X$-};RcbaiUNzdVcCThYK zp~lrAgGI75R`>k9PxbV7oKTzC9@|eWhLr&kB_Tx7Z?6R%+~-sAdV0f)vmdh zdYFlqpGvNJ0ZU{k<-*;50FZTUIeZ^;-@}IZTt98JOuJ_$fi@kJ!$f|Kg z3d-w+-9kj>}H-3w6YTp(OwlWJfS$3`Y?Q}Y;j&O+D)gnX`@H1|*Sk*nvViHW} zVHV5n+47zTS*#2&(0bu7Bxp2<0}c5>zWnDeuYDMDnfa@)>Yaa-mCt_je|yI-`pzHz zY@qbs%CB&jr8CwF+^b8S57 z^EaM2-n?DLYjXF;zKpkV=~5xQco?BHXYQ_cc|Ug*%;!}F5jX77&J0Y!=C@zbs%Pqm2 z;AO0swy+IAx)vr;ia-s|TJ^~AE|@-ZfFrw=V@By5yje?q1+Fy*9O~l3ru|-A>H*$u z*77^ezT`+GnK)ok#X-L)T<7;$H&Nt3h1$o5@~MXt^%PDjP9Ob{=6eL0;$WCZ-PLb& zPHV`Vh132XEl!7KOACadsBc^}RC@I`;bJ2D8Z|_zgDQUCK2k*(4Pj%LFzXG$K2WU- ztU7J~%39ZVOSC%v9JkrO8M5kSDwXn*eotDdv&sr{m^pZ{>wX9%`PgNiaO$2;-Qp!% zk2bBlGugSH&DMUv&;R+edb8ox?ZD^D;k4Foa|5T2J?fMbUW6aERTsmqDq-BC&Ajy+ zhyQ%hZUyM5(&)a!O|M=5L|T50!sQu~wa)FYqV)(a!@oVO*j*k7S7sEHnriHG@BU+F z=zoKhy2kDw{(3(3{r5e~@%h>BKl22~*PB28HuotnyoL4LwC)|tUN7y4n}|=h{=(Dj ze4S4$)agRJ2dJWukR~P?nTTjTv?xY4Vvh>-DCCwqKU4K)m|Bl{-#a|@`&bn=wOY2S zk7gZm%^xNuHcXOo+4BZSAWf7IffyP=QWahz0R;WMnUU{L2JahsE3%7L@e#QJ5vpg?K5MVPO9)J$h#q^Qh?pN7g&PTbN%6b^ToLHHi?y?(NV9NkR;oZq6 zHmUTY;)hbf10-a^l$Zd-%+z}z=wxjaA3E z2RCYUix6E~dIJrixzDN#;FgLR<5bt6+q4*$m%{OPP<`eand1=I+Dz5 zr=N==%pl*4ok)%71dq(UHa4y1UX3fs2AjhW^vS?FA!R0Iy^+c*6+W2^V{IQ$N;En(jwGh zs1vE-s@S)>t(mFM%s%OmmuPCVKWq^7+hdLOxah;{%g)bfO(jJ)eglH3HnJQ6gr+z+ zlplHRr*jXlC2sGvI{yJ1e|L9Qy>Q5T9u>JL(Z1*=>=nlS`+o4`X(>pPSF^{jo=1Jh zFaE4UNhUwwnrf|XhU96wJ04(qBoqV$APP3(rUz?SmV%VR`fIH-x;V~@mckO7uQNug zr;q1cUYhn%r`I;%_f`Z}$gK^h-L&?3UFR1!IVWDSuET`RNX_O585Mwupsu<=I0#!h z%vmuiZOUFT#bFUKe-5f z$^B1`I4=rtz2H%>Z_eE;Xd*=Pis;rj(MZ(mEQ71U-zRZs(N#y zil`PXxY)ZNF3)l8Rg3SLbLm61Cj1#5DQfTc2-E&teqtl9@|wPgTKJ7?C+_I%I_RXIagb7IR09>Z^~sF~DxBM_*9DJP zd+iJ8d)M3FG(7AM`aIsoV}6pS`;k1&v%K(yM?BFiG7d|jiA1f>L?CGN+;TDpcz_zC zG6cO+UBZlTU-q3}<(c>MME7skdEnbPA}?>+Iy3u@kWZb++l>PQLI^+`z;L0FXv`Tv z8-aiX3C#T*kH@O{3c6+Emh>r|lj}tC(90gZm)uJ)AH|(mc3|D#y0jAw;<_$k(EIr! z#ek&{z~jQ06hss{ASWDV*n1EjyrS)*;-@bh?gm43Vb;BU+p!F{b;o(gZ7js~ZYsNC z<30Vn{`Ik+#|SYs;;CLGIe+nCHLVH+2@HZ48%)j80K`}WQDA+G7nMwrlUih6N8Vo} z;FF@HINrgFzxz>CAX>Q=hVQq#+}!AieOcvmuozrc$`Nu-oj73~`zI>9ck$Pe${R3) zn^HlePXLZH^-T}&_JNO7;%@K*dLP4dc-J5eSURyBYqqMRZ|zi;KF336-+pX+SXVn_ zweRI=XN3vXw&p#&{4+0nK20lUs#`DYET`G*hqKZrxx5}m%pPF3=7YF9KTbAM=}xYB z=+?o+)c0RM%HfJTZ9R=|=b3q`HTv3JIreGKd6!Z*94?yHX^xK%GAI}X0-x?} zTf`l)9|<$l_$DJnl4)GZO9DjoI49 z-Ipb)v~2=&8juR;?WW~sYuxrp?P;!yh<+>qQ%;CSdo5gebNfd#n*6zp2pzR+ zYdc@^=STN9=PsB`5w1U?&*6h(UiVpEZ3Z%$o-(FrjODhtAiimE7+$UW<-+{iZHIbC zCFS|U{-PY9H4t%($`~#Ik&c^n($uXfvNCwbBkx+r;k-^)hYA?R*uJL5IrE0;aqLZ) z8NwDd&uDStGUgSpn~^NV5d_+?nkop4G&j|G&zwD_d62JU%$P63*-xU5OObgLRphcY zF~2)Mjz7!r`~VjKEU(5PxG?F_9XH(Q!QSHY*?!HEj8zJ>@^hbW^jBD$Xa8xk@9-<5 zL&VUg29szgS0e_%qY$wVqSTrm(RP=ezi%Be{S0eJl|tzIVjxFO_Ga1MMII6WQXmF@ z$@SA@eL6#1E!xWQ=s?d{6|iyJ_4?3j%{}$%C*0xP(evtP`Lvzwbh~Kw)$9FTZ*=dw zaZ>y0dG2q|AKBT~MmH;XnTKD!8Q9{@D6vS}crW2RcRz$SR73mB&drSHpdz3OfTTgg z1W*7cJqso*CMaZFoMOM9M`QIh4>oco;Ciyy)s=b@EifZ`S-L-LFT zXIQ!X@wn!wCyVJwa3#`FP*=&4xj1Mr#Fbb-1pCy zBm8}JYDV7t_t)Q#Rxg)JfA`ysU;pG7(_wB-5@TM&Km~X{1?z_zL7x}5-g@_)*L9Vz zdO38cYc9uts)5`k&_#*MeVSyZqX;G-R~mrh(oKG!7rSPsSvK@@Bbn8QKr`*i=EE?Q<_qtTwf6wZXkt)OLq}w>3zms46r6@!MZb(!GgbgCj{oynB zAKM4g2z}iD{}*U~e)63pG@Ez|e_R;!0FZSK#n7iZoJRK)QE}H78p}O$t z5&%ABq(p`FCyx%%Zq-3Uewt{tntjlpc&L^)K(Be3D^vOBQpFeN_~?^!ox$a{4p#OB zoz4U%45JE~J#hdB0^seVR-Nq&AG1{_M}!ChKKE!P++=db6DZ;~!MkkEq1H9!G_k-d z;jG}e-~0L7J<7%Hybvgrzua|Eo$1r_AB9lg%>J#=LY&htXM6eD)ThqR!w;HX?!@lY zv$~(A?x3y#5CTw&;|M^F2Or=Hd_-#ph&q^Y&oj^0v)=8EPkr>YryO!$M~t)`%zA(< zB$;g7$ivcD#4)r90iywcpaCGjnt4RRIH2eHIkpy2X{qM8BoarzkXzstIC{ zOTy%ILN}bNvGSD+)7k+v^+1)RKj@OQ6ckvbf{Y#DD6TRMBy_cDe2^?+^0u%uQbZpdDk)ew^yViyObmgFWJg-~8x% z@N;p%Nmleaf!e7zW}EV?W)kFwblf`m_12T{(>P_@&bj4dj@aS#r_I(+^ZR)C->w68S$2>Y7DPlubcbV=Pw%*^KmnlVlX zS!U0x>bh>LZ@3*d!rQK%Z`X1?x5wkF@8tH2cm3|yFEAdDaLKsaToh{(hD6|fCzO&# zF<~pPNQ~@?m?g>JH!Sz$4vP|q)C1-f=!YGL+~Mba!7pR4eITXsvpQSaJh|mui&&9X zsZy(T3k}bJNYB7118~ttRBkIJfs&#Em>9Ndu9ArmAhXz$UQ@M@0}rHe1((7T+8za5 zivk?=fI&m!G~X<^99SqlGuV3R6}Dm9+N1G$u+~0tiuPa=A_+s`hrSQM0)(A~Ge{Y* z@^hBIxv25N?B}z3`@x3WP$DYlI_PjZ-|ftG>j6UT6b<+_E_(Ex5rze=f+?M-t020^ zo(I%+rmG8?s3q@wpDd#NwQ=Y-I`VM6%zfYKN(&$RGUa>o49$!eeyAIlbAh7SpWsc&l`-cQebc7RzCH6g{H#{Vc!PR{aatb?Rh@&>St97y0J*7RFME+m~_q4WSC3 z0fUEwkl8E|WG&K&07TFLG=K;Y0CZFX5)na!(gQqU+O?>+kl-)?n3*)<33z}M=Ka3f z7ZQRP4(Bk>cM~DwI#;9K?}Iz@*7MqKX_=!h*|IeEJn_`md56D_SN)%?W><2) zCqkV8_gM%75Y!m~JcyHEfDsJAld(tj#ue@g`e+V^V07uFrysn|;_KjCu?!cd!#WaL z&th0psq=_$06~4zpc6*GicZSN6cNLLR3fE^3Ig-F4MG>6?P_He&*z(biVObXcUYu? z!+Br-lBN5aiPxLqCvC=6*<5G9xvo2Xy}!~2lxC@!2D953_wTs)^;^uFH_~V&JDz+4 z&B6l+NN7-S&;+I)93w%WZRgLwo-KU&cG=gXqz7|xHuol)3{iSDPeLE<_G}n=Y5va3%>FVjOQ~%m8yG5jGMgxgKs=TrX3EvYu zazyMWprcdvT;-e4gIktrVH$>^cC}7zQGd%;p9>TFr|A&y)J&*=c6DmTlGlIpu4++i z2oenWA08DgIM&10!u8i32Ke?zxcd-aADla}AJ_+9_uC)cm~mz6FLr=PoCf1`59}(= zPVcurV_QV){_{JWKYo@>rR{P{RO|b>?mXXfe_r(S|5(liWEe((Fd~6~XTgIsDbyIl zX8F#u?xHeKT%dKyRb9SVp}#^VAP@jhDUqQCfF!0~EQ zM#$9Kv7eD;gK1vQyylr|w=CK$SxBFA{?}jUR9CF?*gPM!EJMs8_EsdVV-vp?OhJ7k zG#@T~#^01Xm$K?S)HGjvu3;xsFUz}scbj`=%hPe_;Q=wqxttfj{=>6;{ru&>{nZg| z9b-6%kQzf0!^e%QHxfe(z#tnH>zq1oIG(31uU>e69W=N;NkDE;blgo}v*HyD^8+OS z9!%ag+3oM=Lcu<2jk(cb?VDuhVJ@7yX?Z+i2};H&V-EH!-kK(L5&)e~!O8&W(9X}B zakWsn!|z|W_#OLO&Si_g!zSsrEW4@WE^*mNZqBvMZkuG`TZ>JTQ=a@A=2xlWcr#9G zpNpJwl~Wt34q|yCK%Le-{p7HWS zWu4%Ftk0sZ3ASZht_t2iae}YU`!fu#SyN}VIHX?#hNn45Hr0-IePkc#j*T~h)-pxL zQ?B~P?|;32%#|ER{^4U~`v*nm4;L-xv*et!usT9AaJ1q&<@M=Q_b+w6nYvS|QHG_G z4m4^W7nmPUT?xW*L3?jXDdVtDudOpiVL$b~c99R2gofDYwo9&ftj5-aMr1!m$VvQ3 z1zaz^`lvY?*A`#<08$=VLdvkHmk%ln-d6#3F=(gTqWj+DKQ1@2_syEt(FVkX!$Ftd z{qd%2%*_T4n3(NSOW)n75z#7c@xVT((tf&icRxMZhd-UiUH8y=p6c=c`t;;X`Lv-G zf(I5Orsy#)F zAT%F4;?s;klFOXR71r&z*eMp*=*zv8whK=`@8rh`mhXh8b<&zegHN-*a=Ta8Q@7U+ zkUHL#4(RYUOQg^zeYZ)yhF)?z&RJuhT*rL2Mg1ejra%@ps9fvaKJD}A&>Z=kC|3nXl0Y_PP^{OoI6{~WjT9fPTQpU+($4{*Fgpk z>Dl|6p6<&lM|`(P;L@Bq?X^{U-2jN>P;{jw`OF&`|`q zTr?t3)89+) zNhFadz`z6=F6Q*O37~69Sp7P;+pxE+TVCOiQD%oLv-aJGF%EhQ7Y2~QHIU;_!xs#W z46K}uY?jZU^rTnuDjDXQ5A0a{K=Y0kCDFKhI>M32fW#D4lZX&i5lpb-NQ_{jw0*8y zovQHiO<#X_oNJUN`>tjvt+;KecD4PEE%#{9!~$nq&L6uH zH}@6)bMJi5qH;b36UB$2%%=ea0*=B3QiRHZXK)WPK^zVRZ53SJN6(Wwa_KM~;=dhB z8;^$TX3OM#63g@ZqPe_KaHSgr4Kfv=~$dihXP`PEglzwhYuYZ z_SI@S-{r{{y86HCZS&cc_+hDSs~kt(%jG8c(R{Djx3|&pat3~#C-Rrp=1nkil$pDZ zTh;w4k9?gs!`Ln)ngytuP$u;tv}JNS5u+3rs;ov^jgFW)+t&Vbclg33cj~#w7kHv! z^)lVDr#>#FUi6MFSz>sNS6Q61?BQ8D#I%f}k6|zltyYqG z-Bswe+|DKs-Y3pnRJ!T|em_-^rfCv%zOiud#vi%kNY6Pp!unATo}8M;%G3O4*|VEJ zAVx@212i-eKmuHrFf3vb$2z4(*WYBf?=j<3*if^8P^pscca(66eVmj1Kqh=Krj^L2EYw40E3Al;+#Kdz*I5T&<&vjOPcw z%3Q}s;pG>(~dKlQp3i8Tb^eGTvYh3k%<+X5jFKyrZ)0suVNsX8wU*Lyd= zv1P>@B~wp7@E{7M{%FcR95ohxmH;_6UeYeO$|a+`i|G z#n{HNFoW^R-@LtHxR@xioZ9kxDxM@%!wJcIy?T3^lKIvlfKg&Vgq*9!RbTyix8%`l ze6>#-Oyy@57*2i2JHLH*8jIZOcT10`Ze_UC-P^6KI`G2X$IqK<^go=><|W>|v!NUW z=O|>1^Y;Jny-K!Z&Xt(Y4Q6tVV7&jEKbmHU>d6EgGiJvLuIr9=ZBT(F2cB$S? zEmH#TwGn$sLwO#+>+_P_aNL-yUcKlcoy$*?x!<;p+|zleOV5L zn#jQF(1PL{Ln0wMgxa*o4=tW@%1?Wn-|_gzZhzv~nzrv@r=H2(=B9S8%kY)2DK8W2 zUN80B)?Rh?J=>e-jL*glm8f$K&PB%gaG8lc;tW3S%WS>mO&|K0cF{WsY2k{9kqwF) zCjk*FOlK5ENoc|l2%97TgJ5LJ67b+90pU&j$_)!zLzUgtDAiOebwW z5QTb1-(Gq5h!ePvGI*p;-8I&AB2HlA2n8+RHyY2xdeaE(f8y<4y|d(o&dD&mD@FX> z3QEs9?fXAarGWwwu@fwHKd4ReA9d7Teqzv}YM%XswX*k@4Vo6JyF+%z4|@@irlxJa z-jm^ci?=`TylFavF6={~sf!o*fzw~Gjaj7tSY*;+E-sM@Q>}(^vYb zs`U6PVdZ72=%>ig?_$Y_&MO(cxuVb?zHD zT0cMR@b$IBPum?-xBK_BnsMuZnTPtxkY|m>)CBh!^Xrk8r z{y|rF1oMRLi;mrb?UUDD1Y5HA>-g%LgC9L+*>_$zH1;ke)<#w~4ieJ3D8VOC9av6# z1!{GfDELO=Xuc zNbSitCx`ZJT&aN0xahm0|{KkG$G!@vf@)#wf@BMt)|2U?tx#6n7)ga(B9ZV`$2cf=F zl>Rd*1ZXlU9(CaQOPuZW>OF3B>=q~E)M*vEZ1nl`jr}}qr)+HDVbH#IsJ$7Crd^0f zO$H_)JP)FokhX<6%^VOBw8Sxh+y_L6ghtFV;l<&>^F;r^WdIh^hyyV1VI9;wmR+vC zc+gFbnGVM`Mi1s$jXCIDb60w0=<^BUw3j2i=aHv<Xk~2un#P)m zdYVP+bz>%KwKaD=PXQ+w5-gY*ZbBoz6l3c3t-4o z?uMSsHB$;wH?U-#K`Z3k?&i&!G(%3|&L2^FnURM)U9Xr|5yB)si)TL}brhn2edGdI zqddT@^8hX&ZZgP_lhwLtWfEPD{iipj?aT1;! zM#T0`o6=7%RH+jb8ir@S2F>w2*L)*mZ}(Cn#MQo7U%{+{7U~vn`1LZxYrsg|lvlLS z=-7n49YS)jsOZFF56i|DtjXnp(-a(_1_7kbn5&G}VJL88+#oPftufuGL579B4-M3f zA%x{vmSeq!3dA@{dUh@eo90FeWn}v0K10D+u^vHXJtXY1&YAw;e`)s7ruG}KPM-%~ zJ>5wDrYjF12A-@U03;;Wn3!FaHqARQQCTh3Y;-u++SM*%oFvG(cljb+^;#dYH%h{W zqaT0z0l(gUJE3D*51PCOANIVWeA|hlM!YjEnK zfJ3JVL_rILOdj0^&w4OId1RMOLO3^8g!;w=Llrb1sIVlj;J83Z?ElJml6(bB*aBtj;>Sa9b~ z)~yT#cQ*h%L%zx%v0LutOB--5Hf%ln@|Sfa?`how(jaE7XDCl)ZS*?SXoo<-HJP&S4WinrMiB!w z5%f;LYleMIYX)!dq6cGNr)GI-nwN3XQYSuC2VN;-#;Swi(W<~yGZpga*V`R$-nr8A zxcPSU?%Q2Pp6&9@Sz?{`nunPZqg9dSBt|^UeZuo=t(K9CT6d@O=?%t?1cU47QS!1* z-+cF#*6T^?eBe-@%Jaw@0Jr+`|QsTy6yH%rpF#E?j(+7!X+#& z$y6a$^X#=-Gyn9hRllpt;eNVL%wJwT#IxtWxwKkGiw}F3dG+i(`nns{t1E54G00kt0ns2aaI9cswB;UzzvB$BU4DL0~OYbNDu(#~xN{{O45!x7Y zhXS^T5f_(Aelbox2~?H4AN8uwd$n7d77khJE_Q^oakhq!sFF@J091_!h-NEYLnvZB z;GJ{iTYXlG z!G7?4&;0bL_2YwkPnW5RKJD$E)Zyjhfi|Xf1*&{j3R%*laz5n}-*s%q<&T|Tp$(f7 z+k>HNZ}P<~g4aSQ5k>C8!O*3^nj4#zNiJjvf@`?SAthrdV(9PidOTuP1^^NX1>_JE zP&8_vKnZXV$v8X{Nx(`VnpS-}~l;{$lWyo`4Omi5I^CF|h97xg8+-sOzvIVtI zQ88J@9%Mg>zk(`%@_Wf)$m zA-bVpUDu7@-TyCcjGSb{&w-eTtA<&Gn$F>0zf&E-5HS;L1@O1H#Qk>D!<#eD)bgNE zJe?teM-rAD=h;!h2T+9RRmZ9)I=O4yFJ=#D@Ayy-aypW&sWnx!;T;H@n>bZbL?tHr z1Kh)=ipjfiMO6a*rUHln&LJNIuvb9fyj`lk1a^I1=+lxtX8tm{$J?M9PF53o7Pw1b9J-EXHIzc;VIL?(?7PZpQZ%?(wK-B)wFI06JfFa!YMBMDBR|4H%E$wZAkIhrUGlhcrnr>EdG zX%RJo0Ti+vc;JA7euiNfVFEU%4a(Ea_C5|QU{DiD>?6RkfhX4Pt-)_xi!`C&zERR~ z901R6HV8CtvssfMlw%F@0dbv@4qy;=V%W~fq~)b8SQe6Xj=5`07K7E)KF6d`CVX4lP(=e(0Z0H)QidvG!yFT^oH?Vm zmh+WRnU9+O*6v}tYx_I(VQ!|)r*_Dky~TnX@wq3^;x83v)@CR7CPzoHz?SwR(^+sY zR9V(q9CGvQZTOPZl9eKpT)`5qsjM(qw!+ZVQ=aieWC9`uLZsqIOB3*%IMS9I<1SU8 z9;0<*Z7l61(+~h8H|;4DnNVo3Il7tP>6~+7>Js zFcupD!}$Wld`2LagaWj7c|5Een#VX_bj-VQ>NVhWVVIdV*PV4~6MGku0ALUh>6pkS zn*^c!aN3kg0)zKqC`seZ4FGI}KrFEe1~3LN07(>T$UUxxQf&qfTMpM=`?&I!^48); z-KIx%Vq%spTUM*plEi3e4Hzg506LW@)U?cDs+u5R62n)(!m-%o9>EB{`s}peo&A3VBk2$c;)lI zWy;Q3;41_+$RtCzdU$5ZH@eHj=&*91ZjSKGxre!#LgJBr24@@uH^P1LCO6r1 zE%6~&D~I+d1qG}onv(%0IuAL;Wp7Wx^H%t*y!AoH?WWzT2R&(hIA^Q8vQLAYC-ksO z+~BbTKquQ5QY_4^Z>%21)!)wh9p=gnK!$loD-8l)TW@98>Q?=xV_>84stH%|U8Ww>s0#`@Q_H^6b>XGp?@}%)4%8qW7!Q|9*@7vR61;J6Sh-G$XAc z231HpR`%9tF3+VG`*qD%RJ4;eMa!2w@wCvf&O-)PXnEbC+awOv-F}CoikI#8t;?L9 zDvU1O-Oo@)v4e}Yk{K=4mT}0!_7*qnP59{*9yr{BQ(f=@{348#gGDC|F#`2!0!kmM zP({&da%WKzv5K!bP+~Rt$kc(KU1IKDJNLcGlX;8}-Tdut`jl5^1Ejf&>p9 zjAu{^b;)r7cnW}r`5Foa5WG0Har2Dk!{|YhaLr>E-t(pg= z@U{X6fRdS?RWv1|Ze@w~D=&JUx`j1mp@&7%8*h=EZu?rAngoCc1b_#iMw*;3cTx;} zBkpp#Z~gV#l;xYKM~?IS7{B(1bRYlpT2!sW5o6465oGjC=e3N+{YVhmZ5pI!<)3&9 zobUL|9^2D){V~%^Ir&Vf?*%`5qiwAX_0}9v zf;164Kt}*U0|7`2jw@mSC<%bekATPks$=oKIJ&1WVIR?9U^=`+-buvVYLR)LbCz-E zemz}A-;7fL{fa0QsXcwan)yz@{4cHgivOMC&2Q^Q9%Q(GDE_=+$U07H8xI5~1e${{ zz1g7=b8xlsnUZ+>-hP_&>Cf%WllUCDd{WI5Wi?Xbh3?|_VeyL2$?hSYg>JEU<#F#` z*ZzSgec!Dgu;n4$rT2ctyCuTnXc(i5#mM1s%rs30nwcdim6>f@Z_lMUC$-nTuo;1? z!xGj6FPTW0Pw|hc?Bm*xyykfAURT~qUM`&A3X-F8SSLiD{1;Exi#>d%7I^QE{@!0I zs`5G4Z1gK=1Zg`1>+;Wgm&5rYpW`~OevnsYG)-M;%R}6C3>4kpL{F0!d<(8)SEs9= z-gno155nM_5Xr>2e17=GAM%Lr^bTKS$LY0fJ1KeH7vM@Z z)IBMA1fL-$HW;eG(;na>1(0aC5IiZ6Mpxk{zx*qG)kUNbE>Kf60b5#hwm#?hAsZg! z&=Qgw44_~N7Zfz%AdT8Va44jJu#(am#sL#?yhGgUHij!mGJkcJw4fnv5G-Ps$aA?|dY5^b}-xH4Qzy^heK@lNL8AmKhlP`cJ zCIU#(Xf>P$8~`>FB4tS!K-!535?~@XBnj!H=wJTRXJ*;8BS(ahKny;EV{6a(Lw;y0RSIg>~^sESnY1 zZvLcG36^)ItLezUoQ|CjSEZz(7HN4HFj^(>)*~L$1|byEaY)mHR~JiraX5g9=3!|E z6DQW9-#sLV;MR= zNUaEdz!)6OE)V@>AMx798*at!TtjNjvhMC>-6KiD6~Iq6YDhqu6o?>%QQpAes3Hk5 zQ&m+TI)_1Jfsu{o%seHLW9lq-z-HrTE^i8aULaoPYT(nwP*)+48ZvgQ(wQ?vz-x5e z6-?jg+|&J>t-pS$*A1EIUrC{{hRjSQ5Hl7%TbTs#eRaA_|I!uM%X*fUv$88=nD7Ls z=|dXknOH~L9lSUS2EYv#x3M+;xflq=;3U#;;+WB~pRe6sIZx_dyjkh6&cFXcuX;7G zsXd)1am^~u=C*=lem)4bo5McmX@|ZeDmdZzb8^5L_J+|yP9TuXKX;Dnlu^fqOZpsU zE5--7&VT>9Z|uo@d$gy=YHK%ZhjBZP`^*~*YDM2c z%aX$pf;)G)Z2-)jy6DwgmHR49UY?g_Utf>+({oX$j`EegY?)VjN439lgL=RAnHL=X zb9dXu+OIx%P|PE0tn5{?zdH~|4pGk%PxUlIk)s6bv?URq2Vc!EaIabluik+LA0ml! zi;*e;E;qQZd*8?VE&tJn{qeKaXG*Hr=zIRK z=OGA;0gOqwF@S;bfGUsxArLz<>Zrr*%qdr=Z?P8nQM-~KsY_=$7e4J?$IaY4HYHTh zW)0@p+#2RlBsE4&UF{*alB8^W1Pyq#oXuSHfn8?DPwxChZ(E^BzH+`x8gH>h_j-XI zgaTF1gQp-lg2-Nm!a*th8Z z7Fu!Mc{404=il@)zz_zh#$_*>?ika4=?7o;3geaF{xH`|nr5Q--Lh|7rn82a`)74uFZ$ue*v11yMm9I(+}FDfGf^c;{L;6iy48igoX7ZOpZJf@ zv;TzC->|j1Kt>xyf-q`KX&PA>-;jP@Rl+N;lrl1?b{j-4KeovV{d z8I|4I&e6DLL2ex^j{H)#lv_u)9NBW*h2s1L<@4<>^ZCZB9ohJKN9kf-t?0{TP8+F_ z!Mv6VMGKH0I6oSjB*|hFMvfXA(xBU-@mG z{NR(x+o$sWziG+u=f%nlWGH2C;Zu3`PkJZ(*lpv|WoajNmbvZ2&u`@Q^W(qbGrN3x zcFuO3K91XQ_H%o)pXB_};i%oYcH z>5H~)sAq*&r#VqfF<(DJgHdsw(w-T+jHfsQ0d)hRox4l3!<|8=klXw?xA}?_*3)x3 zXqtos_C=Z|>{b2h)+14M?+2+|5rvTF0JUxSh~lw18?kygG6#%&ZK3i}dT!HSEFEPc z8-3tE_=b=2D1Ss(`|9xFgZrwvseDl*4L1g{LVTfM;YohJzrDLWiN2sF@rYV2x=5_{ z>LE-RcjkG|sblW<=aEXKw#3$(SV6!@V^rE1Pm6tmr(s+z9YniX@vEdkW8YThdRt!< z+lUiGxj77nSMimf3YYKyBKhJoZo_%Ja6)2ImLv*!b+lS7BZR8z7|LXzoF8S*zdUm1 zUt%)pvmH)x|Cq+rpZk{8dd`-?;$VFNc9}d3H=~-w`d;|cUkB-`qebQ-{WIxxlpUDU z!DeUfc^l3jO=&&ME1d&*z>VjOE7)x9@@YL?9!wn$mjhvi54o{hC??0@_~Bp#lM7+R zzQraUl6v>2*&KhI^3SKSb3&|`=s(74XKFDncG9ZeiK$f*jz7X)Eb*6D$3@syo4<2^ z`*OUW-xqn`GPYCJqi3W~Icoq@K&-!Da7|Jg2*zM#%&UqJf~&n%242-O)HceL`or{) z9_V1ybu-1NizKEhOt`{GGY!x|knHkce%!Oa__cL#UF&OjX2yc~Z2np{icfdJH6Q%5 ze`(+^^C6CWb?>#VPD2EMGWA55zPiLjb?^3`!1?@l$_S6cG(4bV*Puvun@WrHqm*n)DG36Y;KYC$ngUVhRqhwU9nRMce{q@p!^_9KZ9OsI0nN7=^m;8%wQl#^ z>*0P~c01kot^HfJ54I>;Q0%2R!g~0Ck4~w&U{jxnOI&U?ock1>zzT&oG`aL9p(&?prVnuqYP+s6}WbcS+R0}XBcR6M6=$p`uQJ>8&7=XAUfXCAat zk1;17M>aA2u;U5+?RuV`5cS@do3@%%1zRfMwZ=9j6iNlC4>oyG(m!WCkY;&u!(F3 zs1;7zvg2y|<+bS4Q7)Ckpb1cnbv|YH>^50}qMqqn0UMXIPsbVVKb*O!ydRa6Vw^sg zt~Owufu*uHyk3Tnjt_O%``CZ)`Lt#>^z?CkVZ-Qd$WMNvr|Ex-je4xxn&4b|{iyJH z9O}zQTD-Z#i+yA*UZoFNF>L4UYoSB`Be=yw9(cp8{;Poq@W#@soaUuZYdN>*>rd}d zed!2nPY7ZH5rjwJxS5jByc0)ERSq9*xbG}f;cQ=q!izidp~LaXnW*k~2~)^9 zq%i!6r*?D_7tJu8wV}WBfvw|{h-cl&x|HJwB%POdq>p{FKR(a*S#R?dyY6|=g6t^g z&SQrhiehHYw^fJkTR>i+Lk{lBEQxdXE+C8L!r-U-OZzGpNB(7778ehGrJcc|rduv9 zY`eZXN2*TQsm|xIJa}n5T0QitsIWi8`NWnY?HzXash!PxH<3Btlh44EDyghN6t2np zh7ui zOp?h{=a%c$t9H$DTm7!&#W@Qd?9H&={D)di7uA^mZle*NuddHDz5%#-ZidQMbv zIc3j#-1V1l?aNP}(f-xa<>LZQzwVb##Fjn%rFT5-Tioxj^`kGYKY#EAzPjf7w{O?e zSNeK&LOLuo;rP{sJht?7`x$ZLe5emUC@!Nh@kmAj{oAni7~PDeao@!@w)e-_u3nw< z4(;LIVpeW1+|6_iFSRBE5pmXDdTB>`&bsy4e&iqT$C=ev_`226YcS51bu zn&o+3rq^sadcAk-m8abs5Bp&E?84D&pAwBM%a0IEu6)GBN4@7cudeO08~x&E`KXg8 z+Fc$SHw*h2_A!yUtXNGiD;fvR=EeqNnjut3^P#rWANY3ul83tokEioC;!#ICln$$W zcz%d3p3(J}^WEIHBR+3`-DSPJK29I(<)iEb?yUBB9-iZDAF?(!?;9TuPioqwG;%kg zZ3PiWKIqra-1y+x<|msKkF_6B?{S1Ux2I6oYdKtFBrO2B6@WqO+qMtaL+zAy4!)sO z@f`gWcXD>jha2yEZ+PRovFr@8pU|E>z=MK;&-08C{d12ZZ^)igoUip^ALHF#%+G~7 zO&54KVFJ4yWY$mHT`uhp+GJKvQLNB}lk4W{S|Zybkf@X*%$K_x>C`Uu)W4Trw_^_? z%)2>5qfeIUMpxYQ)NRG{-_LU05zg!NSD)$OAKbcpUiNH-#2`FPR1Li%E|a|UUzsnD zu#0EE*q|La3RzW!N)x5@b=;l0HPo|T)erx9klj#bWQe~X;Um?0f-o@YB0ur+&-OHEP7ZzSY#`EM&a;6^XM zyZwG*%n%7{*SM zR6>)4FhSxY#GJARUL~lmSCG{tKr#-K>pbXj*Dg67(SwOybC%^I;K1~NkrXT6h z`5fj6N)XgTWmDRMLKWic-DkT`-)jA(HMcKFxO}n>=9!P@>p34>r#|?}|Lk<%>t_r5 zIzzv7DtIg{V)9B6&hE!?L6iGB}1qy z9e$a6emrg?j_LZoK!FcE zlwPWZuU}Ff22c|PEPx1!G$aDUo}qYmm{Kb8PL;!8`0|y>btiMGGbQuk<0CC%mZ+M> zV@)|8H!+T&cjkqW(?$ARY?mpffMlU!BH|{CLX*Nyzw;`{rYEF2^OMIt7;>7)1zj$d2vfP=Dy#(Ck|S8lQ0d*11HT{-Bp6 zqEd4h)p(p0Jbu`BBjA>4NrDwTNkeK^O8pYwhUdPVkLS(5_VkB$#LsX;$!FXp@Y+B6P_)&&b)s0F6Wb2`w71DyzY@Zy!`b}+V!!RJ=no6cDMBP zkSM26G*0%e9*jA3l2==xT@sa1Az2pFD14iNMwP?yBIZ6VqK{0~?W?o;IueyejhR&) zM?3^|At3~Eqp^!p?)Z(jebGPk7o>i^Z{9(>O&{DZ>L&4O#<-)N+^ZR;(ttIdIeDnT0=@nF0_R57P<7Sqf$Ll0- zeQR&7CsU|elLt0JVry^FlEUBq237C#jxai=-fdP=ydXJQ`3R+jt4Zma<3+h0kvWq^ zC*$JAHeKeZn8mc!c7});a(CM&Pyzc4YiU|bXvuyeb<`w9cV5dmC$8M|+9h&&=jy)N z8D8%(*MGNPP=q9vSF9iLV_(l7zwi8J`ki}UeeEaQ^yuZWJ@l~U<|OdO;PZwva4lCO zdD7%j7f!ly+5LPvU2>_51XE107Qh+}W6Z;TtoF&>!_^_+?uE)P#w*Q=c-$Qn z>(EbD$SzpsMtid&^+F{c!Qo71?c}l%E=MX=#N{;7$U*Ov!Wcuce+FzW+hlFT(xC>y znBmQ}U=W6s|CF8`tc%;MTGAvW}wU27}M4u$N z%d{Op7EZBL`?!z&EKfGlwtR+%Y1d?y>ooPWuH91);WyjlX>*`#VFzZT8K(WjoeqZr z9N~^)x0^18tqVOgVzy+veJ-S+6RNe3LDe)bdQCs{!NP2W>#6GVC;0lGyy*1%(OGWh zt5#9#Zsy{dYisyg1X}nUA+kPT1VBK0GnAT3ugbvw0Bz@bdbNDG1TxeJ-4T{5<}4f9 zPd%ei?QMALP8I%g7|1M^BkwR*@d}ON7?bkO+6X+)kFVVLo1gw&4X?M1fg3SIE0NNQ zpp!nJBJ&E_D?7Rzww%1)fgSFzck_?Qz!y_r9<-{IM!s zdl`t|McYK@IJaAJy$3GtVeq^tYaZN})qa>%wma?DT}T2Zz_$6byvCjG?)2FfHcs>O z)!g%U)YehA0=|zdfHWw=YRosOg)vIu^=rx%Wh8->$(a>tJeZ?Jw|^stxfgtl>>j+X zVZYOqDo00-SXy*6x@BkNgC%EdO3npvGIquY$;c*`w1>l8;}a*n?_>UYdKb>x*%>bF z-}ZTa^nUXc1KSC$>*VD&ow*z?5i(03!mInicT<%x16FODQlyrk!L z${xHrLH2*KK*y!nRG;rpiZJWUblO+mUlI}JKyh)09)#YYDvcbH%CYr3;JQP4q&3EKnmlQH zb~z-mzjMp4(z|sLCvkjp&u;jz3r!q%E}r)KiW5sFjbI7V=6PP*Ja^=Io`Ygd1EZH1 zbtXNw%WOs>-^^cDUtAZ)zJcG6KIjmOXwz^MM$H!xgyUx2MooT+H@;J&Hx)Yh%mp{I z2*~tkm^Ue4hbTAfiZ^yF8@%v9ig5kEg%F<$*83+hlD$ zY1m4~;e(3y4a%3Ov&dD)Y>_veHE&I!;HT ziC*Y8G7ao3!I?-3mJU7EUrVURnRk<|+wNjkH?v-jcuhvL=DOFkKMv(@-)_BTo~A3T z!x5%n_Mq)-9rNZW19u%*Jrj@koEKi)S%6z?q(`slF=W{Jkrz5ptLjgxGt2 zZ~N(=iSSi~L^zXQMn!J@GJJ976C(iB>EuVnbj1E2ykR-%Ccbn|qppbb z<}St7+^y&6Yg(nI9>FbksaAk5N3pDwu3+qQsM#L2R=inyx=gp=q&Qur{${0F=lcCC zi{?oU8P>!MZiLXSW4aOX^sPEOeyJIL`p&9v>Rb=6lpUrz_|T6JUT4kYZQSS(^nvW% z)X_a!RP2@irQ3?RE&?FW;cDsT0u__zJL58Mev+s9WM2N{=kiV$-L0G*XMW%BKG)Kr zTej(pSuXl=nfI=R}&QRjq@B-HJ>a05JgKyR2CT}*zo~ttTU7j3` znBrk4%;BlXksRNN<4Bx=Jl`cdcfPCVbiK{F8-3+#?vzp6VK(L-F==8*Vo(hLNY2R6 zho~y75%3{~JiF|pai_bcpY}n0nTuJs$hp-wbIZ?-VO;6TagI+=Xnd|ijK&jGtj!E% z;2~}=-G5joezRQzu}WCe2yEk$XD!dMqZ(7~M=qm}kF|%B8cXMP?w8LOlrV~EE+&-j z#{R$j`V!xK*t)XPIIvu}7IU$;TOi1|40=e^8R(hQ#&on!-`C2*9CWCoI%Qciz zbN;-J(p*$wCq+8qRD-^DsqB%vs4ei^?xRcgj8JOQmxv^(9(0Yx)E%L4V3mrlNPjy~ z60ofP2rnsD?M8TgIG%$FekOBYy^e$P>f_i`u9(ix9a~MD?;j*18ChO_jG$%5gv%#q zvZ!<$U9zkYAoD7Yj4Lef$h@Bt54(>!;a&S8UhKW4do?{xQeL)qZx3E|)p=JXu}AOT z=j5@ysMDVCQFax-$vllr4O&v-E@yAjM9n$-BaYiY)7(m7=E|dK{+hax%*2^_PI+;0 z@v$zlWKJtDI&o&p!2=9Xf!$Ck^-$*ibdirvW#g53>X#) z?l?9KK)#wgr|p9lkNIrB?5B6Uul?$)<@J@Qecdmc;Pga~E>{oSw~t-D@2e+_i;=E* z)rHQs%VWDg9_KxMG0DgKEx`U^?r`|Qlkabzh5n9{)_$49pb3%E>-Q5LeD586_06ZI ze|&QP@ppRip|9V5ijRHbM`w5KuI;s*>DR=?Vs;B&Nw&vF)U)4Wb`=*Pbj;qE>BNqD z{#Iv;(`ABskI!?e##fEcxm7*5%B3oB0&(8IWwZ zN6b2Fat3XnC<@ZCZMfQD8xB*?SOvZ4SR^{y113fhVuu^2=)&N3J|vtQnw3m)0O-8P zLA_GvB^UELyw<$Jm9CRHOZW#*fBd$E++zvDK}*fWW^q&hlxf@m7w#2|NQflQnV&Lm1UPpXBBFdGh8|KYwPXr8k~<<>-gDbIx-b6<5nKJ!izI zbLHE11a@l}sw1bv@G9tcE!_)q_CC|@b+?K9ewJGOTe{s7(w)VLfjwysXt z)kB?-$}sgQ5VSdr^=u{DnSCvmoH?te#zC<*x*a|VeZ|v)Zy|~VuI#rRdFOL)e(P3o zAWoz5OPXJ83*Bg=jhBf;#?oLCqu@yZ9()q!K#%mKCm>$)*d{%@CcWu7ud!V8YkLdD zOMK*>dEU>7I(52LYi^e@|W`1Zbitv|5P zr`oc+m~5rn(&OL>;COI+a0zzbCol^6|98rw{S* zEa$B}?M?CC>zspC5fdrXK6c0Y<)``P$KTFm8=fDJ&d2rM;}K8i^vV*Fbhl^qjC!4V z^?8SyIJ-ZHb**B;!3U|K#MAM?iuz78E1klUv*2d1 zh$1J9VAh^q6o1Bgi5&Snj%K~K<9OcRbo!K3OiYX~Y4uaT@~QudNwqaScTCjEcGM$$ z^{Fc~l`moEGWMIvdb_aC4Y8I^jrHO%+j!Tvngb5MMdpNrk6N~~aRt>@43)baUxJlsCzdi6fA>Nc=T&%X7JnW^p@N;hMQt!Q8|HMZ1)PKmO_(7$6QZ|ag+QUu zq7J=49|mI}inx<>qq>5?0WLa@&GY~6+rDUCreA$Z%Zu@69rHYkO@q*DG-^&})Qw-m zr*T7$@pTgqXK3?jEA$VFZEv^kZ8a<3X@l%*;AZ3Zf3=~s>EVO!DXNbYJ$=03l$E>V zQomekzbD*%u|Qne2t0oCQxgojP0_Ri zQI<1*J2N#Yb-EHopLZ7gMx%auxOq*RAJ#Lh+gRF9+5miHS8eBfcwwo#ZT{1|$n|?I!3tCu(K+MV%5@7=PYnc-4bvle$u66U znQDh&mloA3Sh1oDg5b8-GFld>-sJ-eURo!YoO}H$C8G70&jgvDlQ`ko768Od+cwi# zsyD_#%DNlIE`|K0!W$VnW;4FP4Ov|#Mr1hSlz~AX!R9VY2V-WoUM=!5Al3b)eX0Tc z0OA7J%s073#mgkI6E-3@|0P~B>x&2XX^&ZEUcRsXG=D!mwvYDH-k#;1S|St6hXDZu z4v_<Brbrxpw(W1baBoXahs*_oVa*c15nA7%nU&iz#Riy*;)XE)YH)D z0?%ET&-|MYx6$hcd|Z3kYpYw!9X{L0al`G!Z+Q!|rLtIARKCOyv;Y>#pRS{H(Ws1k zqMI+C>MK8K|C4WX$dmZXPxyv6{HVXUGxzPsf;K{85i!!6_wmQKTU_`|U+?Q0AK%iG zJ?^k)^e)%4LwjMH_ltQn_?YJ`Am?S*!sCh8Tz5@}-1DG4)DdT%A9Hg)^ZgG!4d1@r zbx-1_{#>GvAIcp%%-nQ8yg&;~NG+mg(VY)1U_wjbJn-rO#4#wW80t&TdYh%51ybHE zrdA|Mg6Z&__BQX~pP%b~hm#(;_29PQ*2NQVJn@Fb#ZL8QqRQc@@QvovByJ?8PA2`0 zT`xTId6%|y?9yOodvfpfmp{ae6SN>d z1>R3rS3LQ?xh!42d!@rct_wHRM4QuL!7$T!YrHKlf9rTs?z;Py8_o*i6nsJJ=bcW! zC^w4(6~$VdKo@=snA7-mFtP{8S>)I&$c*qn0#I4OB~ned2<=xm4=lrs>QX) zX{>OPOZo@SejQ_3=8(o1oX0rk-Y%cM6h5fOtNHawiqg>&cf^s1fQOHS8;49wl zsj>GMZNR!A>OIKsv3B5sKfc^+-wS=|1+n!oMwrSv`UKE9eacU-9A@1XnZ#{znu*ZJ zReY}XUG4v;yg-7{&;6VVsU=%e=Zpu$xXQLTmu^uRp$fD~1^kl1eo7?aH2cW2AX+w+ zdH+|!vP9>cC)b(ZpDOJ)y~mH_WPbaWkMXS)_dfd5DThnTW|q8O=hS+BZ{U_|lv%)1t53ZMkzhx@)in+plyd zooUB(o4bARk8*p>sc_Px{H*h6CDhUtu1E<_LiC8x7l=iV=y4ArqEXVrOPUvjC>!y- z{A3v{s~=+bVxr#ZHaqX=P&s;DJ)c8Bf(M^6Zv^@!34_o{A25>jU>p(eBk$(i=qO1n z`fNDi!*iiA{M{@`Ol4KmZr8Kgj1o_yP|(OMbQd)-@H4(9T3uwA$7X@EGL%!IQd=H6 zGF0o11yF{%uQIPF+^(BWB9a;d=A+)DTkO}lXoITxet4tPPmeGD9(Op=-@0PPv@hT6 z`0zUqzdmlU)c$zd&!GeB$|+bN#`Gt1~~@59iFRT^Wd08Ym^33%|ll1i%NtdVvJ zyT%@)d(4P-bX-ql&0)TBbfPflNSGHUv(&b&e8KV*K}90XIe1}HKF;bzlE;;V0AlRf z=M*4zctfKiVuxntpg1RsxP8oWrl0M5`{oP&g6nx46x)9_P!P2i+guMcRdv)xGzR#5 z#zRiSJjddlcYotarYD{^`>2aIIk3F8vSOhfWI%m6QR$aZjbNjTvCqDF?q%_V< zP9s}lU+s9^pR$AQ5=(6NKw>>8tf+WGsG1@Q#(EDx@GxMVG^+0E=P%>^FV=qOm|e4j z7D!*+UR@GeQ`}wpX@WgYU%s7}@3%(Y?DfaTT;IRET^ELlk~@48RlKck9doDsx^`t@ zVQ2QC4UxA!QT^K9{)1=X5lv*WK(BXjtLTt4j*+-v)^Y~tOihd=Hd6a?2hWLq|L=Fr z#zmz5K{K8^#>23QJ+N8maQ|G?uIoGu_q-hrb4VCNSQyZaQLq7>EccctkV&>sj@$SreTWNBN@L zg~OY6TiN(jNqwP<(hzU-5yq1aK`ESU@xP&jJDOeb_YNl~`T^8Lo62_qML zFs7GB=3aSy=pv5hH7?I{b*dxRSld_=$$k3{RgMFaBz>3+JBuCo5AGKDEABBSKbpJT z^1zB7nD&Lj-LO1N$Hq9cCe8 zio5Ba4#xR)o`=Isc`#jTzmI$bK0IxjRT8^!-!b)5_>kdfZm~Zhipwt%4y9e`@;YyZ z)Q$kPF?KMw!qebd+bARYftaR;nSI#d34A(bS$?pkshRUU`#hI$`mJRw_& zr0M|(!Uu;Ac|T5Y(#&uL`8643s2SYiX-c6YhCLe9jkmLL=AXY2@(v0ikoZ)azh|6y zjpAxU_#B9^I*S`_STfhNc6-lX{}XN7rUHeLOxyWBz7QUvLm%t#_v6I7-;^2uzA9M6 zcJ2}5+P62xQrX{pX+PO~vq@>=AhFxiVn`Ue(NcY%k4$r9AnYhM5h-fe02>1Xv zF!dVpqt#A%D%;8FBAvDazwmB*(VuN0zQ%3i`RO?(;1 z>bZWllrD5uQkT{Ja<3oxj!#}%uPA!YH`ji3do>$p&q;?-A!NCc8Vn|N@fzK5-;4*Q?R*FN3iVfML=dtyX?Ux)p2_HW~8iNlWBSF8+n={l>wt`&c5 zj7UkCAOxf-V}WGO7q$557N&CgSmZC__|SUx8d)8Tl{nqx<{r`pbHj}ywvdr6d> zBO&H3ri@ecLe`8e`V1kXaiX?TF#{=5&CMJMMiZuSPC4g_d=mMJroZhZdrdNPO@-Pt zuQ1>WN=9)tl5HevQt)88P2=G`ZXxe9-M**OUh$&4bZ%8SJs!XASJH{27&*N?)o$(d zhw)_CasGR|*grE_bNw5#c-39>`{q5pn!^(lh+Lk-0?W&d)B6~vLC zk$A*IIxy;H^yo5qaFSP6Xxp4yoWRp5=ZR=z%|}|EJ*SEczG9TI^^lZrm?e^@!-ZbF zKovQh^K>`W5n0y*oLfO@vIc0~9B|F0op{aiZiLx)+468{P1a{FTV1FYNxZ5Lm(3Yx zdCh&5Z{t|OoO;*?#VcB~ue|cePj_M18%=t<6OCkoiE>``<@+a}oLsW(#>_l5h}eX| zHv{YTZhaB%)D08*;>oe;P0mueep#K%Nfr;0^A$gMarG`HW3gUtX5;CAq?kG#O@+SD zb#O1kK$};Nf-mJ@`3ckPrC{{R8$R9p0^`yfrK`Hcqz|$lF-2Jqy!_#QGEK*g%NfjB z80l0Pr3=UhzPY(^IN_MHW|I+3WI^^Zn#gQKZhq8x0WDHfk2SuDlt|7G#mJ#U>d5P( z`?oAy^8865?xn{lGr3sEw!P;EBb6IkN$ zMQ{rWcu$=`QyrCP=29JJ|N50>_ow>PoX*EGR*HF4r>s6VUTVadmRFSSPWSskIoeuG zCd4kBju;mNul)YashsvMFRG)aVdkr!N~n=a=W?|xeZDl}YpdKAH2t&>B1I(~57=VU zVXkjSSEl0rp?7@FdGE(D>@Kf_LY1q||M`WEmrs=TbNhbN%`aDda~%6+9(9 z+iIP?+kI6|;XzJo`o^aR&%N^Kqg#sa`BQJ#r(5?OuI4TH{+ngrrUNhi<95yLTAh5C)rp%C7yk7+Q^>h9BmKznt-(>&5cJJ=|jXF z?!j55&chZv&%KOt1-hq8$zP^oHoLNe&3l)}(#A%gSli&!#=`~g z(Ya@DG<*U!G(JgftnQbUegbK%yw<} z8J%zU2m5Kkr)poHDeG<_tTEnoJM{F&m`=F!^>N=^{x)W^#AI;xH>X0QkjdDD%4PvK zj{X)(QZdKaM>`s-O~Fi#HseURA1REc6#RG0$TC~qGSm6cIaC}4TQ zJp43xa`VlJi)3eir0Le2IdZDfO}tj+Zn`vP(WX&Ikl9h~6Y7!BmC2eSsU&5LiAZ&v z{kMe1;dLxZa|;Gup=f}K6Ts~DCjytZ`;9cQVPAuJ@#dk>p<3fAX83;SaN=^QZ4QzH z0#3~m7-HoCwK>7{;OWiyY7wg}LydVPZKtW5X_Leby;QFn8L(`cZlVljb)LAfuICnU z&5|P-=TUnf8Lke;PzHq+9WZa2h69%#t_igz_EiZ796btN@@vAKG zrY1GFr4E{g` zr{#6)%f6BHxOWC3HAb39Mj011E;5(o&gB+KjD47*SVWtB#@WoD`A7R6KlK+BXF4!4 zUwmiGbx)Es-HIG)4mIMzgHHf~c6GPRx=5F`j}Mgl&}>wWULx0|6&Iy1?u+jiHYO=w zZB|U(8YWOSZ8SkiMF-tT1J)#bTmpM^=nQg+~0oYpQW??9n`li(9U@aPPc(^RBuOlde&9DBO#ATpQ?58Y)wpG0 z;24od|Dqlcx9(Ck!!b|1;6CF$_Z4h1L{3J9h8Z2T7NX83Kl@!XD&R0WDCGv!X+2*r zR^bXc6cy-D3YsXiT*pnmU9C@B;nU9YL~tT_oOAhHCmlSqOuXN@?mVm2^H&1v^T+=j zr-tq^;EL0_>^|q;aI~zWn%kIhO!hVFPuld=rgVEmOYyC|`l;QA>spf?%5$9eYm1+F z=4+nx#mBe6$E4%?kM%xxRIek`A9>8YLQIj`fd{aLJ>CpyPfHW9xnZuu@XXc=!id+g zpUo&JM^~=ocvFEZVD5cYx@jb1rDuEd>bl)Ni|y}op?$T7juveDAh%_L@5#*Ri_;u7 z-3&SP51513i-y;`(hG5G54ocrgG-fetPdh__mAO8H@I4>r-JU{Oamzy-)}#Bj{jd;V!_(XakwJGck(#vbcl<=jgj zj2-Mp$ebeubiMu?t~#Z!jX_k-zV}RC2c}yX*Es8jSHqys9Ak!}YqogU$;aO7a6^Tp z`z=^*>0epk8V2w)2o!$Lv``>55FP+8jhHD-q{=-xEQs6BKRaH0ePN7gFcC+3Ow1WQ zHui-a6OGf|WTcXVF^(}MVw+bOFozd zB;sToBOTclakW$YIMZ0Pxm*#MCQ+<1DrkTTi)6&i&Og5*cOCcg!lJ~EUcy;}ujC;~ zXqt%IQP1s@s)ID{#kxgjv`sHAlf`81!eAS`uXjf|p}2PS4qJD4;*rkmSxdIVn{D$& z)o7_&M}T3lp>Ew-0TdfB7UDoH4D+cQ?lp4OPzTbu%y*pM-o|}aoL9Xe8MzJG zmtCfUI#s7l)6C~F(t-Dsd)IyMLY-glu5x)~H#S%1MV?^avschv2_#F^Utn`#;%kv2wz8F|@QGn>Xt%}sz}%(; z)1{Z&$-eR{i<5UtZMrp$Z!U}KUGwe#fsu&*eGubt~)&KM+&G;lbu z#yEb-iAzp-Z6_Wbwo{Vm>e^ueHhPY*)mX=w)K= zZZ?V~$PDv9fI`#v5`uEv@;*X;g` zsf+XIe9GqLe%7@CoNe62#nq<$W&z$}%cr|PZn-?KeYx)*aQ zvnZVF9Zr6r(y{;4_oA8THpv%5}=X)0Kn$q#H9 zd;Hnk=lL$|XSJQD8?S5M&8NBl%wu5r`Y$BM_u;CY@NmNR*#uNIpbip}a&*i-MGv{K zuLDcxkHINprsrhvobtl(#o=f-<&jjgxuU9=d9clk56xdQ4&7uM>>v+aw4J!#35~3s z*Ixb7jdruJJWn1+yN+ztqRV=wS|{x1h74!v<`n_ndWQ9w)pg1#eO@$*WaAS zG1WjCLb|BJFfnpZN|jx0i(p(}{C^e0 z(!^ozzkaXRPYxnp`06Kg*sFCTZq=}kL)A78wS=Le@{1~k+u5Pw%h4~}bc|VNEcSxX zqz!-S`Qdq|SZSdjIospSPpia;u3;#$pIETUAnx_J}!Rva`A6D zju@R#CzX@P3G^=QanqiN*;ncmH$b{8f7&)~+Fr3$eGK?q@D%j&@ri@S;GB4D5i?n$ zG!3DRW~?a4srwAW&Y8kAj#xje2t=L}=sr9Ku`a6*`j@eEr} zgHG$j>BkpMVy9d<+Bvy1Zol%2fBDLH&B3Z0s!o{u0;CSG0d>0(%DTlRjLj}jD)PX? z-+*t$>#CB7qus{la|*PnT-7R-Q`F&V(^h3gvmh-8WrTI3mmC)#u8z}AOc57{5n8WT zmxn4dH12uv#fsKY(}%I<5gzc!AFym= zMRG?H8ZY3DHUUu5)p1#?!``1~;+x7&n1pP1uv@!I!!FTZ6y z`;Es>YkBDKuiqJXt%etvsY7f4%(~m3p!3pg>Z>W=yAI!_pY~`&#tvYL(wxtA-@OxF zJVC?JcXt1JSldo(VJQf#wf#!RBy3@^x%#5_erERBPQSb`zq>rTWpm56ZwY;4>vfL~ zOc*-aZG7M2PYe8Zi{87?SNHEa9fnew7gETqr|JY1l063$MfebQVxi@f1O@b7M#HXkVdt94dMqLU_I6{y@pzLo5h$0~6-A!pMCtn*M0l*Yg^utwY;o>cuv$1wlLJ9NIom~ZNKyS z^*#@L2j6+$vOPZC@iR}c&Tg=E14Tv z$i?@r zd;G&LUg@LcgXej1^r%SV&7o)@Z!wpHQ#2t#XcIt+bNM6g)lN65M{s!whrR#p1#`}C zW#nO)n^knB*vb0!-v9D-{d|}Gez#YTF7WW&U9SIf&WVP}uo(IHFWfb`)H8$Th=+=L z?;lOU@pi2ZcRTNevPwOwjK@MfmG~6F}m;fnMx9$bs*V?F3#XVjY%9}4gvbqH;pvy zd>$1|N9~Xf{1W%=TcBNLUsUsAou%<&XZSLau%Kn>K6=fU^uo`M|@`v>? z*V_FeZWB(JxuWVVEFboI=(TB2RKz0k9w#0ZJP+8l`}^v%AN7&i8^;69=uk}E>?HB{ z@D%%ZUu;iY_V@*=n4M#8)}c%U3CfE0-ulM3yUbcL!PDbY2racw^EL&~3_OTZI9vrj zVO#-~v!hf5lQttK5nv@s#8m0fr0`~5%u_H0e-vNPDy}SQZ(?~a;$1Unwqjpg&D$tk zujTA8)*1k9K$5>5&x)D6xgPOhz=Q^z3D*=**4d#;;^ft+4{yD4`wb_5zFV)Z#iR$S zgAc3@j_2NY(%I2;qs9dbd6Anppvh;&{krT~!?Wso;*CeXI=n#;V=nVN6}J@^$M>g> zaZ?d9`GX$k!<=-(?aw(**|m7L#4I9vtE%J4}-^CNVIvwd?QjM8I0}~HTu8Y^= z^^5GEUJtsg-Q~%#^JKqeH}k|a)b8dXXbw z6dUb$xjrsZ$|LfM@L_3ndpNBhy9X#3Rby?OJv%wpf~pPm)Qsv2sHM1xRo^qBYk zgdZmw+bNf~2EQ-L)>5ylvzj^DsBP3%!?N^vjCjZkC-)ye<#yAdwc%cJo4se-FZt!X z^WU%@xckxP_SdCyZeJl z=QkCd6>>Y-18Y0w&mU{q;B!wOI1IL@a^Nj><1h!i>|Z?9hn}OQ=h(5SEH3}eJ2YMB zj5cfJYgOAX@$Nkm`dZ`ZnJtcy9qArEG(uF8ubyjg>-(7T7>7NKZgT-)L;c$+n zV0BxeW8@rO@#L%io8pzs^!ht*`UP*W&rQdEtzKU@@$+jjXj2}gd~+9Gm#?jW2L?S? z=DyXG6zBv$3{1aZ_Qd_VIyP-LbW; zXtH<9;*B3=#}(1SzL~nQ;&Nf`Xdp>|W1v8S5WLqG+0Ly0>Fk6jFfAh;55?7vXjud06I`GUVPNHzj$GN2E>G*ZRRytIwqGn}B z&YBN(o}+)QXMw*%fmGhi_VX*P?mt>_pjM4Xzj@!#b(hXL+y>U?PxA9t{0oqdicN># z*FwRYeVq5dx!JJCH2o4-zqp?!zmn}Er`affy{zGRuSZk{2El%vll)Jc?7{Q zR5TDW4>Z2O3NWnMx6kMx-K0~QN9v_tX}9PsMoJTx>o2n}B2%!E0>Be7jkgGqw8%+w z^GAst(j4oP#ifrFo^A9li(4=B%Xs(*@Mg&Z+Y{$fO{?o`A1}QFa@OKvyf%8B!Tox) z6Vbdn6pv=TdUeS?n}?OH}WBE&V<9! z44dBKycw`8wGV(c02Kv|_fX45lH_%`DlwNHf79pqm--w(eq6n6r3=P!a&Ff!YAjc! z*$7tz(!6%9TJC4+sxIlHr)y@}&q4iuEP* zQO=H)yrqpC#|PV$SS7vTJ8T@|)!OM}xl`o0qpA*B>mO#dLYI{no2&O1)i!)zxfR;o zGVTw!<&b4{-OAB1AI_7wXBv6tt+5Q|QHKnMD&s(gtRB=TPL%X=NYS7Db| zG3I`UuG&nyqZ_-)lYBRi^Znjqfto-bSTUdp#K*k(>O{xFlV*iuh2a_cmDzovEA_q- zZ-n$0x9#+fTXugkCNE_TmG+$`A8V|7oB7j(fNK>6VrACE3pnFDpo6?K0pX zJp3DdlD3)Y^WX2AK_!b}MC!g{6orQ^S@x8}9IiHLMD#P%BA8*7g z15SU*o)ZDx@VJ*DGayQ|w?1`&FFm+^^s8dAi8}r9)$@$@#^sK|r1?#R2DPO+eAoVPZm3OcIz()S5zYi=+M?Z74R6KIswJ1 zk|ySCvQ@eM!Xpfm;deRK#7pLT9#Od>Dft-O*v58mc8FuR1|GxJ_gl>fTdrq~K9O54 zsk7h5U&>tnnw!3Rj=K)?)x!}Jkt$Bq)c{sO1GoX<0bbHX%<0)duqycI0Gy{(`nC33 z&YcP?v$!XDa$#v7uQs>xnXT=^njnebwADrx&NSx&{04 zb82;z*!tuR4C&X%(+W?MV+5NBwKYX3yvuA0hVC#ONvT0Z$_?fQQCKNt_h5 zw!};ki4L0(9@s%TG|uFG5;2jo@6xN9OE0~&nMMRGXRt!8ic~Ffp~&+q3?Pmv5tm+Z zt(C{a+(zBWbjknvhUHea=sCB)W7F&?`HU9FCgr6E&gAT|VQI0&HAS zQbfVJ1j7;v%~d+&oRT_*3OOWfj;El(H-!`M0p?&(FtWU0WKJ0@G7ytF7``Z3Yk-2% zH2@7DUJ_3REJ-@o4H|AXt<|G?XQxxe*oYZs952=BeentM|hp-y7$nm)ktyzw3K zeotNbsvbx4Xd;@hBww=wW41bw$$l$+HH;v7AT17Zb-y?Y#TQCq{i$fSw4}vhelDAN zuXg>1cMqNvA9zV+Wvy&Jn7ed`%Z*{}dU*^_ZWf2@A9OJ(x`x8Oyp%k- zEy-Nw;D%XYPFh*6ZJesVzu2h7Ntv}p)_PxCkwChm;7zNtFZOr z3C?cjD%WFP^OA&$ee&Pz!pVDds*%kT6ed)_7!u%V4%S61w97?etFH92WV3N&8*@ghs*R6X?Gu@b34EyLc;$;hy~ntEUYnZf zl(W3_A#p9|n#>tIw{9*d#tSP?@$32A|0d7!kA2$l(1l}0p{~NPhIuuwe&LqqGJ4eK zm0pB?h#79(9=F>snVu($tUI50^|yZHf6S-%p>rQ@!!v9Go@#9 z_z8`=q0Wgn#G7|^Z!mJfdH?%sE$n^KSVr=`TjfoL^ht=pxjCyfit}xnV${mC-hX-R zwB2T%4LqmAGxW?m{kYY6yK@axwPS;gb+%9}v=U&N_a59W>lR{|BxU%Xhegmr1s&&YncIO9zAW*+VPd4!L$;~Fl# zv_sFm^wLXd=0(jMG$NolV2GFSOn7za5k_LhJ*NKAJ>Nu+U_R1Y|KwKI?DXfXSNDWhp}Q7*SXeH zD%;0Q{H=|GUVPWh#!??qoF03Fy zEO_Ul`lFZg@h{13gu%iWsYx?UeTANP@TmMipuJa{jm z=H4|n?-rN$!Wjn_;G^w%)YIu7^f4Xd?m~z%(v4c4+P zkTp#)l~L>}uRhnx3>cT1mo{t$I9ftMX+U1xd;5{sdrjxA@0>lYuahvH)sKNhYU~^+ z%UO(opX&wl3p??kpU>yb1u#lPC7VTm;j&-v%O2R=JkUw!7u>BIqUMaP{oxaRpC|tl z`423cj;!OvBI=Uh8&*sJ;I(gEnb=_7g*PU?vWC0MM~cs^UNWqcY#VjJ*4K*TLX59& z9KkZL*XX!MohJ<&Hs?LoCcJ#z9xhY)VsUZkxENKovze4}KA1BlW|>=V2xy)UDQy{^ z_%LtrPvl+w55NBm|Nl>Cygpk-zpFEuyGC5of$jVku73M&Sa}a-rG0ZlUz@j@(VzbV z?%92ruWldVAL6BcX0ZbJX2y^;Hi5U{ru&L-sbcpskCt3hUSH}9 z!dE+t5>r-Y0Z@j0QD9$wl_lv?OW`|Ar`s{<0eI+Qrde|^6SZOo;nl@~5l?ws z(})pcV3{5)Vi=j+*&re$$@Zwks4)PrJz`OEv{Qxco8!ASkwjDD$cE?cK4zch?3-;L zL*3-WfG0HP2-G6RjY(ji1}17yau?VRhwB=jv6L+y_+>12=MN-zJ#-Sm)ip5~6&Q^G zsEIvNqXk^vVXWpsO3rgDVRnr-K713i4_C%Fxk;rWxD$jyiINk8=!i^{>7ze-i`Lraw0h2V^L4$a*g5l}L2bMChH;*&@?A{gLAA2`h7#HkG>aTM5=>l&a? z9MPwUZU7GA%8=fc1oE!T>SQeco>XgomC{<|XJX3urFpuivB zPes#pRWhR3qo5Q~>ky@Ykq!uvPQe2s_bAfq^`2+P2gYPc zb2wgxV$fHjCUP^{OPA?z+V*LdtIcp*Pt$_PT$fkMr;Kq+e$D#l*&=fK^InV{h7k8rc8@m{iV>Z9NZhO}J=$Sf1EzavRXy zLrztUN!mu%#vG$&(Bu8w`3+A7MG0_dJwht>xufwymx=b=@tL>z*}l*hxx)+MyL107 zN~dTpt9#6qm-R=#`YSj4OI(>$TD95xT|#@F9jlM{&f`(OyN~pdCw-?ZnHZD$5ds7R zBU7gaWw2Zw7tuVgY-r}wpH1~qc*UZL!{w%qK5*0qRg^FJC8nTSA;KuFL%WRTxykYQ zf#YI}xM<8*>I2_PEN}nRt{+wmiRz`=VNGV4guD)eT99rdM-B;Po;PF03%Jk0keQ%0 zJLVf`Tj(%-^CkB0@=Sk!pZ5ReEq;%sOBX~F`e=i@0?^jaJ9J;ufd?Um8i@n;hNiV2LN#djt1ao3B< z*VP-MEK(eTnqii;h9;u8F;2=0YJTnxF=jBthiS%A%t|T zMl%IVn4W-FZD-Q3Ob?#mbTWp76meIK2CN1FXgpK~q@~mJilO0H#ghW5?zZ|2{DR?^ zE~_q0d^j9y=#?jB)j2ZshgcQ{tk1#0Se^O3HZoHTe7L6Xb}AjYeCsX#?)x<_Tw}Vc zVz>8uRpQ*LqvN|?=NEWQfRHs*u@Y1F9B%Sc!+8u4dHT|%7$jQIWbN=5w;|mv$T>np;NBbLY|89PYWz1yr8t?)2 zffY*w^BMVu2tbkm)n34G8QP(QxmfTR<74!}h?O!_nCc2*Vd@jW1|N#UqUa~bO?e$Q z)=CRtfcK~kn57;Z&_LrS1HSRB7#z4JQwBX?0l+t$T?b&7oZ;4U)aPehQgDYZD2H>s zOIXX0gG*-_sWMr~%<$kHK=4Aq+Dl;{3Av)P&;i`}W$yfWZuiCB{`s7Cx4q4nIroF4 z?d;dvTb+G1#bzD);+1=9PkG^M=eo%gZumiO@Ex!HXrsYWjHfZuF?mi7Bmi(Z;4Fe3 zq`&R)6K7t;>S_)ar7722aD1)Wp=g(-Yl%@FN~3+f)s@^1eb^`r%JWP4>N>u%p`W;0 zjaCiYAlYk~nvBG47MBa(y3vs%G$RM*L8)2WqwG0m;bOc|^SJ_{@w9d2Ih?fJMegwX zkLUdt`U3xIZ~ON=>z2v*$~Hz4Qq4^-_4Brj5a0U7H+aRBwOe=TdIHANMvryi!D_MQ zI#+#^cl*d6lgPmGULCAWH29N<@0v?i{3oDIA2ap&CAzcWV znRsJ81O$i~Ci-gvX-H&&EC9FGl|aIa*JCswO#$2rDR^)SMiUsNHGAwaTpN+5!|;W{ zFk2C`v*wxZ-i{o$;92=$ z5?vc@+4C`)aO-Qf#glK@X3Og!Umlz{D&RH{m`ixC0eAwIhpg)wy$pc@N(l=w%WNcR zo!#ItrX$c0Z-5k17_D7uB2Gd886tQl053BlRmD84(G6m>5zc0Wh{J_vWb-t0G1IWP zo;`#ZvWy}|2i5(!Ry-xaN!*#T9L=H~4SgH*#y=Th_kz z)Gb!qd?lgs9xlPNvMHEpIVH4IxD5xi1Q5bprf7yt1}54vPg2Ill<0Gr01E<4K0U;? zfHj=tjI#<;@Y+;p-ZVOI<_w78X?-<;!eb*xz>E3`q>6V+gJmm-7(ujQa3VMXOxgyI zAIlggy45*N6QdgQJjBL1BIZ*+sSX~-GjD|0#sgICi9v_YVXelQF{cE}(?aMhdX{f< z=ewNttS3L`&adZ`i#spfy^-wnIDPRFvqa47&;u6X*35~s*G6x!^t#7f=Mjq!mSwk} z9K3XdbA190RE~+zWVAV?rPaEe(K^?C`9mUC@tq$D{hcbUZy0LD@o{Wgac04XIuukx zRQ{aSOS$Z0Q!XnXbMWKnCZ%-s&{58|;z~cagO9r!zOx;!7v>|0m8QxTb^Y8v%kg=A zqjMMYa^2IArl*U-f!gzh*^zy5*v?HGd{fb?-*8FC7FId+=X3Js?7VY~`MD7@k)U~y zA`N&eZOn>Ua552X)$dC$oi8#coILRcUh5;oHJ8rklGg;MWOHDhAha0qTp6E)`4WkE zobI3PtMB~Itq0<+KV?`o@3n|n2gTNutA}c~E9J7Z!_q3KiJa)Ar<8|_UhbVI>&P-+ zkWuMF9+;GBwfJK04~ca~wn)2aI9^ANW_NEb4s2`!RH5u3efE_jvar%s_`3dkx991y zQVD@nBd$Xwaw`EI0ooVRh_LJdgH&P_R-tATfY6AjERfMK9|-tN4OoXY2nIk{aAA2O z1y@@2S$G!Gh(o53aZMosQ$hj){nncj(lR}m89B!RkE#T7?hK@pDs=E5j4!T6+z2>-^a-ZjV=usvzJ;x3rE*MP$@PGpW z=3s~^K2($Od%MOmEPB(|d{?_pc9eTD)ApIY_w%m5BfL_npCwt-(2nm;O12E!iY@Bd z1isI>vtVQnjo>ys9lf@8c6_Uz?hRh>1jfaY& zX3L7&@}#%B_gFqW>hLt7GOr*Q%$bm(B6+@FwP|9N5GD?7nR*Fc#ic;NgL()K9aB0A zVM0Y4oCF-e!SsnogC}^R$CDB5Az9yGGC&K%hQkEkdJhPgH=C%W9y}2I*gFkQ1QdeP zX2=*q9|*@-F+&(M0H>f?8sqN!q-a%nUgLaUXlJUvVo%xkd0OILL%gk2wQY?DeDE}Y zhhZ~jh~Y6w%YC3mBS%-e4D>Iaan9+N?7Zt4Z#{G43}m^xOoDdV-|1JlLv9~#8#z8b zF}rw!?Okp|)rBKyalQahM}|=b-vog-SAO3v%ALPrH7BphYUolo?P?u1KD4Je@gIhR za<%7g0uujJts34?g#DS8@@4O^9(ROkU$3^6#zFPwvR#&DQwNhB%Vj;|=6y0^k{5DZ z#;mpZesRgS?l-jSB8qEl3}c_ERJvRot$OoSgZEfIX7w70=YxPu+}Ir`)^N@#Y=d+%5A>!|U^Faynl7wk>r!Jz(t+ z9H!d1@l{y|CaBwxdC~g92k^N0a(I1<3-`M(tk(^YcAgyxx=O4o zAmPBu27pc?0IQ&&kxGFGfGUSjFi`;~(9n1Q7ZeX)Bs>$ym_Qs2 z0MZZu;vPI<)munO)Hfm_Ef?8-8XOle;Z>vF@{o`&gl7RjJUtp1myyVzUOj;cbJRQ5 zja&&Lp!wj`q^MIj%#X6jjOo66x#uwk8NsP4t+b#lSSddMt zq7H)`f-uk!G9PF>1OV_7fD|5J0)Vt6gb6@!LM9r8X9^E80k4`sGBe2Y8cl%)0myo< z7USX`Oh^GwnA=$OfLRUGbJKtocTwv)9-#+=VOHlQ#byeWm_G|`&kVO@$kIQ7x7$|G zmwt1~2hv5s!?}{_ItK1tu6B%Dw8K2-LWh~QQjf@YP0~3T-WWHMdO|s%EmW>O8W7Z>kFb_{dpRgV(hU5GZdVMIhc9_SAhp-P&sOBd`!?G5Wv(i!X!l;S*y&) z;GBZXNCm)aXbGONlZ-}+=n>>P9KZ9?We%8%IWSj~tOH!y%SYyef)5^?(iJc|USV-?(L`-{re$=c41xf@`R{}wuiRrr0 zrx^CT8u3^7OWZ%Ds_>ReSUKLX}RssE)JznZaPY}4_|+6 z+i_6{ELtC>*rwMPO2yOHpY*d|mkE>V!m>_B3yEvt=`zQ>S)Qirl6CgTMt$zOeGV?Y zjR9s=)tGM$COJ5o@fSQE2$fq&als>`cx1p7<7`G-g_I5@xDQa}$SmxveuW zf7qukKGIM%v3wqUk&dqfT2Hz+ACA|lc#sS~e7*dTtLajGQ3ugJxs6y-b@f!oyxpe5 z3-_keJ;}1BG>i0M3(Mtx5B=tpv~BW7&Si|4yxz?4@>a#=;`o?~eZm-GnK|9pHWPX?S=rvLAN5ik$JoE8`MQ#mHIJ+>o`JCCO&Kqk46bDXw9JE3LkBRvm*4jcUv zRZ5AL7AF(TN6-SJfHVT2xTb`J_}~L9AeOL%ghU!95Q{?sru0x3he&(E1K?Uxz%_vk z(x!*)0W?4~iVREuahNg<31LFI@Jzt*FdB#h8SjpdcSdG)dr#l`vPEh8WQVfN$W7t? z$wi&>?&{Gld%k(l^wt*xo1@W_q{XQn&dKPl8>Nju>_VTb10%OU){dLfaAi%MV_w0r zF{iF-TB+*k{@PJC90-}W0M4xkLlwnM++a=zP9}3JH9^s~Vgl=sduKJQVTjcp)?pp= zn6eIYXt6Nt$$K}Rzo&4-sX3S7a5#+gtBP-N9&;5xWt6#oE;7!%@GWjB zvM*x`%y}If1GIR&C-+rwq)Mb@o{TDmR-JD+>eD=}b4hIFMk5_$F#j*~A zo24Cnbk4SThRwXxwXTqD1{L1J6pa##6H9`Uu-++D1b}@N6{}=i!UX4X@hJirO-ra6 z%uB69%L^>GMx5fjf&f!#cJ1^^Ghtrq{3l+ApW54n*EX(JxH|Z#^um9Zy0V5?916vK z!DyQs#$^~mS*^y=hzRs2yyXLvp8Ck;!kEynO!|9x%l@qnSIVB_t0{^YvW_vvoVD)_ z9pmN*jWe)F;$jA-4CWfplmuVD6MZ&C+nvvLii0+Vf)D~1pn{D_7wkrc3{MIs1QHaK zgd9K!gQ)05fP^ZSU_zM?dzUm7D9q`F8 z+lVoXnTW}`+PsakQ^0byT(kOoyCj{`m1*sikb}V=|559rm46|T1k&L0XL4_Fd@ik0GzqQ3!=a5P|tn$*wo2{z$ zs@b@FP_qH^&>Epqyx|}OKqo_`!7=BkhcxG?yCMro1@aajF*Y(+9stfaoB;~O03H}) z2rXWU;E^@-tH&!ZcTE>Re0`vbw{iJy?avE}@`1PDqQ z%*u)7vW%wM)hE5lT$5I= z>ajc^EsZh)f?=($AP5J!#7#>byEdi5Dg!k@qyWe%i=h&ad*<8*TLVq=&)43rfs3$6|Ll^~bAnfY`*#RocA!`89t%``j_42i=Q z6jo6QxE^U9RTWS}WI~_-0752^P^%Qm5C{PyxH904+NfmWC51M4ncziJU`e1JE?{{H z&yo;nc;;v;o`&n0GMPrbM6$v1#=&we#1WGwtTcoNq=D)b;3nM+kZb@NfYJT2JR~4v z;t_ygV*|WTSp~<+s^-(#`=mSWZScIMoB1)ytZ~9(%)2~^VKRnqy?23 z3FL}5SC>)?0l#E`q`JZLJpibMNlC+lr~*K%xCRZ5v1iT-fYY-jy?QTc#sCQmNcAI= zrW78`82YKMcU%t^;^_vnTDX=6*TT3)2d-5Qcrt0m;%Q{+0h#nn>B13c089_U&yLS( zbM@&k-Z1Zlo6d{Rp+cUd%SGGn2HN3kq)Rw{-W<(kB9_az@H_7_CCo7gZtnz~SvC(D zG_KTD@U>t$w!fto8qujg>eS`Me(ykCjR!g)l`8lPQ}qH|k}C%La8q;S0p}YRME#^9 z3t*VS8jO`T3~L82H?P<_Od7^&=Rf|!L1A*>t;`=70aR7OS@1xmRpr%;sT5t|ffb#>0j7@6i!9p&Uc}8jQ5WGV$xIgIFGFCpG1QR>%NymX6tv%<| z`7-O(Wh|o$UKF3-mmA~>!L_wvtvUQbh`bF%l6^qqIO?!VyrwyFU^<$WG~fYd;tL0x<6*_C@&PsKwRxsA-hv!cd^y3hCE3xxIa>wA>Vi~3hP8(zJC zzRoXY)%VxYLJoWv%^0V<6F&C=N@LqxzVY)T^M(U(+JTU;9?gUKfaT-?Pu6?cllj8r zG7+Dn%rvjv1=3Y4X^yZrydyd&P)7(QY7vA%s!#w5aDh+?z=epR0l46y9n;`3vY-gV zv;v1H&;YmqOqnr(v^CmFyNFw^uwoLX3o*$=JOk7tRSnatu209)@PIh1R0j2qC+jW5 z0o&?N9ngS&8%$(OR~&;XYn(tZ6mO7wYdgVmm$Mo2{Wa&{a=(px&dc!X@H0IJuOjVZ z-9FBfb6@be%NH*qF6uq|9so3W2e8ir*a{9twJXF+1*;8}4O5a@pp2TlWK=y&4t^2^_vw&=yox~9{Tg}kLX9Ubzz zjhv3YNatI{{Z-8I_C9NGhsPE##ut|IL({Lw3qJi4qXD1ag( z;RstmF&tjKH<)`59xx9R^R0TT9V*~gz``U&(@=A+DFK6vT2y6V2BrSbFlAc-qe1vrMU+7_Gu4nRR01REgtT9cAHkM68 zF2x7V&AXLz_~EiqE2`1s6y>}9Y! z)2EKfF=4Gw<0eI{_sAFN4c@0r5Nc60FI#^8T#B;~QpkS0NLNpnvD#&>QMYEe-xl+s z-WbMk&$uj;&wtApaBrD393DS=$yKt}OK#4&Ih$rx?HcV_@fwa3viJLt3MM&~ zK|0{@c5dKCd|vb`r!#Di1N<;DV+v<5T3%&_LP!z&oah8t1waBJ01OK3gCpS4cHK9m zo{)i{Oe+B45m+V~vH;SEBaJwKT6k`Ms!ddTsD}%{@uWxt@GMsX2>>RlA!2dB158*D z9sr02j84N!yKy`fxBx9E)G!BcxEyl&=}-6X%KD)k4z`j%->hdv&94`z@!)=LN)-Eh zSbY8&<{24-hz8a{!JIZQ-EUzCLda1Upifl+tOtTbB7sqm5b&x1jW7U$gZ<#afISeL zK&wE#1hRneq;O$o1Y%Y{0`MSWNeLh(%uGEj^w$L}7qDDZn{WYm5+V)DjRRRA?F#8x z7;Q=m!h{DR={gIC#lqUeBzU@g*2tKZ7%Cx4EHYd!V~~3NU|L>U6~l0}P6YGL?-`53 zTGGU}eBM4?7I?;xLYd+PxT1yHyQ3Zoyc^Yk#fb&2a~z-!0`!63Tjw@GYkTGQrsM#s zP&a2%X=`MVSL^Qn|689Ox*51n0&qta_~9_bBZQtA9y&N)1qawZX=ANAPlCXX8&4J z!OB@lelg|6BQ85q0Mqg)MR61`fd=rnCampKJ^BYvmvK`0OjFf)LY8Jl#mw(k|j5Xlq&Q(AN+LF`apdMH;$PnLz#v?9} z;qsPtY%&Qbg*wD@z^N-difa{Qf`K}G(6VdLQ3n46)oomDJiNX8AGWTAKFYcCqgO6p zJ>m@$NA9Rz3q9~^mTFFj;UkNKC;)L0p=`nsDsw2t7#w9l%}Gn1df@`na$#1d zT>zs!2U27K;Yru@gk?4ecvb^MKPEB_NTx+~8Xf@D1MxI0(EtwsOiOt2v^4vd1w6Ro zsZ5w)9)&=gny(vI;}qY#x83FZw4O)t%Sd1NC;H6xLFCv58Knb0zzVsIEh*t< z0gSfb0@*n1{>0+2AZ$yz5bzwh!tx;C0jNiRC!yZ-fO@YQa9rRK#Q`82hjc9y{Wi+# zdJtB0q%PzAWZY0a?&a*Ti;<46xO+3<7xu+%Pp)}4IuzG621`zcJTKC#V>k@hZZ$vj z@C3?0Fu?}$0RVM)HIg_Fo(icI$5G3z$;OTVU}BiHk@rt{sAiFGKQFs=eqlj#onI`ChPKhU&B1%`8;^maIuB$m z5&(5A1WyA(sbM_eks8bkdlW07!ZAb$8dfz?`fDut-m1NtGn%0s-pAd;5SDVedYj#L z_||91Z{sQlc))x>)gp@GFtVB^fZGogh_@WlII$5p;{g7Ejb=7N3aB6l98^Fc8!*%m zut-h3*Gh|=ReT9Yb@fG+BM4;0QaOD>G%uS;D4$4|FD-j1>{iS@>( zF>Ed;RG0Tp@1?Zmhi2T@#FDEa)VNT!jf3I4eFJjuWagtadki8S*0>A|m)YJze($e; zk(WT_+cr;g0Bs!V8IoiBCj|VQK*;<6D=XD8aE>V}HaRY*a0eLlF!d?{X8pv*`^sF!F)g`Ec06 zN7uk4da<>~aHu0r0CWL(0XmvxL=()Rfk-EXWFG)TKqNH;Fs8x0Onr?52;%Vp8U#m+ za2)_HJPUCbE<6jC0ARX}u+jqNXuy*KUc!TfoA6*kNDpva0C6A<3-L7I0YZwjK%*=0 z`Zc672G>ysb1FmA8o=s95Bs*8K%=N~J5pYa+X$0-tsLKGzQ}#I;bLpq0*wd9n1R-* z1P}%l@+wjf&;Y6kaJ>+KD`j}t=Vk>7_-250s9W$dp#^|?0BOL2kRD)qa3KZi&QIWB|BuAz-1uX~2Sue$yi3N+GROf5tjRu0CTL%ypWE+(Iqf;iAbS zNhhNtC+AI$Ja{(w`e$%D+QZQ?DBX-T;LebTjEVW<=kjV5{5k|yG@oO-VL5;f(KK&{ z`==uc)aez53rg^SM;t3IU-(I$3qwy0cnG-QqyhzwVM@qQTN`M{ZIz`a?Z&VGK_*N? z|J%R!p-UquN6bTDT(!zd)NB}irKX~7?ZLNzHewjQRa)?FY`KY3%;6>2xWy)ZngSsTYrgJr@uzbBe(2M(L*^ZhfTa8yQr3vcTKfS+BMVx2*2WER~ zey(X4mnM-frXSpX-7L&`x=Ws z9IJ2Mys!6j$(1$^O2=}PF*9^XndbpB4e5)&0`1ml)xH|$^_@@N{dkEpA1T<)8X2ni z(|oVHuh_P2Bf%j8nF%&-8O$-psA#a#@j33{Nu`I+!B{EN4kUI40XkZugOHtZ5Nr>% zeK10mJ%a+SX9NtYqACEALgR7dOo4!S@ToK)n9l&fbO8^d+J&0|U_k(8gJ;2ni#SY3 z0BNAw#kdSC7l^|%(VsK`3;lW!(nZ4Z8v0|Uq}iXWnxcrU19(zT4eC(nu)R(+t$MoS zZs`uMM*ll2^E_Q-~8r|1|~DV*0(q972-Wb5)hve8Er{3uTH4M3Ty1JBt^y@m{+RXiBrK-VD& z%rCg-uJ?Yr-jKBpPYCd&ff*4xRPgP=3cxeh`5eFi?<#M2+!$@aX_ktDxeNrX4d4L} zU$ z(EGUuu$IbWhL_+|$N|NK0*{SgRsuSmG0G{66~Lo#6r%(qI03^HHz-$sS(6ZtayWN_ zUt=$ATz2FrtX2zRql@$On|8HNS_JF;-Cyp^;ukNqRJyI!@7T8X>vXtpzM2nT-9)39 z7v20E2)#&%Hj@2=ix=swuo~k^zT3%&cFetp(r;PayYzGKRGwi!{h$8l7XTbwp_R5% z_5+xQB$#64;viJuT>wxp^X%CL21igh#X+hb)K3HI$PI{3cLb(a|9xexi**ph;viN( zudHmPwuW=}aVYq>sB|MgFzzdy9UhhQ=3Pgr#&S4nyU6p&ZFVrbTSl6y4d1+d&3Ap8 zYSG~(mKeG0HI;fZ@2a7$BW;Fww20v3gXE|GOgj1v1_TEXs2f(?2zZAIRdA+pkQR(L zxYx)Ura_<}o&lx?gzOSG2s8zS6mzObNB{*SbId7Z5=1ZoXC;Bapl`r~OdxpFV0jP? zK)nUeM8DD%mIuoO;94#uOaO>m9^k?RGWCc98Pf%TTGN9INE5Ko0AT`FgV&GaHYTJA z0Y5>3At>aeL0LK9-)qKWu-parg|k^3oE@F26VW4Y9$mvFeSl71^TD;}@RF!y!W0D< zA~a+H1jcjb8!itRjRpb0&3uUjfl*Zf$RtocAe2TGIJ~YP?9m6H86fzW69-%XNLz8h zg(pSQ1Q0_Uo+(8JfawYgkN~`t1wabdgyl8@CcGrvxCNx)0*E7R0j>oP;t^m3-WY4- z%EvetQSPG-($Uo*$IRj}c~=|ek$>nfE)}z~QrQ_ppSdMo)e)CL6Th>8g4}$qC z7<2$t0auuJU)`}Bt%n8s)#Di)u4*YbVXn(}=Z{&*c{McV08C*ZD0nObgE52J%CG^0 zNeKZoKZ$$pxaZR>nq?(1CjkZa01+7M-GlkY;RjgJ03XCF7CcVCYA79=f*Hct1L83V z9()RdfoG30b*vbsIS?4c$uJyF<<-gCF|P|Wy`e927MG3Y^UGP$gObA7JeU$7cpOJ9 z$M%DR!b3Hq1ZWb0aybT&Q(j&ScwmeHL&R9&X0>~Ny0Lz?f!x}e=G6lWZa-h38W~d_ zu1+ZJdRJHd`lWq!s9w3kWwFwBhIb3kR&AnsT)xBz)uk~peBXRM5cy{M&PT3EE}q`L ztybHMvG;FEn_?<-j>Ly~7g9rUe;U}o`2KZ&hDZXA02MaGZ=h2~1W+45CPt}43#=## z0q_Cv>QHj<6fjWFLA|pT&;gsN^avbSOSrmN3o%>!`Xd#=%2`37JY2q1j|f*kW;9!S zTIAaA-tqxQ`M%k3O7|0nVSgN}bm_GHUjDZ0b51n7V(trWk10}kn3DZEs6;qAydbBO zQ~Zv;=ptO8fhm~5oG`HAbYofp`7t&yhc&}}!IG<)>SOdMhZ7XB3ka+$3N~JV6Q}Ju z?EW0@swVT=X~D0C)h> zfI!>>oEQWkWZH6t^h|h3G}HlbCHgTTGxY!j#i0gkE9}vc!n`*VZPF=<8fL=Spgm3F z`JpyPi;4B^99J&yk6sL)OH34pQ(W-cWMTvugNH`QQwXsS9$iHswSW#+psNNyX$;m_ltp zWcW3|HNwX@D9&L9*==Haa*T1~0C_-$zs7@A0vSw|{nIOO28^h!rUFM@xx8^-9RV1k z1iWKV(r@5D^uUJ`rS7AZ8Gwfd0xLVD-BYVL1@Nwcgv5}fd;G+`cii*9u&hkZVVEZG zZJC_FAdnk8*{CxK1$BdNq(-Yv#8s$F_{)7e3qrCky}zt0Z^oi_!5Z-R$v?95U5K<#Pw z!xShKpu|9N%yKBn0l|u@F4w^2>Hx!3EQhIB;5O?G2ONDF2Sz!<-Sr`zqXnaYC7LZ}B=8uWiURZfE3!P32td^DB=;YIoP`C>a9CB{uToEHrCEKeQ+o^wOIAiD5{?=|m^L;_&s%{;2j zFjo`O92&?C7!m{wP;fY)0X#Uc{b0Bp#d(fG(_}4mhIg#c7grC6R~*N&v9Wl$BeK30 z3vvR^ece$mbE!{+mpkcY-7Fc)gbRlv76nNt>oM|T-u*$pSFZ(i?C#r3 zH6PTtw;hGCUFAK*c*jS+usQsN{jTo%!Rdu)@F71&WCV|Mf!qOv{di}!_y#dPCoCdc z<-=LYBf!8DPy-0{iF66YA%PG^MMfhMC;&!bPK^i!poW)a03UGR6=MuW2?0QOkT4-( z0$DeXcp9F7XTgPKNmhgc3Mp!OH8ki(p(~>SS!gMXbg9(`u zn1VP2Z3$U`Hb=1`kk8-m^4#@iPS>w7M%l`^d$s4-W_Y?<+&rFbH;>M9|L)K)vd^CV z(QkMg4Xr5SV?G4>zyl3JC>wkLFeNF>7DP1XIAIv&7wx2nuR<4Nn(=PbvQFm~HQeP-wNVq_>;Je-!^Q}mM>64D zA-pPcJ+Dv@S#!3-$$fz&g>_&K)e26H8UlEz0_31jxA>Y`Xf?x}s>|@r*!#+NXn(tR zzdljZsX8Uk0nZqNIXHpff#8AKc_Qj^6C2Yyw7rz|#M`@^{`vF04=nb1;q<7(GM*zhU03aY@VC>N#cOZTkzO*+j z`UeBwtX`Vra&GFf-i$%&3vlb7-v-`^NI{}9e!r%m- zp#uTkfMI}&clyYM>;Vsl@3$kx32zA4$~qLua5|bPAbe;86aW!HCy1aiC}03+AW%Yq z2V4LpYv7omhCmk3Zw9Ufh&1}|dZr}-z_kRtIA8--0v_PTfp`}E#sL!&9^e8Z19-v( zEZ9j`qRs=-h^Jsp18WdUC53g!0bc_C@cPouz6$a*%tmjK29vOh=z4M4mfpb;h{ObGxc`YR*=SZTNbBw#{9!1N?M!2BB2#OZ8l zt|w6=OOCm?6Ff0RGsHx+(V7m_?4QnO&to9(J9NZ`j5z4qgSYqo?E@ACsG{p8r|gIh z40w2PmLq8;JRaWUAg3UwbFD^}clcqx_s!lGr7f4UiBbN{PMwWBQB50A%O)g<*$_R)*TneRWeHTwNx+J6QA(x1*J(HEQ69a}-148%XOe;R4V zK02jQDhrR2Hy2T4eh!_d-LZTy;++&SrTpO+T?*@ zL=!A+7~x_V+ZrKqSU!h$0WF4QK1Z+u12NQyOD9V>Sz>l8#Z-TbMW9*kzfrEu=JMtL z!C%9Bz|rPRPFb!uayeZLUaWMmk8Jm^LtgvSo1fZv)>_ufIJgMPhn*X*3%=h%x_Gy} zo;(Sz7BnnR&DHQTTue`&JTTCQ3uo9aaY4E;8o!2#ucF-a^TDHOy#Giq6#K@59&HA8 zhoPC-dH)0dkpIEYe{y+_^$WnGZELmdlAQGNV4?<6<*Xt5ZkRb9cI5fF_AL6^?KyY5 z+91`1f*&e>XDY~ot8w}8!gcL~RGT-`#d7)K^iSq;Lb;rZ3j|O9ai5!G+jRzyY47Taf(vW|x8l3Yz;WSg zNteHUY^0*Ox;U_cp2d9Ro`(xq&1v<-;!%1kN#(40xqVQ)7<}o=-^;(fZswi)xBF!} zKECC#v7(9a?BSeY7_0yu#5M&kFwPk5bomUayoX3$!N-wy{RQfgr^p_5r%-0iK_V=%jLOV$6rqtHrv>c8Rj4Cde;2s?-fQf%fFco zVusHN!vUEt{xc(NGi~GlGyh2pC5ci7nJ&gejmYPHieK3G`t0e8*^Tmy$YCv}zPRrs z{owS16(>ddz^tq9^Xc91`Aps}b6m8f#+YOpgDP3q&8Uz$d@wZ~VuVut*o8Z%YOs6SfsM&YMznFq6+$5^wN{=I>p+THyaD76=`|8=FUbO!wruZB1>krRmp8wam(csvU2-EVrjlhEP8AYZ^pQsiGMH#F|F6F+UHG^zBYn5#bTAjB;FS7LV#UJ{2^=FIkJu&8Z=5;>3bswAQ)J^hR zcf%3Z_Sld)5>gQ*5x{I8C&cK*(L8(M)}|{=xX{ltD>z2YJzrZy$ZAL_Z!Kd&e^_9eWJMu1vt>5a+#bwbOHE$YRVre2_oa zF(@AN*m-z+wP%n=K9L^OHg2X5H+*0|4$?MW%%Q_HH?CEYX5GAOuWPCWzF8B~M#PBE zz8CAlX71|H*v`H`c<%M0{lKsMeSM#=eJ1<%{YM%zx=b%CmlLN;6}L^DxvA;D4Y|V0 zO6d78%NNbvl0s?ZYMWmK(zYgs5tnFBEREQ_nON0ESm9)_LSN$j(`XBamg`cXZCBFp zl(DEpD;B<1v!t@^_?B<}hkniblb=p616?H7ER0o`{9RU7mL3)dC%sa|1hlvPy!Gk6 z=bbNnRAeVTf>~(@p0XTSeyc+|iK*K@KHnk5T^j;{bSW!KrlJfX6@Toh9WIyY3rV38 z{*C|K@+bHge$CnU=Z!h+ zcpX?@esC(I&IfLt1f86x9PGK>w*9tc>iC6y)j$3o|K9ifpZp)5@vX=`t-?wr`J_l# zT3s#ubX_hdgoeYg!?MFMRBy-&)*J2bNYvjK=8gPV{$q%9uyEn9-5eC@a*Q`$&wyJ) zZRQ{x9M2iw>)-U{Z}y&bUB-J%Vbo5SYD;>ue(_M)%6W30ZWF?docIp!E&ScvzdQao zpZ4V-7mGW`Mys?Yo_|}kxA#T5xHOwK9Tuxii+2`6vp$Up3FYsejCFGe3|*9^-B|x$ zbDO~O?_ytH|DPZE-y#1Q?ajxCx{xlGZF+M86L4CAf2nIc{^9Pg^n3bVUv+<*$@Q{- zZhmfrr?+Dy)wh+JN7T7BeU^3f?E7;J)`q}#^ZPSF6ULp5zV_~+ma8L69Sv`?-}YJ5_7?G@{@b^o@pC?3=*`jgp=%S#$K`t&T-oJuCYMB4Vbnu4ghaVQm+ev~vMl6j^@=X6v7FR; z(f{@D^PFo-rw&5Hx5iq(r}kO4VcUi!dctyfo#Em?9Gh`SUC&JSWLLH;CG0NWY}@w! z#`XENR4StJjDN@KaX7^#6SL^r+|Y%a9}Yv%t`>c17+bu^?@#9g^8@d{V8um4n-7-U zG|Mw%!jFb_cp1>S%go88ZE^L#__x)$xt%y{$2MF}u~biFOyL%z3CZf^;<@-7+;^Ar zz7rvhz$|&_GR=w$gDXrmgk>sqV36U7SmcF^H78c8m0~ts7Ux zaGuK1%4^8JT<=cJ^^K9f$SZ@k1c%y}L!}kkP|`5NOWUQ)@`757&;7eU&VT;9+n?`T zeX?ApZ(QQtj*Nx2UA-3CYQ3;q@!f0n(!}`T|LTk}e)MlczcpAl*GC7`qT$ZKL0au~ z;RC)&#fKk!_i1}<*7)~$fuy8~%1dp8)z9Vn-~O+ERpfK-1-iUp*39YMYJ^>@hl1W~ zn6sq2I}woy!BvH(nwE=ks}qqmX&bCp>XLO~eSQa11kyeWd9f|`RSs_--JT`$qbz1t zw|GW723U!Jwte`EE**Nu3ztXE3+fKgKhXo~Fstf*cs?0!i;+Gq&J|2X8U7j;*BVmk zhi!9zln?3C$xAWv9sf7IvaR6~|R;`zMJrSI{>A>MtCSPMbs#o{d)kqhqq z);EW9VZ0q)f3r=GaG1wh-CRy1su>|`)ymCoNxep|Fl{+nvm_S^0)09zq;0?N7n&9C z-TW(rAt34J=U5ik*{#mQcAp2rOo^Ul+v;n5&N?piX|<)_SSEfzqi)ca%VFC3TnwGT zv`cdf+TjC=tYIDmgXX{GM`jln20(uCR(jt8kY7?yXeiLk&AXG z8C~JDY%kQoh*LVo<&Vzg2itOSCzp%8Tnxjxglcoa4oO%zH}CDhyqAarn~!_5CvS(# z?1k~>(k`z+Gn*L>JDgLqZMV+s4W4kd%9}lZkH#^d?~#=Gl*%H#2yZW(5F3idqKNbs ztoonDJHn;5;#-vQgZnL99HH{WLAp2>FabScGZ24Yxn-D-{mi;_vA78?gqN4;aPMcH zIb8a}5f^7~cAUx9Q<7jxTZJyuT3x2&Wg>^(>Lvd^YP=VK=!AYc4d)<%x?Jy69r*pL{RZSGzbD{^Hxmvi<%G3(Y?}W3&Fm zuwQsbaEhs{>$guMM(_;Z*Y=!a5n;2BzITJ9vMvXtug6|h6;@I(P(Z+Z4{F{#PTT9K zalK%js?(*%d{**e1@vaclH2`-hsE4abQ+FkW8Q%RXy4M8H~p{O@q(kHZ(cl$O+g)xuh4Pqp+$&RWv-Pew8CpAk=UjYMR*G9RStedjojD{c}BlWvo3RI!h?-5LnU->+$f}ryL9pH?S(e1;N)O~ zb)}5$1ZP;b^`C4E>JpmeVo2NBStI?!@ej}Z82#hc>mLZSU=8xY$;+pxrpMHO-#OHds9iZC_W6faezuKRC^@DSxi^blp7P z$W3{BuC~<{R?E#3>y3aqf>{F|1};uZBz>xvSQk&#d`#sE2lsIeXItgGD!UwSj~rfx z*9z%CQ}ias3X)~Jg1Ho&EN?eU;9ovj7yc!faB=(_bA}b>jf*?^*3~e+{l@ruQ>@&~ zZ~o`JvDaL0abbCL`1JO2v3cSu+YhJAS>~O&Oy9hl3-w@#KpC9x!Cs{h7+24&hQwiD zKpAPi@Q`%@sgm8wkK}m)54^&+H_LSySv~0OskdF#g+3;=7)+pP(BqUxe?hmu%%xLf z!U0rZ0#ncDak$Jx8V}cN5pq50sEPi=_G8+^+wGOr<7S0;WyNgqZehf&J7($6W8A#Q z3&Z&6zvhAGeLh7hBgTv{BL-dd;%YwOZo>6b?)!MFDVNvBWXGf&ZvH8%{|Oe_53TsI zryp&sjq6+G?e;@*q0Xt;DnS1F1%#=Fozf9e{7eRRDA)fZ*TDT%o0pnSIV2|V&Tj3*79cMrJle_rI~f* zsV^-*Vr5h@4e?=Wn-{dP(ko>OwH9Y3as(t9?h>-hw>qpWp}INa6C7 zE2(&1;pu90-Q8Pzdsd)XXt>!13@UBQ)p#>(FV&e#My*S&2Mb=VC~5T%xY3)qO}iWe z!K!m*qx{Esy`W}$i`%$(Ly8A;A0Zy(C}+kTtaixZ`08f&*72@?z`h(<@gLmuKh5-o zVElME`6p;~^Y@tlbZO!(ZDRR*n17NQuKo#l(=N02uK8r7UhE@qe$7BcE@vtm8!$j* z2Hg^JFvu-PV7}Zt$8}Nkk_V@1w@>cox}LovP4wMwB1f&*=ep+8*8AnbvdLRzUO3sU zXRl*V-mSE~R%@9BhPF<3edrx8`ISF`+7l6*R0AJR^K4p0&z<6}UEWb{A@6wVm`L8k zi$50+FIHYW%x!sk#N%#`kEk@q4?fFnog>Fbr;*>u2j3#ORw{3P3r{%{<5LJzh>Nsv zH@D?7f4{3(uid>aSKe&;t#6(kH#+aRJgv9$QyRikd|Q-$v}>7r{+hJ&wEk562iLW) z&ap%*CS0GE*VU3Sd`{Wk1nOI_zw$js@sgj*t(+e`NYhFN6fBY#PpOXNtC;eb&Y+l( zVOF`;d==NHJe&+sFstjjI>SKBx`2*afAYP`pMDqbd6&&rCf_`$9Q%P;*E`AF5GU{D zTAlpC#~W9jE~opq%M%y7DYT#w(zvRS<0(kaqpYvl#M5=bj+)%)P`SMLT=L0T9z5FI z`K&y74}SGr`Gdc{Yh7)-To+@4+s-Z*H(E^bl2(5VH=op%v5a_aJg_s|e41navd8p+ zQ`BtHm1AzE5@vFiGdU^`(?oq+AHKYIRvsibqc0wn*^;d+o6FVl%7biAD&#Wzg{%6m zM^9Z+r#v|LDuXL`UhCR8D~E%4>L*uX%t5ob(ob##vq!=z<2u4qoTyQ?@rHqo3A`D;fOM_3cqz!-GGaY4wwxhufceQ03t%EpIOuac*-+y?1x8mpbx3lOE>M#k!h1|=hg9B7l+Iegw{5w2h41y@V%zG{`@35Yht*67W*6vN zNSNt8fluPj5Bv}RJd^d>tFrdV#K@|TB;Wic-&Yv^;-=>&P~P*eab>8h%MXrt;x3e0aihUvQgs zx=`}!he}yKklMiLUQhTs=7jOIySk0iWdJdErW9?q5`{P}43h z+YXaQTNDO@mCclGPd!A|v_;?Si;{h7vFfH>?i!3kmM0?VBWn=ZtWC3&6m7{bPbMRG zFP&5}9Hvnx`9%f%?eQnnjcZ}P79X@gXHe~8`q0rXhGB&0XW}rl)wWAN?MGUCS>D{? z_H32#z)mFwVQ~ij_{7@aALq^O*(!tCi5uJ~ryo9v<#HEuAxwTiuHE9d6w_l@5lkCT zyw#UpFsq#oh-B=G$aj&76LQu_)+Z`zsk#+3^Rkl%+VZ0>COih>L^;k>wkzQyM`+6v zUmrOxmLL5p&nrCF)scJFBundBsY1^BM7d2Dr-NMD>42hd1apC?N%(xa*~cNvNYP5q z0pC8{WE94m4e_(rMCw(Lp%3m((hzAl+Mao*GDz$`Z+wai?yBHsuE|y0W^O?)V5k<>8#%mZHaURh;T35>LHcJ$u=<)efc?*BYdS z6$eEV2ZZZS)62g4%JOCydU?CA3pL)(m~t`a1C4d<`E$J2r;8abuFt(I*7kNN<_n_} zzQ6S{m_-P1`kmrtJob~p=>i7f1W|)QDvaboG0j7fZT*#HZvAeLxqYtsit+~;>)Fd5 z&}w_PEcH3LE#{c+)G0j8SlqN8El|M$@}Ga#2k&^TZ~M|d zS7V#%q2{?<(~bMh? z!;5DV!;(?C+yA(*->lH`KE3G3&XpLw5T|iDsd387{ZyO&+)!CbJ?M!Ol^-{5&R@F5 z{gmsyhp~)PMm_C&j-1|2^-$i_JXp=kc8SetC4Y?xc_^a~vJFpy4C~pC7wZ!@<@>Yp zMJ*0a_PKxF@w*<}qMt|4k#@+rBhjmxgVQz^Cui0{Qf-B?{e(XVUc$RP%aIfNc$7~2 zs2h*jud8o=`Kib9=c=YxTK0Eeg96@Xx>NLvDZWkF!SGXYKnZn7XI;^WtD8w%z?D+@B98-HO%WYZ*T8Bhtlw z9;vCe?y%9H$J)BAv95SAXWc&Gn~1Bu^0}-|OVkK&E`os7VU3_1&+o^R{Qi~~0ZZZ$ zZZ-o_KZER*z3q&J-*D*ex>ndoXg#pG7i_L6TxvVZ)O<9*K$-lH!)@ZBRpDt zsWqvxQZMl|`xPo(Ezf4jsJ48IQD_fLySF7(^ITWgrj&s%(QzU9{R$j95BR?c6%CkWH3#Px>?tK_6)bN_SjuIaeTb9cCT6!iq#8ED#*>yu4PsoUd>!!DjiKmVy@+z&Y!6Pj zt~yKB?~+ypt@W&9Q=e{?O`snR&P-)9kS^o) z)|10%rruv4b$rNC%|?A!I9ASc8t$AVWlV4699}vU*)Rb$yH>6r&Q!O6o|3i&2cxZh zrCzMrrXT z9`ZAl=#S^orA76{Ng);y#=yG!;x2k{T^DNljJ0}r#_VCPd0#H;@AUpr?UiVSMQ`cj ziFtDw>U3fAkYk=VZsEqM&WrU~eiz)rIxk$Aa_2hGm5T{_g~t1b8zvNsb%DP&n;wea(yruS8S)Mx1)g`=#)0)rx7!zdW^Kez)>Wd}X zSiPbW)48f0b>^zHZ>;t@9L(<7hx?y{vaV&CJo&}CdUl=;g=vX{Skv^nFuhdghjnF` zaNGG{ILU)(s~El%-;2-1)YnHUCfBFc_x2e2=%2kN{e@#Xsa(ms3)g7jHp6_nEbX4R z@WXlj}()Ou8XHjUoW+_<8|$a$`KDAPYyo#(dIUFkoMbi zlb=OaE|uf2#YE5Qxi`1|)o$d+v|P%{c(LC$&$jTOPCDJSZF8*P!`yL{o!#Z!)WI2U zF(q}ZCp{}nhwb3Hyq%m12d>&6EH(7fS!X)3zM_0D@vSc2ZlmPl=(tQ3!^878VoFQw9p6AFem0 zmy2md`h0q(?Tubu9~=~K?;n$JhRfBEJu5tu{4Vd|_>Ihg=L1a(X;^(Oqxq6nZek|u z^JY6fRI+v}U7wq~cIk`R%focGu~jrVuzaf1Izujx7q4|C zHtTcTZIjRUcISbY3G4c7!fdp-d7>@N7572fZF5EcY|HkHbs-ERQf0YL^&jEThDy~g z&hkl2;GKuYi&UG(i}Q+VUPq@lPW6(};dmn7!wFXf{Z7E|18N_VI9<9f9+Y8oWF9f8 z0v*0r>4oMxp00(J7j}k2+C9lw@^#_5`tIV$(3XC>cXF8gI1eM{&nLpoXc($)O)M}i0 z_`$XuPB=>*6?sXpS?2{0f|90W^=wZ8X&n>`Vn5(d#KT|YOr z`Nr7Ob5(ix-a9bCh{4^^6B|(TkKRHti4bUhN92ZEw5t)@wKMHp%SF z@7(&LnhDpll0*Epth)MGn6DCbg;(6{2KnGTYhAs0KCk`cPTS%k-fnuSJ#7Z}7*k|l zm{9!}X?M*-k65Tl)&ytWUaYI_o-7m4@ARj`Yjv z@)fJSw=#R=vVFEZ_`*@Md8u`!m$pgN>0o{4ro%le!?V2yqQ^?7dt9KGD@?C)U9K;b z(t-%%5FT5bDBl+WRB<9B&`_-jZUF25cgdWuK+-eIop zp9WzVfiN7-q-05|FD)z5snW$@XQUN9_bmk)4Hlu$W@)CtyF}e!@KZwsYZ9E##=*|Wg3%naU<_j zs)8`p-Scitiool6KPTipPwH3?#7w+MS2KD(va-_ZrzBWCf88!b+pQ;#m^ak5ZiY7< zH&-!y^tjE=b;Y2u@GHIb<<7dE+KMy0*Y)nr`C?DCa+9mlY;qiqIytSl#C!7;yGu_= zXCVBvDW0xqv%)rcaS%DqsFjr{ep?^f!xQJjvpM@jyG8)wj#B9>D+ej^^9(tS>&#B7Bbiqs#zmu)XN}1!4JCPrW!~FjNXJ&U-5?&imixt$3TsoR_wc7@sysG~7-0n8 zAEsPwXJ2XG%q`e$A9Y?DS|J@SzT_dH9I3wTn1C~niZu5GAEkN5`r5ntuA(i<+>xBUq%YHl znBK(u)CAE^CVX&zSr>~R`AnD+kt$p4LuI z@VLqIqKh-+D%O`xek@LY``Ad4gr%QvrLJ6|pQ~|QYI61!P0|_nNgoqmG;Ei4^zBoI zGyN>ye_Fz+ro*Kf!y$V%F7TqqZo7H&!MA|>*0yHDU^Z~IOlT9&SZUi!7W_UCaKbOsgHC**NnE+#)^PNSYm>0uXMb?Fc!wybIQ#1@)qWGA@QWI@O4QE30ik z@WkPRmDB~|!T#;61J~8d`9T`o=?x;EULKaOsL8_vFWV{CBZ%9q$*ybXM%VlObRmbQ zw{kZ(JW|swIvgD_yl#Okn$?@Tuh^t=D=FBGn7EC5i?M>=oOM-ssGR!ZaMpKmc=L-W zxzziYE_08hT^vhR&`(z^?Fl!H`xU92L0Toda_(0jB`OUFe*>x^rvOgBi!_7Wlhl_#1vsqku{<->T zyJ(xa3#VgR${27G5?Xlq$z9#1!!vDDpqkmZ1uy-)5qZ3faKrB97T$dJLn<%U)$#T0Tz>g_N|%=|>2UhN^WdO*AUB#t zJmJV5ba4~L^OMKelk!JYHYM*_=aOBn>c;uOjnz>szeO?Nwlag;!F@3Ma#D`=x@%h< zup&)Ukk&dnySSKxXto;OmMioUy^vPBjHk=3bB()RZ8eUDsm*m!`rWoE%NMMUcN!kH zHP9Bc8V@}56W8U1=ek)*!xJBDdhSZcK;KZ1T??r+mTl+yFxu7AtU%gXpZVlHT&Lka z73tb?qE6R(kMrss$+eD&IVYDF^M3Gvd5PDBqbXZ@(HCc~sIl|Gi5CYC=C5ysR7*d( z^585lD{lJX^@jGde`Mg3Y-B9+7IGci?<19b@X?qJ#Jxltqo24T+o)@&@NMt#= z-eu*U;}?<7PVrnlJXqJUFI|$`?vlT(B&)r1eCrSOo39dM_Ht0c!|p^On6cF?S=>67 z7xULmRDa_Z_pIwH-NyP7X}V8WQdqd0=p3Tvx(0t);_F?P>#E6pf~#lpz(6w7PydYI{M&KO-y`+vr&~P#oO{}nikui6teDT5)hX1$ zo+c}Io2`uC_1Wuk_`y+{*Kur!X;}9wuCgnId8X^ZlbTr`P1Q^2H}i|f6+Z_nAJvrT z)9Tf+G!sQ~Pk32lf9!JEhbydpufASd({-!HS+~%5^5FWkU2S%PC0iUE#l{EU-%?q7 z4YI8oL0d*7I}4Gg6*b=QlPm3F)*M`?I=T0AUH!RwGQ3>x`TUB$4^GRql5>l2uY3QR zOVt5OK*2%A9LNe*j$QY_V&Wfo+p}^dC~FISFhRd_g$XN8r^DsXWzwSJYx56j`{~4) zCcR{}vpZOK)=LJg0~|oZ+*oZYnQ(n-XY+!J`MIp`#=eML6J2sRo(*f`bzmO~TIJ~> zXd1m-j$5URHKxN4&k}uIF6J?Kw&w`dLYH5^1zikc`qcw(yK-3RH$5>Oc78mGnj9UT zaHZzLksh!03?0J_EA$lofc=kHdSa|lr#wGMzvRzOy$ZiS7Hy?aQk_{#7~2*%_JO5s zml1IFf<;Zz91^`sXR*Fjhpct&@`pFWqHE||82in3N5VZxd~ou07lW;~Rbe_>4o5P) zP0IL$VUPC|d>7&Jjz`DKl)Ja?xwM=NSEc2Y6>UXM=`7roi&>m1(t9g%ea;FD1bw3< z_T?$HSX@V8gr4Q%pW|amhiNFeFPP5@BUjPuQ+nDKEdFaEsS{w^%nGDM;)$NKcs_VZRf-n>;C zmp^>Jb$xnvu3oBRr6hO?{k_WN$W^Dy&0rI$rY*@wK7D@{88wz|v-I2oo}Vj>zEzyI zS%!e+;s}}>Vd`qNXE5)*bfRp+&UD_?_UOYCM{kN&{mM&)ha8v1Hi>dL@6CGc?0D(Q zhLudPFFEz${t|^DtkDZG_;R;RyG8SBNEe%*y zwOB~pyqr=6!ax%Du}wbQyiCWkZPw?SP2_16Wc$@)>6PKP{+R21^wo}QLhPt?x%j=T z^%f&m9110nM96YIqZ%LVKk-TCDf0H$jos<6rI>47OV({)r?`Ne>YsYjC&n*d>&24?*XJr zl4bvcn@_m&;!MF|`wYHmgd$f#p6rWj(fTDu!l`dvHKD3AM!rOjhH<2 zbNS1B-o!9G%ty<8@*wFFSI#U(CmX><%MuN{5f30W7EPpQhM16-Gzj*NdxxCmpq~VO3xOuiiOZW7m zpTT(8?8I!`!oy-=cG2gY^|dJWbldsaaU+!!oBP)>9-c(r3r^%y zT_*MzCm$(Z&VvVyTK&EJWkS}($dB~q;2v##d5YVOZM2m`H=ViK8U69T=@zs z_*&EaN-_E;D{qD^8P-i6Hv8MD>ES>M=s;{e!idET$=W^vU`T3cPDRmI6 zcSn?+gJL7w>e7=vCAi{UXjqKPhdR?f_xKvue|yc-?BQmI#3C>V$%MlReb0k0 z<#LbZ0%n)8YRAij=;UH#FPI0NLkmtb%oCi35h%!3{nkW2rd(ffqAfA>60DxjSwX9* z-PWV>w7WXOYV6L#!^=dj-Q_%nMxAgZ7D5s|ciZ92z4ZJ(pZG+FVUj?tnb47QP&pml zm}itXpEMc#gbCZ{wcOz5FNP1kBiGIdOiW<3rRe4Qw7rhD1lp{iOpc%$M-HVKv9wp* zFk)qw%c~7Ut};S)7?=;ajOdRg$2Ob}S$f&NETS!PKIrsjT3+38{A zEQzyRu3SkdX&a`_ut)|`=)_{gbos$BDADaewMy+U2O-xE`%Ny}I9Yv|N`&oP2$xS-E(XpT>>`&z*X?tJ?K{me^wI}> zMa?2^C)#06pALtUwrtyW#nklCm~g8L{1%qWyR@bQvDDY-ciYbiOWh!PEI!vnGdSQ} zxWI&+9I-iGCME?9Z_zoZu-c0|Mx~QbS`IU)bhJHEUP3M;Ji?HxI!ri_G?t5XHRah? zmmkX5SK`_~OeU!sfv~BbZ~3);L%lU$<9<6}6KAuiDc21mfo;C^ z!wFIXM_2HC(m9S^b;`Rws;n9<71?00G++sAl)s|~NmmeImOr>38X)7~yksZj% zAgG;DKxf#77MM}vTbpVs7hBpHP8=MZOjw=IPb^}tXK9xkdd*$r$s&2MuWX)IWSuu3 zW5cfT@x7;C>6iIJq*~8Lx~qbJOwa9m`2A~H$&)U2Q>>e-6wZ6jjZq#950ue| zTIpqbZEM1lm-zOJbeIP(Vqf2_Zmduz9cyT~*8A1{bOu&$-lL>`t}GhyNAr|%=W6g; z{_-7bbY+|<9$|Q~Tx=d#y|n9kimX%O&UsxvpQ~4vj+>ln>hXrF5o24+4@iHFiFVy= zb1dsJda6`YoYcib40<7kusHP%F_n>{njRd~6sr(5-P?Q(UE&v$Vv zqpxLUIr$t*+;;nIuFKT5rp>-I4_IeeaOKmi&vo)EH+5Ri$Pd1}w~YI5ib%yzKGRsy z29--H4KYiYq|0CL@~*F>^=uV`amu*&pr(2lw91Jq@=NukgYxEnvzlk}a3f=A0&}>9 zo4G%#i@w@cyAkhMB2~U(`NAF1>%tip?Uqsw*~e5*?uWelbIL&YT^_^I zA68a;J!73qFQMOQgk@)5dMdiYL)ji(7oBHQ9UGhP<>O3xMH=OUgJOox8dJ>;Miv%x z@Tiko>`APl5#-6bO|MsK7Ib2D;JJD#|C+er_?n`ID@&I}C$65{D)Pkyi)RNDx%BFv zTv;~`?h8GMvNg-0d=!T^`Z~qa$}UN+=vv>xE{rv}cFoFQ=b0=r?56UPKCr#&9}QnR+)^Ws()EfX~fvieV1#;>AYu* zx4YR85~E31Yaf?Gdcx^|XWWF#BcSUx4tDM4`NoRGvqyOICQ6SV&a%CQ(Zx8N!Biyl zPmvy8x>l+zq{}5m4wgodDCC0ZC0$sFW8SDWQDJo85NMLQlaD+jFp zkaO_#MaGkmPmKdcZN{3KahH9B?TF$jgL3gkmwm3w+qcpaZB?3U!ELHpO<~BOOt_fB zeB`3bAMiN}AuAc%>j*aYX^B{;?c(}c+V+^X^m6TI*G(+-wejR*&MjIvI%NQaKzqMC z96~$%$bs^MBjA~0gg!E)qqF)=n}%qW79_!}&qNu4BwU_?)dEJMOnCGY#k5ByrXi?- z+o#L(af5v_sJVq(X|#nDo=)NFPI__%)Hr%#5+#Y%Rwteaa<%QUXK8cb46J#)2(GAH z`^r5nm@Z+>_*|J9rM(sHwhmN|DNR^JpDtUtoDDgZJwh)I$5c6xbQ+vx6SLeJ7Wt8< zFcuk$33cJiF=C?`LLa6-bno=!e7Kj@qN5|Rc&@dWnAGaB4cr+du?h;-CzM+< z%JtLjHa{>u%6GW!E~ag;NEBhEH>*h&)rrtL31cqoW{lO1Hi8K-02O?1YMKL}Okhsl zhpRCQF(TWW$!amQB?+|}>8{hVE$MPygvH$9;ie2$mv#$Q8=C6Z@#az={NaF5{7y&S7|ar%_B+Hkj!|Br)GkiZM7*P<=varOIpc}aw}i9@v}{( zu)dV!Nsv6RwK`!?D!IB`Ik%#7K`BKTEDQ|6C#JDHrg$$RPAEIy?NSR$L=Q7ch`5Y>>i}2 zi}`^t{H7?R_%4-h`-K~YUfQxv1xvv!Ho{ao%~7!p($&xm>sGzuWsz1E$#fst4I_1AQAI#^h4Thlaa?=adCPtw%np%te&hN=N zQMGN`&8k`+4^4$2+zn-$z-~+5o&s!jnH7m5A!{SCV#4L5Fne-%`+|exEgbC;*+5vY zfIxCpOTg6CO3Us$i}q>>Iixw2d+ zVVm%<*WDbF^mC1`*FhQ+IVN|QklJB7!^_P!3^fE|(kgn{20d5;qblBwpT*5w-(Y)RYvdMk$2U) zcIrwNQ?7n|aiyOufs++2pdlxPX-I=gwYgl7e(+?)NKLS(^K@7njDyB_^YC`F@e=mFP%PYO~TG9nh%`mt7$m8FedgCOozkKGd;p(xNOXQp`}7o zwpms}kzGusx?&0s(p32Iv#txLZA{=+-_;H=N~2abPSS&Lxm+*O#Ud#`oG$N-brrLd z7Bnohi{n5`my3G?S#XuVw&y*WmkZ{c-01Q<^TI5daVdLey;6_D%@68DS4P@X9>(R6 z;qn9y^R;$d*NB>!#w}`);RnkpkT`3Hod=y&7?rD6VjVyd7!`Q9f5jx0yXzxX$Wg+@ zh8rA;;Imy;hwDd~kV}_fB~Ah>W;Y@|GvrY&-!#fVAW7w+9|HMeeXZnN37dm*HZPsj zl?{T%EcAk@cBE}QSF3cR8j`Y-Mp+W^_dK(Uj2Z*i6?nP4p#9(=jB>d>U9D6`xSUZK zVfjK`aIJwr;+X`CBI%l5n^clQuPgVHk}+Z~T{aj89SpJwaWGt$p2DU_6S=aHi7-g_ zK1{zXR2z@k{rJk@EC}DLno88Al^V)gfnX+A}R&6(;gfr$|pVVK!G`jh3;rXIf1d6_a6hSr*!m zC77int;pgGH`+4`gYag^OID-gP=s}%U%$lRh7ntM75TDLniV?KEYVsyt_1XvaySbz zMn7OcI9)bgyI7rYUz%Bs=w*cMtV)IKkD*3VB$Z)tI*=*}PmNov@2GtG;efe$WWmA# zy^+^eD1)o1w)R}9grPPmdJd;{Z#Ng4o7GA}NxkS!WBo*zS zvds_-Zbud~bfkigKuR(I8en+ADY3P;gVzVG2s;atrrcfMt7nBe6#uA}y^<6C7i0l4_iU?dC{_Fm#!~lEGOxvEX!6V)v9Jlx;~K z+#A-_^$ls;MJ^a^1#g{Yn};mq=1W&itIL{op6Ua?cII`^0n<@RhL+FF0 zz4QW8q9*)S^i_iB1s zwz27V+6p9+LLn{3&r)?doIZRZ-{SdWT^uvg$dOQKOfMbOmy65gO4Z86w4Alc!<$o^ zMjpbJG>s(>ZzhTqm});h1<8f1SS}}0_3R58B3B_% zd7;vemPFZZ#$}GegMtYqk6o@_J8HtxOej&!lS*)FD1ezTM> zZWL0T$bfNBPLZ&X_fO{=m+kh2zwozg{Wu4l=9`+>NLS6s905E#uDW%SJiWFU+&mgK z7U}K6F*=29od+i?7Nj->VI(dG!{GxA#l{&T$RX)~5ryIu9brHLgI>x6+C_>7i=4{k zYO#{GUBPGy5~9~pX|+mh%}BN6D$=16Ws(lo*k#}ua?z_T7dID!r6xl>{??(ixsujJ zGGaKM42PAIIyqw#83qcyS;(aoB&HyRDXz9tw&CqW85e`ocDY_sE>4i;bTA0M39F%C zh#X1`GAM&c!Se!=5^_+!g)n>|J<-eGnQhfuN%_%>z8GvV9-z|kiL_lpX_F|pu@93< zb0|!P+{u9?!}!6rTwaalj)Y3vayb>Gx8rGqw-aJMMpBSV6Hg?bm$<@H+Sbr)QXr-b z!kcMZF8pZNlwpRAA++@J6cbRJA%${z&?E*)S)T-2dh_b(?~F@)7fvgrC72!Sf>dw| zy!_K&`r%ZTmI}$igtHKvWxEf-tZXsmazbIr_Q9m3QIPB`Z)UkrBuZ|SgwR>0(0r~b zS4hf-Y^>U4Rr+gC?(Oc=%(=Df^U z*Eu~7MkUmU3loTefDwz4gkcHS2*$-oMh%fdK|;AaBtZ=+{P1M_ z?>XOj>=!v~B;&|-a2Uhk<}k*ANT{*xH!fZ`YRJvDTzDdti;Z>Z$M*|3k|#%MYW%%T zEDT8h_rpW7&ceR#pFp~E?lX3D^fq1M=ltom&W9Fj7XL@_wn{kHCGk~ zij`);6$^>yTa6pVm70ykb)%3JLtwbt4uYp`{6LLh^CXfdSDyafNS^-QCKm*remJ;M z=f;)oCC6hVG-tA3cJ;%>Ah^*a#*slbXD{8n{OUq57b4ZAK`>ldwwoI-+rV(7eSRoX z4ObRg;l@6j#^R8r86LK~%gGYdX*;;u7CkI)Mk@lc2`7u|(R3^#ke6a`Cir zqx^8W>}w$&9M{5jrFL=s9lN)0G=;HE+d_^P;o^gna&bg&7{c+VTQ6gnm)ExFf@9un!>8liF?Rr%M};c`7W_8w1EuwA86+zoxu=VVtfb(4Uk46=H#eC z0kM4JB&V&zP-!)rFv<@{PQ9^Q4nz*tg-JHjT(~k6lAv5p6zPeCVqC>s2n>XqQC&EQ zC0#iqk2Gf=4$T$C2%J9@$R8;3LZq4t>a9hhE+mTnp)w+EpBG{?KT@0gu{Zy&$iK(+ zLXB{72yZ58ekfd=3s>sIJa!o^$$@$pZ$A_w*QTF-^hVb~C1WbWbaOc7hp72`ijbRd zF)yfpin#EN56llqp>56(Qyj2&rH+JR+GL#=q7b3k`BbdUYN)|s>z-X;uT4i%}fSk?JnpQ+3CF&QR`UJj~l z+?VXn+e_5y2^V{%)k>DtS#9be)_2!rDBtQ#A10RYUA?TRt`ySU+Nt4WC&u$3u3Sn7 zr(t)gsb}oxS=nm3F0bIlgX?gvPv(OuSKMs(vAtTFr`0@o(oaW{yoe`Pm)D<5KRJl8 ze~X)XPdodA#0az+7q82@XYmK25C2=kGAhn*TXJVcKdA0uwIFXDE_I%9Di`$XNiP3#kAFc)H0 zWxJXR&kVQTEXsV=#Epmc)N_sG=KQqkm?hnNV5%Omw1exrePj#IbfBCWs3hCUWG9T! zuu>a48SkGpu~N|!ay}N3_F1!wn{7|9`pJU_yXCZv(ZeBQwbk7Fo0q7Z`1Y|dJ{L#! zlb35UJ}|LZ*+oX(#^;D)!W>^~AN!4Dzt!W#Z&4r;=&5lajwAHHMC;X0h6cxKE) z#kXD+BhLy(Sbbp*7CmusuM-)r3@R;Td&aEY*6tpjR9|^=){n%bYpN-zmRPA-d9oAB zyEoHL@x(rxc7}6B4Na~dVu@8+wzATqRZEu_H8c-am-E6QP3)7w9A(m3BtaaGUF6ix zBkuFz8K3)e{ll?)|2ldc{p7)eo(o6U>REkVUYjuN;~|C~*UhNqmEz|7kcE}~w34pj zcDv1_OC&JO^^8WG`Pu-ss5?XmaVok9XTo}oyy@`b}t{ai%Tkvo3#hr@R3YR$CT5849aWcM{{S{-h$>V z7I9PUl`I*Bm3qp4v+~oF@`Y!HyhB@A>^E83A)tQ`h>l4>Ib z1^c8Mc9#zrLimG1w86?Q7keBYFVj#SFi3o08aI?a#2-CME7B57e!y=^vY;)8sV0L& zO%>afYmtPN3_@>t>g@@=J=KA$Axwv9gc}SqmXos5*Z1G>5+$8lzoEppaxr_&r$sz% zv&HhRsYccXH%c!VmeaOuvl@?ScbJy2Zn(IOi_PlXbK*e%Cc?5jkB<{%hxCM zO{z}rJzSUBZ8O}zQO&Hu<}i%?VGQ=Jx?GfTZP9Xv(Q{XPQu3sf5dtVaHEqw4% zj&OOOSS>zL?<17Nk)ECQC98e=Shn9jth6NOuf>i+TCK=solIKiyhCt z@wiYI8o3x$1AJ}$`Pw$mV{x?;buP4$H}vg#?5WFr|msiXSn#iJFqnLN@YoH ztk^M#6ZialSJ@t{mr8F3#5#G2o9p}T%~On6CFzk4?s}F#m#8*J?!#+ETWM~8c?q`* z+tN?2OBlhs?e%3o`i6d!)>l4GN&H21ZUbREn`l#58N7J*x{@v~PAUtPVzq7BF6~`4 zt)-g|r z&nqO3lNW)jDF$H$lWGJp7p{s_YPe+a}dhaierv#A)_?o$$a2*!t+hHedo4 zE!=oAK36ItO}h)j)eG<9i+xzv|6)Zi_SvjdUmwFd51*RG36>re3}vCKZUmy3fss2r z+CEutJ?Kj6@_>vO%WiuwryY4NcB@NL?OSj5N!#{D8aE4*Dtpsb-L6@EcWl-)3?f~a zreZF=97wp9vAwR%c6Qr`nuK`SJ}#9kK6Ax*5jSGs@uHB&l*^cO%ym+?lfk9L*^Mm=IA#Mv%m?jAxfU*BPU8Ku`oz(iWr zvp?LL5%2g!-7u}fH$LtU?$C=njOB_jcF#zd@Gy37Kdxk5?2B==kRx0S`MNYFmsrU} zo;Tr#AG71;V$43+@z~UFvfL%ag)D{`+el@bm{`>YnsU<%m!4fDdWj*HaN_mw82iM9 zW>{h9Fmbc0N4NL5%FBD!l(#7k0nQ)zb!uGEyTnUd8cH>)vaj_{Q z+=yoz#En+D7_DG;3x8CjZ^5GRT$(`YSyAvz1Pp=4(3Q)?gx#Cxv3WKtgQ-r3l}+{Y zX8XAe<%%p`H{tDZi7DG4sR_d;%V3w5?Sb}saC14dby~f|3qxQfvxf(~{OIim>bQ2& zR`fiTeZ`;>4wnRpx*!%}WO1o^xcKzu zdWDH0U_px;Q%t0S$O~`we0*D1?Zt}qdt&zMdiEQ3SQ?V4VQm%#1XwyaPnq(jr$>X= z;U)mbJ~6_IFn(^A$*k?bDC8<)*_ZF$D=>voi2`8_~<1DQ)#n zcUGI#;*R((rxl53*OHro&v!rixl9sjr7p&8NBA&!&Bs(?ES(i_8?oUD@%8k2Sm`5q z_U1vQq4L9$Fo?CAg2vs`ay3`O?F^THxcz19JaC)Xy?wKr{o&2iwl>9WBc`qJlFNg5 z=~Q1gfovCh>?`Ay?S1_*}NURE;H1CdQ5jL$gGdQ5}X{AwLFlVdc*2Mx@Fb8V6a^ z2>Dbig*R_yK2qD}q1QgJ=v$ZVh#R|$xm0Ii1opXk*f+g7Hav~nQeKw*#JxpNI}eXK zk>&C<83jwPuyT4+xmm-}n`YUb2v$;FX?>1Zq6fQC%$^nFgRQHd7x7p7#s2VpID>e4 z^1#*pt#X30^t!T-Cs&JpUI$lCeZMlYhl6uXUF{=WOeDb*Cc^PRJ0szsM2)7Pkc8)y zF<0Y=Jdj(+ZD)1Z*Zqq7e6Zz&o-#U!X+yJu>hvCl-7SnDKi$z6qU5L%4|W$%by{hE zZ1imG4?p_a^v}NfsVnPNEp30~DL8Rm9H)J(AF|&>KD}yxAw{^ zR~LAA{m91%93xiPMLJCO&Y$~#A$vvZ1?5lO2&HYnX%cebYz!rWxKn%*uvD36#IyWtWj+x_Z7pw zNEh?vkjswO4Pr`atWAtPV~s>??1Pw#-ln=_yeOB0j=ESaFVuTm;Z*0%@uiM0HJg6x zJD&J(nS6fbWHNfm%5(W@df@K*&AQr$J=~a}1#>HvqxO0h8vNyhj(IJ3~#P$Ke^e*56AxgIggPa zw3Ri)xd?BrW;p1Zr@*W|QATc5V?Qr0o^GLTt?ZjnX%>6TTVE^8u|p4wxV0z6wI`SL z(eUjrPaebd>2sEHWo6e8P+ac^@UA_6?-pi8-sM%+)DYVPY%Qg#H zoVY4j#PYkO3O$1NEMc_mGHn|cS+ggpgc#qx-^VqcG3>voP!E#?AEqP$vrLBFHe-45 zbhWOY?JPHup7FBs6qA3*21{V=w)xc5&E?%KQY!805NKtDujlGwn#0?ZwOLoRDcc)euuh0|LBp-fo(o;W zLn=-y+%9!_dl0*etKp&W{zlSanMzj|t)w3uajVwL<>5XsPb52!TiMPSB6Bx9w*K5?U;*UfTq$M*r(XKz@3Jnbxb zoJudQ3ONFY$ym)?Tug;tT%WqE_$Fj|bI773S88nc_;yn+ zUkhm{9q$sVz5Ou~F}AT7Sy+EgH3#XbD;&oPiSdIm8AIHg zmj`?!Y!7k~7NjSw^o9|eXQu;6F(hT1MO`JHE=E$Lb5UYkj!A`Gu8hKvsL<93+d;s0 zQJ=X;H6n}pCZsaZ3T{Z%7Q{eCd2=9@w;LhXpjzd++J}W}A)%O%Y622Z5UYC8(DVO7J;ZktO%+Q;QsNIR4VwNhi1v>BvtoJ#asp*Q<_2<7jDV?OKBML!^Fk6S1sEVT zBqs+4Zb-h$2STD%=p13WYnvQ=)TpGrVTDDnHe*M4k5qb&E7!W@-(p_lg-19qQkf-g z=s~P~h?^eOOWe>5$B5yxhPa)@{F_^CvWvN{9d2EE{TD0ZTde(Gh+Jsf+CSXT1a9s?iab9fkw}Pk!V z5Q#w$-X2pe+*D7jq&*>Q+6XI4kFZj;?Sz$q%>*mMycM3!x?)PRt{7p&h6#=`M6XC# zIMfg=!^KNOMGveb=0f1v6Dy;dl~00njB*5)yjTa(i$V;8k4-+saJ8LSXh-R+tQos7;P2+jQ}%A^vb71Di>x&#|sU=@CYF`z>R+ z))3#ShgeCq<@y$Z5k^cHKJcwBjONIxa510F$`9KtR8C9=g~mdTx08Gmy~M~G?dpmV zn1UD?jJNB8!Uc7KEZamuZcv}bh?^fSLNbUNWNlIrk|{_9=?6dB7)J_IZHVQ{5Z;)p z373b0jv>}JRC80!O89_;5hx=z6O4=D;)tgpnG#GELHf}pYM_bX)m&V^k9Y)AJ+Tr~ z9oBVtZf?PZDLseO6H95<_1PwPcSV?LBh@Cus2)Tw#T7ytR^$Yqh(!p-F{;C)g$@&j zSi-1NtPDd>QCB8akGfjlP(3!b1sN#`;1EW?^JKk4e~YPdaRC$@=#2?fu!Et;fpnY^ zS~)_mn93Dh(|-raLs7RC48e5UE-Re9R#0bmxO%$#Xy5v;SeA>;Qf*vqi(Iy!EKD$( zMOTJJrSVz3({hPcBMI;9#k7zixe6I%KD4(El535;zp#1F(Nj2z3= zfFX|Z;d12H#Dw_@0wxS5AQ;I ziHWcsKNv8?TgugTSxhT~Fv8`w{P+-le8SuPbX~mgFyRL$nDB%D5`-|W2IHG!AeL}( zVPm0P91+XKjZF_sxY#o*#J_|IHVvbM6nDbf>v1_)k0gLPF5B~AgxQ%mX>lz)YUg~j=m(tt3u7b)~3GL#$Fg^B>l{L+=$+wiZYvduR z?^Y1Gl#^f-DQuUdoE%aK@{R3^{jO)*&A|7T6P(gPPK?sBP20}ESSP-mqBoF_lVUzIn>r-9r5YLBKr*! zICwo=XSItH;^|x=`S_z%%#VCWNkuP_t1*Wz+uIp+(@%Aa$eGWl1KF!m&nR8U%Hs## zJkAQZ?Mr)4Q5TZ-4pFFNGM>1`{x1ietZ9p#{wa@6Mqu^9K91+-_v-wCN=2d&R{Lt< zh}CXB-K~bUyj`|=p^H1&^x`RbYmy3R*F zBmS7b-uoYpd@mV{y7b-gTuvc3VIba%wjerBs+aLRS6Q-)%8;wFvV6A#T}FlLlFWrJ zBV?I_j49IxiIT8(K4-bEuB*e!QPEc4$4V-iC9$ILe2AMpYh7LJ+1xWwc>yKa2eEQC ztw>8(!m4!AR#Dl>vQLam{la@GIt_ucfcI(lrnj4{gn~#C0+n4udCHi7?G^ERv3Yk@Sgkh3NbFXZAn&_ zv97kGU0CQR8K%St7vDqT#lyug5EyUnEF{9X40!f(^)@zNy^qy5QQzSQhmZ3{-`he$ zuieeXBB|*MZKJx<)T6o;B*upzPQwaso>pkf#Y#TeF%Ze9)kv53dhBnFYFFzY|EVz& z-T!>$Nu++~bLrwHSZ%4!O-UvUb{2^u*|}Mb)uC2Gm*LiZcX4Q8dka4}oAo(LT7B1f zFbb)Q$i*lZuWePkX+LFS#f8((vc6o7S-t2FOIu&lcO-<%)A!Q#wN^pB(7_a4wm(9( z^mZ-$YpfHNi+?VKq*h;8tD)~hkhWb+uXVK@e{Ugm;ijvx;?nSsQ?jtywzzdLt0`in z*u1?|8*+*AWjjjJzZkA~x-S3v&ZNHcwPLlJb=7JjF@mL6GcJ~k>G;<_SuOo=b>B;` z3Hpu^7N)Rhlnl$olO;4y+jTWAci%~UKi7)1tzPNP*mVDFar5SZvcGlLK)&)2uE%)x zKPQ^Qj~w0}abXbsM27c`78cdyGJ)Y=uf@KyK~hqHZ14Re|EizKem}CPsQ@RsS9GgD zL==dVqC?sZ2RW4|(_8d={<@WVTyWRct1jgZEw?j3r!b7H#!fpBvf=2JKt~@Ba1LS6cMay{cKP?CQQ-fvy)q z(Z0D^&@Qgs)jzk@wD-2^nGtUsR>KrsN2aUA?rID{j0xf#7Hz_XH#dSI@;`HS`THBo zKVf6Rw70{kgp$GX=4`^nA?R_?C6^I?IN)!Az+8ms$^|i-z-<1Fe-YX!xe(e|i21DV z7!-yTp_w{17=&+KG0k!@jL~6vJ2*}3UjEe;>C;ye!E8;8y4KVclK2j$To)%tFbIP8 zCC&~QMwl34y5wS)Shk-d-UX{SbmbcMOAHZmxfq+-#E9{uu|ecim+Oi#-V9+}9AOY* z7z87x!>pJvh!|pwuCx#Y#zY@hQ?$zvb7E%%(^z6N7touS2s=#p#G@G&v*{r+#>h>fx5yKM*zreuuIG9ocz z(Ur5pOD+K;#F!9fAY43&_i)w(`R?C%MJ^`H4}7&II;PSZ+xM7PSKr|M-?%(Dg%#7h z)A0icBWB7~Oqei%30Q&Y=XJ&C3zHVxJ7wVCPs~LuBc3M4<#@voV*W`CdK1RyT(IZj z2ZC{NL=rmUB@chqzs--l>&x#;rA_6hZ*UQiR#HwHxe z>iBAm(VLY@_T7)hB8{ujYJ^YeGGGK*wABkMdYFuRla?JhQ7*=$5p^l=*Jr~DlF?$p zbRkvlYiQhJ5K~<;#&S7W=^woETH2QFqQ%ORC98)r`fY#o(MJa(gWbfD>1cI5GFGC! zIX1se*UOSmXK-C=Hj#>~vAx>fn+c4J%7T6)sSD}e+H!HeYO}sV8DuH0tgH*EFe%E# zRPCm`opoj9gfB&pR}w)5_Cn=mftMh}$hU6^ZOUF*D% zQ5dVWLl#*UrruGmC+k|WT=oj`V##)8x};?FZ6_AfEL>5x!324sHs-2UJFPH{$V>BC zb3nskoJ-J~LD(*df*2RGCP?anRLJoGahsh%SYJvWS`9{|l8T%tZ?@&ezVx~_JfSUF z-cC=i`Ot5w=}MPNGGQHP%QpGAloVfcsJHUP5H2opDQlrEmsdM&4{v|Dc;~w*m&>a_()L6cZ*y z*7#EIF+Q1ci&Q;sQAS9Lu61oH8^zMW6qX{3wqi3bHWu1ScF0Xhm5OJETbG2A6iY}o z+9A(M(#6`zsU0hQ-mF{%$~P*NaivHUq>_apNSBLLBb>n+d*u2iC>Y|>4=OOg{73nb zzl$5TtTzr}5X?+iE3<$<*kL&zpG=>uYfeW%k0Z|yi<_&xmC+S#+(#sqF1W6)TzQlR5N@s z?q92~*2~4Kr5A1C*^Z2-c2A^zj#Tu-H=-O9-!7KX2y?jD>0Je>+In)8yQd5*ZFfW9 z$_UGp>5xYo+EX0j1a1(KS4;Sexr;D@;F$&4M-#D|8L?624JHdUA?X zNZYlq&Ew_rqfvw0v3yyS>-4ZbDVmM*-Q}vWOIJ^q*XQaHi4T?IkTG-_gmvli11A-W ziBn~`xFBKd4sLO}SjJ~6R-VO#i|KOVka+1eu|%r2{Z>&4OS_$j<>Ee8cr=4&V?`=X zkx%sy>B*IvUM^=j504%%4v0jJS4Q%*jfE>iX_gVDWh^M$-&WvI+6ta7KQJYCO#5Ep zIB9|!mKaQDIG(;Nnp(7Pg5za^2cB)kSFH)YCg#KH^~G4xi>J#EHEGA9@}b5rLiVzn zlUbq1VSBRnxUt(Vw2RkepkSi0&`hkzQAxGApaTgZu7n{RE?)fOf9O0;PkZ;g)~Li& zBn2QL{CJV=e+Kx2mFfm5b#ePPopnVpK zJ_uO!pe?RgA0*Dw=9GN>54JtpC~A11BZ*%eUGrapq=kl&xt~8BWsgt^J(|$?x zqRgiiP7AAXT9MUDZz@>wjUk*?i*-SsN7<(Hj^L}@r*A=`IEm%!K=WLTPq(11q!kXA zF@E%g8d~ZLtlacMW!e7u^gI%piL|2D={!h!;z=%>9M*(L(_!kG%)_l!Qoe?xY^S{) zOUt+0f#9DXF0JTKw}%RThmmvIY6KflUqSI-O9 z$$W*Qrv1)n+*|P-Ze({0xmkEoz2b?z@zLrf7%XT`}r2|T1 z+4hCVciX5z+YVG7Tn!Y{o9>i|qk&D-JU@HmW1mq&@W8Tjl_&xMI(}Xtbjcs5gzs_{xfF9O|ADmiUyD)SpIn|#; zi)NPQbU+CgzXi4&aXz=Yjj_OKP*@k&9Kk`cpB@<>Ikmj6*DHPM`$m!~H`T*K9Zg!# zhAX{>6?NKXTU07I#gd%DUB5>v&Pdj@yROJ_rRF2WyMCw1qu3l-G;ZN(pFFb53MVfi zOffqfxkz~uOH%ocev$?0P|mupFue$A;KNAUwe#EYYL`W0ac@QFi(-)~DHp#+CBGPv zH5DH2Z50he791;lIK@jGuM=X8%XgP0XM^;UHV4-Y`_z%HFkxv%V7=%Ce;Qcro?Wou zI(mgC=OL-2XmY94ciZM&ui$D>sTY-Me2!Q=+CDdBd%bY+38%xhx$WlQLT_T_i%OT9 zr(D+-?>)c7%CjVtNGgY3SV{C(g!|8N* zD?bb^56jD&+1w#FON{yO=1o_Iseb>wUGfEnnI23{jUW7re;Yor+2I+2nNT2{?vOBF zbZ6s_@(1ILt0@&Z!twL|l|}aXYt{DRZtTRG!p|1gpXOt6aHg{kWOGrjwj8Svx^zvp z@GTHaj~-D>inZwl)e||fN`{&+VSVvzh?@zk=rtYASn~aYlbDUUuCggdT5Vb#esrDk zOUW2mzm^&&9oB}Qoo5}6`HH1Cy-KdyM;co5a6fC7`Q+kBlv$To-(Y)R@*__x*RMCp za<#_ux>2dPYJ}h7 zj&`Hq6c?&f-t`iv*@-D5tPWR`XILgfeOt8E1>?cTcs)dV_7d}%@9Gx&xUkqKPS?eO zI}cWvdl#ZrTCg_qt^SUZ?yRh)jLp?n7fmJ7>R9fxBB!HP(8^0pl4ro;`>vnz;KAqU zr+RR;_Jq1=Sfx!bG1nfR87$5UR*a*KFOJ#0hg5wdYC8PkW3Gmexv+X1T!+3(w8axU z53aN|f6wX*jfIx0%aRt&&LYO!udCvl2XUIi{c}=r9kE_l?gMLgL|do(2oK5qVWrK3 zQO;P??(MoNdtxQDRadiHFCH$Qv64D{+(_CA?LsGaZ}cjTwobmVc)56e=7v&fT`x3c zWoa9p2R)ICvqD)wE`&P9LpDQiRZq}xz6I+peVe!j)lAkV9+|e$kRx1@}68A=~RtAx?imT1`#Xb9Ky$+kR za8;|{qjNbn4wqY*+skVcpDwn}lU@#uarisJ$`9jnVx{$LhiSh2KwCD?;BdxrBE_>U z9ylqSVB#qNKlqzI@4d+9Utcc@Zvi zGs?w37c6uoex1%-ZZ3z{^@5}Xqm_8@-b7hw?FLWRaxsf^vi7t-EL?dud>IuMhi`(3 zW(rvx%%5uHQlH;U6jHgo+PdOCb{^%-9j%l{x@^nUls9wLm!cP>+_vPZw|gIJiOI;j z>2O^B7=3?4u9{!)*1G6B^b$E<4{JAYEi7NkHi@x4TcVTZS~QZ1M*xh?%|!V zZRUWJ^kHoCDCjvPg=RVLSF2L{>gQ`}x)kC?kT%*_J`7~IHzcu7xw&%xt>h~Yi`iwA zZ=@-9HtNt948$jtPIXv%l5qJ6KX^$C!IM@aR=f|Yx3qX_;c97@IlK|(@ob)sl;Qqm zDzL88N-w!~Zzf@RY2GtNA*5i9W_)OJQR(~zSEEwdUe^o0+XlnulSNX35!M&`%A-!K zY`bOssmA`jQfcejHLzqs?$pC-mE!jDgYy-(i<0ck;Jd?5 z7u(W#*iQId)VO?{dPo$9_2-~1%F9EV5}Wgeu&vQnTW!0p^mCozR%pnw@P&96)X?Q7 zl^~VQImFZD>9yJ2Y-4k-@awd_Qlk=1htxmAYSTQZHp1c-qwNn%t8Z9s_E`Alvux*v zLMlF`oM?|JcXl40VrkqN`xpIQF7}F9trxEPPMR>plP|Fh9q?pvz3|MiIuFb447tX~ zXb1yGZ;CA^KUn6z+t-Q0t(R^4!Re0$>Eeg?Y;W#RleR0M7bGgFX1N&k07e^T`UMYB zSo9zM=Ep!K3F!?o>1k18*Kv zFds}?WUJ|wA67uWC*^v&SiVE83*_9*8{_i!+x|SYb5Xz2;@J+?)w9#IRaW@S;bqq6 zhb^)n*W6>~9CvY=Wo0<)A!BZ9Y~Ihs{Sj(zFN%*Vbz0FLytiSMPw)9Tg5%_6X%8I6 zn}s+z)34;<#fz2E-!XTw=X>AWzru@qx&=EYw+^+O7Bm^23%#zWan-fFELVP*W>53R zlilUK>dnzHy;8?CKhr8&;ukIFlj1!*cy@Zp#foyS>|f4hPBBz3=CFPo3{ClTmwyeP zIlmaFyPUuB*(@66{-9NtmnOFCEIKi$T5w_Mm4OE1}c$fJzUHEje}Uc4jA z7r%J6@57s)=AO2}!&$5_&2M!btNF#cdNIK*(y|7Q6_mJHL5!UPfAlJdHi!vrBd2Z% zeN3!pV+AKCH|!U#>Nmzwr{Jc`#Yq)OTThf8VTbFb#$lw zT;$8V%9rKUk+(oHC7;0`;*Gl#!I3B0q7@D+%dF4@o(`Lj1N!j2+V;ba9bO)e?-o;? zq0Bpfd^!tGI2aqGO6F&ZKzz&MT&=-)%|qnDBETh88#v=q3JWN-h`M1oRV22IbAi3yvP= zAq(-|w5LbsgehViBWTvRvUb%s$yq++nzllsIB{JVpT*MLzi6?%FPsFjL6fkmI2U0Nb(*l8;bLB5!c-?^ zY5a6`rJvsZCHp6Q49%6LYi#G^Mn=tk@eCyy8N3|Q;qBeUapm2o z3GXc$!+~eG_-LMRvokW-v8-tb(^d>^xhh|^T>W&J7BLc57}3?f+d{hs1GnfY^3YEg zlO1V(>D3mR;Js1n%L)Ub*R1@c)K@!QzC>xkypDa5Fd`8@X+t|4P9|`iq_fubF2m_8 zl2ufGu1X9sk^8{G<5ac}FEA-s18^X~#>()&TRj8Jb~$&=8Mz1~Ab{Mx^{-!7GfVdn zPA9O5`-@fKXos!-V0qK8#Ua-J!5V$#z^G4twg<^sE{4CN`l#&R;ZwY0bCibi{ud8q z^~xGLM5~EEXzDBIch&PQa_lZ%Cl-fv^5Qr>aP-e!ku}NZGP{u0RGvPZZro2!Zdh}V zN1omn&F?E8%7fzwzd1PcSkvLDgBzZauchC6@$QT9tRV9;+9{~1qkCGn80+>?B=0Hr z%GZ~Ray7=U^my4mC^WfF%l^$y$fZnHQ#{@hqtbEecahY&g7dnb{&aUn`9npv@34j< zh;Lr}8rRceq)M(bZ>eK4+pfEhD zEh9fooQ{6R)LY!znt30ud^QKK?A%*@^Qqn?bgZYYk;Q|rRo}x>;S@YD6-{#Z(SD3y zmMc4wbHz*PaK3YM#!dX8y+^MziZC}`?Bj;yk8q&a`R=2a{_ydeqpYj5T+BL;bX4Mn z-?kOrg`WI{^ui*;&Yi5zcOF!40{gd>9g9$H@!oKX+mvff-+l3*Cgv04SGt8BhFEq6 z4}09y$%q?&t$aztubh)6HV&6B4Rvol3dN0=V19muC$mKIY3{*@X$M!&&&3}&*B(qP z&Pc_6%uKOo3Y)m-m?qqs>ah5p4!zdif1qFM*krB zt9bUS&%Se8)~}ime(JbUe8+V8T$6{{N$Tn>mcH_iQ9?~$x#GD5S?F1x-pLOW{2_K; zskiC_Wo!KuHR~7Ki~F=9(l4BxViC&-IO*>X%eP+ufk1x08eg-b+M27WJ`&|SsQKdF zo8(ODYKgw?W3K515Plf1z9cKCCVA%+PK!d0aBiU(x6R;QF&XM;k15qyMArSi|cyf7C$m9uhhi~SqyEpikpWQ9xq-GkF(Ad zx!RO4pYL3azr1(gc?;RRZ#ovasg-?@j=EImElyZkdEi`e<|8bfg{cl$yxhgf&b1iG z)hb=?=gr6PIS#q1jrD1pYRHc~70G(@ew$&!^;o>MJVTB?>ynEKR|3;^_(t;q4e$+>=<;!$}zdz1;ZlTQOgb z=E*TJUpU}(IIUo^y4DpFc<(uDU7(ad3+wtc}D9}DU?@stM%N1e7wIkIgl-m+Ru#~d!F z+8j=Jc?{Jl#&(fv>VolU$tcu$_0EcmR-ZNYyOC@*vX@yMtl}i;#jhUU?I>5X+dkSF z%6Z`FPaY_}E;N7m>qow8MGscll6$3*WEn$-@bRRV&cc zNL;^lz0d2#gUYR{t+D>aiVIAIQ`Akqf0|XDx{iEmZF7|CALfpuH*D>Ret1S#u4$~5 zwtsWndOnLQ@ozo&X1wNKH`bh*jXgFJe_2j&#^3DfS7L7l$^l9PFlh1$liaT!>it@Kv$k%QJlpoBt{29~RF{NU z=7Sd#ONM7F%ct9T^!RH@JX?_CKymW0Jzma37N@v5*GIw#)e!I4$NDrL7tZ3rEzaU4 z<30A_`;a?N9y+}1rw8lW@Lahg_@dL=se`f(-e-g0w3GuYouy?wjSN=lD~o16xZ&yI zmG-VzmleK=t`Yg(g!hh`czSe_9-btRSEfbHHnQVUEK3WKg*S`eg-UHRP=grT@q?GE6h|pBCk?Zc$_jAMY-fS*$omEn_IQ0&4R$=<)84j8*j!} zdEby-tC#3m5-pZ$esiq4awx?KAo$Fd>yr`{{nq4l7da0Ii;uonM-#%FW5#uEo+*dSx+W^}@I~9p=6EMOU@2ya;#;^X__}J`??e zRd&%&$4cso! zc__R*E}-;8jrC=Pu@6^P#iNA<1K<@+d(t!qA`PoKWp zO9*H`obg0oVD0mfJf1y#)d5+i*9A^RIvh?zsPBd%-J&K#^R$hdHz&-l$V=N7@BF#i z$0(#ξ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 a7V|(Fs_UCOuHK_x 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ν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�-n?2G&dtN)f3SK#*;}~0w}So};z@@T zkJd|IIKe3ieG24`02VbWFRt(RX)d9eowP7} zR9i)Lr5m|Yr8lxtFkl!;28kC*63dPe&NwoLz@R(&sRjGCjS{L1Xh22+5?y7Ut&`K! zy^qg!`^#3opoMVAz3wdIfl=W{5OUO0hLSD~!H{WWf}Qn;&HcBXVSJkV^!q(yJ!9YY z+1Gcw`Zjh9bp=Y`Tu;_32JHHlaOMdr!Bw>4AP`g>ZlqX_%!`;=(@gH0$0S8a$q68a zq$vg%2~&X-(wtxs+KHK_=f$Lq2@X0s!=Ra|8dQQQ;W4p(Goi|qpybe)G{;T=fduvy zuux-4>_id(EZ80_Bs^lwz(fpYd@~Jv;Xb_dE;1%f;c$UQJV!HZqquj;z%;Wo@#r|> zfC&#KEED}&a3z|W=DATc51K4t6iHE}au zx6PBxW|=@@J21gBEg-dBy+JA9R}9ktTvq|81thYf$%X&`D`kpT8?b_S@+6BJ?X8Pd zWUT>kX$%pNU>+DdLvbvyvrY9)Y{u5rdNXdL@?msLqunyJ!J75r_T`5ygP&2!Oh}47 z={0;F`5aKsMqh+^|IzDiazh`be!&R41Ipz-5T_9~?WwCNpLkb)w58V{jO|D=%VC@`+ccjDn<&d1L5T##_l4=PELAfcn6`C>UUE&BTeYc!nok}`{+=0Rzz$p;J5B2}J$wxWu)3f{P@zUGT z#9moQnc95&?F_4!VQrY*Iu;_<8EPa(#znn8vgY~pO^fdKp!2*RoUNBO#RS3%xPp=T zKx%96CYCUiL-rzg#qa3D#S|^dZ2(h2WOc!w5WSWfJsRDbZ4M*UhV#&l z?Y*nxp*jaFT1UOInBlOsxed}wa(&wvM(oZq|#k6L^(NA&Sk$odBER2+8tk= z?&ZT?DxW2H4wl5B+rfvKR}nB8KKM%BJbCb7Fu@(MzBIi@&JHPgrefXKB{0;M6ab?K zfK@d>%Ry~v=ia)Wy@cO0<*#=vDQa<~E$f$0;e$4*T&DK-GpeC9w1SK3fs%63;bdIu z`?URjM#48g+h9$19hk-+XEsi_-Y2VGvuPPeL;6&^;nk<7Y;=5r6R4`S=V@URpul?!1MrE@XQS0x(z!TkWMgq;CNy!(Zs?WQj<(! za&!%)AOXi6rwQ+4TJ_xMFd@yUw;Vf>fj&Yp0#PC&x{ldc{Wfnx-Ed?A3ESIi48^oj zGQDB&NRpL3AxhE~lf~gpZCOsd^CW)Fox6ImyJvJo&7{DzNWJ2uffB$OP5@c}!9Hjx zjROz0h~1wL?BBh_mhrmd^2vIMcLVc~WRY){P(nr5(8D6M_#tHlWM<3S^7?Rw&v68! zR4322aP!0V16449RD;^dI%)~JJfExC>~WX84(snTpQn+AGO{`S1F^J8vIL4M;e5qX zMktx9RN@^*lze(Mez|hx(Z>HKvwCypJ@mtIMmjl@lr4L0dL_O8IU~x>p~6Dg$YGhX z0$3o;u7H6PyutcdqnL{_3FnOapdR~}E+~tlL;V0a;n;CbOU|Z3On9m6c$Nr45SsB_ zhontFK-#Vslc|?$)bE2p)aPR;#ghq#n;h?1J`w;@=C<}E1@V@n5KqiQ`^rU4UH%9M^T`TiIIAU@R0c#bDy4X!~%g(vJj-jM*Z3~ZpTcYW<{ zADmivlmen=OFE{|!9D7>iZytgknm*yn3%Ry5P(cMF?g_H!ZH&kCWBF!tQE0@AvxaU z)ug(Xn)9xPIEdmf5|rFKQl$kkqer~G*NvXtHF_m@ehkdPxjp3LU2T#qwuqn#$s~C% z%Y+!4`;i{HD<8S+1axVoU47z4vy_m3@g&$kDcOtUhdva&nHutjL%&j|j4?llo4RrR zB$Yc03)9hO)>~E#F?GbX^P?@}vEE6TG(a!LiN&n2Kx9ODY3${Dp#8#y|0QW8bLY^?`3_0WN4=C+PCa{n@|u9 zNvR1O9n!BB-S+KHxPK1dy2p}ZQ#R&Xr5X1@6>%^Q(}219Jrz>FpbYsX-c=h-DW!u&C=pD`+SxAq7R)_WdSNLqx}0lM zi}`{P0)XHFScO$J$Vfr!jA-?}IBh5fx#hB0q-r%!Pp6FIv5}dIS<+@5M?k#z%yxM*gqWHJRf z;xRcUygnyJ9;d~l)sm;4=D4Y)l2uQ_0&xrYQL!3n5_mt!1qroih{ zcUHpX$*=8`S0+N;Wx?KJuUx%7kA3h|z(mJ$4fxemQXyvC4Tv~446b-|_lRS?ah~xX zVNj{KNen;|>1Q>hgj*%ClPcB4e=^e191R?fb9J|1?^W+M!;~#*=mLn@Q%MGLczczB z*XIys1XdFscYuol9R(R@s{5T_C{bQyfB!H+VQ zi*?+wveMzGhax031Rxo}ed_|$bHm4Q{C#};*1j?F2NJ!+3?r$#G|zPqtAQv;OT!su zH_l7n-)R$G33<*ybPLD!65t$?I}k!17lcZbg{2ltBc@`*q!KVdA4*27!Z*dBFC;1HN`|X}@=I_}$I_d*37n%pv<^<RwjknwfLIc`>Zw;h_2F-Ff3G>6`HNDD z5zgSmSB!6G3V6aBprQ7apTTwDLD*R}d8f4&e?ob!1CDvp!>Kyux@MLdE%!-g6(^{y zF!B1;ToE#ytW=kz`i8%@4L7g9`UtObjv6}b5^!uxNj(=&8?S*j73910^6IulWmBgS zEvbnOz0DxPM(M?3uYNQvn4F4F`rON&d-*3W^~^)P*D>$(Ua!vX>s+j+ z9Zr|A)S{q`FlYoyEJ4vWgBoH!;zy2hIGVFck8Z5vJkO)e%F=@S}sdE%DSZo3Xw176{5%4VY$M(J7W1#rqYX__#U}xHZkrN_k&Fy zlXEdl#P1H3nplJlV7E={8~0%O`Nkjnzx7 zSV;uiFi_lj;BLwAu%h)Epb83puf+q32e+@?uCQZq3y&5=%YEriKDydXCHE2=jb`U! zP`%@rlT$P$Vv}`(VE4UjdGBWV6G)-j_uTnVkVFEb%8%~ySxzmX1>$8H($$K} zpS%oW1<8rZ8WEpJb!5$SrZmE_Pfd4hPs0Zpb&B{F0P+C0`jN^|@oZux)jWj{n-w_*BF|CO$* zUbyD*{@Q=-ro-_Ivp=h(9t;^Z>hQ7u3EQS$D^x>Q8^W`<=Zm{tym4PlGY3r~Xavo{ zLKYq`+UYUpKL7t)j~z>oKhkz##n00y0HuHeQY9t2Fvvx3tq!iqFV|M;tMgIyWnT_5 zok#FbT^2xvB@KggRHp_m%EBEciB0@E`*i%_2kVD#)@ubb%6RCsDy`VC@)b1a;#ZWM zl6?uhW1>rMnM&E@PMQ1d=*zhAB2DPaT#Nv(r{>z;Yt$5`w7wCKu<``=mc5CGt( z9=!SX*JyQryW*KAxD&3oUx4ss1+GJ1XNB7BdY-9&wA^@x54-D;fgH(@!r{c+NZ1}~ zQ*}NJ3<3pB09d7eaG=vaS1Vh`z(cS8KgFJS#724?8hLW@|IJ$MZw+G5J0csS=Zb9N z;a5jGLWoc>(xa0844<&l9f8YA|MknU_1+z4?Cbm!DML=;!n1M^s4OmN?7U#zEjC`f zRy%iF(RE{I=au-*p>Fvaynbcs3H1isfIs#XxB1YM4z$y;#%M+U0VqNtC_%Cfpz2bO zm%Cju%5~{{=U`Vm0RX&_fp!DZeJ}OU2N8-E7$OB+?)>O|vX8yPB9k9};_@xm4sF=D z)XWai7qwq>8hX>M0tIT}NZzyEgsCbRqmfDQ01p&ADa^EI^+FE!E??4I($P45dSOkl z&y&p$o4Sdni6+F}Jn_OoR^Ve3}Q~9yGS;5n!}@@ zD^;V5Gjeav&c|)*z>0m{r^ZufU+(no8R3lVox<(g z+cu|~u0XfwYv88~U(tXK!5pX(HdX3WJz;C1e8!Ex>ipA7!N|FAod1`#z)Wa{kK+<+ z#Y~L5R8`4@;OYb`edPx%`EWY#E6smX1f*v$L4%5=1EpjFu_}4YY@Qs=qL9C=^ycb8v$0V}s}U=kQ3%+1y4*4MZW%T1H9qGBIm%&9(GDMM zWt)Ot^a+1VspX?xVs3|TT=()%D`jYxIq%`(AC4Tv%7=nnRzgt%h@%0-Tp+;$^x0}F ziP?mrp-n%i#)MVM1d)Q23WHJ)X(J#28OK^ga3F}ZHi!W!+-N_SL6-_6Nv$oDPZHKr zR|3IlWx!AZ>aB}le5`pZZ1f~27-EKJh~6L?U|qPEbpIL2N|RtI4qScw4cJ7hkM!{ zL#%iVzLY``CId7~j{EimsKvaqauRy7B*;?T??I<>67uSfrB;*v2>jQl*I^VHA{cr7yjz04E(CP4Eokk3LY7A5T43uCSG?&A6T3(+_|)AYWwCsVNAxwp@#8CtKcjT=2VuZ*0_6(2fG>8BnHCM~Q_ zz&Tbw|Fq+|B1WoqNoPB*_x$3^PwM(7?|AncHl6#v9jBY=xB8a1!`=#LO#~%b@_okl ziM(mk(mh)}KB$og4@19&s%=%$bfw3&;pYZZnf-fo*t_k9{P+2JYY6;XnvbI zee9HY&hdD+8`?l3hqOkdbjgCZJfahQlU~m3$88*i147_dgx%=Tl(4c9;bZ6*MhB(G zir7fMv%qt;7rajY7e|ND*4Op;f6l+U@{k%Z(ycw;^GgvR>a1a=-Z-4 zuq*aoPdKuCR|L`h&E>(TQ*4QsuGpC4;ch;bk;FVoO{*3&A5c^@=+)yinnD%eQ6FA; z@ExbU-)_JE-hv~-L|JFg1=YOhUBvmnyma?r*WH(Se)|ZoBdTWM=kp<23<_B5U=DP8 zy?EWYZT&QYQ3iusaOoYGZQZZu+4Q(cBQ|Mh(@uJA z4t72K=J0&(yyGWYIwIDpk^mmIkvL3;n?2ycIH0LrUNL((GGi;G^4!O*Usw}b*jp@( z1wM#p*||wL1YR42w4wxpLRx}Ifxy6n1|egCoo2~2x{KaU#T}7pqWgc++^^yRgwr+P z=;3NGB$@PUhAqhPP3) zcbHg6M8If7lE?|JgbjgmxZl`~xrU~r3Pn|?e!ST}9}eJRXA3$4=hTtdLb7EYk~)tI z!MSx8dv?$FclLkzd++?S@Ar!LeE>s1yuViQ&py39)tuU#nd_{$3)z+K24}#XlYjQk zBZi^Z?b9oN~>Iiggc}f^%92pOEI8B*_MCViu+BUk{exa zuJf_SR+4HdqjY)4oKqg`aLQpTh>vme=jhy3XuX+WM%`F>!r@=5FL1v6>v_2a+l&s# zQiZ{uD4!WAQiUE|Z4dKufA;-D{#shXXAzHUgkha)UUKO(ZQoeGopSo+2-m#)^v+vG zl6&Yxfw@=YxsI>hO%C*!c3#)TJJTOJ1o>dUK{YC5_rL|?jcz@2!Vxg}lz6Pl0Ng6K zdS4f*9rXlfk2Wa6ju_{=?Mr_65rqtT{U;lj;DvEwAYlV^n5Rlz@AGl9D#mdv)ahT1 z2s=rgs^LwTH*t@RH?4lmjo%y_ZV{0J zzTAygFL&W4uf4&t|5cbfBKpj)WdsQh+@Pz+iDBnXOs?`-e|aCfZRO2B`|3~ICRd*G zjPt+c>^t{Ja(bej8>_wEn>_xpo8J8OkqPQ!!X^*|i{aVw`eUDVn=|Jvh4bE-!~4Uq zrxF`|uW`w8ye>xh(kkLIb}n!lzBE=sSCh*@%{#1%43G3Rj#eMnUQ!vMO(CEWj-shO z&t~Kt9^-RHeL*@lrsL~bXbY8uqt_->>q6fdnd!1H_oZZR?3^xF?2VKw-|}_!o@;`D zdH`<(CX%W$jF8`Y#V}^UVOjyJpvnW~5K@!|LJ6%*5JN&?AlzRS0jW|TLLow71R^wu z9LY>p;J8=N08GIltf;gMI;KTlebJBnT|e&s(%bg+>kYv#=z+!}5??3nADar}P< zDu9%sNEzt?2GqzQA~IO_V03`98~*r4|DC9?^&e2tQ|lR@+lavtBX#zgf7!x|V*!tEXE$t5Vc1af7kQr!AY`zBr zqi)JjvSDX*8m4K&sx=8|fR@8eETNQCJNnv_(D~Hr;-FSJA_sG>Uz}IF|K&CQTDQEs zJk!HRDu$&s(Q#~oI>c@6=kNQl@9?KHzKQjgD#Kyep%0T!i`P;LUeQ|VtSjilzI*3rv5C|zLa{$V50i2fII;G8f8QxZ5i~SvN#E9GO zw$42Gc-1GGs(?nhiaC`qk`s?J2ac2*oM){+dcQG0^4Y!wmkMR{zIJmav*uml!}W29 z>*}q=i*@aR#oj4Hv)OFOKo;(C_PjFEk2=wRRBhrARxyXTy)MCubC!2FUC*mU7zJ>j z9REpg|C`@Xe(>56QBgsBAp_ipQ2_9ETk_=4_hP`y0;P(HeHcPTL=e8taM{X+d)19i z`^|F0-VT>2t`%6|NFNx4y5YM1pG0B{BVcL4+}XXpXr=C6x18O9V6B;qM$vfBtKiX-WR}k)>)$<_-PG;Wxo+M2Am~5Ha$vr`iYsRWOez1 z0SrPaghKA702*tKfA~7re`xbE=57VI?0Q`JI8EO2168n~$~Q`1Vtc}x>yd4}^Kul1 za~!#JzEG51SQsdrxy*rvOu@ar;s~4kBXN~y8Ig9*_|Bz=*4^Ok}oY{xHu3ph6 z!0UZpwmc(YM+>Q8va_dA7%IBT>CHv2`RXmX9HH|R_ged`%d6`jZcp`sbML#w-~e#d zd)zDetI&(TxtDFax@Tt8a+aE{21f~BZ>=^dKHFO@vgbz+lIO-t*SwrsKAi)(N(V)T zEpN$|oBE6qtn=9Q>HSJ9Z(HH<;C0SDd2X90m}>Rxv*Y<*wMUd*J#BUW$)2zO>Zr>n zu2KG3aTZG^CPQNRc= zCx)Z>e!a@a#m3Xk?Zxv*Av7RU6$#0OT!mp#c9Q+5%9)}T#4d)`sT5ohWgSOqP#C8f z#OPeZHB1IL#4Q}c=y6$kWX||r-TPe{=qgr5KynM*M0SR5AcNqTxb?nIC)ZuxeE(zl z%m4A``s6Qf-R!MrnH%lyUdQ(;39m0UMiRDK=>5m;n0qax?anO^*BNi9JF<+V6rqr^ z8+F(RRUj_+5gV)K`1^12(MAvTVq;5_h)0&Mk@~U_>GOOZt~Cm4}M5fTAbjtL2#DAu9_UP23R>j!-opj zD4!55K1lkWcsEwre9AX(^l1CvS~KfLmZcMqQXn6*_k!M?hO>>i^+?St5c+fJl?q6U z{c39AuXXX=TOI2r4Uw8o*uQ-{Rd5@+TfPiAi5$u3F*#&CRnLwMtvd>ZJX5|oCw z*Rs#I(G3a#0a#}+Ia+hc=h3IC)yvIQ`nv4??IX?nY!NXCWM}Eo7Fu&(#Y6jGDxpBp zx~t_giwK4Kwm*JqoRQ)}z2xVu$$W#2a0vMif8W~^PvDhHlv zKrzz|aqXS!*FN^|{R=;_Kk;wcqUTcX^!x{19gzr*G(pG2#E62ij@Ja>*lT1#O~_1o z9RtE3_l_5qbKDuY9bB4uOYCe+S?d#S#Z#hD{ z%r}T;%4PGGzIxaTO?By-8P_~5d0N~pNMgUq@8R&%1M|t^u=xx{r0SNewNW|d^CoP4 zUx;vd2nQm*tdo6&uNRVglW8^_6)SrStXHn>zy#Axf4d!8dGS!=Yxvr6N_|xYAAagf z&jd2`m-J{)CUZwDC@U8p3O;--;rl>OuQm~9=UOsC2nDDZ2ixah~={^k`k_AR#`#J{O>B2@d(9Gz9OZ%93O_-rq>`zN-63$R15vt%oMYz zMclH9#DWGzGJ%Ee7Eb*7V4RLEI z5yl?J&hH*zMGHq*3JIoA5@4Ik)FDyeY64Cr;9ZXY~fAE;=#9=ghg7_tEDqaZr_L?>r$cbl_RI!I9ZMz^1*?E zlqf<%@}!TDFifdg;Amn_B=yY>kE04xkECkRj*E!-T+=m^ue|HcN6gvR=K_w{lILve z^U^oT7G!mpWgW)Ulf9NZ@;NJCBvXy0@1kUl!_8D6fdWHup(+F01Ow5ribtQ4oHe_G9c1iy z{qTLplWKk&6h^f9s^ar~zOB|neQ=(8?~W33qqmhWjdCZy-1U>|FJ1+VOlntqwu5iI zpIvsB{Flek!EXk=gc1y_l;O#SS9X>%l++)m`Q}rn-_9H#bWlLmF3WX@nrw@v28a6E z_OaG7G@?TB=}y{1f)Qc(;ogz)(Bg5DD!2v9YH=U=$8)r@Hg~fU^>z;{Py*=ns_Syk ztLFXRNh|9hUw#b^GGNMUPg-@8_?eIWDj%~T>eKmM%V|6R@$*sktXo{?>b=&*e#sB{ z37>N-00;>%8)XsmThk1Mt*ObsDX?vQrg;di6OU|X{N~#`R+bYhxUtKm{!y{RW z(_{Yz4AJ8idIu4!=7x9pAuRUn*3X}r`YR9hMnotv1f*qAo!{S;jpz9D4C|t&7h4@; z@WBe0q(lsFrz`eg^1Xipt}w?i(|50L%-0sv z!n9ztbbK)~A-Rw=a9FuK6VM!1ktG2Xe-okt99-FNhVBI*Le{Sbj90eT=h<`j&@_5@ z3L-Pg&Bt>ePv=zqt62)y<;IO0I!%C53PwaDMRKXp!FDh=yqz|v$R|&)3lnQ30fr@X zWE?8W8%a|XR7(ho3#sXXTH=CxBo5G+uY6xo^_6n6jmbNCSuIecoO{9g zoPGWH&Y5RpiFV5{(hZ>~Q57pFBIa~h`zlnEN;NU3z3dq~$G@F~+wW{`<1xqW-CA=J zJW-w`PeQL1t}U)TyuS8&$iIazTOY!pV~^CqxK3G&sAU<7>k1E?_UDHkzkDr>d8WBi zQFz-ReT54vm(k*JbUoLJZ#e_DGPX(%t8tviM6FLK?ti{s_r6!!JzVSBw_Vm5@_bTt z?^6|dDa}#yn{2#f&o#qCz_%Z9D3BhOJC!8roQ8Ym%c;ax?gX@-|a}6Ip(gpD_kKc$J@W* ztDysE($DM*MKd&0pJXMSHi!bFvzbAf>t`%~n?;W(zjAb#RI#rT$UC;-D7&VwsWvit zv0s(i`6UBu>xE}taoNJWmX=?7FJyNnjN?$6r?eVBnW#6C6iWa;@P%bCMlCQs-nsAG zpw}$Zn1jl?Q@dnljYx-6#mUc*Fqp+8SzI<+x>}E_fW=xe56f^Ym$7nTjBj3fB}e>| z)(VXd^C4%MW8N32l9l;3w8<$9^o8_l);nopW~azi`=b(H_&b5M*7A*i%0d}OOjQ^p zj?DciQx8#)0ouSi#7GjWE?A@;Kl zCsl|#tc~QL;U5cVE@H&-q2+jgD(@L;p&&!s7&&Ut2A}~7S6SrFMp(JkY~N>TnV^fC z*Yi-l0V{o}b-t}{)?U&OQv_=8kD=&P#5;=^rTwxh8GC7@sMj7u@tpB2F@bE4(6s(apl7wx5AS}|$DANGON`l@+?n@ALB*Bw|i!mtY-^_HfLlziOlqkz9K@^aJ zj{KN45kZM)57U-zn8JfD2&=y3HVN+wQQ*LiobXlJ*#N1jePp&iyqxSeagxy&%iGV@R1 zWf|-|TolXIH{C|Q8jp;dF8!jhF0?hb+GOZ9LT<&XrVo>nk?8BVif%hj>YymLvXIq%hO@5D{3C$Ap5!1_ELt-}gn#W`C|^>yzD{W>!` zm?ts;V=89`Y8=Qsiv(b1XXQ`@fdoF5f$lr4(}j*Y!hlu81J7}FV`u;cNAA+t{76Qv zgj(&uz@piR`7KX#+E?r>m`5I(b@;Ok>N7+=#vANOMV@*6Cf#Z5FYeqYBTFLmSHID9 zmU~_Jn{PWfL-D0ZWMV{8;#&)DVw8-NqI!2Pgti zRhX(cjeyVTM&wt2`IR9qcimQLvgf=MbF4vd^dIdDm+eLM;?a)xU6OBYEMT=yyw}t} z?c9#ada5K;^+OKV4P%7dhdrk1n(X?!mK{GJ~VYwotEVN65ug$QpHdS`^a;g{W@oy92(~EtV2LM1`t@{XqM@4 z!3#{xzUuJKeYhK0PV=B%^f8lm?UxWu zhR?itN=QXL#jkrdCl?oIoyH=gWVAQ4MRU?7^19iYCfAY^IuBDl>v}aQIh(mLyRpRK zA)LVnw4=ub_GD%ab*4_Ik+3gm$B7+e>$ac#F^_zDz1ID{r`Dc zX0c4%pQr_@&{l+snr0=>r|*}-8)n`PvEC2|IoF}9qrLW5xm>8^fEJfpq}Gvkl2;9$ zYx|wnkLy5&V~s&jSC`AKO0VD$IAQxu%dl?}t#a@}HRgo%Eb-YT}_g{^8`DI<`H1-*;6bPWD;X*e*(?pdp03LYSb`3;~S*aLLOmwN} z1IiK?cz%8jXHa$QUWYojZ|Ri|r>{f>0>DuN{6{FZR12TWA8Kue zt(zt?VVBJ-lLZ48Fa#)90l?`6=_8lA;^s)ZdC0GN>7!4!#N9L&+sSC>MSnYI`;$+% z`>dPYj{7ktD;z83dR*Zt?}NBVd%f%7m{&YyA0NNWL8DPbws`FPt>*(fmGx-CEKf!^ zb}Yzol)&Aqn4?Xfib5Gvr?uYumo%Nssnw?_w^S3IxSU!^LP;IgGQ{B{8if#sQOZi~ zxE`h;Gg#$AR`ld6J2}sTK?xK*9fv-8ED#AtNbeR z)@0cc_lNpav{7AkuV*Ob_*T^fBO}(WlA+5MT*LuEC5o6hK7W}Dsb}aJHocGqzb~Wg zh+N8U!qKr*oG%qsF(aXMDl`xgVE{xR&UE!!%I)mn7?KGiXN*_Ed5dk_!TPdohtoh~O)Kh8d8YIg7K zSRgR!j^62N^a1s97$}AusWT8~0gp#SjvhVpz`Nz^S>Jz;@KuCYgxf`UbWFc4w%xlh zWvLBskGsFF*f(Jlh$WSP+OzM?Zx8;mM-o}dWXKO4?(C7$&0nn_3{wBjZCF$zR5Z)d zoL_pbN??Wo#6=K_L@6chH*)wZ?g~AK==KV;e#(P3{2}K?9%~p6jMaN{A*l9_I(0&; z97xq!chMe4HXnV&Cr=~f!t0eBrM!xf=`$brMP8I`Jo>P{kMU%`Q;SmT#PHRE_VI8w zFMQAHuwdGUW^Q6kqli33vVB|S zPI!YLU7CIaSGVw2=uYl5XqO?@L!Il>C;OHYNkrXYtne^`x%2M)CElX5bf)zc#ac3j z<~CS5+h9{uQQ^(YPv!#*pq3k{pd=1elAF1jG23`Wd=Vg8QDX4Xt{>q~6U}g0Ez3OH zE!!8qx>8gVS9)IeOjHiiX+z+`5F^O<*X zv&n9R4)5M_$2Q@GwG2%R073;uG!YQ!=1o*~fXFwQ-P0g5H3yU`XSN@i)k(D?M5+|1cLOfH&4M#B)twX}QdUMJmN*XMn4noAqXvlPLQ!J=n)MWVg>z7pWb@r}qv!#d+(&wTF2=_~SYg zbzn1^DssSvDX87Lk(~JD&80L_C)&qjWaqWjKTprvudzA;D<+`UD z{B^1jg&etK*i>_%ildco@tgae?;gD8!{7%^()$XAl3(G9=nu$kS~ueLvOa93<#$rc zp4NRFXc{>d2J+;CgFaz{;OQUV=<@ci^*m2ltccKb^=~sOl%-)h;RzgIXb?n{Bq%jZ z2*fEwGnR+1_l-LI%*fE1n@WKmTd{_<$?IJ0YFE6aX4sJ3us=9F&3sOr(%PT4t#j81eqRVs5LVE%FlxJ1C+!cq+~oeHwsH zDH+AK?%&V%8fomZtaScq1Qce^u?!n+dg$GUAw1WOag)_a6Uw_M`pH$R`n97&F505O&khxD57 zq)Wa#lz4csv-Y};vR;pv`Hyd>`yD>-iaY&-Bc9x%)xC3jr|?_+z?y2*Y%s8pkV!!_|NB_YnRRuZgkV zpu0x?d>b{rUV9sYO20@yDCui?{V`yfc3kzsqy7c{sdJqjSv|^#=JsG<7-FEW%na`+ zbvVs}8dNU8#0$`BzLhS(CFWqI2*59JM>3k&42uqB9=kdtX+xT63KNDZANxr#eYGwf zGc8v}^Ed-?lqgMfLK)k!-z{@eEXRnkpHI56yuH&foU5<|3MlYL1;fA#k~<927|QfR zbhC@o_lS8LZ|)N>y^kIw(ObH1{AolY?C7&{u%f*^L$Fwj`~DVp|LH!clSXG8VrnqC z#F?SGX5(?Xf5i9<1Z^gkX&U07JA{+b5n-3!yDisx#?t-F zIVaUM|73&7jhLc&X?0N!*EAkvd0c`l(xisaS1>d#j6-_?<6;RFacW(8pJ5wlvi`TAF=wqDZ_8-gN}#em>E-@*zGo7#ofPP_ZO~G zk8Sn(9>#pULkM9Yj4=?{$70yIt9ExK>yB{AU6zteKIu4Fv00u^8}i^lGyrI=)3`rm zqMs0>ECDMp!g6mz1am)omfrqV>$n+rwlbmL@1A+MeziuXOVu%1g}&)Her%gw;`F0I zop<1Y)ruu|4WAn*79k`<_S|I5BR39qzs2|8o)uryaS+! ztqG+Tw!!@q4-R+y=_{2Z=GQo;Zm)3e@|~wYVb-g--&QN%^p!XdUwE7Y7a<9Rh=I^i zGo`klsL7w94-mHSCG=@)1r}O%vzushu%UG9C;1q>Q>&m!9>HSxV|&}^FnVt6lT3}Q zYuY_vY^rBWL>UtdcY*Nz2jFu~h?0tXmZ4fPKwz^~X~Vz?L+x(=Zu!Xjw@C)Vqp+@JYKGaWAMm*p7`!HdX;AK7;-Gs|Hokd{0rF+Dt|eUK&DxG6q0 zB*6j2LL|!$6k{C;#iTltkTv5qSJrQzN6xBOlXFtpVY+A5Y%3pc@l`$U9K-9!lTTK5 zx?-scBt!%OXUt%O*YxlbI_^`@!5UPC6Z8`eqI_A9HJReLp&U_vJE z@4xUEIqt=JEI;)X`1E9R9&W^^2B+(Fm_m+0r)~$H=IMEsyOO1quJv2!rf=fYR8AmR zpOYCFMh(l>ow^lcR+ga3J{B6q&AT+w1a*dplMTo}VXCj}O`&yZ^)sli9!|P1)?L3nw|HF7KYOjQcbZ1pudv(m@^OfW7!hPGqP_)uP68qn6+F8tAtOC}={Ua| z4$mu;#g;_Ac7S&z}t?v0337dhj_p|Ce~icPHvL)GPXa%0~cz<%Z?55jdoM()++t%__2h?N(X0_6*9&eCA(?_x8Qk{zRl#7bqpT{78#!i?YikcNGCXv`U^NY0phU(^|;Z{oZ&b;Q2|B_O| z*UKOXa=X`ZhG|YljEZ{UTMT7+e-z_|zFK4SXmv+~&Y?sxsN&G`E_GBa=al@BXZq83 z#}{AOCbk)(d<;Sq#hbM!%-454$~Y>mI6mbX$-nOxZr8`Qqvb#CU*eCyI14F% zqVN6o+*f1hf-(b!10tLxfw@ej8ArP(+p5&>*DERuJNkT-%Xh2cy z4;4q1t;r;$r?f;T8Eux{J}c^UKWXFayCaXXel#Q0ymiMZDtrgLZN;0|!j_C{G*@pk z6WYAE4Iqs%42wi*fdqvJAyT9xjIlbg>w+D>^w`eM_sdO5__4&;rGr)fhJCCXP$ zWu)KtL1{*7VPZC^P&b#Q@JL#v+Z_sG>&Ugr=Hx=AX)E-JXo-ZaB} zG|b_YDvP*JRhJzKo)|4OXorDOv2V??y%u@)E<3T31EJOJhf_tM7jImdm^(wDwKDUg2DMJut@Mhtoop`RK6e zc|7?~OO!-v_3zIBa2SSxWocvI`1(GduH4eUfr@Dw)IA7_iI4epP~8MW ztT#*SG?bF6a?n6(G8s5hf)GNud}p^^Bbz-@G^Z;;VsY3nI<>=gv^LLQc=5+zF4L|a z!!LRG!r`zBwHly1TNDeMdaT18tOZN&XIPOL5Fi1`(=d|IgDC*DDpZUS8D917<@7gS zAfFlgSB4a#dBkV7Jazgixt?Z@dtV`oo@1iag%*XvRfFc)3+5xiXiJ|?$oe{=9LCVf zAfU=doyIbbLtFHndCr-=uQ%8uRW|@bRRCzs=bCsW_3GQNThfW`X~WsLTNhczf74fT z)X0}AfaS@GPb&Ay?xNNWx9;bMu+(AjL|6$tKG%eJy8($^UYJGhoD1PTbdYWuGZ%K^g1h;ESCdtWAsU=!=KSs;z{qHEdDjvr-p`}` z$5*EQ$c3U8-gW1eE3b?q!axNON=Kzip#XiP19Pm|+J25J&b`(t&N@ds=i6nd;&E3k z=-u)hM1FAy@G*SgiHFmiUq0@~he}*4sX}Op;JwMxeedVehw=!dDxgo-XXdoc2b3wO z7@zRc(M?aJVU~lm3^5JEuFo{w!Kfv6eVY8bEP5J6FH@^{__a^lU(GqA&65hBnA!zZ zX%urHwm2GP`;Sg+N-6L4UJEXl}q#ruW4xg*iU=wZ~8a~ zPIfwX{e&Lp$*!)xm05Z2VOK=N#NoZ$|tv zRJ;b7I{a}e2$%OioW<%dM+N^o@4lbjuMbqT*Sp=Z=aF(kW*J%aKjG)yv9xG?7jc#Z zG+pfL{gHnY{Ti-^i!q9U`a7VM*{Lc=Ibeh{WiU9zA9Z_~?5MoL7}SAN215BLcIQ0V zYagkfGxKWoa^~y4i2l|~bNL8$A~AAcS9L5yzRb9TN7S!RtDlu;vv=Al|jxTdRdoQCa3Ped||Gv$rr^VB#9!ZpCaq<^77 zFv+c>25Pb_CSzE&;|>#pT+^SbJY(hGM8#9)1irH1;(6<}O@DgG{dJn@y3}}@=Pc!v zzb<23wT4ME=mzO=v?AngO*4JlD4(+}a0t#72A26aa?fwD&$WCJ#GBE(?U!@AEx-I_ zNnhIXj_!sAAOs-LB^i-SGAE~<38NVp@EwJLCZa)ZCuE{y4p9{A2&N1fq2Hp6qE2Lm zrQUt`kySG?gZ17UzMIaY^3@(PTpUaYR{&o)l8Y@9Cw;yDI%5~o$i&*2^0xuq z)#baW3kfl0Fh<5!4@Ju;%^x>`=NbEMdrv{$qUJFSd%oc@mJk;5KElo&AwY+9!aT4(!03Vf? znRD5W_^_X^ejhwcUEHOkRA{&P`JTSd`+eJ{e$}}ggapcEaO7Plg_RH!bYs4*@7<;H zAzx?v1>1<%91=C@r4PLIk%$SqDBvfh;#xiL!L3Wtw7=O4pT5}c=T9BEUQa!bd4^s& zj*un;*PCW{`c#LwO!>jJws_EPu2AuM;!NKA2M=Dcu%a!&p}L6S9?Vc*tw?54Mc%7d z&JCol7LV4)m?`%~!lee=5H=q=-bm<_D*H_j+T6F#{mfe@lG7BriTnVKZ^S67bTdf} zUITj4;l6yqLbNhEVqK_I`>vnPtG%5?B?1HhZtG>L z&;wC=Uth3y#MV${w*7)!`YN1HHOu6;XuBOdGk5|7 z8ypNZ1Mv1S&~ zb+^9gA!YkBP4nUFv-}_}yJWXt^W1BllwXp&6%+%ghZT@|d3Ibe>(5_ru}&7s>!-`k z)&1OO$t-Y2*$VRpQjfgJKVFP}9IImLsuJ^0eUZ!~z~vT^BBp9hUk6e3^Xu612$yIV zs1V5zQMiO_b>l47MMTJ`LjgdChr}KeaT$h}LU(AD8diSPJZDxG_1Yp@5zmWmJ*Ka# z^qkzB+6@%USeUIH_IhM}LC2!t3-$9pqtUD}Qm%45Zo};~dsObIe zwE#6ui6$y`1D4D58~u(z*bp^ZXWg%FH>@gk&T!r?`SvHRxGLBhWdM$XAjT@)$OXf> znEGcrHyDuj%z?a@qQH!gsIlj1p2LX^s5Qj$T`aP=6AM__Kuh(x6AHlr z=w>CRztv6lRd&4$&*nCt(Bg>Di2Hmey<7wFRP(K!S)N40-}Y2Gp2?J(_Wsnh$Z=4NcOa6K7! z4xDz2GyGBNt=r?ML)=;fwOq#GvMGVb-LW~?>Ttcw>eMbX<)l;-V+}}`_{>_!Dqh?j za%fj(|P99G@9iz%7)VGUp5Nd!TBlb_sSt`k00`1l2NM7ha&?TP zj;jnul+3!>j?eP4>zvKeoR)?%!68W)29z39{h=rt&2P)eM25;{lPQw6$m-T+Ojduj zVUJIs26${n4AKc$UPdx?wNaYp@`Wq4dRD7)A9so+Sz53Rh3Z|=a{8s-v9H`t7RP8q z8s%nI+quvD?O$Hzo0GrDWP5j9=A!?qAMAhoZu#_DGC#MGd9Ht;6K-gGDN~#k=I3eu zFL&*)W?N@SFjB^C|KC{UYp{f37@cMYCJ@NonE{MopgU}s}X|Ceyu3GQcZuxap?yzs(kxa2uqY|OJy4`S4{!4E1Yw!1Q9`{h8 z%x)uR9{jUXL62C=U#$E6!5{gpUAp{Ydz^pDXychTcK03&nixrqfE+*wrT~URff39W z8MjWgJGSDpzC@`ssavBXZUa9aket%2+>b{o2W_e%jqydjZ(xblM;v*(qbxN;Z=Z>W zEqYYG9JE~NNO!~Ho@3AvJz!H!#^hCNdYk?-!$ZE*?p|~qP(&{gE<}<2z#O;AQ7;jU zjq6v=9Q%^n!DqDIZ}TdrKRjRSFW)}-OLoNFf=%HVvXAVYyx!&qzp@g#aR2#PEB6Tz zYT_k?4F>v>;mK{NGj+|(sx&Bpf5^3S-TY3z)|){CerHrOhKa!B7p5l0DWf8E=UVGr zuW3Ks_Buoa4nu&3FmhMy^zdO^ir><;bedUFD{l2?-lLyO=~xc0JIEzfBNsG~F2=aZ zQsM|e7^1MIp3jWo#aCxKR>x_NwdPSG!$Y=snER>I02K1=?$3B=j*4NldC*k9|J>}q z+ZQ;_qaMoeQ;r%PjdKSd@GiKsCrGJd&ARsCcklS@8wp*x)@|3Dw!3NRITdqTo&_h4 zSl9ckUq2to@zg0aJL;L+G8FW!>2DF5<6(;9Z*P=%v51J9XQ!(NZO0J)go4q_?#?>I zX~#2VAyN>UQBf5clO)6Na|T-@&Q z^V9#ly6$l~%itxZ{qkDQ<^H%kbY*j+~Z}I%HFYE ze?9BvGP~G{0)-%=L$bb~=Kegb>b8TIeETdZQUJ2`&L(q6!%5JZb5CM{L5LAd!UkYE z4i&2wCQzwi7FzR62!-QyE2(3Y02>kdR4>_WM-fY=f<<-9Aq< zuTI}i9Wjq+KF+zm;9FmX^LDi7SQ#ETg6BQ(fGwiZEez{38Q5$51h#`Xaj{tP$egVs zQWc?~j39*=D8e(IO(uq>(H0ozy+cp2r(iT8#aaNUqH7@>xuoNxb8UaURG)hm{dRNW zHg(J&ZM>$d=W{!PX&B!A(0*=e8&$Q=+;EU&l?pRp2(oBEjJ9Rv*xvia7klq~(_VGU z_DI-n;|`Rrgg6~R0Z89%Q+4Dx+Qf}1q>+s+2u z{-&KvP;H?vWiY;IA)+s-SkrK_9DH>p%u*9SkdX~U7>Z~{ID`34w=TVEs(Nj**1u)p zNuhD%cUmA+mJtMIX-a{Kl!%4hcTPP_=kOJZ??2Y|!z=UsW?w*k;Ohb~DSgP)Y1EM40_FO5+aatrl<1a zHD_b2j$g!Y()oD5WS)ob)A{^9ZytA6t(`2-Uu>zSsZ_hSOZA2BU}mPcqvYv88BGAi zfdkzB^Ion?u?o0w?`<5(5E5K4nPZxI&s9HK{M%jpkBxJ=cXqtM@y8oBE}x%sBy0EA z^Ty))&&-_6p2a_57k$Qg96qe)T=_d1>X(=4pEF9QbQK^~5lI-Qh_cE(9ST5-0c{wJ zB4WngRkSZW1xYnB1zC)Ft-Cy=cW%PxEc0R8yXTL1mf7m}<;al=)U4M9_mQhZ%@LPNoufeAusoRxxKp$t_qy zvNX%^Yojh!uOErC&X@!Xl(>$~i}6?XzRY|3x7h8&Y1CuYJJ0txdWk=Wtxs03x%O&3 zzQDh7|M(e(YIvZ^2mbrZX@#mH;>a8R-v=#JW#(ogGb;dtoDsxBk*SG+FZCE=5F;7D zjGPV(p7mEKzOIP157bNCH`#?-$5=Wc z4AWoPL#jCFpaX*wI;PU{d(oxoGd7Nr9NU?dBf`eel1x&2|EUUK(r z_Q+Mp6G63v%#HiKLs1l5*kY=)thtg6tT#=y<>tbkxehJ?#)y#0)eH(zGWS~NudQi9 z7n5bOkh#HpT>CPoT=|~Rlfk1iXHf5vd~puB*2dmLw0OCVdbOtfX&CKJvdh47Efet?e2H}_3y*hgP05S0XV6q{_!sN;uC6Iyd%$##vf z(jxqNAm;RiZju=px2b>svDTkI()K>T{R`+?h_zB@xe&<6Q5@QD?r3!gL|)_KOIJee z5C<8@jIM{oePN5f!&;x?R(D=a~HK4q#?^xxOEh98#Ua&s_(NWy-d@Uj&Ji`7gl@7N1QZpjMiOXnvVo)*-eSd4_%*bAQn%zmwb19v*vELls-1m>yx@Xzni<8 zNXBd@FiHUEd~~@OuU8j*8mqry1TtU-%{IC8)9QR+#ppn$nkX{2dw^2n&2vP*F-Qlj zgN}e4i2iwMW3s(E(f1^lx|}w;s-;#elxQo4;3UlScA}=J29|!DY(pzFWdfW%3+6>d(P5Z^k;J_Vx(j6u8^gJ0_FDTI zZH9+=^0Mb4wQ8-#ysibt#DP334*4%!NM6K*{R~mL55=HBP=JI1Z0R-|hG9Dm^EJ}c zGZbLJmfKC%-Nd`rNQDq@`x(3e{%jvmZ*m&Lf*TAUo$@%sZYrG2;w$~hZMbfCI;GN= zTFgK&+03%UV4Dh#G((1~NJWpze|cf*-+j8Z)N8Ie5Z~vOrLw9Pk+bu`S=yslPd|H^ z73C-&qLX79r;eWH61|Iiueyiq@BQ$1f6$4z3G>p}v?W6$X{S+Bm&bL85^9YTZZ@JEy^^ulhBo&0N^@wej9(`Sj`?UPY&eb?!9| zJVgavl`nQ#ede#B9$iqIUy4VOt>RJ>c!k!ZVFeWC@o9_04 zFijJ3*93w|VL;DEH%t?5dqIp}*G9duaZ>ig94sR1#II8&JChPgw8f|DcG<2MZE#;U zj@mTB;WCpmTn53C=8V~nmo#mXyH~@2i=$bNhv=Eiwe1sOQuul&Obua0&>M{i5rOII z*Cp#t)r;R#mQo4R8t}46UpPi0L=hjThC+;(lDfwP5zVYmM)`VlyRJpX=QNvqc5dw0 z;xS{?)6QIjQ3XzO&1x(fRtX_&{sCUf?UrJ!J2Tw}@7L6TxkxiZ`pH2;E#d!myyyn?WzC zIJDpOqh?SP;6yFVeR3#cAf7uHz(^@{S8c6xC}~r4mFHY*t&t55C4zaX8+X+ zdl~4KM?E0&z81&D%DbRKr484Uhx=^S$nS1%;_}@~(=AreP|vHqSadDeLD(cx944AQuZ5hB9K?BB9vu=+?x zI_hm$x|m^I;M9lgbzuK4C+y#~f0xw77=#bA`E^E@goK2OG=bK~14SDoQyKQht9qAk z#)Wdn$!wEn9AQgS46@$*N((sJ#efeasDj@hCg4%+Kmko;?4;*?+?hvqpSQ2)=01=5 zCUI2`Z?J4wsk85v6$2JNtpgfWVzUoExM6EZD3N8#rQ0@xt#WsO&;zTHe}kmv!lRnx z0O%%cKJjZ56U{#2ZANcu(fTE|;+%^E*~6?|7O6hJes9ylmd$DI^mkAy>FVYYyPw_8 zo=$tJ-Fl~8^mda5KIScT3~Rd~x-nR8<7}op$)1y))>%t8h?ZB?EEc^gQ&5uA`PBUG zm6lmlIbGH|)6E*vL!38u`KvXNaTF-j2*buwGtZ`%yNC5gZOEXpG8GtBqmur4-tG7&Gwzr=I&G z>EV;Z2On-22*Y@^+&PdT;6*Qr3@hidM+VHAc_9z0 zdS7aerM}q$mwlQB?36}>;gx60KN7!Gmi3}t4UkDJErj8eHW8VEfIDIGWCJju$vaIp zr3B#RIG*Lf;BjQcPKmd;jU!*b^WrjKFZvXJI8+lviXU?w9)o@1qr9*q(a0nrcbZ&w zUIV{S=CziWmkTR;ZQ%mhFU&#A(PNe#)PxwVwHlyWWd@3aK~48lMO${vhl+a-LZkFLvy!ua)()xom_MfK-4(hf?%vEzzj% zZm+cVjFl~W9JfEg^%h8MX~A*(i>wT;&G+S;tBPHCHS0K%k|V#kmtixRDc-Hf>CNeJ zGQaN0vpo#GCsVmTtw`?7sIbJVC07+ZdQdw^j0v%lV7Hc^+sWexs8Dz-(Y z4h)z~1y!^N#R(@EO%jQB4Vozx1S1ShR!ASsW9bD^1IaZg05Ea<=!pgp8zk#P!_dl@ zs_+6g<-Khk!M5Bbj{M~Xt%M;aGv>}OWl3TpX#g^yn0>63mSfK2Ht!y{-)Y?;?)nP?t+eKlzBB#X7`Ft2OqBphg!}G+B62PCp*)a2|OD&RYTxie>qLq7>Md03?#gaKHy( zr;a&AKtts2WX`}@2d1MHgyArxEMAyGIo@MW!rn^k<$c$5Qb=FQZMpu3+OWydf6AQu zm9Kvf^>1%XM6r3c%kq}(#!Zg8FQ^rW@$dM;?r=2cQ>qwWahi-``C~Zs`$2U~`0ZPD zK#;lU+E+p=<#Ggy)`BtPa@}3tpT{www1Ah8rTDSB2Cp_*;4A?1fH9b`D3#CDZ-!?x zk|vJeit0QptccY0HUy4STiqBj=bbNA()A6meZu04 z{^kUWg`#y7l|I;8wP^axyx{9rjM3V27ZJl7S3ums(u24MRRj$TWaLWcMWIH{EKa#zmTLjgf7?=#ddEWovWtaSaKW588X$5?_t4ncHfTzkLaM6 zJx?#nD>1X}GoHF~UpeQ94wukO0x5ytR%j@yAPgZWuU{W{fnZ;Sa8zL*l?DI_8P8>u zY197jQ02(pLv50wq_?nuXC1nqldiI(-I+h1176A2+tKbnKcWcJV7|FKxcAzbGCU@vu;X4im*^4WYPJ{R8Vv-v zKF0=ht7nGmRAA`TW4XyeGn)4_TulSK4dB&V`{b?OQ0=bw10ibZL|)V3p0)}$8~CIb za53uCi6>Il0*iBE{qUNvo9k|KGn4H{UxU?hV4YnAi~tHO9-G&lqCV`Sp|}hR3N;W0 z-~+NhgX3WpE`$e}KvA9>~cC*P1mH4!OYXWH{2eHiX=s!}J zDlN&hKf-&h)|AmpM*2rGZk@V$7?-cY{*o!JdS8UO1l`e(&8gGWQA?!?LyI2C68+XW z)8JpWjpyQZCciRya>|k1^aGA#R0B2ECPL_zP*Bhaq0;>a6KA9+3>UE|4P3R4JsW6F zQh*^0ct+!aDherlJ3J8!?rWE~{7xmzkn{|Kjb~_tAu7Vp zbC(f1`J=3~C~bJ`NBQ9Oo;!SyG>D0{@9-vDmZumgR4M{10Rj<%7)HlNGX_-*N+M~> zZ}7Vm*}B8vQ0>c=trxp%M7OIESd-g9;K+cTbe9rR`{8RnJ~Y9<4a4@z>N8%)jdd4M zp0b_-C|yvf4+PK!JlDssKY+V7sNE$l(62MhGi)^ik zo_zaok0opZW6Z%mjV!-LM^SFP?Nb`X7&GVQprT+I%ktb8%(|)}5JiWrz^WocoKFwCFNp-rg3fvMCNS z;h4G#2|#e<9%XMIY!zFnpSEm}X^C6Y?Cla|@q)RQ@|rq>rI@FjF+h3R@rO5AlgUD-Jv(W|;MWk>aI!?aDw;h`JTjKFtC4 z5sS2foq|hT?M1p_R|CbMvO_F(bQ9=dRcJ|xHnPFPp`nont8mAA}!`X!#KLN3A-Z9d~I%%n`O(yCoRZ4wL zpN}XBz(6K?qeqVzseldxMh>C|0yFf0EcfP3^ZnJ^)_01kUJ%$~ozrPGrLTC^bsHAO zs;r8j^LzK{vCgL4OB&K1IcIIU;U?SBc5)wBE&iGV-G5)d<%|c<$g6G}QL_|w`c)nj zQqp=4S76I3h9m)~4^j?W9DD{{Yh@mD3 z)Rf+5eGw(gRe7?^3G2pryyx}~;nB;G)jc5yt}m~}hzz6fxfeti8KY`qa*`Md4DfHL z@O9sBuUf62eq=0yr-3zDW@xTZ3fyftsDc?W3`PNg>!`$pczeSf>GJ8a(5$>RZt7#vG8Y+(pCvF5&(TR%SEK#Ogw&lB>iui(j(4*`{9oFxtE zwX1A~TH{n$uS(t2wzJZvo*6D#({}nfyYw839G~>!+>lp2x$05J+sC6k> zESQQ)A%Ifc*sxCkeV>JQ1S+TgIkzl0UOAzH0Ccji5VckrlP>i6i#HtiHjgIIZ~7kl z^C|9Mi2#8Om4kDnDBVj$R$Uzc~&1V97-0n0D^!)usTux?3dddXD6Z^Rb3|*WOmg!`Fjp0%2jteX*jX zWI!uL1j8_d=z{Af#d2Dq%BWvDf@>H+5C5T%rGQrg<(}HmDX7u zK{7~38>CD#Qc{r+jl)3 zKK0y|_k=>juY=3J(l>phZ=Mol@%>HfXL4lc5jXmka?Yd8NA$Tqb(!^c1W=?>;BK{7 zvGj){=7!PhXI64NaZ`-T@-?4N(poV-)=zAzFiLzIr7d1>IZZQPDmzt1$Wd5u{7Ews zA+joEea_GMS;xUZycrXth>;*}0wPH$1x%Z9&jZHK-F18#k1Dm1v!S@@BoCN*oG%y` z0gdEGVSsK@SUE&*@bWLS+W}>$&v80GBO>dht&y6+Fc$+GZgINPi%W&_*Ap2Pb*e(N zmcL1}^aXbJ8#J_`NowSx%dB#QJHPtzOxDo?8ez~Ky3RPDMWKSeYug?&1xvIE-<{m> zyGgM1T{-nnFHH5NeOs@3(@VZqxg|lj{>-8?lHIIs{;IySr8gfgUt5RYC29h`F!%6N zF@wr2nyI1FrCYZQj|_Y3Cf(wr`pYOdqjI31^M)0-5IamCxjK?=5)le>M8O!W9WfCG zBaG{`W3jV+q~11<^w39+uiqR%o*UtdvohHxG2u-Ich@!NC>GV;T(-?h4uL%%F30Uy zzHz05YFC?dSzSvmX+6@_p)`{7R+m)UO!eaKY@Eh89n5O>_B6*j8O#n}m}{aRNm{7t zG`jbW7&!5dZ@Onpxof0(fx?d0W|m4|9@&ILvw{oora zFmWpV@_t%wZH`M~W_g@buV=9zJ?6bU4CP5~;s&FLIQ^as!ITX8EJR5;0<{S4_(+iA z-CjAIKxw8h3@TmNsK}b5(#3t~iq1DDnytp7D_2}+nr2bR4D_|%Hz##rbPE6Ga9Q7a zwBnr=l65NK@*Y$qCYEF=wMQN?a&E+zBg8<4a}RWkZEWGxJE#3*Z_9dlUM=}1+-x3v z0>7m`4C`{Z4Y+8jtEK`y2~@*`z!mg%j5Rx`=P_qO_s2O&cTXw>y<~cm;q_&z<_P5h7aU(lA+Ew>$=@8ieM*Bh+ zeG)kV$&4xZMHZ;%l}K&|GP!Z&7lDi*9VgFdky|SKz*J0XYA~Dw1&d=BN#8(vJ+b}% z@qF@mhj|wkp4fYZHM?Q_82+aJ6;6tF1|T)#Ya`>Og`!Y`9v4*PbnqR@6bh&~R-dtNgWa)v?p;(^k z2^Py`Qh{6S=J?}y|M>OSOL?`P-&edYl}@S{jM<8VzFnb_xggmaX>q_E=rH5p`YsXu zDefL%+cBNg08XohibOz7bs}2xG|qra7g<42VizUJU3#_#Ra0`-!Ql!e(N|wmz9iYMTGiA!i>lR4(K-tI!P#msQjw!ekjg=<+_&z@lCo7W zchP7HalzSp>yNgs=dEYvKW^{Btq7Ygd#i1~>5Qw;=TYWqXceE+8wLhjK#VhX9I_?q zgX9g?@pH;uUb*11(4}KihcbF(KJ@kRfG4+=&SBeW?ZA~;Bpg`LQk?_L>|oHV!@$SS zTAkZ|09>st@o5bwX?wF*kR~SYhiQh2-e5!z53G(2|2T0GNazkmFQBQCBDS z@J{TUdY8-+n2uK0cSYqW7It-ZS{|e6@ED~cdovs_NPvqzD@U-buED`M* z$q_=Ja5#?ba5e#MD8i5ub+q<6bvfpWEXoQQS(J6Nz+yimrBH{LYJYd%^U`-(9FyIE zP!SP976Ph9pPjP;(P;5IpNx|cUp*Zynz9ESDVtR#V@RTlAtfq)`*CHP^0xDB4dHX2 zo_me@4iIRsQ;(0PJwLPy&w`oivn(!FUlRuz(awwXGBJ37UShFt%m^Xy=UTFjr=>ZY zK98)gO?Xz09}Wk5lB*i46c`qQ2r?WBIAJeU-nyzIBC+$%Ug(3ZyZi;E^Eg7QL%S8ntC>j591a4jcit$nv@5rPT6n@T9q;`bw;0PQ z+fZ{GmE%07%>zvBPo)Q?2lx`2GiKEBf6pNaz*5SbKa?39Gh?YxWw8Ns3lgB+d5VhI z*TGo%y>iCwH?j*!L?4HoafGxD(1^!r(6F%S1-b05DcdkHg_cnU7&3zt5Co1I z3xPk)8K1v$k8*1L%vTbYf2TG^;;pvOY|G!4PO7kq{#BOf{C^D%H=M!1tRff^zDa2Y z;X+4+l5H-nbp*QT!{}*M=K32}0MTL2{DN5?L>T2__im$?0T@Nmb7rM8c}F8cS)*n z48SoaBKAnrm3qO+HJvlbt2U4aV=ZZX&|G%G=bko-I&V_J=4iYPZ8|sAfgNLVLd2In zkHRJqd*Lc9D*l;53ap~wm3u5v4ky-5wOX73_q3l05v z0Uk5YGuTHJcES)+^mTHJE&wQt9-{3nxWtt`w791u%f{Kt-d&8_Ye8k7(HV={GT~&f~V`GgOIia@(|$IicBK z#RvAjU^QYD)A#7O!^|hQC5nlCTRfUdEJYC?qkv8?j+~yulqNu(!^SAiKJB>M&wKh! z&qcnvFZ9*^>Kn2%Y9Hyhu-Q|d{ZG<*pE_?d_fdYwmo?`?fBLt%t7V72qyJDg{16ZN zcv3=7_iLT$9}}A#>kfYlKME5Ed}3~iodFJ3gFupkRh(pMhgnw2t;w0ckz$BgsF@YW zRkzXmG<^LO)K$runUYHI6iJXAlexi{4u%+dM3SaH=#48%_l^#iRAX>q4?^tL%%9+R z!7dI*)jMNIIj9CTl#yyosX>q|i43wdFC1~4BYRzhxwyBM2_Nb%g(0{H9&iK7=YV=& ztB2=_RkUx{zU@gn`BOh2*J)eDfey{5Z(`G1Q-tsL5hR4GJ^+jEotu|g)#`h5%XOHs z(TqDz>EcW|rCL{mZ|fQe#LRia>T1=5G?5Z{yL6d-q~vv+^N#9yyC>(V7tf&Ez+D?v zz+G-p=$xFj@ZQr@>1<+z;h;$a*lgnCMaiv;2c8`Q zG#CW}GDwL~6sBQ_5(^_P@#ejS&{vIVDo_R4xAL29|AP7D1+=kN!!4RMzKR!8+OmX9 z#J%P|-DLaIp1J}O6vsGn=dC;>yB1LBT}*^z1PczRMT7ug8}uMK(fPPkekam+r-N?9 zy{v<2BObo&!3;Fu%_45j74BQ^vX$>TSMaNSM#eGP+nO1CnWfgZOJAn_2OD^-a2A;gBTR1Fcfm{O>CKp)Ewop*k1&M)eAbG_*nE7tjw zJYCgAU;X|@U$)y%$88*o1Ba~pVbnc6{`rBm{AQ;wI|Qjzfc696M%Z8V=R5u<&&+&4 zsR!Nep-Mx3e1ytB=0Az}+`#*$aEaXqSVgtBHkPwFkVx?pFKq7hP6LP{_S?o@iIFYhknBRQ4pWO_qJmXs_%ug!W_qaRW2r{fi8U}0Fh z+p${+3d(w|J$hh;lP?_I5+#9HdV^uLPs3ZdN*%~GK93o%DFQw6j_w_cT-$Nx>=Rg< zaTm0z&(T~cUsLz%gf6O3!aa`KpJl(`0mBC_uyhL2_M-`znK_g936wjy&vroRfs#2m zD==kkkf4Ez7(pdUVqzUyo${QeJw4ac*CGbzegCK zYlKI(KC^AL8g0vGQfn)e(Vd~ediQtGrU(ug@MsF!1FA=i_|iqsR>6?FV^$%h8wYqK zVZPHZqbv3sc=rk%KQ-szH&pugqKDe%nTGbW%Geo$5oUza`5G(de$|x58ZHra4gaV^ zzd+c)im4O|cB7{OhaQ4){rN|QU;+5%;ovpAFMF$^GY?Fi6Yr>46aSMu(Z*Z%Rx)zH z_Q>3ebH&O61Kx*?TA$#v6)|!v!^u$krYx1N>0pDao8|Fxq*10moMI?o;uve`-f_@-gL3IKc08#9@EAOlysiY z5(afB!PbG5`}uW^Ywu14NgwEN6F0;hLP$G96l!DNVI7P<{Gu59 z>62j~+;JnV2juK^H)gNfS#m38^>I!oR_ipOCPxXaQbh!iE3ucb%1^cg8KKErj>tUA zM=BBneNvzB=X!hJK)#;C;{?)6j40iNf)bwu_9&1520I!ZwGjK727_pJ4wcq3G-7r5 z>Gg?pSa}dmqx6(q-;Utqt|k<6xg2V_dXp<97QWyQz0xX}T`AZ5bhUZ??tOV*qG71) zu6(hQ)1IIizjhi9pT;U54$Hla@P$dZzCpKsyQ%X$XChaur4-LtF;MrCE7g(5F$3vQ zg0m8I?9Ej*T1Ct`3&>WQGj1jirmO%(cuJ3;!4` z)A=bS#$8-V;P(MeOdC8Fr*{d!)ELzO&~%6<09N(t12K|`a^b$G1fSa%j}!0Dl6vXk zxa1)?mO?S(s3Fy=(T^~KgLrZv+u}Zdf;;}+d8C0(J^5XkA>=YUdtT`e ze_+IT=W0?`?x|P2bXTm;O5>oB;d#yLoUzw~pc_1Ct!MSXD7#sg{zw1ck8~N8rABJ5 zo;|kDl7vy^>-`El$j5v_5-53;k3^_lqHVd^9UnO{^MIToLxr zL7GX`ykO+VWoE*~5-h-?{i&9Z*PvjN#8aL^fjxdNKhMAC?X%h=*3oK=>E*J%G$8;I zkuDE{vtA@69M%s%NnYfoEqq|U)#StOmpR<)i!S-?yYnI{WH8Xzw0j*Ux(!TXnW|_} zDCC?T3J@WoBvi+N+GwkYeb9B*fpR~!ckdowkfW;^Ls4zRon3Z!ihAkGX})pyr+=e) z2{7=1A{^Tg;bkX@{?&LUP}Na z{F;#*Tj52MeXQnKwLuVqb1;n+12?t0Rg02gs6BG>lqKb~aUF}z**i9)pr}`AK~So2 zfBG`C0j;#0XkyFP$kA`jfoE!lfOJ>-E@-8d-)h=CZ7@WIJv|X}BVBJiUKH1r=u5r( zF-0<9n1^}>LQp{oP{`wgg6M^D^GmmVcyaps=&`Sh>1$ovM)jqhL(cJ~PQ~NcjP5>G z>gsLB-==;O3=LV=(&EFng-9Zu{^h8~v&Bv&N0NC&t@L&>GLkemD4(UZ2bZ#~3R-qSM6nhu&hUpjfI! zS`t(6-9Ad3w9LERwuk82D$R$^OdP$qKUd_7_+$c&M;>YEw8nSh^-+)gvblX_R>(pX zSw+@AT0}%iv81SolfiI08T__8%@E)1L23Z05ETg#W-V=F%{7cI`oqIZc+wqn$1nKd zNB_}1aeYpm`SE6?UA<8D+4`pm8*a>0syOOxCdjE`Vzv%%&_-tAv_-p*RrcGOGUs7- z7S9Fh{qFc)wvv7kHVeSOoTV%@SQa3NWYyqFLs5&849rKCTl_tC&xY$G-ME##&Svj? zJ;uVLoHoh*^$pA9BP2W>yPbK)eK#;F=PtQb6i6afJ)uf<6kVuqR1cVYjh0b{Lm>wm z#!;WlV$Rq4fXcwb=FzKnaAV0$f`NvGjKctY%(Vl{*I4iEYWp#dYsFHPCH&jAJbe{U zr8(+B-6ZQNZ}n!IT6V5X>U0b=n=ln(mtkM2Du3N921N|xhBne145=9`2DK=921m4z zTFMc^FA$PjPi!*G!;hO-g7%XU$JZR)u!M~@jB3ZUR>zYS>vF?)Yu`X{-QZ0JMl18- z!}&2&Im!tFg^ew}r#Eh)2r&?~Wrs30Y*{Af0zN|yQPLHou*Cpt5T&RTbBF*56BJ^w z+=-yyL5J@04ay=t$-B3uG9i=`~;Rkd_@hjvRdWn zp}9`k%XYosi)J(sfaJ_UE&!=i#a^9^bGeH^oz!Uqt{NOALS%GsesA8!6|SG$O{CL=+)anX zlqfi0ngnzX8~ftx3$v$+Q}5BwrKvxd2Xib=nD?=(HBe*PjlfTbr{442Bu~srqFOR_ zF)*$1M}{S%MjWi<`&-&z{?<1Gf4iwS^&-Bn`htX}`|!3n+!piL{L%PK;pzP?|#s6|Q2i?jl==1|}L|6#yQLRH{55O2c0gLKUkJGTMmFa-jo=VYE7iS`-gq zS(6l~81kWdnXE4v83Tv_YUJwh-~of6K8+LFTKmf{;7;LchwDBbGk7D>LAWtGM^#h&`hXfbh*9J!_0@J4=NFM!HQ zT6c-#+TM~Kcf0-G^$PE~A6GItEw7n7LEi*p75tRbI{jP7> zXQ@h+3T_S45E@_twn0gW452wO)Bx1MTCtICeG|Ujh*l}Fwl7)Z*W3GMGkg-167B2# zXo3i?QbT&dWUr}YipMHZpJ!iRYk%8%zE#Ampr8B&jbiJ6xO>s(xX(vw$VX9fA9m@e z0#E=4KmwApk#TYz%H-67qz~wTrx|=yyR$1=v_I*jWR<2E65`}BKj}Jev9tfx-k7m~ z-2Y7<(ANE!`H*jUS}aJOb?`O-RSkT&K0>prvMlQ*N4R!>x0G`yb2dlIh4dpZZ^jf9#A_HcEf>22AG;HO)wvrXwtvvcTFSCD6_Gn;PN_@@*QUwXA zQ1C|<6D>FpDTpe(r z#9TFzk~R@`;D{v|=Xw6p+NWyF%a{pcqds_csn0Xi*Y$D)R)t6Bo@eFEf|u5c^Oi%e z{AH;5sNfW144K0msCRXuKwKBTggVOTb8|iKrw8Au>t4-1w=3VT=d3#R=da)U4X4B2 zf;LQ?xvwEe4bWvgmWeDia*z{5EZ?%%u5&J*;g@Tlg5sCG)6?kipc(vnv+KBGdf~#~ zJNN1o+j6b0tF?SG5X+zO@EnWf__2*IF|=YIfGl8F(ynIu-WL?+xKQay)kgOUffa@- zp=*o4Vxp&kW);IDUl|^ws!Cx^W*{R>%c2kkEh5B4Ltj%lTo~TYT>HRYUQIOT7A*^< zsfDmik|g&b7~Mn81o8qXODcy4c;Lnz?svAlx$ItMEtBDS4@^GUleyL8Gz5)QsBo~R zgpLk$x+BIBWBX?1=vDKNzCi8H;|J4F!qOu%e%iB}uY5G?e#hRP6m))fi~m%OJ0$-XO&N}*|tJE z8TTr_AG9B5ys~_3IhR>SZOERYJefO*Hn5B^C2ED;R<4V?2|A#R1FxxhmSvw#t9x#N`R0>HPIvmgSce;D};*Y|-$Zl*;p0?a?_&T5H1LB;o zI_)8H_2dNPlY8|O4@=bj;}vj%L`v4}^~}n7P1EBvr8=id_}ttO&LQb+-J^^`P0@FDzWKUkPt*4EwW;Us!jLwbm1(Mu|kODe9;Rc2NQ-C3zR z5c1$TLIr;D5J&(Zi3|Mx_1BwKbm{%Tr?7#3*FqomH*fT~9YyX_>@nZCzA?tIM!nr$ zjBK!))No!~>nG3k@)Zr0%kUe%rd~~clac&t;maC!<8>Y8*H%c^;7EEnrc zKg2BAMkyl2Qz`8GtU_p`h|6D|ZeHagZpVD!63Mf_d~K8Fp;|>pi;7K=P{n@sxp_*k zh7vqOa2fPMmS-7U7+j*bMWFYtR7M@rX>SY$Cb^OqCqv+v>+QFxKfaoGe6Voo60m~( zoL1QX?ipXR!43lz>X6Y#s=&JwsuV0O@rb+A_~W z*S4X|wD*qJ=~p=rfj*n0v6`7f*`pZ*z%v*clj*T@rO|Yj^NWM=LHMxTH9Z_H^6Y3~ zW(C?|e6?b5hiUDh4&$E0 zIq-z1uf#a=oyUvd>2Q2r?qz&F&JLH$I~*Ub7KC7`2U*3&6pemg(WhgpSC7woJwXE= z{MfOgx|;P4TvUdAXdCLqg@CUR3StV}Q}hxb9QC->5UYpb2!PWCrFqb9+B09fLM}<5 zge(_SN^}ouRKr1)0%?r-3ZM`0M8=sIV(8Ek&X!`~{(M8Du`#z0E>WqT!4yFt$8o5V zvz?^dAXVT^_I(+JEG{{&fHz*rq7l*C;2=%0AhG3d+H^Xt^jQ9g#$C0d4qG4m`mFm+ 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 d|FqTHa{ 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 Yz+5j)*q8SrIJ~NYnWenA{t`~!e$kAp zXK1sTWCc$eALeH}mnVJJ(e83=Q|_}IZp_E_Fgzx{Af~9101_A+#1DeO!3yz-As~}n zVmb$t+7ee8eYXUt#m5)QZNcK93_fM6wgKl1>(0!)tcW-Ca2igRSDdk?6&ba0hZ=C;QSSotKm(a)BBUPX z1OW%&b&NxOzyZpT(4aV+cnd-URuX^))?gw~;R;6s8F-Ak3_cj34u-)8L@_`v0&_)Z z=9nXpNPwXMZ0QmTI>PxbaiO4)VR-OlAPr1l40D)67Ss(W&KTwd@C|GLYXbpbNW(gc zLm^}^hgMjHGC&1$m_Tr#fF~npHh@8fI)MfcA)8ZVOg!3#5}Qa1HBP+*XFi7pCIdnR z3KMWZHNZYGpo0Mt_J%2YffCjU0G>Hufee%Z zC-^4Nfb%dx1sqhyU<$bbNPyHchDn8$gbW&wi3_^I8W5lXb!32thJYt4JOmtg$Wz=L z#<1?umQ)}^EknUKFo6u3jL>-IkUIWvMn>#89fI|Q^F=SZjfZ^yQi>FxMZM?qlB&DU>I5VALD46y%W~$Jf1d|14)F!fHqex1JF4e)Z*>48aX0bcEuKbH`;0|6Jucs zBx^+W1-7Zt8Bntqyu^gUTc;kOv}%l)Eap)3%#(5$E8X?ug}3RuvCZln(e>~saWip?Q0AOy5244AZLtT9bHb2AmSmPvLvQ{Ik2HM7heGi$j} z10evY1fe&;7X$N&wzw96<^YWdFbFZY2__wQeasE8$#RMdyqTl$#)?K-7q2w?3GHeO zyQ(i_l-5OEBbI)0@`xh~Q!i7AW{iX`MUsZ~C`N(=-NbvqV8QLvkS^CIA@l5RzzP@F zS&(YzP%5*>jp;dU0g=v#$YwMW(5R>~kP9cN4DQAyThWuN>F6-C7-m*O80U!^cnFAN za2^0M-v9&#hbRFWKY+9Z$gnyC)bI|0215WGjA>Eu$ONMz;8_P#33$gGpkR$GKm%Y1 zfC+e*fOtcL0pbAjHG(-5#fcA|0EQ39j@Cf8YXSih(p0({fH+;-*oDv^06d&$CLn&(GnCq83iDi zqrn3X2rwc59xwyM8Ok;K_3tY4YUYh1&5acP?MGBz(AI&C%_OR zcT#{+25^v@58xC$Aybb5AwvNsRxu|7G6D_$>^MM38DUVlK&E&wAP}SlVI}X78Vqy| zQiOJ3z3~`+YzR13Rs-MazB)b}t(gpm6WZo*_yG;KWw`4>k5Jgc$^xf=8<0lvLA=Io z9iFe8ObjO~kSCuC;zz5E1ggk;~;8v{+&U$*F4G^?i<2 z#_Cln{VT@TiUT)euJjCh zA@;(a?g~I%)MjcrY*J#-AnnG6 zVUa}N<1?8vd+{V>fCRYNZErM`%eU~tetrqwm&b0wsrIK&8LSv;UCz7z9B%0!Rl?y$0KFP&YwDfe4I9JT>&2Zu5AlWgt;kMar zZ@dsUJ0os&R-^|t4B`9J(ipK2UM%eav-7;-I(M366@!p{;aBH9CeBo=ezQ`rcGKdC zJU8Mo&Jf?#$WJT7lljEeFx1Mx&NIa~;r=Boo*Q|z#@z91F%(-L%D#AFiJX^XgWK$| z{xr?{fbR+?k7uCMdo>@J#Z^x%Sh!^qa@DaNw&TNd{}j(yPd<;KB-SGV;?psY9X!FPS7(t6<*I)+ zv+n76z_* zv*QL$c{_~V%^!CATy&gbR+;S0f>aYPgo}KMYIXD*7rvCaZGbprH5qC zahZvn%oe#3&Rcj#?6jYo-%Gk4Gj577+6a7hk+$>?4v}I zYlmEHdi5dc;h>V;v05$tTqMyGZj_-9-{nU$oWTJ(R$Osk z=nJ%q%a!(oEVD?J7{KvMyhYADvvkPieXFs1+TKsg`>Nd;4l8on5ZN6IAjd_oKj9A4 za&dh+Ww{|tuPy`OCY;N-*lN+_)Kqh*d1?-_xPPQ8U=N@7?6X6tFSMC?Eu?C z>dpR%f7D{GS6=y!Ke$35z~Uv#YTYc>xHF;2$Z4ZrItZ=gwVQ3 zxu}oUQn!(u2U22)n5V!SEkd6)$)mwf;SG1m+C3z~45#eZV0ZCme=CczeJI!aJy7kd zHT!ej7&Ns%wHo2nn~C$n!PgWbmwu{{c-M7v#qPVpF{gi}AH~i+*4z+vYKJA<}YjL>ZE1S2>PXqA8Bu!MW*; z8v0hVv^dlihO!zGCP;A@$vNW zTzfj#?(*rQ@}>53#j!bHB&V8dMtBh@2SpRqH@teXYo!mIH#;UUw@})|gzb$+Nrj0; zy4Y}kgK3|0U_oov_!xOS8?MFD7$Tu2sW4<#`5Nx2RPEi}a^r%K#R~aUL=P*8PbJrW z_qor_%9SqLG^vTGd#tEZ4{AX zwa62ZmUFXRA!}N=v`PkNF#${XhE|>_l3?NKLK}5xetw08zLa%|R!hQeB`=qBaT|i& zL|Zrv`4Wr1@w5rzH+8Aao(nhrTH2cCFY-N>tTv0_iYNWFB7LUp#Zy~^XFK&)@=@NQ zS*#k>8wG)0aT*~Q}Od7-)Uncj7oMg9dhO0zgORsrWtV$nJ7x*p@tvMl}Tpjgq> z98|708@Eq&5|_vWS#4IY>yApR$r=ZpW$hAI?#xALVv%!k87pi31FlM~^hXC%SFJWu ze`Jk~>jk^(GAMarx%_o$X-JV5hGXM(G+PepfLNH0o9KZX#dKI#Q*;Hx1#tq&U4qd7 zzUTSj{-Us3#bJ{$;xTu$>ZBGJkldz0#0{YPMHZVhO}U)!p9^&c`wc6rb8~TAsyFiQ z(NT+aX_1s4UcK~%tBX(D_>%V`SJ9Pd_nPmoBd?S!j?R0tT!%X`%>*`6xyjjHVs0LH z>LGgMDB)A&Lk;EDd~SC&3*rIbU9kR^otlAd0r{ZMcKx00fk_NiSTuo1^TWo|ow#*| zJA1Ky{^$9%w^n^-UgeX8!=Bf(LW~2KO$)s=F6Y+Hy-!F*;lVVc@rIJmmBrwxr#$UA zH`~;cww+57pddp^0^};%+cy_WFWQwpsSC3cIADJ}ob=7LP+d7)rp3wY^eUOG(k_)o zxZy=5H+G~&e?U#j5B1Hm%C24{{O~<$y0GaH=FY>tizPWbn4;fuIuy4l=mTAoaVz+; z*jciS$Z)Z#*<^Why}K-18&tY{D8vg%^(k5z(F;WfV<&OnT=7J6^C6*zFYb8ZZ%ZmC zi#U1kF*+&Z@Kwyp>P5#g?af8H7^Vwy=@n+*5UEzr&mR>f4kj6bU$rAl!j9$Ax=I&|X;I0V4iC>Bq$SYG z(-m&QQ=Pdox-3ZBXZ!erKe-Pr52A*BJ+Cxx#NoSGj8~VV9%OM^A@OXnOhc`!ROn@N zFe~`e8UzQ&=5phpG-{EJsr>Ztg2k0Rd9sM+W;>*7oJIY*&fZg(pxA=%<2mZL7Td;T0fV> z9Ue~U@WX*AsR&ui<>hLj2~0R@H8)oJz0*cej4wIm`VwPYJLW!0hY=3g7WSqDIc$?* z@GaZz&CTWX?0HQ-;cVTIrnQhJ>dmat(=I2rdldF@*vkC1Gy2@Lo_vP3PErz{kY0}@ zAu;Dvk$Aa>w$@A=o4Z0OXLy}Cuij{G9?O^Z>DS(V;>8cv9_3^=7%HJ)D04&pg3hT6 zQS-G5#TQrA#oX4QTIsoY38#zQ_~j%@*P)N4ZtJ;nYpaeG@BX5aTf4K5{@dbqfpDwu zPi3r(eZ(_-6U$5ZgCAX^!+|%qcJ5BGH067Zm{{Ipc{0ID=f~N1lOfIHse{E%GPnA4 zt1GWXJH~wSpi(tiBY9G6#0!TFo1Ac{BUn1s_JVwCji z_aS=~o}cDngq=U|r4HNv4y$^l9q;1nxjwTNO?`{^;6e?qUM@Zq$^qcN^a-`n%L z(u9fKTXNIbUA|u#K9|o~nk?&rwp@AY?HYAxQ#R~8m9JQ%XNeR_^5UL8 z({rU?^mjGkhVtb;e6o0MmTQ~Xd`H(2@sQsA!Nh>%SiAzNfq;@xM}YMihX2Q z-~PI^Fj5f=9sFU3CFAM3spMKod3P~&MeA4aU{ntMpJb@w+H zm8yb5tahW&d~n1>4YA&5-B@SFdU<_E(bh}aT;vO{nCJ;-%t|TOcch*tAX@MMhT)WA z12=m>xHWP6oMWC->ST8|IwQ-8{nDkJXwppWe8Rn#d?lfTUY((3XhCaZsN<=PJqSbs z(mHLM_L{0-YSzqC@x>hSnJXLbe6bil?Z%hA?&UG}YX9J^V$i;wYAk{$5IB(5IrO_; zM7r1tt4or)7@yNY-yE=fedrtaaY1vGI9CU&ObIM#3ytwNMc0N~B`SMFtr3H(8lSAG zy?Ui#gM;#+92b=PJi@4zIV9A*uxX)_Ax>y z%CHc8F^+IJYQtgcTJO&G>gq^IP}Vn}w_asFNeh+U+?m66+S07Zv4@&?|8hBueL*9a z^ZO?3}4o?3WsR#21PY!0btxIKAZ;d;2-a5y&WbtErgi8|VFeG0Oul)G0R zc-Mr3+?9M7fDa;o1~{O5KleO^MqT?0Nl*-Lu4I8L00tO%HNZZp^dg1R!ffIs7;&?2 zZYVkx@2)DLk|G&)(}zbpmsnlo+HJFJZ}cGWvq?U@cYMcCJiHmnuRJX>OBN%J?|7=d z^(`oGZjRSaI)j%e^o22BGR){}z_+@N3^F=`-HqAgP z^AwQ*_6_@C@i;GYlc!yG<(yxm@4efCLx}G@#c%ev_f?~aajUK?|4^$dat)1&o1RLn(1R5QK1Srpz58|I1;J8> z%ibiGHOu#HQXDJR!Al;_EWf@?t}wPcJ2&wM9c!1y`W(tCS5p}+HSWy2 z?O7cU-MSO4rV%crqo^S$X+&JD9Vt*+=b zF`S3%;uT)paP%6T;fcezqNZ^rIqZh5tghqXHHR==SmLb1_osaCyZl^yFnpI7t{0w* zFRs>ZZo!BeC9+q(*vaxuJmozrTOPJA4m><{Fs|!GKY4InJ^00o#KU}=B9^wDTWCwe zU`12>q`3vzip6i-V|MZN?s}n%VTHyYeDX02S1&@P#>Rn9f@Dg}5-rOtf7U4S=&V&q z&*p}|c(T6g7BV(d)U4jvd_Apq#wwjbtqX%I!V|-F37(v>{K=E|5M%ygeZl&W!;>q2 z;@x%+Ya*W-5A7QcCRiLg-`GfI(JC!5X#B;E+tl^MJ~+aMh^@9|!gwYGBj}4L)CH6C!A`jd zhhYCy@6^h3MUODIi8U1V3%BzG^*mW?sXz{=$!5!@L9tq;ZO^!^r;lfsTP9d{tD~FK zeCOYHb3;4Xndx4OskFIfEhUKoNmh&uuXWNc;$zZyYoUs%&F2WU2XKv_d^Oj8cs2io~ zLokZ4yq)Fk;YtjzfOp&cL|Nln=h38buur*8V4Xy|BfV@_+b*uWuw2h_G2;m0arI7@HyMXr53uRM4%pVn2K79w4)X(b&rOC^yM)81Y8 z!4hd(Lr4?U2p5}VmMAp2d>ATWK~nw_IlS`Ok!LPG%8SIoxBBzFS6J0dWX*;4F7)^X zJs-O(EKM`<)~=Uke{xj+pd(K)#jGoot{nNx zm-#jDrG$6aUQ^=hW0ogZt}f0+Mz6xbVOauVs(EsK7s>A8&GBrn>(Xy^xs|pu_%7Qk zPZzIhE?jE_Crc~YtV!I~MScn-iz$NDCdMu7s9bG6-P0TsO!!W1kem&5jhG3fAQzX9 zNN>E4IJKp2BVxUzTo_^}WiSP~yTY8etDi07{nHbAwWa0@2ga4^A&hO@u$z^2(&d5l zBG!nTk5exmj8GD$+IA&{58r&|JE2B1!GjjRB-X*p4;ICdew?4H8fn*DS($P63GiXpw8d^bTt}FJ! zdwZFK{9^hsvoAP{#FTZpToC?)s1t!8&aFmVH@(`fbg6SrB^^1`^~EpY`j7JnPrLop zd(`J~(oU^o?x*B~J*?40tg=2Vsmn5r6{a7a8g8Gs-F)N6H#9f>X7V<-xhO~1rd*E8 zl9q7!gc#iZkuH`LS(ed+i&L)Ts^iM)sN>4k7qPA!mcYSaSIbepG0YnTt5@^hh%%(@ ziss!XjP>c_1RUrU<#Jr6U(2_@WfLpsVv{w@E~cmcjSZx%E)dcHQ$t_I5pJn1n%9xUCw?3bw!}#26FN`*pR-}inN#$ZH zmzTI;6SmC~Mq4gb!U(PUgm^q%c6g=j(c@Mv%R^}wM__JIo#qWi24h?DLN%`MyO)5m zT)i2yd9!TKBT?sJn>ai>;Yu;KxvE{?NM(dvx~3ksP0VsC$%XITVcJc#i%SQKQ&zCz z_A(t9#F9%w3!~)NAQiKM4KtxvHAFAtHP!mO-zxjD#_wPW23{Ull8 z?|QcDx^Ou4f?YZ~=aMkoJ7HbuU|_ZF;&hp899F=w!9Zm3Os*){gzbPq!A7)EpOs6Q zkQkbQF6;^#<`oT_!)X9gD^nqZ^0+{dB7nUIF{lY&%f%t|B6nzu*&Io=u{{F|CR|?E zeKv;^D@b-jFxK?~+IFQnU5@2)IZkh8{d;Vr$p3;YWVx6W)iybzO>v=!3ulW{*^Qu0 zxf)b|hHR@S>o_E=s4!N%2+icdQ(F~qlWmIdJkLuGsH;O0I%ha@oav!?9oiFEN1Vf% z?Pto|`z?G~wmj*i73( &1ha#-WcGHL?AOQM_bSj>^(Y4uW9emvhoXZ}OOv9^79* z4tM6_)YPY{x|q5YlLyMEU!pO*Id37=EpSD-z zstY5$oyw@I?Mh0|61Jzuluww|d?e{U1o@1vkyzRfu7)BlGISZWWG^jbtr!;@Pp(gH z!89zICD7Ejf(DKZR?~GOAE8v zudHUoN)&c~k@41xJ56Z|iKDC~spddF8@pq;`P{r~RJctOuwq=k@p=@*gQwmc)Yxgu z9ZHfEG?q8}>AGC*6{~Twh|QphyTKKsy}hnA@w;${g_dKZTyKWYEUc4Q&@L{TXRh4h zWbch=$}Rfbm5(WfLuIXYRKM!YB?QJA;Q^olaJn>U8%NWQ{~w zv}U20t2Zk*6G?e$4_Q2~F6Tin{e;!Rkn3~G#VjhhsrL6LtC|T)MqF35`kzOi_iK5N z-kgVv1-;VOm(GjwnCe2ly|cQA7nKTH!SF!t$K9h6P-LujVK8bM&8=|LJ(n$nEbH&A zT(7Zjn?IFT>^)A+m;4yZN#SfP`0xvUr+kF(;oCU7AA^jtO2%3>x7_F#iA*=s*~?xW ziG2^>7Z(q|R}T2bw$*vW$6Wut#)s}LBINS=)OGF232o~L;;lH5;K5b)`D3gp$3ANhQ zv!k?XN>9dkwM@)j2KRn_xOnpHxJ}r0y^BFxpSM9wJoR!N6t1>US4_qz+LEEBxfz!M zUB0z_C1-;)xrbqqo6{nw(?TSA)&`;7C(#a1orq=oly3}y*L4kAMK0|fGKv@6C=$KU z3uSXLn6gdU61KBJiGFV@i9s)D3o+U~(x_gEzz753pK_3~s--h#wI{)s;wgKHiF1V^ z2A|8t$(RbGmp6BhGFlc(V|^`ex2M3s)w1misuQ(n+eKesrReo+Zo+n;tsf|tKXVyv zm5g<&uN4fHxUOo>TE3$fT_R^GSG|m00!x=#aC!w5x@uD;C<{UGfba^tc!!)TCC23vqLz7tdZ_hs)(VF;Kepb88Mi zG&w75You^-E?g`U%K{m{=s-A?oR&=(+uX(`T^_axSEm@}aL-C0f$tQ<<4DlZ(fdei z_Y}@!eAzB<##y~mOfe!it1DfSALEHDOFvyNELX2}wa>=V{_bU$EdD82u3ZxnkE8e7 uS25@9QIFkw2YTM1+IINrPW1#uTX#Nhn11b*)t|T1m8P0n-<|ha@6Q8~WjPA~ literal 0 HcmV?d00001 diff --git a/docs/explanation/session-lifecycle.md b/docs/explanation/session-lifecycle.md index bce5c1b0f..bf7b21290 100644 --- a/docs/explanation/session-lifecycle.md +++ b/docs/explanation/session-lifecycle.md @@ -8,6 +8,8 @@ description: HotPlex 5 状态机 Session 管理:状态流转、确定性 ID、 > 为什么 HotPlex 的 Session 需要 5 个状态(而非常见的 3 个),以及这套状态机如何支撑 Worker 进程的完整生命周期管理。 +![HotPlex 会话数据保留、归档与清理时间线](../assets/data-lifecycle.webp) + ## 核心问题 HotPlex Gateway 是一个多租户的 AI Agent 接入层。每个用户会话背后运行着一个独立的 Worker 进程(如 Claude Code CLI)。Session 管理需要解决三个核心矛盾: @@ -174,9 +176,9 @@ pool.AcquireMemory(userID) -> MemoryExceeded 配额释放使用 CAS(Compare-And-Swap)语义的 `DetachWorkerIf`,防止 stale goroutine 误释放新 Worker 的配额。旧 `forwardEvents` goroutine 在 Worker 崩溃后退出时,如果发现当前 Worker 已经被替换,会跳过释放操作,避免 pool double-release。 -### GC:三层清理策略 +### GC 与独立生命周期时钟 -Session GC 由一个后台 goroutine 驱动(默认 60 秒扫描一次),执行三层清理: +Session GC 由一个后台 goroutine 驱动(默认 60 秒扫描一次),处理运行回收和 Legacy 终止记录清理: **第 0 层:Zombie IO 轮询**(RUNNING 状态) ``` @@ -186,7 +188,7 @@ Session GC 由一个后台 goroutine 驱动(默认 60 秒扫描一次),执 **第 1 层:Max Lifetime 到期** ``` -Session 的 expires_at = created_at + RetentionPeriod。 +Session 的 expires_at 由 session.retention_period 设置,并可在有效输入后重置。 到期后强制 TERMINATED,释放 Worker 进程。 ``` @@ -198,11 +200,15 @@ IDLE session 的 idle_expires_at = entered_idle_at + IdleTimeout。 **第 3 层:TERMINATED 记录清理** ``` -TERMINATED session 按 source 差异化保留: +Legacy TERMINATED session 按 source 差异化保留: cron 类:updated_at ≤ now - cron_term_retention(默认 24h)→ DELETE 其他:updated_at ≤ now - term_retention(默认 7d)→ DELETE ``` -TERMINATED 记录在保留期内作为"resume 决策标志"——它们的存在告诉 Gateway 可以尝试 `--resume` 恢复对话历史。 +v2 会话不进入这条短期 Legacy 删除路径。其归档、会话和正文分别使用 `lifecycle.conversation.archive_after`、`lifecycle.conversation.retention_after_last_input` 与 `lifecycle.content.retention`:默认 7 天标记归档,180 天保留会话,单条正文各自保留 180 天。正文读取会排除已过期内容;v2 会话到期后,GC 会先检查运行和恢复义务,再退役会话并排队异步清理。用户主动删除也会建立持久清理任务;所有者可通过 `GET /api/sessions/{id}/cleanup` 查询会话元数据、正文和 Worker 副本的清理状态。Legacy 存量数据可经 Admin API 只读预览并逐批显式延长期限;缺失可靠活动时间或已经删除的数据不会被自动补造或恢复。 + +**删除范围**:HotPlex 会立即隐藏本地历史、禁止续聊,并异步清理 HotPlex 管理的聊天正文和关联 Worker 会话;少量清理状态、执行去重和审计事实仍按各自保留期保存。删除不会撤回 Slack、飞书等外部平台上已经发送的消息,也不会修改已生成的数据库备份。恢复包含已删除会话的旧备份可能使这些数据重新出现。外部消息保留由对应平台管理;备份保留、销毁及恢复后的再次清理由部署方管理。 + +v2 轮次的 `worker.turn_timeout` 是绝对期限,默认 30 分钟;流式输出不会延长该期限。`worker.idle_timeout` 仍独立负责空闲 Worker 回收,因此延长聊天历史不会延长 Worker 运行时间。 ### Fast Reconnect 优化 @@ -250,7 +256,7 @@ if ms.info.State == RUNNING && ms.worker != nil { 3. **IDLE 超时不可按 Session 定制**:所有 IDLE Session 共享同一个 `IdleTimeout`,不支持"A 用户的 Session 保留 1 小时,B 用户的保留 5 分钟"。 -4. **TERMINATED 记录不自动清理**:为了保留 resume 能力,TERMINATED 记录不会自动物理删除。长期运行后数据库可能积累大量历史记录,需要管理员手动清理。 +4. **到期退役采用安全门控**:v2 会话过期后由 GC 发起退役,先复核期限和持久化状态,再原子写入删除标记与 cleanup outbox。仍有活动 Worker、未完成或状态未知的输入/执行、排队任务或清理任务时会跳过;存量 legacy 会话不会自动升级策略。用户主动删除和到期退役共用可重试的持久清理任务,进度可查询。 ### 并发安全边界 diff --git a/docs/guides/developer/session-management.md b/docs/guides/developer/session-management.md index 1543a48ec..cfc31a0fb 100644 --- a/docs/guides/developer/session-management.md +++ b/docs/guides/developer/session-management.md @@ -14,6 +14,24 @@ Session 是 HotPlex Gateway 的核心抽象。每个 Session 代表一次用户 Session ID 使用 UUIDv5 确定性生成(`session.DeriveSessionKey`),确保相同输入参数始终映射到同一 Session。 +## 生命周期时钟 + +v2 将运行资源与会话历史分开计时,默认值如下: + +| 内容 | 默认期限 | 起算点与行为 | +|------|----------|-------------| +| Worker 空闲回收 | 60 分钟 | 无 Worker I/O 时释放运行资源;会话记录和历史不因此删除 | +| 单轮运行上限 | 30 分钟 | 从本轮输入开始计算,流式输出不会重置绝对 deadline | +| 会话归档标记 | 7 天 | 最近一次有效输入后标记归档;仍可查看和续聊 | +| 会话保留期限 | 180 天 | 从最近一次有效输入起算,新输入可延长会话期限 | +| 单条聊天正文 | 180 天 | 每条事件/轮次按自身创建时间计算;后续输入不会延长旧正文 | + +查看历史、分页或修改标题不续期。`session.retention_period` 是兼容的运行到期配置,不等于会话历史期限。当前代码会按逐条期限过滤过期正文;用户主动删除后的异步清理进度可通过 `GET /api/sessions/{id}/cleanup` 查询。会话期限到期后的自动退役和物理清理仍在实施中。 + +### 删除后的清理进度 + +`DELETE /api/sessions/{id}` 会立即隐藏会话、阻止续聊并排入后台清理;接口返回 `204` 表示删除已受理,不代表所有副本已清除。所有者可以查询 `GET /api/sessions/{id}/cleanup`,直到整体 `status` 变为 `complete`。响应仅包含各清理部分的状态、重试次数和安全错误代码,不返回聊天正文或提供方原始错误。 + ## 5 状态机 ``` @@ -28,7 +46,7 @@ CREATED → RUNNING ⟷ IDLE → TERMINATED → DELETED | `CREATED` | true | 已创建,未启动 Worker | 瞬态(<1s) | | `RUNNING` | true | Worker 正在执行 | 整个 Turn 执行期间 | | `IDLE` | true | 等待用户输入 | `idle_timeout` 到期前 | -| `TERMINATED` | false | Worker 已终止,历史保留 | `retention_period` 到期前 | +| `TERMINATED` | false | Worker 已终止,历史期限独立计算 | Legacy `term_retention` 或 v2 会话期限 | | `DELETED` | false | 终态,记录已删除 | 永久 | ### 合法状态转换 @@ -42,7 +60,7 @@ CREATED → RUNNING ⟷ IDLE → TERMINATED → DELETED | `RUNNING → TERMINATED` | `/gc`、Worker 崩溃、`max_turns` 限制 | | `RUNNING → DELETED` | Admin API 强制删除 | | `TERMINATED → RUNNING` | Resume(重启 Worker 进程,`--resume` 恢复对话) | -| `TERMINATED → DELETED` | GC retention_period 过期 / Admin API 删除 | +| `TERMINATED → DELETED` | Legacy 终止记录 GC / Admin API 删除 | | `IDLE → DELETED` | Admin API 强制删除 | ## /gc 与 /reset:何时使用 @@ -210,4 +228,4 @@ type DebugSessionSnapshot struct { > **注意**:`max_lifetime`(即 `expires_at` 到期)作用于**所有状态**的 Session(包括 `RUNNING`、`IDLE`),不限于 `IDLE → TERMINATED` 转换。它是一个全局生命周期上限,确保 Session 不会无限存活。 -**注意**:`TERMINATED` Session 的记录不会被自动删除。它们作为 "resume 决策标记",告诉 Gateway 之前的 Session 存在过,Worker 的 session 文件可能仍在磁盘上。物理删除应通过 Admin API 显式执行。 +**注意**:Legacy `TERMINATED` Session 会按 `term_retention` / `cron_term_retention` 自动清理,v2 会话不会被这两项提前删除。v2 的到期自动退役尚未接线;当前仍应把它们视为可能可恢复的 Session。用户主动删除时,Worker 侧记录由对应 adapter 的异步清理任务处理,进度可通过 `/api/sessions/{id}/cleanup` 查看。 diff --git a/docs/guides/enterprise/audit.md b/docs/guides/enterprise/audit.md index 662699c03..d3c819cfe 100644 --- a/docs/guides/enterprise/audit.md +++ b/docs/guides/enterprise/audit.md @@ -12,7 +12,7 @@ description: 用户行为审计系统、不可变存储、跨渠道身份追踪 ## 1. 审计系统概览 -HotPlex 内建**用户行为审计子系统**(`user_activity`),对所有用户操作产生不可篡改的审计记录。该系统采用 **SHA-256 Hash Chain + Append-only 存储**设计,确保审计日志的完整性和不可抵赖性。 +HotPlex 内建**用户行为审计子系统**,对用户操作产生不可篡改的审计记录。生命周期 v2 将新事实写入独立的 `user_activity_v2` 链;升级前的 `user_activity` 是 legacy 链。两条链都采用 **SHA-256 Hash Chain + Append-only 存储**。 ### 1.1 核心设计 @@ -54,9 +54,9 @@ HotPlex 内建**用户行为审计子系统**(`user_activity`),对所有 |------|------------|------| | 认证 | `auth.login` / `auth.logout` / `auth.apikey_used` / `auth.denied` | 凭证登录/登出、API Key 使用、认证拒绝(`auth.token_validated` 常量保留但不再触发) | | Session | `session.create` / `session.delete` | 会话创建与删除 | -| 消息 | `message.inbound` | 入站消息(含消息文本内容) | -| 交互授权 | `permission.response` / `question.response` / `elicitation.response` | 用户响应工具授权/提问/MCP 引导(含决策结果 allow/deny 及 ID) | -| Tool | `tool.call` | Worker 工具调用(敏感工具全量记录,非敏感工具记录摘要) | +| 消息 | `message.inbound` | 入站消息事实,不记录消息正文 | +| 交互授权 | `permission.response` / `question.response` / `elicitation.response` | 用户响应工具授权/提问/MCP 引导(只记录关联 ID、决策结果或受限 action) | +| Tool | `tool.call` | Worker 工具调用事实(只记录受限工具名和调用状态) | | Admin | `admin.*` | 所有管理操作(Bot/ApiKey/Cron/Session 等的 CRUD) | | 系统 | `system.audit_config_changed` / `system.audit_export` | 审计配置变更、审计数据导出 | @@ -104,6 +104,11 @@ audit: sinks: - name: log type: log # 输出到结构化日志(推荐用于起步阶段) + +lifecycle: + audit: + capture_content: false + facts_retention: 4320h # lifecycle-v2 新审计事实保留 180 天 ``` > 更多配置项见 [配置参考](../../reference/configuration.md)。 @@ -117,10 +122,11 @@ audit: | 配置项 | 类型 | 默认值 | 环境变量 | 说明 | |--------|------|--------|----------|------| | `audit.enabled` | bool | `true` | `HOTPLEX_AUDIT_ENABLED` | 是否启用审计系统 | -| `audit.retention` | duration | `26280h`(3 年) | `HOTPLEX_AUDIT_RETENTION` | 审计记录保留时长 | +| `audit.retention` | duration | `26280h`(3 年) | `HOTPLEX_AUDIT_RETENTION` | legacy `user_activity` 链和升级前旧记录的保留时长 | | `audit.full_content_retention` | duration | `2160h`(90 天) | `HOTPLEX_AUDIT_FULL_CONTENT_RETENTION` | 兼容配置字段;不影响 event store 或 turns 的留存 | +| `lifecycle.audit.facts_retention` | duration | `4320h`(180 天) | `HOTPLEX_LIFECYCLE_AUDIT_FACTS_RETENTION` | 新写入 lifecycle-v2 `user_activity_v2` 事实的最低保留时长;到期时间随记录写入并纳入哈希,改配置只影响之后的新记录 | -**事件与审计留存相互独立**:event store 与 turns 只按 `events.retention` 清理;入站消息原文由 audit 记录保留并按 `audit.retention` 独立清理。`audit.full_content_retention` 为兼容字段,不会延长 event/turn 副本或 `event_ref` 的可用时间。 +**事件与审计留存相互独立**:event store 与 turns 使用自己的内容期限;legacy 审计链按 `audit.retention` 清理,v2 新事实链在写入时按 `lifecycle.audit.facts_retention` 固定到期时间。新写入的 `message.inbound`、交互响应和 `tool.call` 不含消息正文、答案或工具输入。两条链分别验证、分别使用 checkpoint;Admin 活动查询合并显示并通过 `chain_epoch` 标明来源。`audit.full_content_retention` 为兼容字段,不会延长 event/turn 副本或 `event_ref` 的可用时间。 ### 3.2 Collector 调优 @@ -169,6 +175,7 @@ audit: | 配置项 | 生效方式 | 说明 | |--------|----------|------| | `audit.retention` | **热重载** | 立即应用,下一次 GC tick 使用新值 | +| `lifecycle.audit.facts_retention` | **需重启** | 固定之后新写入 v2 事实的到期时间;已有记录期限不变 | | `audit.collector.batch_size` | **热重载** | 立即应用 | | `audit.collector.batch_interval` | **热重载**(⚠️ 实际需重启) | 变更会被记录,但刷写器需重启才能生效 | | `audit.enabled` | **需重启** | 禁止热关闭,防止管理员在审计盲区操作 | @@ -192,8 +199,8 @@ audit: | `auth.denied` | 无 / 无效凭证(HTTP 请求 + WS upgrade) | denied | 匿名拒绝(含 IP + UA) | | `session.create` | 创建新会话 | success | 会话建立 | | `session.delete` | 删除会话 | success | 会话清理 | -| `message.inbound` | 入站消息 | success | 消息接收(含文本内容) | -| `tool.call` | Worker 工具调用 | success | 工具执行记录 | +| `message.inbound` | 入站消息 | success | 消息接收事实,不含消息正文 | +| `tool.call` | Worker 工具调用 | success | 受限工具名和调用状态,不含工具输入 | | `admin.*` | Admin API 写操作 | success / failure | 管理操作(见 §4.6) | | `system.audit_config_changed` | 审计配置变更 | success | 配置变更 diff 追溯 | | `system.audit_export` | 导出审计数据 | success / failure | 导出成功或失败 | @@ -221,26 +228,11 @@ audit: ### 4.4 消息事件 -`message.inbound` 在 Gateway 收到入站消息时触发,**记录消息文本内容**到 `detail_json`。所有平台(飞书、Slack、WebChat、Cron)的入站消息统一记录。 +`message.inbound` 在 Gateway 收到入站消息时触发。所有平台(飞书、Slack、WebChat、Cron)统一记录事件时间、用户、平台、Session 和结果;`detail_json` 固定为空对象 `{}`,不保存消息文本或其摘要。 ### 4.5 Tool Call 事件 -Worker 每次执行工具调用时触发 `tool.call`。根据工具敏感性采用不同记录策略: - -**敏感工具**(存储完整输入 + PII 脱敏): - -| 工具名 | 说明 | -|--------|------| -| `Bash` / `bash` | Shell 命令执行 | -| `Write` / `write` | 文件写入 | -| `Edit` / `edit` | 文件编辑 | -| `MultiEdit` / `multiedit` | 多文件编辑 | -| `WebFetch` / `webfetch` | 网页抓取 | -| `WebSearch` / `websearch` | 网络搜索 | - -**非敏感工具**(仅存储 SHA-256 摘要 + 200 字符预览): - -其余所有工具仅记录 `input_sha256` + 截断预览(200 字符),兼顾审计覆盖与隐私保护。 +Worker 每次执行工具调用时触发 `tool.call`。当前只记录受限标识符形式的工具名和调用状态;未知或不符合标识符规则的工具名记作 `unknown`。工具参数、标题、内容摘要和预览都不会写入审计记录。 ### 4.6 Admin 操作事件 @@ -324,14 +316,14 @@ Row 3: ### 5.3 保留与 GC -审计记录默认保留 **3 年**(`audit.retention: 26280h`)。GC 过程在单个数据库事务中原子执行: +legacy 审计记录默认保留 **3 年**(`audit.retention: 26280h`);生命周期 v2 新事实默认保留 **180 天**(`lifecycle.audit.facts_retention: 4320h`)。legacy 链依据当前配置和记录时间计算清理边界;v2 链在写入时保存并哈希保护每条记录的 `expires_at`,GC 只删除已到期的连续前缀。哈希链只能安全地剪除前缀,因此某条未到期记录会暂时挡住后续已到期记录的删除。修改 v2 配置只改变之后写入记录的期限,不会缩短或延长已存在记录。两条链分别运行 GC,不能通过缩短 legacy 保留期来代替 v2 清理。每条链的 GC 都在单个数据库事务中原子执行: -1. 计算 cutoff 时间点(`now - retention`) -2. 在写事务中找到 cutoff 前最后一条记录的 ID -3. **先在事务内写入 Checkpoint(锚定待删前缀),再删除**——顺序即 `trg_ua_no_delete` 触发器契约;若表被清空则追加一条 `LastSelfHash=""` 的修正 Checkpoint,使下一条记录成为新创世 +1. legacy 链根据 `audit.retention` 计算 cutoff;v2 链选择 `expires_at` 已到期的记录边界 +2. 在写事务中找到该边界之前最后一条记录的 ID +3. **先在事务内写入对应链的 Checkpoint(锚定待删前缀),再删除**——顺序符合该链的删除触发器契约;若表被清空则追加一条 `LastSelfHash=""` 的修正 Checkpoint,使下一条记录成为新创世 4. 提交事务 -> **SQLite** 通过 `writeMu` 串行化,**PostgreSQL** 通过 `pg_advisory_xact_lock(819207)` 串行化,确保 GC 与写入不会产生竞态(C1/C2 race 已由单事务收口)。 +> **SQLite** 通过 `writeMu` 串行化;**PostgreSQL** 的 legacy 与 v2 链分别使用 `pg_advisory_xact_lock(819207)` 和 `pg_advisory_xact_lock(819208)`,确保各链 GC 与写入不会产生竞态(C1/C2 race 已由单事务收口)。 ### 5.4 后台 Verifier @@ -387,27 +379,18 @@ Spill 文件使用二进制格式(4-byte BE length + JSON payload),崩溃 ### 6.2 写入时凭证遮罩 -Tool Call 的敏感工具输入在写入审计表前,自动进行凭证遮罩: - -| 模式 | 遮罩规则 | 示例 | -|------|----------|------| -| API Key 前缀 | `hpk_*` / `sk-*` / `AKIA*` | `sk-abc123...` → `sk-a***************` | -| GitHub Token | `ghp_*` / `gho_*` / `ghs_*` | `ghp_xxxx...` → `ghp_x*********************` | -| Slack Token | `xox[baprs]-*` | `xoxb-12345-...` → `xoxb-1****************************` | -| Bearer Token | `Bearer` 后的值 | `Bearer abc123` → `Bearer [REDACTED]` | -| 私钥块 | `-----BEGIN ... PRIVATE KEY-----` | 整个块替换为 `[REDACTED]` | -| URL 凭证 | `https://user:pass@host` | 密码部分替换 | -| 赋值模式 | `password=...` / `token: ...` | 值部分替换 | +当前 `tool.call` 不写入工具输入,因此新记录没有工具参数需要遮罩。升级前已经存在的旧审计行不会被重写;它们可能包含旧版记录的内容,并按 `audit.retention` 到期清理。 ### 6.3 内容存储策略 -| 事件类型 | detail_json 内容 | drill-down | -|----------|-----------------|------------| -| `message.inbound` | `content` 保留消息原文,按 `audit.retention` 独立留存 | `event_ref` 若存在仅关联 events/turns,并只在 `events.retention` 窗口内可用 | -| 其他标准事件 | 摘要 + SHA-256 | `event_ref` 若存在仅作短期运行事实关联 | -| 敏感行为 | 完整上下文直接存储 | 无需 drill-down | +| 事件类型 | 当前新写入的 detail 内容 | drill-down | +|----------|--------------------------|------------| +| `message.inbound` | `{}`,不含正文、摘要或预览 | `event_ref` 若存在仅关联 events/turns,并只在 `events.retention` 窗口内可用 | +| 交互响应 | 关联 ID、allow/deny 结果或受限 elicitation action | 不保存回答正文或原因 | +| `tool.call` | 受限工具名与调用状态 | 不保存工具参数、摘要或预览 | +| 其他标准事件 | 按各事件的字段白名单记录 | `event_ref` 若存在仅作短期运行事实关联 | -**敏感行为**(直接存储完整上下文)包括:认证失败、敏感工具调用、权限拒绝、Worker 崩溃/超时。 +此前写入 legacy 链的旧审计行不会因升级自动脱敏或删除其中的内容;它们仍受 `audit.retention` 管理。新事实进入无正文的 v2 链。`audit.full_content_retention` 只是兼容字段,不提供独立正文清理。 ### 6.4 CSV 导出安全 @@ -853,7 +836,7 @@ groups: - [ ] **审计系统**:确认 `audit.enabled: true`(默认开启),生产环境**禁止关闭** - [ ] **Hash Chain 告警**:配置 `AuditChainBroken` 告警规则,确保链断裂在 5 分钟内通知 - [ ] **Spill 监控**:配置 `AuditSpillRateHigh` 告警,高并发场景适当调大 `channel_cap` -- [ ] **保留期**:根据合规要求分别调整 `audit.retention`(默认 3 年)与 `events.retention`(默认 30 天);`audit.full_content_retention`(默认 90 天)仅为兼容字段 +- [ ] **保留期**:根据合规要求分别调整 `audit.retention`(legacy 链默认 3 年)、`lifecycle.audit.facts_retention`(新链默认 180 天)与 `events.retention`(默认 30 天);`audit.full_content_retention`(默认 90 天)仅为兼容字段 - [ ] **身份链接**:为跨平台用户创建 Identity Link,确保跨渠道审计查询完整 - [ ] **Sink 投递**:生产环境配置 Webhook Sink 接入 SIEM,不要使用 noop 默认值 - [ ] **导出归档**:定期导出审计数据(JSON/CSV)并归档,满足合规审查要求 diff --git a/docs/guides/enterprise/disaster-recovery.md b/docs/guides/enterprise/disaster-recovery.md index 88d346b33..bd8ac4994 100644 --- a/docs/guides/enterprise/disaster-recovery.md +++ b/docs/guides/enterprise/disaster-recovery.md @@ -79,6 +79,8 @@ sqlite3 "${BACKUP_DIR}/hotplex-${TIMESTAMP}.db" "PRAGMA integrity_check;" find "$BACKUP_DIR" -name "*.db" -mtime +30 -delete ``` +HotPlex 的会话生命周期清理不会改写已有备份。若恢复的快照包含后来已删除的会话,恢复后这些数据可能重新出现;部署方应为在线及离线备份单独设定保留与销毁期限,并在恢复后按需重新执行清理。 + Cron 配置: ``` diff --git a/docs/guides/enterprise/resource-limits.md b/docs/guides/enterprise/resource-limits.md index 0b03dd65d..b873a90d3 100644 --- a/docs/guides/enterprise/resource-limits.md +++ b/docs/guides/enterprise/resource-limits.md @@ -15,9 +15,17 @@ description: Global and per-user quotas, worker limits, output limits, pool mana ```yaml session: max_concurrent: 1000 # 状态机层最大并发 Session(默认 1000) - retention_period: 168h # Session 保留期(默认 7 天) + retention_period: 168h # 兼容的运行到期时长,不是聊天历史保留期 gc_scan_interval: 10m # GC 扫描间隔(默认 10 分钟) +lifecycle: + policy: v2 + conversation: + archive_after: 168h # 7 天无输入后显示归档标记 + retention_after_last_input: 4320h # 会话期限:180 天 + content: + retention: 4320h # 每条正文期限:180 天 + pool: max_size: 100 # 全局最大活跃 Session(默认 100) min_size: 0 # 最小保留 Session(默认 0) @@ -57,9 +65,11 @@ worker: max_lifetime: 24h # 单 Worker 最长存活时间(默认 24h) idle_timeout: 60m # 空闲超时(默认 60m) execution_timeout: 30m # 单次执行超时(默认 30m) - turn_timeout: 0 # 单轮超时(默认关闭,由 execution_timeout 兜底) + turn_timeout: 30m # 单轮绝对上限(持续输出不会延长) ``` +`worker.idle_timeout` 释放运行资源,`lifecycle.conversation.archive_after` 只标记冷会话,`lifecycle.conversation.retention_after_last_input` 与 `lifecycle.content.retention` 管理不同的数据期限。查看历史不会延长任何期限。`session.retention_period` 是兼容运行到期时长,不应用来设置聊天正文保留期。 + | 限制 | 触发条件 | 行为 | |------|----------|------| | `max_lifetime` | Worker 存活超过 24h | 强制终止 | diff --git a/docs/issues/2026-10-data-lifecycle/D01-session-history-retention.md b/docs/issues/2026-10-data-lifecycle/D01-session-history-retention.md new file mode 100644 index 000000000..eb375f58f --- /dev/null +++ b/docs/issues/2026-10-data-lifecycle/D01-session-history-retention.md @@ -0,0 +1,53 @@ +--- +title: "D01:会话运行回收与历史保留期耦合" +weight: 1 +description: "空闲运行终止后,会话索引可能先于聊天正文到期,导致用户历史不可访问。" +--- + +# D01 — 会话运行回收与历史保留期耦合 + +- 优先级:P1 +- 基准:`4c7c685a` +- 证据:Source;用户报告历史会话消失,当前尚无对应实例 Live 复现 +- 状态:实施中 +- 位置:`internal/session/manager.go` 的 `CreateWithBot`、`ResetExpiry`、`gc`;`internal/gateway` 历史授权与读取路径;`cmd/hotplex/gateway_run.go` 的 `runEventsGC` + +## 现象与触发 + +运行管理和用户历史保留共用或互相覆盖到期语义。会话进入 IDLE 后,Worker 空闲回收会终止运行;终止记录 GC 与事件正文 GC 又使用独立 TTL。若 `sessions` 根行先被删除,历史接口在读取 turns 前无法通过会话归属授权,即使正文仍处于事件保留期也会表现为历史消失。 + +新反馈要求不占用运行资源的冷会话仍应长期可读、可恢复。目标默认策略为:空闲 60 分钟释放 Worker;7 天无有效用户输入后归档;用户会话从最后一次有效输入起保留 180 天;聊天正文从各自创建时间起保留 180 天。以上期限可配置。 + +## 影响 + +- 用户无法从历史列表进入仍未到正文期限的聊天。 +- 为了保留会话而延长 Worker 生命周期会增加运行资源占用。 +- 单纯延长 event TTL 无法避免 session root 被较短的终止记录 GC 删除。 +- 跨 SQLite/PostgreSQL 的策略不一致可能造成数据过早不可见。 + +## 验收条件 + +- Worker 空闲回收、会话归档和正文清理各用独立时钟;运行停止不删除会话。 +- 默认 180 天内的冷会话可查看并开启新 run;读取历史不续期。 +- 新用户输入延长会话到期时间;不改变既有消息的到期时间。 +- 未到期正文保护历史入口;已到期正文在物理 GC 前也不会从任何读取路径泄露。 +- SQLite、PostgreSQL、重启恢复、分页与并发新写入使用一致的期限计算。 +- 默认时长通过 YAML 和环境变量配置;升级存量数据不被提前删除,延长期限经预览及独立 apply。 + +## 实施记录 + +- 基线 Source:确认默认 `session.retention_period=168h`、`session.term_retention=168h`、`events.retention=720h`;运行时 `worker.idle_timeout=60m`。 +- Live:未对用户实例做配置或数据库检查;用户报告的具体会话 ID、创建时间和实例配置未知。 +- Source:新增 v2 lifecycle deadline 与逐条正文期限;Legacy TERMINATED GC 排除 v2,事件/轮次 GC 优先使用每条记录的 `expires_at`。 +- Source:单轮绝对 deadline 绑定 Worker run 并持久化;恢复时沿用旧 deadline。超时收尾先终止 Worker,再持久化 `runtime_status=failed`,按序发送错误和失败事实。 +- Test:`go test ./internal/gateway -run 'FinishTurnTimeout|RestoredTurnDeadline|TurnTimeout|TurnDeadline' -count=1` 通过(2026-10-08);`go test ./internal/config ./internal/execution ./internal/gateway -run 'EffectiveTurnTimeout|SetTurnDeadline|TurnDeadline|TurnTimeout|InputExecution|SystemInput' -count=1` 此前通过。 +- Source:v2 会话到期现在由 GC 复核会话期限、正文期限和执行/队列/清理义务后退役,并原子写入删除屏障与 cleanup outbox。新输入与到期退役共享会话行锁;过期 session 拒绝新输入,已接受的重复 ID 仍保持幂等。 +- Test:SQLite/PostgreSQL SQL mock 覆盖候选筛选及退役复核;`go test ./internal/session ./internal/execution ./internal/eventstore ./internal/gateway ./internal/config ./internal/effect ./internal/messaging/slack ./internal/messaging/feishu ./internal/worker/acp ./cmd/hotplex -count=1` 通过(2026-10-08)。到期退役竞态的 session/execution race 测试,以及 lifecycle 指标变更后的 `go test -race ./internal/session ./internal/observability -count=1` 通过(2026-10-08)。 +- Source:成功退役会话记录删除计数和期限到退役延迟直方图;查询/退役失败按固定阶段计数。存量期限迁移已提供只读 Admin 预览与独立双重确认 apply;按批延长事件/轮次期限,并确保会话根策略在其所有正文都达到新旧有效保留期后才切换。 +- Test:`go test ./internal/lifecycle -count=1` 通过(2026-10-08);覆盖缺失输入时钟、已删除/孤立内容、双重确认、stale 快照、批次续跑、正文先于会话根迁移、策略变化回滚及敏感信息不出现在预览。 +- Source:生命周期 GC 暴露 eligible/blocked 积压、最老到期延迟及 unknown/fenced execution 阻塞 gauge;超过 `gc.max_lag` 记录聚合告警,status 查询失败使用固定 phase 计数。分类处理量 Counter 覆盖会话、正文/事件/轮次、投递事实、审计事实、trace/media 文件和会话清理项;标签集合固定,不含会话或用户标识。 +- Test:SQLite/PostgreSQL store 单元及 SQL mock 覆盖过期/阻塞计数和最老期限;真实 PostgreSQL 查询仍未运行验证。 +- Test:`go test ./internal/session ./internal/audit ./internal/observability ./internal/messaging/slack ./internal/messaging/feishu ./cmd/hotplex -race -shuffle=on -count=1` 与 `make docs-lint` 通过(2026-10-08)。 +- Remaining:execution/Cron/effect 幂等根记录的保留边界见 D02;provider-specific unsupported/远端清理结果没有独立指标,真实 PostgreSQL 生命周期与多实例仍未验证。 +- Test:真实 PostgreSQL 生命周期迁移与多实例运行尚未验证;本地 SQL mock 不替代该验证。 +- Live:未执行真实实例配置变更、存量迁移、数据删除或 Worker 远端清理。 diff --git a/docs/issues/2026-10-data-lifecycle/D02-delete-cache-dependent-semantics.md b/docs/issues/2026-10-data-lifecycle/D02-delete-cache-dependent-semantics.md new file mode 100644 index 000000000..3baf176f5 --- /dev/null +++ b/docs/issues/2026-10-data-lifecycle/D02-delete-cache-dependent-semantics.md @@ -0,0 +1,43 @@ +--- +title: "D02:会话删除语义依赖缓存状态" +weight: 2 +description: "缓存命中、缓存未命中与 WebChat 删除接口采用不同删除路径,可能级联丢失执行事实或允许历史继续读取。" +--- + +# D02 — 会话删除语义依赖缓存状态 + +- 优先级:P1 +- 基准:`4c7c685a` +- 证据:Source、Test;未在用户实例 Live 复现 +- 状态:实现完成;真实 PostgreSQL 与运行实例验证待执行 +- 位置:`internal/session/{manager.go,cleanup_outbox.go,purge.go,purge_store.go,purge_runner.go}`;`internal/gateway/api.go` 的 Delete/cleanup-status 与会话授权入口;Admin Delete handler + +## 现象与触发 + +显式删除的结果取决于会话是否已经进入内存缓存:缓存命中时,`Manager.Delete` 将会话标记为 `DELETED` 并入队 Worker 清理;缓存未命中时,当前实现调用 `DeletePhysicalWithCleanup`,直接删除 sessions 根行,可能级联删除仍需保留的 execution facts。WebChat 的 `DELETE /api/sessions/{id}` 也绕过统一删除路径,先转换状态再调用 `DeletePhysical`。 + +`sessions.upsert_session` 目前只在 `deleted_at IS NULL` 时限制更新,但显式删除未可靠写入 `deleted_at`;当没有远端 Worker 清理任务时,旧 Upsert 仍可能把 `DELETED` 会话恢复为活跃状态。历史 API 的统一授权入口也没有拒绝 `DELETED` 会话。 + +## 影响 + +- 同一删除请求可能因为缓存冷热而保留或级联删除执行事实。 +- 删除后历史正文可能仍能从 turns/events 读取。 +- 远端 Worker 清理无任务时,迟到或重放的 Upsert 可能复活会话。 +- REST、AEP 与管理端的删除状态和完成含义不一致。 + +## 验收条件 + +- 缓存命中与未命中执行相同的持久删除屏障,不在业务删除入口直接物理删除 sessions 根行。 +- 已删除会话立即从历史、事件和会话详情读取中隐藏,不能续聊或被 stale Upsert 复活。 +- 删除事务失败时保留原会话;成功后持久化 `deleted_at` 和清理意图。 +- SQLite 与 PostgreSQL 的删除更新、清理任务入队及重启行为一致。 +- 最终物理清理不得在执行事实、未知投递或清理义务仍受保护时级联移除根行。 + +## 实施记录 + +- Source(基准):`Manager.Delete` 缓存未命中分支调用 `DeletePhysicalWithCleanup`;命中分支调用 `MarkDeletedWithCleanup`。`GatewayAPI.DeleteSession` 调用 `DeletePhysical`。 +- Source(修复后):缓存命中/未命中统一先安装内存删除屏障,再事务更新 `state`、`deleted_at` 并入清理 outbox;数据库使用权威存储行创建 Worker 清理意图。REST 删除调用 `Manager.Delete`;历史、事件、详情和续聊复用的授权入口拒绝已删除会话;upsert 条件拒绝恢复 deleted 行。 +- Source(修复后):Gateway REST 与 Admin 删除入口均调用 `Manager.Delete`;缓存冷热一致地事务写入 tombstone、Worker cleanup outbox 与持久 purge job/items。后台分别清理 Worker 会话和事件/轮次正文,状态接口只返回清理阶段、次数与受限错误码。 +- Test(修复后):缓存冷热删除、Worker 清理重试、正文清理重试、stale lease fencing、已删除历史隐藏、WebChat/Admin 删除与状态读取回归通过;`make test-short` 通过。 +- Remaining:真实 PostgreSQL 并发、进程重启及远端 Worker 清理验收尚未执行。内部生命周期维护仍有 `DeletePhysical` 路径,不作为公开用户删除入口;其数据范围按调用场景单独核验。 +- Live:未检查实际数据库是否存在已删除后可读、可复活或已级联丢失事实的记录。 diff --git a/docs/issues/2026-10-data-lifecycle/D02-idempotency-fact-retention.md b/docs/issues/2026-10-data-lifecycle/D02-idempotency-fact-retention.md new file mode 100644 index 000000000..00ae105cc --- /dev/null +++ b/docs/issues/2026-10-data-lifecycle/D02-idempotency-fact-retention.md @@ -0,0 +1,43 @@ +--- +title: "D02:执行与投递幂等事实缺少安全保留边界" +weight: 2 +description: "不能在来源回放窗口未受约束时按年龄删除仍承担幂等职责的根记录。" +--- + +# D02 — 执行与投递幂等事实缺少安全保留边界 + +- 优先级:P1 +- 基准:`4c7c685a` +- 证据:Source;未对实际部署实例进行 Live 数据验证 +- 状态:安全细节清理已实现;幂等根记录期限待来源回放契约 +- 位置:`internal/execution` 的 `execution_inputs`、`internal/cron` 的 `cron_occurrences`、`internal/effect` 的 `effects` / `effect_attempts` + +## 现象与触发 + +生命周期配置规划了已结算事实 90 天保留,但输入 ID、Cron trigger key 和投递业务键同时承担去重职责。当前没有强制的客户端或来源回放截止时间,若仅按记录年龄删除这些根记录,迟到重放可能再次接受输入、运行 Cron 或发送外部消息。 + +## 影响 + +- 删除执行输入的唯一键可能让同一客户端消息 ID 被当作新输入。 +- 删除 Cron occurrence 可能让重复 webhook 或计划触发再次创建执行。 +- 删除投递 effect 可能让恢复或重试路径再次发送已完成消息。 +- `unknown`、活动租约及缺少可靠结算时间的旧记录不能被当成普通终态数据。 + +## 验收条件 + +- 区分承载幂等的根记录与可独立过期的详细 attempt 证据。 +- 仅在 effect 已进入终态、结算时间可靠、租约已关闭且 attempt 已结束后,按可配置期限清除详细 attempt。 +- `unknown`、活动租约、未完成 attempt 和缺少结算时钟的 legacy 记录保持不变。 +- 清理详细 attempt 后,effect 根记录仍能阻止重复投递。 +- 只有回放期限已被强制执行,或具备可验证的最小墓碑时,才允许缩短根记录期限。 +- SQLite 与 PostgreSQL 使用相同安全条件,并有并发和恢复回归覆盖。 + +## 实施记录 + +- Source:确认 `execution_inputs` 以 `(session_id, client_message_id)` 去重,`cron_occurrences` 以 `(trigger_key, generation)` 去重,`effects` 以 `(occurrence_id, delivery_ordinal, target_revision)` 去重。 +- Source:确认计划默认 90 天只能安全适用于独立的结算尝试详情;根记录继续保留,直到其来源回放期限有明确契约。 +- Source:新增 `DeleteSettledAttempts`,SQLite 与 PostgreSQL 共用相同的有界选择条件;Gateway 在 v2 策略下按 `lifecycle.facts.retention_after_settlement` 周期运行。 +- Source:新增 `CompactSettledFacts`,对已结算且无 fence/队列引用的 `execution_inputs` 清除 owner、错误明细与单轮诊断时钟,保留 client message ID、payload hash、worker run ID、最终状态和结算时钟;仍可能迟到收敛的 `failed` delivery + `completed` runtime 行不会压缩。 +- Test:覆盖期限边界、未知结果、活动租约、未结束 attempt、缺少结算时钟及删除 attempt 后 effect 仍阻止重复投递;执行记录压缩测试确认旧 client message ID 仍保持幂等。 +- Remaining:execution/Cron/effect 根记录在来源回放期限被强制前继续保留;阻塞事实积压报告、辅助表清理和真实 PostgreSQL 运行验证尚未完成。 +- Live:未访问或修改任何生产数据库、配置或外部平台消息。 diff --git a/docs/issues/2026-10-data-lifecycle/D03-audit-facts-retention.md b/docs/issues/2026-10-data-lifecycle/D03-audit-facts-retention.md new file mode 100644 index 000000000..0a1ea4b3a --- /dev/null +++ b/docs/issues/2026-10-data-lifecycle/D03-audit-facts-retention.md @@ -0,0 +1,45 @@ +--- +title: "D03:新旧审计事实没有独立保留期" +weight: 3 +description: "新审计事实需要独立 180 天保留期,不能用同一条不可变旧链的 GC 阈值缩短历史证据。" +--- + +# D03 — 新旧审计事实没有独立保留期 + +- 优先级:P1 +- 基准:`4c7c685a` +- 证据:Source + Test;使用独立临时库运行 PostgreSQL migration 测试,未访问实例业务库 +- 状态:实现与 SQLite/PostgreSQL 验证已完成 +- 位置:`internal/audit/{store.go,gc.go,verify.go}`;SQLite/PostgreSQL migration 044 + +## 现象与触发 + +现有 `user_activity` 与 `audit_chain_checkpoints` 是一条不可变哈希链,GC 只能以 checkpoint 锚定方式删除链前缀。配置中的旧审计保留期约为三年;生命周期 v2 需要新审计事实默认保留 180 天。直接把旧 GC 阈值改为 180 天会提前删除仍在旧保留期内的证据,也会混淆旧链和新策略的验证边界。 + +## 影响 + +- 新旧审计事实无法采用各自的保留期限。 +- 旧链正文不能安全逐行改写或删除。 +- 管理端查询与审计验证需要能识别不同链 epoch。 + +## 验收条件 + +- 生命周期 v2 的新审计事实进入独立、无正文的哈希链,默认按 `lifecycle.audit.facts_retention` 保留 180 天。 +- legacy `user_activity` 链继续遵循既有配置,不被 v2 GC 清理。 +- 两条链有独立 genesis、checkpoint、验证和 GC;各自的 GC 都先写 checkpoint,再原子删除前缀。 +- v2 新事实在写入时固定并哈希保护 `expires_at`;变更保留期只影响新事实,不追溯改写存量。 +- v2 GC 只删除已到期的连续前缀;未到期记录会阻止其后的到期记录被提前删除。 +- 管理端查询可跨 epoch 聚合并返回链 epoch;相同数据库 ID 不会造成客户端行键冲突。 +- 新链与旧链写入、验证和清理均有 SQLite/PostgreSQL 回归覆盖。 +- 旧链明文迁移、删除和存量期限缩短不在本问题的自动处理范围内。 + +## 实施记录 + +- Source(基准):旧链的删除触发器要求 checkpoint 锚定;直接改旧 GC retention 会提前删除 legacy 数据并覆盖其原保留要求。 +- Source(修复后):Gateway 将新事实写入独立 v2 链,legacy 与 v2 各自运行 GC 和 Verifier;Admin 聚合查询同时返回两种 `chain_epoch`。 +- Source(修复后):v2 每条记录在写入时固定并哈希保护 `expires_at`。GC 仅剪除已到期连续前缀,避免配置变更后误删其后的未到期记录。 +- Test:`go test ./internal/audit ./internal/session/sql -race -shuffle=on -count=1` 通过;SQLite migration 验证表、索引、不可变触发器和 checkpoint 删除约束;PostgreSQL 存储 SQL 有 mock 覆盖。 +- Test(发现,2026-10-08):PostgreSQL 集成测试最初在读取 v2 新插入 ID 时失败:`LastInsertId is not supported by this driver`;夹具已改用 `INSERT … RETURNING id`。 +- Test(修复后,2026-10-08):本地 PostgreSQL 18.6 独立临时数据库上的 `TestMigrations_PG_AuditChainsStayImmutable` 通过;覆盖 UPDATE、未锚定 DELETE 与 checkpoint 锚定 DELETE。生命周期 PG 包集以 `-race -shuffle=on` 再次通过。 +- Test(基准):`4c7c685af12524c8dea5c031b2570a09cf923ee0`;失败来自该基准之上的未提交生命周期改动。 +- Live:未读取运行实例数据、配置或真实审计日志。 diff --git a/docs/issues/2026-10-data-lifecycle/D04-execution-time-window-test-precision.md b/docs/issues/2026-10-data-lifecycle/D04-execution-time-window-test-precision.md new file mode 100644 index 000000000..1381d4c7a --- /dev/null +++ b/docs/issues/2026-10-data-lifecycle/D04-execution-time-window-test-precision.md @@ -0,0 +1,37 @@ +--- +title: "D04:执行记录时间窗测试依赖毫秒级时钟恰好分离" +weight: 4 +description: "ListRecent 时间窗测试连续创建记录后假设其 created_at 毫秒值不同,导致窗口断言不稳定。" +--- + +# D04 — 执行记录时间窗测试依赖毫秒级时钟恰好分离 + +- 优先级:P2 +- 基准:`4c7c685af12524c8dea5c031b2570a09cf923ee0` +- 证据:Source + Test;在本地 PG 集成验证期间复现于 SQLite 单元测试 +- 状态:已复现并修复;重复、race 和 shuffle 验证通过 +- 位置:`internal/execution/sql_console_test.go::TestListRecent_TimeWindow`、`internal/execution/sql_store.go::Accept` + +## 现象与触发 + +`acceptN` 连续接受两条记录,`Accept` 使用 `time.Now().UnixMilli()` 生成时间。测试以第一条记录时间 `pivot` 构造 `[pivot, pivot+1)` 窗口,并断言只返回一条;两次接受落在同一毫秒时,两条记录都会命中。 + +## 影响 + +- 时间窗测试偶发或连续失败,影响执行记录分页/筛选相关质量门禁。 +- 失败来自测试数据对墙钟精度的假设;本次运行没有证据表明生产筛选 SQL 存在边界错误。 + +## 验收条件 + +- 测试显式写入确定且相邻的 `created_at` 值,不使用 `time.Sleep` 等待时钟跨毫秒。 +- 窗口边界仍验证 `SinceMs` inclusive、`UntilMs` exclusive,并且 `[pivot, pivot+1)` 仅命中一条记录。 +- 单测在重复执行、race 和 shuffle 模式下稳定通过。 +- 本修复不改变执行记录生产时间或 `ListRecent` 查询语义。 + +## 实施记录 + +- Source:`internal/execution/sql_store.go::Accept` 将创建时间按 Unix 毫秒取值;`ListRecent` 使用 `created_at >= SinceMs` 与 `created_at < UntilMs`。 +- Test(2026-10-08):`go test ./internal/execution -run TestListRecent_TimeWindow -count=10` 失败 8 次,均在窗口断言处返回 2 条记录。 +- Fix:测试改为显式将相邻记录的 `created_at` 写为 `pivot` 与 `pivot+1`,不再依赖系统时钟跨毫秒。 +- Test(修复后,2026-10-08):`go test ./internal/execution -run TestListRecent_TimeWindow -count=10` 通过;完整 `execution` PG 包集使用 `-race -shuffle=on` 通过。 +- Live:未读取或修改运行实例数据库;本地 SQLite 测试使用临时数据库。 diff --git a/docs/issues/2026-10-data-lifecycle/D05-pg-lifecycle-retirement-placeholder-rebinding.md b/docs/issues/2026-10-data-lifecycle/D05-pg-lifecycle-retirement-placeholder-rebinding.md new file mode 100644 index 000000000..dbe3e5382 --- /dev/null +++ b/docs/issues/2026-10-data-lifecycle/D05-pg-lifecycle-retirement-placeholder-rebinding.md @@ -0,0 +1,44 @@ +--- +title: "D05:PostgreSQL 生命周期归档未重绑定清理 SQL 占位符" +weight: 5 +description: "PostgreSQL 到期归档路径把问号占位符直接交给 pgx,导致清理 outbox SQL 语法失败并回滚归档事务。" +--- + +# D05 — PostgreSQL 生命周期归档未重绑定清理 SQL 占位符 + +- 优先级:P1 +- 基准:`4c7c685af12524c8dea5c031b2570a09cf923ee0` +- 证据:Source + Test;本地 PostgreSQL 集成复现,测试只使用独立临时数据库 +- 状态:已修复;SQLite 与 PostgreSQL 生命周期回归验证通过 +- 位置:`internal/session/cleanup_outbox.go::retireExpiredLifecycleSession` + +## 现象与触发 + +对 PostgreSQL 中已到期的 v2 会话执行 `RetireExpiredLifecycleSession` 时,生命周期事务锁定并检查会话后,在原子写入清理 outbox 的阶段失败: + +```text +session cleanup: enqueue: ERROR: syntax error at or near "," (SQLSTATE 42601) +``` + +该路径将原始 `*sql.Tx` 传给 `markDeletedAndEnqueueCleanupLocked`。其中 outbox INSERT 和会话脱敏 UPDATE 使用 `?` 占位符;SQLite 接受该语法,PostgreSQL 必须经 `DialectPostgres.Rebind` 转为 `$N`。其它 PostgreSQL 删除入口使用 `pgExec` 做重绑定,但到期归档路径遗漏了该包装。 + +## 影响 + +- PostgreSQL 上的到期会话无法完成归档/删除事务。 +- 会话脱敏、cleanup outbox 与 purge job 无法原子落库。 +- 清理 runner 不能接管该会话,生命周期 GC 会持续重试并积压。 + +## 验收条件 + +- PostgreSQL 到期归档路径对 cleanup outbox INSERT 和 session UPDATE 使用 dialect rebind。 +- 真实 PostgreSQL 测试验证归档成功、敏感会话字段被清空、cleanup task 与 purge job 同事务产生。 +- 两个独立 PostgreSQL store 实例并发执行新输入与到期归档时,输入成功则归档必须被活跃执行事实阻止;归档成功则新输入必须失败。 +- SQLite 到期归档和删除行为保持不变。 + +## 实施记录 + +- Source(基准):`retireExpiredLifecycleSession` 通过 `markDeletedAndEnqueueCleanupLocked(ctx, tx, tx, ...)` 传入原始 `*sql.Tx`;PostgreSQL 删除的其它入口则使用带 `DialectPostgres.Rebind` 的 `pgExec`。 +- Test(2026-10-08):在独立临时数据库运行 `HOTPLEX_TEST_PG_DSN=… go test -tags pg -p 1 -parallel 1 -race -shuffle=on -count=1 ./internal/session -run 'TestPGStore_RetirementSerializesWithInputAcceptanceAcrossInstances|TestPGStore_DeleteFencesConcurrentLateUpsertAcrossInstances'`,到期归档事务以 SQLSTATE `42601` 失败;错误定位到原始 `?` 占位符未重绑定。 +- Fix:共享到期归档事务现将 cleanup outbox 与会话脱敏 SQL 交给 dialect-aware executor;SQLite 使用 identity rebind,PostgreSQL 使用 `$N` 占位符。 +- Test(修复后,2026-10-08):新增的跨实例“输入接受 vs 到期归档”与“删除 vs 迟到 Upsert”真实 PG 竞争测试通过;`session/sql`、`session`、`audit`、`effect`、`execution`、`eventstore` 六个包以 `-race -shuffle=on` 在独立临时 PostgreSQL 数据库通过。 +- Live:未读取或修改运行实例数据库;未访问已有业务库。 diff --git a/docs/issues/2026-10-data-lifecycle/D06-deletion-scope-excludes-backups-and-platform-messages.md b/docs/issues/2026-10-data-lifecycle/D06-deletion-scope-excludes-backups-and-platform-messages.md new file mode 100644 index 000000000..d2e3ddc80 --- /dev/null +++ b/docs/issues/2026-10-data-lifecycle/D06-deletion-scope-excludes-backups-and-platform-messages.md @@ -0,0 +1,40 @@ +--- +title: "D06:删除范围未说明备份与外部平台副本" +weight: 6 +description: "会话清理只覆盖 HotPlex 自有数据和 Worker 会话,当前生命周期文档没有明确备份及 Slack/飞书消息的边界。" +--- + +# D06 — 删除范围未说明备份与外部平台副本 + +- 优先级:P1 +- 基准:`4c7c685af12524c8dea5c031b2570a09cf923ee0` +- 证据:Source + Docs;只读检查当前清理实现及可达用户文档 +- 状态:已修正文档并通过文档构建与链接验证 +- 位置:`docs/explanation/session-lifecycle.md`、`docs/reference/admin-api.md`、`docs/guides/enterprise/disaster-recovery.md` + +## 现象与触发 + +用户主动删除会话后,HotPlex 会隐藏本地历史、阻止续聊,并通过持久清理任务删除 HotPlex 正文和 Worker 侧会话。当前说明没有明确指出,该操作不会删除 Slack/飞书等外部平台上已发送的消息,也不会改写此前生成的数据库备份。 + +源码确认 `CleanupRunner` 只调用 Worker 类型对应的远端会话清理器;消息适配器没有删除平台消息的会话清理路径。灾备文档把数据库快照作为独立备份处理,生命周期删除不会追溯修改这些快照。 + +## 影响 + +- 用户可能把 HotPlex 本地删除误解为所有副本均已擦除。 +- 从旧备份恢复后,被删会话数据可能重新出现。 +- 运维人员可能忽略平台自带保留策略和备份介质到期/销毁策略。 + +## 验收条件 + +- 生命周期说明明确 HotPlex 删除覆盖本地 HotPlex 数据及关联 Worker 会话。 +- 明确说明外部平台消息和既有备份不随会话删除;恢复旧备份可能恢复已删除数据。 +- 指明外部平台消息保留由平台策略负责,备份过期和销毁由部署方备份策略负责。 +- Admin API 删除与清理状态文档不再暗示跨系统完整擦除。 + +## 实施记录 + +- Source:`CleanupExecutor` 只接收 Worker 类型和 Worker session ID;对话正文由 `PurgeContentExecutor` 清理本地会话内容。检索 Slack/飞书适配器未发现会话删除时调用平台消息删除 API 的路径。 +- Docs:`docs/explanation/session-lifecycle.md` 和 `docs/reference/admin-api.md` 描述本地正文与 Worker 清理状态,但未写备份或外部平台副本边界;灾备文档将数据库备份作为独立快照管理。 +- Fix:生命周期说明、Admin API 清理状态说明和灾备指南现明确外部平台消息与既有备份不随 HotPlex 会话删除,并说明恢复旧备份可能重新出现数据。 +- Test:`make docs-build` 与 `make docs-lint` 通过;文档只读构建和链接校验通过。 +- Live:未连接或读取外部平台账号、备份文件或运行实例。 diff --git a/docs/reference/admin-api.md b/docs/reference/admin-api.md index 3ba602ac0..db3e97757 100644 --- a/docs/reference/admin-api.md +++ b/docs/reference/admin-api.md @@ -56,8 +56,8 @@ admin: | `config:write` | - | - | - | 🟠 Write | - | - | `POST /admin/config/rollback` | | `runtime:read` | - | - | - | - | - | - | `GET /admin/executions`
`GET /admin/executions/{id}/timeline`
`GET /admin/executions/fences`
`GET /admin/effects`
`GET /admin/effects/{id}`
`GET /admin/sessions/{id}/runtime-plan`(需与 `session:read` 同时持有) | | `runtime:write` | - | - | - | - | - | - | `POST /admin/executions/{id}/fence-action`
`POST /admin/executions/{id}/queue-cancel`
`POST /admin/sessions/{id}/queue-clear`
`POST /admin/effects/{id}/action` | -| `admin:read` | - | - | - | - | 🟢 Read | 🟢 Read | `GET /admin/logs`
`GET /admin/debug/...`
`GET /admin/bots`
`GET /admin/cron/jobs` | -| `admin:write` | - | - | - | - | - | 🟠 Write | `POST/PATCH/DELETE /admin/cron/jobs`
`POST /admin/cron/jobs/{id}/run` | +| `admin:read` | - | - | - | - | 🟢 Read | 🟢 Read | `GET /admin/logs`
`GET /admin/debug/...`
`GET /admin/bots`
`GET /admin/cron/jobs`
`POST /admin/lifecycle/migration/preview` | +| `admin:write` | - | - | - | - | - | 🟠 Write | `POST/PATCH/DELETE /admin/cron/jobs`
`POST /admin/cron/jobs/{id}/run`
`POST /admin/lifecycle/migration/apply` | > 💡 **Scope 蕴含**:`admin:write` 蕴含 `admin:read`、`runtime:read`、`runtime:write`;`admin:read` 蕴含 `config:read`(#877)。 @@ -151,6 +151,30 @@ curl -H "Authorization: Bearer $TOKEN" \ > **注意**:会话创建不通过 Admin API,而是通过 Gateway API(`POST /api/sessions`)或 WebSocket `init` 握手完成。Admin API 仅提供只读查询和终止/删除操作。 +### 存量会话期限迁移 + +| 方法 | 路径 | Scope | 说明 | +|------|------|-------|------| +| POST | `/admin/lifecycle/migration/preview` | `admin:read` | 只读汇总可迁移的 legacy 会话与正文期限 | +| POST | `/admin/lifecycle/migration/apply` | `admin:write` | 双重确认后按批延长存量期限 | + +**POST /admin/lifecycle/migration/preview** — 无请求体。响应只含汇总数量、正文字节数、最早目标期限、策略版本和临时 `plan_id`;不返回会话 ID、用户身份、聊天正文、payload 或凭证。`expires_at` 是该预览计划的失效时间,计划保存在当前 Gateway 内存中,有效 30 分钟;Gateway 重启后需要重新预览。 + +预览包含 `sessions`、`events`、`turns` 三类摘要。会话数量统计拥有可靠 `last_input_at`、未删除的 legacy 会话;事件和轮次摘要统计需要延长期限的正文。`blocked` 中的缺少活动时间、孤立正文计数不会被迁移;`unknown_execution_records` 与 `active_execution_records` 只报告未完成或运行中的执行事实,这些事实不会被迁移接口改写。 + +**POST /admin/lifecycle/migration/apply** — 每次最多按 `batch_size` 更新各数据类型的一批,实际最大值为 500。请求必须重复提交相同计划 ID: + +```json +{ + "plan_id": "", + "confirm_plan_id": "" +} +``` + +Apply 只延长期限,不会缩短已有期限。每批先推进事件和轮次正文;仅当某会话的全部现存正文都具有不短于有效 legacy/v2 正文期限的明确到期时间时,才将该会话切换到 v2。因而大历史会话可能需要多轮“预览 → 确认应用”,`apply` 响应中的 `sessions`、`events`、`turns` 是本批实际更新数。 + +若预览后数据或策略发生变化,返回 `409 PREVIEW_STALE`;计划过期或 Gateway 重启后提交,返回 `409 PREVIEW_EXPIRED`,都需要重新预览。计划 ID 不匹配返回 `400 CONFIRMATION_REQUIRED`。该接口不会补造缺失的 `last_input_at`,也不会处理已删除会话、孤立正文或未知运行事实。 + ### Runtime Operations(#877 / #946) | 方法 | 路径 | Scope | 说明 | @@ -370,7 +394,7 @@ Bot 列表与详情的 `agent_configs` 元数据只包含 `soul`、`agents`、`t ### 用户行为审计 -`/admin/activity` 系列端点查询 issue #833 的 `user_activity` 表。所有读端点需要 `admin:read`;导出默认脱敏,`include_pii=true` 需要 `admin:write`。每次导出都会写入 `system.audit_export` meta-audit。 +`/admin/activity` 系列端点合并查询 legacy `user_activity` 与 lifecycle-v2 `user_activity_v2` 两条独立哈希链。响应中的 `chain_epoch` 标记记录来源(`legacy` 或 `lifecycle-v2`);两张表的本地 `id` 可重复,因此客户端应使用 `chain_epoch + id` 作为行键。所有读端点需要 `admin:read`;导出默认脱敏,`include_pii=true` 需要 `admin:write`。每次导出都会写入 `system.audit_export` meta-audit。 | 方法 | 路径 | Scope | 说明 | |------|------|-------|------| @@ -503,7 +527,8 @@ Gateway API(`/api/sessions`)监听在网关主端口(`8888`),面向客 | GET | `/api/sessions` | API Key | 列出当前用户的会话 | | POST | `/api/sessions` | API Key | 创建会话 | | GET | `/api/sessions/{id}` | API Key | 获取单个会话 | -| DELETE | `/api/sessions/{id}` | API Key | 删除会话 | +| DELETE | `/api/sessions/{id}` | API Key | 删除会话并排队清理 | +| GET | `/api/sessions/{id}/cleanup` | API Key | 查询删除后的清理进度 | | POST | `/api/sessions/{id}/cd` | API Key | 切换工作目录 | | GET | `/api/sessions/{id}/history` | API Key | 获取会话历史 | | GET | `/api/sessions/{id}/events` | API Key | 获取会话事件流 | @@ -511,6 +536,12 @@ Gateway API(`/api/sessions`)监听在网关主端口(`8888`),面向客 所有 Gateway API 端点启用 CORS(`Access-Control-Allow-Origin: *`),支持 `GET`、`POST`、`DELETE`、`OPTIONS` 方法。 +**DELETE /api/sessions/{id}** — 成功时立即从历史列表隐藏会话并阻止后续写入,返回 `204 No Content`;正文和 Worker 侧副本由后台任务清理。客户端可轮询 `GET /api/sessions/{id}/cleanup` 确认清理是否完成。 + +**GET /api/sessions/{id}/cleanup** — 仅会话所有者可读,返回 `200` 和清理任务快照。`status` 为 `pending`、`in_progress`、`blocked` 或 `complete`;`items[]` 按 `kind`(`session_metadata`、`conversation_content`、`worker_session`)显示各部分状态、尝试次数和下次重试时间。响应不包含聊天正文或提供方原始错误;`error_code` 是稳定错误代码。会话不属于当前用户或没有对应任务时返回 `404 NOT_FOUND`,清理状态存储不可用时返回 `503 PURGE_STATUS_UNAVAILABLE`。 + +这两个端点报告的是 HotPlex 和关联 Worker 的清理状态,不代表外部平台消息或既有备份已删除。Slack/飞书消息遵循各平台的保留策略;恢复较早的数据库备份可能重新带回已删除会话数据,备份过期与销毁由部署方负责。 + **GET /api/workers** — 返回所有已注册 Worker 类型(`claude_code` / `opencode_server` / `codex_cli` / `acp`)的二进制安装状态,供客户端(如 WebChat「新建会话」弹窗)动态过滤可选引擎,避免误选未安装的 Worker。命令名取自配置(`worker..command`),缺失时回落到各类型的默认二进制(`claude` / `opencode` / `codex` / `hermes`),再经 `exec.LookPath` 探测。响应体为 JSON 数组:`[{"type":"","installed":,"path":""}]`。 ### WebChat 多租户端点(spec ① / ④ / ⑥) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 0b4ea7ec0..8767d63e4 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -218,12 +218,37 @@ Session 生命周期管理配置。 | 字段 | 类型 | 默认值 | 环境变量 | 说明 | |------|------|--------|----------|------| -| `retention_period` | duration | `168h` (7天) | `HOTPLEX_SESSION_RETENTION_PERIOD` | 事件和日志的数据保留期。过期数据由 GC 扫描清理 | -| `term_retention` | duration | `168h` (7天) | `HOTPLEX_SESSION_TERM_RETENTION` | 普通 TERMINATED session 数据库记录保留期。过期后物理删除 | -| `cron_term_retention` | duration | `24h` | `HOTPLEX_SESSION_CRON_TERM_RETENTION` | Cron 类 TERMINATED session 数据库记录保留期。过期后物理删除 | +| `retention_period` | duration | `168h` (7天) | `HOTPLEX_SESSION_RETENTION_PERIOD` | 兼容的 Session 运行到期时长;到期后释放/终止运行资源。它不是聊天正文保留期 | +| `term_retention` | duration | `168h` (7天) | `HOTPLEX_SESSION_TERM_RETENTION` | Legacy `TERMINATED` session 根记录保留时长;v2 会话不使用此项提前删除 | +| `cron_term_retention` | duration | `24h` | `HOTPLEX_SESSION_CRON_TERM_RETENTION` | Legacy Cron `TERMINATED` session 根记录保留时长;v2 会话不使用此项提前删除 | | `gc_scan_interval` | duration | `1m` | — | GC 扫描间隔。定期扫描过期 Session 并清理 | | `max_concurrent` | int | `1000` | `HOTPLEX_SESSION_MAX_CONCURRENT` | 最大并发 Session 数。达到上限后新请求被拒绝 | +#### lifecycle — 会话与数据期限 + +这些期限与 Worker 运行资源分开计算。新会话默认使用 `v2` 策略;升级前已存在的记录保留原策略,存量期限不会因改配置被静默缩短。 + +| 字段 | 类型 | 默认值 | 环境变量 | 说明 | +|------|------|--------|----------|------| +| `policy` | string | `v2` | `HOTPLEX_LIFECYCLE_POLICY` | 新建会话采用的生命周期策略 | +| `conversation.archive_after` | duration | `168h` (7天) | `HOTPLEX_LIFECYCLE_CONVERSATION_ARCHIVE_AFTER` | 自最近有效输入起的归档标记时间;标记后仍可查看和续聊 | +| `conversation.retention_after_last_input` | duration | `4320h` (180天) | `HOTPLEX_LIFECYCLE_CONVERSATION_RETENTION_AFTER_LAST_INPUT` | 每次有效用户输入后向后推进的会话期限。读取历史或编辑标题不会续期 | +| `content.retention` | duration | `4320h` (180天) | `HOTPLEX_LIFECYCLE_CONTENT_RETENTION` | 新写入的每条事件/轮次正文从自身创建时间起计算的期限;既有正文的期限不会被重新计算 | +| `effect_payload.retention_after_settlement` | duration | `168h` (7天) | `HOTPLEX_LIFECYCLE_EFFECT_PAYLOAD_RETENTION_AFTER_SETTLEMENT` | v2 下,所有关联投递都已结算后清除 delivery payload 正文;`unknown` 或未结算记录会继续保留 | +| `facts.retention_after_settlement` | duration | `2160h` (90天) | `HOTPLEX_LIFECYCLE_FACTS_RETENTION_AFTER_SETTLEMENT` | v2 清理已结算投递的详细 attempt,并压缩已结算 execution 的诊断明细;保留输入 ID、payload hash、worker run ID、最终状态及 `effects` / `cron_occurrences` 去重根记录,避免迟到事件或重放造成重复执行;未知、活动租约及缺少结算时钟的记录受保护 | +| `audit.capture_content` | bool | `false` | `HOTPLEX_LIFECYCLE_AUDIT_CAPTURE_CONTENT` | 当前只允许 `false`;设为 `true` 会校验失败,直到独立审计正文存储和期限清理实现 | +| `audit.facts_retention` | duration | `4320h` (180天) | `HOTPLEX_LIFECYCLE_AUDIT_FACTS_RETENTION` | 新写入 lifecycle-v2 审计事实的最低保留期;到期时间随记录写入并纳入哈希,改配置只影响之后的新记录;不改变 legacy `user_activity` 链的 `audit.retention`。哈希链按已到期连续前缀清理,未到期记录可能暂时阻挡后续记录删除 | +| `audit.content_retention` | duration | `4320h` (180天) | `HOTPLEX_LIFECYCLE_AUDIT_CONTENT_RETENTION` | 预留配置;正文采集目前关闭,`capture_content: true` 会校验失败 | +| `media.retention` | duration | `24h` | `HOTPLEX_LIFECYCLE_MEDIA_RETENTION` | Slack 与飞书下载缓存的最长保留时间;后台每 6 小时扫描一次 | +| `trace.retention` | duration | `48h` | `HOTPLEX_LIFECYCLE_TRACE_RETENTION` | v2 仅清理 `~/.hotplex/logs` 中已关闭且过期的 ACP trace 文件及轮转副本;跳过活动 writer 和其他日志 | +| `gc.batch_size` | int | `100` | `HOTPLEX_LIFECYCLE_GC_BATCH_SIZE` | v2 delivery payload、已结算 attempt 与 ACP trace 清理每批上限;存量期限迁移复用该值,单类每批最多 500 条 | +| `gc.interval` | duration | `1m` | `HOTPLEX_LIFECYCLE_GC_INTERVAL` | v2 delivery payload 与 ACP trace 清理扫描间隔;媒体目录仍每 6 小时扫描 | +| `gc.max_lag` | duration | `1h` | `HOTPLEX_LIFECYCLE_GC_MAX_LAG` | 可清理或被义务阻塞的最老到期会话超过此延迟时记录一次告警日志;不强制删除。积压与延迟指标见[指标参考](metrics.md) | + +> v2 会话达到 `archive_after` 后仍可查看和续聊;达到会话期限后,GC 会在确认 Worker 已停止且没有未完成/状态未知的输入、执行或清理任务后,将会话退役并写入异步清理任务。正文到期会从读取结果中排除并分批清理。用户主动删除后的异步清理进度可通过 `GET /api/sessions/{id}/cleanup` 查询。升级前的 legacy 会话不会自动切换到 v2;管理员可先调用 `POST /admin/lifecycle/migration/preview` 查看汇总,再通过单独的 `apply` 请求逐批延长期限。迁移不会缩短期限、补造缺失的 `last_input_at` 或恢复已删除记录。上表标为“预留配置”的项目不会因设置参数而启动清理任务。 + +生命周期清理 Worker 在 Gateway 启动时读取保留期限、扫描间隔和批量大小;调整这些清理参数后需重启 Gateway。每条会话、正文和 lifecycle-v2 审计事实的到期时间在写入时固定,之后修改配置不会追溯改写存量期限;如需调整会话存量期限,使用上述迁移预览与 apply。 + --- ### 3.6 pool — 资源池 @@ -251,7 +276,7 @@ Worker 进程生命周期和环境配置。 | `max_lifetime` | duration | `24h` | `HOTPLEX_WORKER_MAX_LIFETIME` | Worker 最大存活时间。超时后强制终止 | | `idle_timeout` | duration | `60m` | `HOTPLEX_WORKER_IDLE_TIMEOUT` | Worker 空闲超时。无 I/O 时自动回收 | | `execution_timeout` | duration | `30m` | `HOTPLEX_WORKER_EXECUTION_TIMEOUT` | 单次执行超时。捕获僵尸进程 | -| `turn_timeout` | duration | `0` | — | 单 Turn 超时。0 = 禁用(由 `execution_timeout` 兜底) | +| `turn_timeout` | duration | `30m` | `HOTPLEX_WORKER_TURN_TIMEOUT` | 单轮绝对上限,从 Worker 接收该轮输入时起计算,持续输出不会延长。v2 配置中显式设为 `0` 仍按 30 分钟默认值执行;legacy 策略下 `0` 保持禁用 | | `default_work_dir` | string | `~/.hotplex/workspace` | `HOTPLEX_WORKER_DEFAULT_WORK_DIR` | Worker 默认工作目录 | | `pid_dir` | string | `~/.hotplex/.pids` | — | PID 文件存储目录 | | `env_blocklist` | []string | `[]` | — | 需要从 Worker 环境中屏蔽的变量前缀列表(带尾部 `_`) | @@ -798,12 +823,12 @@ log: ### 3.16 events 和 audit — 事件与审计留存 -运行期 event store 与合规 audit store 是职责不同的两类数据副本:前者支持会话恢复和协议重放,后者保存可追溯的用户活动原文。两者按各自配置独立清理;延长审计原文留存**不会**延长 event store 或 turn 的留存。 +运行期 event store 与合规 audit store 是职责不同的两类数据副本:前者支持会话恢复和协议重放,后者保存可追溯的用户活动事实。生命周期 v2 将新事实写入独立、无正文的 `user_activity_v2` 哈希链,由 `lifecycle.audit.facts_retention` 控制;升级前的 `user_activity` legacy 链继续按 `audit.retention` 保留,不会被 v2 GC 提前清理。Admin 活动查询会合并两条链,并返回 `chain_epoch` 标识来源。延长任一审计链留存**不会**延长 event store 或 turn 的期限。 | 字段 | 类型 | 默认值 | 环境变量 | 说明 | |------|------|--------|----------|------| | `events.retention` | duration | `720h` (30天) | `HOTPLEX_EVENTS_RETENTION` | Event store 和 turns 的运行期留存窗口。到期后由事件 GC 清理。启动时生效,修改后需重启 | -| `audit.retention` | duration | `26280h` (3年) | `HOTPLEX_AUDIT_RETENTION` | 审计记录的基础留存窗口,由 audit GC 独立执行 | +| `audit.retention` | duration | `26280h` (3年) | `HOTPLEX_AUDIT_RETENTION` | legacy `user_activity` 链及升级前可能包含正文的旧记录的留存窗口;与 lifecycle-v2 新链独立 | | `audit.full_content_retention` | duration | `2160h` (90天) | `HOTPLEX_AUDIT_FULL_CONTENT_RETENTION` | 审计原文的兼容配置字段;不再影响 event store 或 turns 留存 | > 网关 INFO 日志不会记录消息正文、prompt 或 `Envelope.Event.Data`。为支持关联排障,日志仅包含事件类型、session、seq、`data_size` 与 `data_sha256`(SHA-256 的短指纹)。 @@ -818,7 +843,8 @@ log: | `execution.queue.per_session` | int | `20` | `HOTPLEX_EXECUTION_QUEUE_PER_SESSION` | 单个 session 允许的未派发输入条数上限 | | `execution.queue.global` | int | `1000` | `HOTPLEX_EXECUTION_QUEUE_GLOBAL` | 整个实例允许的未派发输入条数上限 | | `execution.queue.max_payload_bytes` | int | `65536` (64 KiB) | `HOTPLEX_EXECUTION_QUEUE_MAX_PAYLOAD_BYTES` | 单条输入的正文上限;原生命令的 invocation 参数一并计入 | -| `execution.queue.ttl` | duration | `24h` | `HOTPLEX_EXECUTION_QUEUE_TTL` | 未派发输入保持可派发状态的最长时间,超时后按 `QUEUE_EXPIRED` 结算 | +| `execution.queue.interactive_ttl` | duration | `15m` | `HOTPLEX_EXECUTION_QUEUE_INTERACTIVE_TTL` | 忙碌 Session 接收的交互输入最长等待时间;超时后按 `QUEUE_EXPIRED` 结算 | +| `execution.queue.ttl` | duration | `24h` | `HOTPLEX_EXECUTION_QUEUE_TTL` | 未提供交互专用期限时使用的通用队列期限;当前 Gateway 忙碌输入路径优先采用 `interactive_ttl` | | `execution.queue.sweep_interval` | duration | `1m` | `HOTPLEX_EXECUTION_QUEUE_SWEEP_INTERVAL` | 过期扫描间隔;设为 `0` 关闭后台扫描 | | `execution.queue.sweep_batch` | int | `100` | `HOTPLEX_EXECUTION_QUEUE_SWEEP_BATCH` | 单次扫描处理的最大条数,避免大积压长时间占用写锁 | @@ -929,6 +955,20 @@ HOTPLEX_SECURITY_API_KEY_1, HOTPLEX_SECURITY_API_KEY_2, ... |------|----------|--------| | `HOTPLEX_SESSION_MAX_CONCURRENT` | `session.max_concurrent` | `1000` | | `HOTPLEX_SESSION_RETENTION_PERIOD` | `session.retention_period` | `168h` | +| `HOTPLEX_LIFECYCLE_POLICY` | `lifecycle.policy` | `v2` | +| `HOTPLEX_LIFECYCLE_CONVERSATION_ARCHIVE_AFTER` | `lifecycle.conversation.archive_after` | `168h` | +| `HOTPLEX_LIFECYCLE_CONVERSATION_RETENTION_AFTER_LAST_INPUT` | `lifecycle.conversation.retention_after_last_input` | `4320h` | +| `HOTPLEX_LIFECYCLE_CONTENT_RETENTION` | `lifecycle.content.retention` | `4320h` | +| `HOTPLEX_LIFECYCLE_EFFECT_PAYLOAD_RETENTION_AFTER_SETTLEMENT` | `lifecycle.effect_payload.retention_after_settlement` | `168h` | +| `HOTPLEX_LIFECYCLE_FACTS_RETENTION_AFTER_SETTLEMENT` | `lifecycle.facts.retention_after_settlement` | `2160h` | +| `HOTPLEX_LIFECYCLE_AUDIT_CAPTURE_CONTENT` | `lifecycle.audit.capture_content` | `false` | +| `HOTPLEX_LIFECYCLE_AUDIT_FACTS_RETENTION` | `lifecycle.audit.facts_retention` | `4320h` | +| `HOTPLEX_LIFECYCLE_AUDIT_CONTENT_RETENTION` | `lifecycle.audit.content_retention` | `4320h` | +| `HOTPLEX_LIFECYCLE_MEDIA_RETENTION` | `lifecycle.media.retention` | `24h` | +| `HOTPLEX_LIFECYCLE_TRACE_RETENTION` | `lifecycle.trace.retention` | `48h` | +| `HOTPLEX_LIFECYCLE_GC_BATCH_SIZE` | `lifecycle.gc.batch_size` | `100` | +| `HOTPLEX_LIFECYCLE_GC_INTERVAL` | `lifecycle.gc.interval` | `1m` | +| `HOTPLEX_LIFECYCLE_GC_MAX_LAG` | `lifecycle.gc.max_lag` | `1h` | | `HOTPLEX_POOL_MAX_SIZE` | `pool.max_size` | `100` | | `HOTPLEX_POOL_MAX_IDLE_PER_USER` | `pool.max_idle_per_user` | `5` | | `HOTPLEX_POOL_MAX_MEMORY_PER_USER` | `pool.max_memory_per_user` | `3221225472` | @@ -940,6 +980,7 @@ HOTPLEX_SECURITY_API_KEY_1, HOTPLEX_SECURITY_API_KEY_2, ... | `HOTPLEX_WORKER_MAX_LIFETIME` | `worker.max_lifetime` | `24h` | | `HOTPLEX_WORKER_IDLE_TIMEOUT` | `worker.idle_timeout` | `60m` | | `HOTPLEX_WORKER_EXECUTION_TIMEOUT` | `worker.execution_timeout` | `30m` | +| `HOTPLEX_WORKER_TURN_TIMEOUT` | `worker.turn_timeout` | `30m` | | `HOTPLEX_WORKER_DEFAULT_WORK_DIR` | `worker.default_work_dir` | `~/.hotplex/workspace` | | `HOTPLEX_WORKER_CLAUDE_CODE_COMMAND` | `worker.claude_code.command` | `claude` | | `HOTPLEX_WORKER_OPENCODE_SERVER_COMMAND` | `worker.opencode_server.command` | `opencode` | diff --git a/docs/reference/metrics.md b/docs/reference/metrics.md index 3d37f0092..0ba5716f6 100644 --- a/docs/reference/metrics.md +++ b/docs/reference/metrics.md @@ -66,11 +66,19 @@ scrape_configs: |------|------|------| | `hotplex.session.created` | Counter | 累计创建 Session 数,label: `worker_type` | | `hotplex.session.terminated` | Counter | 终止 Session 数,label: `reason` | -| `hotplex.session.deleted` | Counter | GC 物理删除 Session 数 | +| `hotplex.session.deleted` | Counter | 进入逻辑删除状态的 Session 数;不代表远端或磁盘副本均已擦除 | +| `hotplex.lifecycle.gc.failures` | Counter | 生命周期 GC 的查询或退役失败数,label: `phase`(`session_query` / `session_retire` / `status_query`) | +| `hotplex.lifecycle.gc.processed` | Counter | 生命周期清理中被删除、脱敏或压缩的记录/文件数,label: `kind`(单位随类别为会话、行、文件或清理项;未知类别归为 `other`) | +| `hotplex.lifecycle.gc.retirement.lag` | Histogram | v2 会话从最后一个必需历史期限到退役的延迟,单位秒;只记录已成功退役的会话 | +| `hotplex.lifecycle.gc.backlog` | Gauge | 已到期且等待处理的 v2 会话数,label: `status`(`eligible` / `blocked`) | +| `hotplex.lifecycle.gc.blocked_unknown_execution` | Gauge | 因 `unknown` 或 fence 执行证据而阻止退役的已到期 v2 会话数 | +| `hotplex.lifecycle.gc.oldest_lag` | Gauge | 最老到期会话超过其最后必需期限的时长,单位秒,label: `status`(`eligible` / `blocked`) | | `hotplex.session.start.attempts` | Counter | Session 启动尝试次数,label: `worker_type` | | `hotplex.session.start.errors` | Counter | Session 启动错误数 | | `hotplex.session.start.duration` | Histogram | Session 启动耗时 | +`hotplex.lifecycle.gc.processed` 的 `kind` 取值是固定集合:`session`(退役会话数)、`event` / `turn` / `audit_fact_legacy` / `audit_fact_v2` / `delivery_attempt`(删除的行数)、`delivery_payload`(脱敏的正文数)、`execution_detail`(压缩的执行记录数)、`acp_trace_file` / `media_file`(删除的文件数)、`conversation_purge_item`(完成的清理项数)和 `other`。不同类别的单位不同,不应跨类别求和。 + ### 终止原因标签 | reason | 说明 | @@ -93,6 +101,15 @@ rate(hotplex_session_terminated_total{reason=~"crash|zombie"}[5m]) # Session 启动 P99 延迟 histogram_quantile(0.99, rate(hotplex_session_start_duration_bucket[5m])) + +# 当最老可清理会话超过 lifecycle.gc.max_lag 默认 1 小时时告警 +hotplex_lifecycle_gc_oldest_lag{status="eligible"} > 3600 + +# 阻塞会话持续积压时通知运维检查执行、队列或清理义务 +hotplex_lifecycle_gc_backlog{status="blocked"} > 0 + +# 未知执行状态需要人工或恢复流程收敛后才能清理 +hotplex_lifecycle_gc_blocked_unknown_execution > 0 ``` ## Worker 指标 @@ -481,6 +498,7 @@ increase(hotplex_audit_sink_failures_total[10m]) > 50 | P99 执行延迟 | `worker.execution.duration` P99 | < 5s | | Worker 可用性 | `1 - crashes/starts` | >= 99% | | Pool 拒绝率 | `pool.acquire{result!="success"}` | < 0.1% | +| Lifecycle GC 延迟 | `lifecycle.gc.oldest_lag{status="eligible"}` | <= `lifecycle.gc.max_lag` | ## 参考 diff --git a/docs/superpowers/plans/2026-10-08-hotplex-data-lifecycle-luna-guide.md b/docs/superpowers/plans/2026-10-08-hotplex-data-lifecycle-luna-guide.md new file mode 100644 index 000000000..91e82235e --- /dev/null +++ b/docs/superpowers/plans/2026-10-08-hotplex-data-lifecycle-luna-guide.md @@ -0,0 +1,628 @@ +--- +title: "HotPlex 数据生命周期与时长治理实施方案(Luna Guide)" +weight: 10 +description: "分离运行、会话、内容与证据生命周期,统一删除语义,明确起算、续期、迁移及验收规则。" +--- + +# HotPlex 数据生命周期与时长治理实施方案 + +- 日期:2026-10-08(Asia/Shanghai)。 +- 用户确认(2026-10-08):方案数字作为默认值,并提供可配置参数;用户会话和正文默认 180 天,365 天是配置示例而非唯一可选值,归档不缩短保留期。此确认不等于在运行实例开启策略或执行存量迁移。 +- 核验基准:`4c7c685a`;实施前重新确认目标分支、HEAD 和相关文件。 +- 文档性质:用户已确认目标与默认时长;本分支按此方案实施,存量数据操作、服务重启和发布仍需独立安排。 +- 范围:WebChat、Slack、飞书、Cron/API 的会话生命周期及关联数据;四类 Worker 的历史清理能力。 +- 所有“拟新增”字段、文件、接口、配置和测试均为设计,不代表现有功能。 +- 证据:Source 为本地源码核对;Test 为本会话此前执行的选定测试;Live 尚未核验运行实例配置、数据库内容和真实渠道删除。 + +## 1. 问题结论 + +HotPlex 已有持久输入、租约、删除事务和异步 Worker 清理的基础,但没有统一的数据生命周期契约。需要优先修正的是生命周期边界,其次才是默认天数。 + +| 问题 | 可观察结果 | 推荐方向 | +| --- | --- | --- | +| 运行期限与历史索引生命周期耦合 | Worker 因空闲结束后,会话索引按终止期限删除;正文仍在但普通历史接口返回 404 | 运行释放、归档、正文到期独立计时 | +| `turn_timeout` 实际随事件滑动 | 不断输出的轮次可持续重置计时,无法作为绝对执行期限 | 绝对轮次期限与无 IO 检测分离 | +| 删除依赖入口及内存命中 | WebChat 物理删除;Manager.Delete 命中缓存时软删,未命中时可能物理删 | 单一删除契约,持久化删除意图和完成状态 | +| 长期审计保存输入正文 | 聊天正文默认 30 天,审计输入正文默认约三年 | 审计事实不复制正文;历史明文专项迁移 | +| 投递正文和部分事实无自动清理 | `effect_payloads` 等持续累积,删除会话并不清除这些副本 | 正文、恢复义务、去重证据分别保留 | +| Worker 原生历史清理能力不一致 | OCS 可清理,其他 Worker 没有统一删除实现 | 显式报告能力和范围,不能把 no-op 当成功 | + +Source 级复现条件:使用默认配置创建会话,轮次结束进入 IDLE;空闲 60 分钟后运行终止,再超过终止记录保留期,GC 删除 `sessions`;30 天以内的 `turns` 可能仍存在,但 `GetHistory` 先要求会话存在。未执行真实实例复现,不据此判断用户某条会话的实际删除原因。 + +目标是使“停止运行”“归档会话”“正文到期”“删除聊天”有稳定、可解释的含义,而不是把所有表改成同一 TTL。 + +## 2. 当前实现与根因 + +### 2.1 当前事实与定位 + +仓库根目录为 `/Users/hrygo/hotplex`;下表路径均相对该根目录。 + +| 路径 / 符号 | 已核实的当前行为 | +| --- | --- | +| `internal/config/config_defaults.go` | Session 活跃到期 7 天;普通终止记录 7 天、Cron 24 小时;Worker 空闲 60 分钟、无 IO 检测 30 分钟,`turn_timeout` 默认 0 | +| `internal/session/manager.go`:`CreateWithBot`、`ResetExpiry`、`gc` | 创建和恢复设置 `ExpiresAt = now + RetentionPeriod`;普通消息没有逐次续期;终止记录清理依赖 `UpdatedAt`;TERMINATED 内存缓存另有 24 小时 TTL | +| `internal/session/sql/queries/store.delete_terminated_by_id.sql` | 按 TERMINATED、source 和 updated_at 删除;不扫描 DELETED | +| `internal/gateway/bridge_forward.go`:`forwardEvents`、`processForwardedEvent` | 创建 `turnTimer`,处理事件时调用 `turnTimer.Reset`;Done 路径停止计时。开启后更接近事件静默计时,不能据名称当作绝对轮次期限 | +| `internal/session/manager.go`:`Delete`、`DeletePhysical` | 删除行为存在内存分支;有持久 cleanup outbox 和运行态删除屏障,但不是全量内容清理接口 | +| `internal/gateway/api.go`:`DeleteSession`、`GetHistory` | REST 删除走物理删除;历史读取先 `authorizeSession`,再读 turns | +| `internal/gateway/commands.go`、`cmd/hotplex/admin_adapters.go` | AEP 删除及管理端调用 Manager.Delete;AEP `control.stop` 保留会话且不能触发 crash fallback | +| `cmd/hotplex/gateway_run.go`:`runEventsGC` | 每小时分别删除到期 events / turns;按记录创建时间,默认 30 天 | +| `internal/gateway/handler.go`:`emitAudit`;`internal/audit/gc.go` | `detail_json` 存输入 content;审计默认约三年,按检查点锚定的前缀清理 | +| migrations `023_user_activity`、`030_audit_no_delete` | 审计禁止普通 UPDATE,DELETE 有检查点约束;不能直接逐会话改写正文 | +| migrations `026_execution_inputs`、`037_execution_queue`、`038_execution_queue_payloads` | 执行事实仅保存指纹等;会话物理删除级联执行事实和队列;队列正文随队列控制记录删除 | +| `internal/execution/sql_queue_dispatch.go` | 队列取消、清空、领取、过期结算删除队列正文,保留相应执行事实 | +| migrations `033_cron_occurrences`、`034_effect_ledger`、`036_effect_attempts`、`039_effect_reconciled_terminal` | 批次、投递事实、回执和最终回复快照与会话无统一删除级联;未发现这些模块的自动保留清理入口 | +| `internal/effect/store.go` | 已区分正文缺失与事实损坏;租约过期进入 unknown,不恢复成可发送状态 | +| `internal/session/cleanup_outbox.go`;`internal/worker/session_cleanup.go` | 本地删除与清理入队原子执行,远端失败重试;当前仅 OCS 注册原生历史删除,未注册类型会 no-op | +| `cmd/hotplex/gateway_run.go`:`sm.OnTerminate` | 同时清 volatile 缓冲、持久队列和附属 Cron;运行释放与逻辑会话结束的副作用目前耦合 | + +图谱 generation 为 `2026-10-01T23:35:45Z`,已对核心引用文件进行 coverage 检查。变更文件、未索引文件和 SQL 未解析范围使用本地源码补证;图谱干净不等于已验证运行状态。 + +### 2.2 根因分类 + +1. **领域定义缺口**:单个 sessions 记录同时承载运行状态、历史鉴权入口及删除关联根,运行结束因此牵动历史可见性。 +2. **时钟定义缺口**:复用 `UpdatedAt` 作为保留起点;`turn_timeout` 名称与滑动实现不一致;`worker.max_lifetime` 存在配置,但不能仅看默认值认定进程年龄回收已接线。 +3. **副本清单缺口**:聊天正文另有审计、effect 快照、原生 Worker 历史及调试追踪副本。 +4. **清理完成契约缺口**:本地删除成功与所有副本删除成功没有分开表达。 +5. **原先缺少明确默认策略**:用户已确认本方案数字作为默认值,实施应提供参数覆盖;调整实际运行配置和缩短存量数据期限是独立操作。 + +### 2.3 历史约束 + +> 🧠 **From Hindsight memory (HotPlex 可信运行与投递演进)** — 历史方案要求复用既有 Gateway → Session/Execution → Worker、鉴权和审计体系;不另建 execution 状态机或平行调度系统;unknown 不自动重发;SQLite/PostgreSQL 迁移成对,协议变化同步各 SDK。 + +当前 `internal/effect/store.go` 的租约契约与 unknown 禁止自动重发一致。原方案入口为 `/Users/hrygo/hotplex/docs/superpowers/plans/2026-10-03-hotplex-evolution-luna-guide.md`。历史实施报告不替代本批验收,本方案不重复认领 PR #986 的原十项问题。 + +## 3. 目标行为 + +### 3.1 四类生命周期 + +- **运行**:管理进程、轮次和排队义务;到期停止或释放资源,不删除聊天。 +- **会话**:管理身份、归属、上下文代际和历史入口;无输入达到归档期限后仍可读取未过期正文。 +- **内容**:每个正文副本有创建时间、期限和所属主体;到期不再返回,后台完成物理清理。 +- **证据**:执行结果、回执和去重标记有独立调查窗口;清理不能重新开放已执行副作用。 + +```mermaid +flowchart TB + subgraph Runtime["运行时钟"] + R["轮次执行"] -->|"绝对 30 分钟上限"| STOP["结束当前轮次"] + R -->|"正常完成"| I["空闲 Worker"] + I -->|"60 分钟空闲 / 年龄到限后边界回收"| FREE["释放 Worker"] + end + subgraph Conversation["逻辑会话时钟"] + ACTIVE["活跃会话与历史入口"] -->|"7 天无有效用户输入"| ARCHIVE["归档,可读期限内历史"] + ARCHIVE -->|"新有效输入,启动新 run"| ACTIVE + end + subgraph Content["内容时钟"] + BODY["正文创建"] -->|"每条创建后 180 天,可选 365 天"| EXPIRE["正文不可读,异步删除"] + EXPIRE -->|"逻辑会话期限也到达,且无未过期正文"| INDEX["隐藏历史入口,保留必要最小根行"] + end + DELETE["用户删除"] --> FENCE["立即阻止读取与运行,持久清理任务"] + FENCE --> PURGE["清在线正文与支持的原生历史"] + INDEX --> FACT["事实终结后 90 天
满足去重保护才清理"] + FENCE --> FACT + FREE -.->|"会话和正文继续保留"| ACTIVE +``` + +图中归档是产品投影,不是新增 AEP State;90 天事实期限不覆盖未收敛义务和未闭合的重放窗口。 + +### 3.2 已确认的 v2 默认策略及精确计时规则 + +以下数字已由用户确认为默认值;新安装和新写入记录默认使用 `v2` 生命周期,允许通过 §4.3 参数覆盖。既有记录在新增策略标记为空时按原行为保留;存量期限变化必须通过预览与独立 apply 完成。`legacy` 仅作为明确配置的回退策略,不是升级默认值。 + +| 对象 | 默认值 / 覆盖规则 | 起算事件 | 可续期事件 | 到期行为 | +| --- | --- | --- | --- | --- | +| Worker 空闲 | 60 分钟 | 当前轮次终结且无可立即派发输入 | 新输入真正开始执行 | 释放 Worker,保留逻辑会话 | +| Worker 年龄 | 24 小时 | 此 Worker run 成功启动 | 无 | 标记待回收,在轮次边界替换;不复用旧 run ID | +| 单轮绝对期限 | 30 分钟 | Worker.Input 成功投递时记录的 turn_started_at | 无;输出、交互卡、心跳不延长 | 中断本轮,持久结算失败/unknown,保留会话 | +| 无 IO 检测 | 暂保留 30 分钟 | 最后实际 Worker IO | 实际 IO | 先检查运行是否仍归本 run;不能当安全重投依据 | +| 长任务 | 显式覆盖,例如 2 小时 | 同单轮规则 | 无 | 不因归类为 Cron 就自动无限运行 | +| 交互式持久队列 | 15 分钟 | 持久接受事务的 enqueued_at | 无 | 结算过期,删除待执行正文,通知用户 | +| Cron 队列 | 保持既有 24 小时上限,优先使用任务 deadline | 接受事务 | 无 | deadline 与 TTL 取较早值;不得事后突发执行过时任务 | +| 逻辑会话归档 | 7 天 | last_input_at | 首次接受的有效用户输入;重复 ACK 不续期 | 归档显示;只释放空闲运行,可恢复新 run | +| 用户逻辑会话 | 无有效用户输入后 180 天,可显式选择 365 天 | 创建时间 / last_input_at | 首次接受的有效用户输入;读取、改标题不续期 | 同时无未过期正文且无运行/恢复义务时才自然退休;7 天归档不触发删除 | +| 用户聊天 events / turns | 180 天,可显式选择 365 天 | 每条记录 created_at | 无;继续聊天不延长旧正文 | 读取时过滤到期正文,后台清理;用户会话不套用短期投递/调试 TTL | +| 会话历史索引 | max(逻辑会话期限,最后正文到期时间) | 创建 / 新输入 / 新正文的 expires_at | 用户输入和新正文可延长索引;查看、改标题不延长 | 先隐去历史入口、清内容性元数据,必要最小墓碑另行保留 | +| 已完成投递正文 | 7 天 | 投递义务收敛时间 | 无 | 删正文,事实和回执继续保留 | +| 执行/批次/投递事实 | 90 天 | 确定终态 finished_at / settled_at | 无 | 满足去重和引用保护后删除或压缩为最小标记 | +| 新审计事实 | 180 天 | 事件 ts | 无 | 检查点锚定清理;实际审计要求可覆盖 | +| 审计正文 | 默认不采集 | — | — | 有必要时独立 opt-in,最多跟随聊天正文期限 | +| 媒体缓存 | 24 小时 | 下载写入时间 | 不因读取延长 | 保留现有目录所有权检查后清理 | +| ACP 调试正文 | 48 小时及现有大小上限 | 追踪文件创建/最后写入时间 | 写入期间不删;关闭后固定计时 | 只删除 HotPlex 创建的 trace 文件 | +| 普通文件日志备份 | 保持 30 天,兼顾容量限制 | 轮转备份形成时间 | 无 | 不把轮转期限解释成活动文件内每行 TTL | +| 本机备份 | 策略默认 30 天,由备份工具配置 | 备份产生时间 | 无 | 仅受管理备份适用,恢复先重放删除清单;不据此自动启用或删除现有备份 | + +登录 Cookie 有独立认证有效期,不能用聊天会话 TTL 替代。账户、工作区、Agent 配置、用户工作目录、外部平台消息和用户主动导出不纳入会话 GC;明确展示边界。 + +### 3.3 用户可观察契约 + +1. 自动释放空闲 Worker 后,列表和历史仍可用;续聊开始新 run,不把旧执行重新派发。 +2. 历史只返回尚未到期正文;响应增加可选 `history_truncated` 和 `content_expires_at`,缺失时旧客户端仍可工作。 +3. 停止本轮、重置上下文、归档、删除聊天分别实施;`/reset` 不是擦除历史。 +4. 删除立即阻止该会话的正文读取、新输入和恢复;返回的是删除意图已持久接受,不是所有副本已擦除。 +5. 后台清理状态明确区分 `pending/running/completed/blocked`;不支持远端删除返回 `unsupported`,不能映射为 completed。 +6. `completed` 限定为登记的在线清理范围;需要保留的审计事实、最小去重标记、旧备份、外部消息等例外逐项列出。 +7. 调整策略不隐式缩短既有数据期限;已有 deadline 重启后不重新计时。 +8. 已归档会话从持久存储按需加载,保留期间无需常驻 Worker、连接或完整历史内存缓存。部分旧正文到期时说明缺失范围;会话仍在保留期内则允许开始新 run,不因历史为空强制新建身份。 + +### 3.4 用户反馈:冷会话长期保留 + +采用“短运行、长历史”的策略:60 分钟空闲释放 Worker;7 天无有效输入仅从活跃列表移入归档;归档列表仍可检索、读取和续聊。自动归档、Worker 退出和缓存淘汰不能设置 `deleted_at` 或清除会话归属。 + +用户已确认逻辑会话与聊天正文默认 180 天,并要求可配置;365 天是示例,也接受其他合法正时长。逻辑会话按最后一次有效用户输入计时,正文逐条按创建时间计时,两者不混用:继续聊天延长会话入口,旧消息仍按自己的期限到期。任何未过期正文都保护历史入口;即使正文全部到期,未到期的逻辑会话仍保留并提示上下文缺失。短期 Cron 运行详情、投递快照、媒体缓存和 trace 使用各自策略,不因用户聊天长期保留而整体延长。 + +这些天数是用户已确认的产品默认策略,官方原则不规定统一天数。相同每日写入量与存储格式下,180 天正文的稳态数据量约为 30 天的 6 倍,365 天约为 12.2 倍;这是容量估算,不是实测。数据库、搜索索引和备份仍有成本,但归档数量不应决定常驻 Worker 数量。列表使用分页与过期时间索引,续聊按需加载;容量紧张时先预警和展示策略,不以 Worker 回收或静默缩短历史保留期解决。 + +本批不增加默认永久保留,也不依赖用户打开历史来续命;账户删除、用户显式删会话和经批准的存量迁移仍按独立契约执行。 + +## 4. 推荐解决方案 + +### 4.1 选择范围适中的分层治理 + +| 方案 | 改动范围 | 判断 | +| --- | --- | --- | +| 只改配置与注释 | 小;不改模型与删除事务 | 可先改善说明,不能解决入口不一致、原生副本和哈希链正文问题 | +| 分层时钟 + 统一删除 + 模块 GC | 涉及 Session、Gateway、Execution、Effect、Audit、Worker、WebChat 的相关路径 | 推荐;保留现有领域模块,分单元交付 | +| 所有正文迁入统一内容平台并引入逐会话密钥销毁 | 改造所有正文写入者、读取者和密钥管理 | 明显扩大范围;本批不引入,可在有强擦除需求时另行评审 | + +不要新增通用事件总线或独立调度服务。内容 GC 各模块负责,策略解析、时间语义、删除意图和状态汇总共用小型接口。 + +### 4.2 数据与状态设计(拟新增) + +继续以 `sessions.id` 为既有逻辑会话标识,保留 AEP SessionState 枚举;归档是独立可选投影,不能新增一个未经同步的 AEP `ARCHIVED` 状态。 + +Session 增加: + +- `last_input_at`:有效用户输入首次受理时间;队列接受也算一次活动。 +- `runtime_finished_at`:最近一次运行结束时间,普通更新不得修改。 +- `archive_at`:无用户活动归档 deadline。 +- `conversation_expires_at`:逻辑会话保留期限,创建时为 now + 180 天,有效输入按当前策略向后延长;归档和运行结束不修改。 +- `last_content_expires_at`、`history_expires_at`:历史读取及索引期限。 +- `lifecycle_policy_revision`:确定这些 deadline 的不可变策略版本。 +- `deleted_at`:显式删除或逻辑会话自然退休时间;单条正文到期不能设置,stale Upsert 不得清空。 + +内容表增加或由等价索引承载 `expires_at`、策略版本;既有创建时间语义保持。对旧行的 backfill 见 §6.5。 + +`execution_inputs` 拟新增 `turn_started_at`、`turn_deadline_at`、`turn_policy_revision`,不改既有 `started_at` 的含义。成功投递时条件写入起点和固定 deadline,已终结的 execution 不重新启动计时;后续事件、配置热更新和进程重启均不得延长期限。存量执行缺少这些字段时维持 legacy 行为,不猜测起点来追溯超时。 + +拟新增 `session_purge_jobs` 和 `session_purge_items`,不设会话删除级联: + +- 主任务包含 job ID、session ID、授权归属快照、删除请求时间、策略版本、总体状态。 +- 子项包含模块 kind、状态、attempt、lease token、next_attempt_at、完成时间和有限错误码。 +- 不包含 prompt、Context、metadata 值、凭证、原始 Worker 错误或用户目录任意删除路径。 +- 唯一键保证一个删除意图的模块任务不会重复;claim/complete 均校验租约 token 和版本。 + +**控制事实保留的最小实现**:不在本批重建全部执行 FK。会话进入 DELETED 后清正文和内容性元数据,保留最小 sessions 根行,直到其执行事实、清理义务及去重窗口允许真正删除;因此现有 execution 的 FK 不会提前级联丢失证据。界面和业务读取已看不到此根行。 + +该最小根行仅留身份、归属、状态、期限等必要字段;清除 title、context、工作目录、platform_key 等内容性/定位性数据前,必须把必要清理定位符移入受控任务并登记期限。新的事实入库不得借此根行继续接受输入。 + +无法强制限制旧请求重放的来源,暂不删除最小身份去重标记;报告例外和容量,不以“90 天最佳实践”为理由删除去重键。后续若要清理,必须先实现并验证来源重放窗口或身份代际退休机制。 + +### 4.3 兼容配置(拟新增键) + +- `lifecycle.policy`:默认 `v2`;`legacy` 仅在明确回退时选择。按记录保存策略 revision,配置切换不能改变已写入 deadline。 +- `lifecycle.conversation.archive_after`:7 天。 +- `lifecycle.conversation.retention_after_last_input`:180 天,可选 365 天;空会话从创建起算。 +- `lifecycle.content.retention`:180 天,可选 365 天,新 v2 用户聊天正文使用。Cron/API 来源必须显式分类,未分类输入不得自动采用更短正文期限。 +- `lifecycle.effect_payload.retention_after_settlement`:7 天。 +- `lifecycle.facts.retention_after_settlement`:90 天。 +- `lifecycle.audit.capture_content`:false;仅适用于新记录。 +- `lifecycle.audit.facts_retention`:180 天;legacy 审计链继续使用当前配置。 +- `lifecycle.audit.content_retention`:180 天;仅 capture_content=true 时使用,实际正文期限还不得超过所属聊天正文期限。 +- `lifecycle.media.retention`:24 小时;复用现有缓存清理器,不扩大目录删除范围。 +- `lifecycle.trace.retention`:48 小时。 +- `lifecycle.gc.batch_size`:100;`lifecycle.gc.interval`:1 分钟;`lifecycle.gc.max_lag`:1 小时,是观测/告警目标,不是正文续期或强制提前删除时间。 +- `execution.queue.interactive_ttl`:15 分钟;既有 `execution.queue.ttl` 保持 Cron/未分类输入 fallback。 + +优先接通既有 `worker.max_lifetime` 为“24 小时后轮次边界回收”,复用 `worker.turn_timeout` 表示真正绝对期限;`worker.execution_timeout` 暂维持无 IO 检测。v2 交互输入的 turn_timeout 为 0 时解析为 30 分钟默认值;已有明确正值优先。拟新增 `invocation.turn_timeout` 为仅管理员配置的正值覆盖,由运行计划解析并固定到本 execution,不能信任用户输入中的任意 metadata,也不能用流式输出改变它。 + +旧 `session.retention_period` 保持运行到期兼容语义,不再用于 v2 历史到期;旧 `term_retention`/`cron_term_retention` 不得提前删除仍被内容或事实引用的 v2 根行。配置解析同时指定旧/新内容保留键时,以新键为准并给出脱敏警告。拟新增 retention 键拒绝显式非正值,不用 0 暗示“永远”;legacy 的 events.retention 非正值回落 30 天等既有行为保持,不改旧客户端契约。 + +策略变更只影响新接受的运行、新输入续期和新写入内容;新输入只向后延长既有会话期限。已有正文 deadline 只有明确的 migration apply 才能调整;升级时默认把仍存活的旧行标为 legacy 并维持原到期行为,不能静默缩短或以迁移时刻伪装用户活动。配置展示要同时返回声明值、当前有效值、是否需重启和适用 revision。 + +#### 可复制的目标配置示例 + +以下是实施后的配置契约,包含拟新增键,不能直接用于当前版本。时长使用 Go duration 的 `h`、`m`、`s` 单位,不使用不受该格式支持的 `d`:7 天为 `168h`,30 天为 `720h`,90 天为 `2160h`,180 天为 `4320h`,365 天为 `8760h`。 + +```yaml +# 生命周期默认启用 v2;此处列出全部默认值,便于运维覆盖和审阅。 +lifecycle: + policy: v2 + conversation: + archive_after: 168h + retention_after_last_input: 4320h + content: + retention: 4320h + effect_payload: + retention_after_settlement: 168h + facts: + retention_after_settlement: 2160h + audit: + capture_content: false + facts_retention: 4320h + content_retention: 4320h + media: + retention: 24h + trace: + retention: 48h + gc: + batch_size: 100 + interval: 1m + max_lag: 1h + +# 以下 Worker 参数已存在;绝对轮次期限语义需按本方案实施。 +worker: + idle_timeout: 60m + max_lifetime: 24h + turn_timeout: 30m + execution_timeout: 30m + +execution: + queue: + interactive_ttl: 15m + ttl: 24h + +# 现有文件日志轮转参数按“天”计数,不是 duration 字符串。 +log: + file: + max_age: 30 +``` + +将会话和正文保留一年时,同时覆盖 `lifecycle.conversation.retention_after_last_input: 8760h` 和 `lifecycle.content.retention: 8760h`;两者也可独立配置。未显式覆盖的参数使用上述已确认默认值。`invocation.turn_timeout` 默认继承有效的 `worker.turn_timeout`,管理员可对特定长任务配置 `2h` 或其他正时长;`2h` 不是所有任务的默认值。 + +#### 参数校验、覆盖与生效 + +- 复用现有配置加载和环境变量覆盖链,新键沿用 `HOTPLEX_` 加层级大写下划线形式,例如 `HOTPLEX_LIFECYCLE_CONTENT_RETENTION=8760h`;L1 验证 YAML 与 env 最终有效值,不另建配置来源。 +- 时长缺省使用默认值;拟新增时长键显式 0、负值、无法解析或超出 duration 表示范围须报错。显式空值不得悄悄变成默认值。永久保留不以 0 表达,本批不新增永久保留开关。`gc.batch_size` 必须为正整数。 +- `archive_after` 不得大于 `retention_after_last_input`;旧/新正文期限可以不同,历史索引始终按 §3.2 的 max 规则保护。审计 opt-in 正文的有效期限取配置期限与所属聊天正文期限的较早值;没有可靠归属和期限时不采集正文。 +- 已存在的 Worker、queue、log 键保留 legacy 校验契约;v2 的 `turn_timeout=0` 兼容解析仍按上文转换为 30 分钟,不能禁用 v2 绝对期限。拟新增交互队列 TTL 不要求小于 Cron TTL,但任务 deadline 仍取较早值。 +- 生命周期参数热更新生成新策略 revision,仅影响后续输入续期、新正文/队列及新轮次;Worker 运行预算在启动 run 时固定,轮次覆盖在投递时固定。已存 deadline 不重算,历史延长/缩短走独立存量 apply。GC interval/batch/max_lag 可在下一扫描周期应用;`log.file.max_age` 仍需重启。 +- `lifecycle.policy` 切换作为明确的实例配置变更,初期按需重启;不能通过热更新绕过已写入的 v2 期限和删除屏障。配置诊断同时显示有效策略、revision、来源及重启要求。 +- 本机备份的 30 天在备份工具自己的 retention 参数中配置;HotPlex 尚无统一备份管理器,本批不虚构一个会自动删除现有备份的配置键,也不扩大到用户导出/工作目录。 + +## 5. 详细实施步骤 + +每个单元完成条件满足后,Luna 在获得实施授权的工作分支上检查暂存 diff 并创建原子本地 commit;只包含该单元的业务实现、回归与必要文档。项目要求非 main 分支验证通过后 commit + push;远端目标必须已明确,不创建未经请求的 PR、合并或发布。本轮只写本方案,不执行这些动作。 + +### L0:记录缺陷与冻结基线 + +- 在已授权的 Issue 渠道登记独立发现:滑动 turn timeout、删除入口不一致、运行清理导致历史失联、长寿正文副本;包含基准、Source/Test/Live、触发路径、验收。 +- 不并入 PR #986 原十项,不自动增加其完成计数。没有 Issue 写权限时先提交可评审 issue 草稿,不能声称已登记。 +- 文件:本方案、相关模块 AGENTS、既有测试;新增 `lifecycle_*_test.go` 为拟新增。 +- 完成:保存当前 legacy 行为回归,明确相对日期、测试证据和未验证部分。 +- 提交点:缺陷记录与已通过的基线测试;不要故意提交持续失败的工作区。 + +### L1:时钟契约、配置与成对迁移 + +- 文件:`internal/config/{config_types,config_defaults,config_loader,watcher}.go`;`internal/session/{manager,store,pg_store}.go`;sessions SQL 查询。 +- 拟新增:`internal/session/lifecycle_policy.go`,小型纯策略计算器;迁移采用当时下一个空闲版本号,SQLite 与 PostgreSQL 成对,不能修改 001–039 既有迁移。 +- SessionInfo、scan/upsert 参数、局部 UPDATE、SQL mocks 和所有 store 实现同步。仅专门时钟方法写生命周期字段;stale 整行 Upsert 不得覆盖新 deadline/deleted_at。 +- 将 §4.3 已确认默认值、完整 YAML 示例与环境变量绑定纳入配置实现及诊断;验证遗漏/显式空值/0/负数/溢出、归档期限约束和策略 revision 生效边界,不能只新增配置结构而不接到实际清理器。 +- Execution store 和双方言 SQL 同步新增轮次起点、deadline、策略版本的条件写入及读取;将恢复行为纳入迁移 fixture。 +- 提供 `Now()` 注入与事件驱动 timer seam,生产用标准时钟;不建立全仓通用时间框架。 +- 初始迁移仅加 nullable 字段/索引并 backfill 辅助值,不做删除;原始 TTL 不短于原契约。 +- 完成:legacy 默认保持;v2 deadline 计算、优先级、字段扫描及升级 fixture 双方言验证。 +- 提交点:策略、模型、迁移、配置测试。 + +### L2:绝对轮次期限和安全运行回收 + +- 文件:`internal/gateway/bridge.go`、`bridge_forward.go`、`bridge_stop.go`、`turn_clock_test.go`;`internal/session/manager.go`。 +- 删除绝对 timer 随每个事件 Reset 的行为。timer 与 `(session_id, execution_id, worker_run_id)` 绑定,在实际输入投递成功开始;Idle 不运行绝对轮次 timer,下一轮使用新 timer。 +- 超时复用当前 turn stop/dispose、synthetic terminal 与持久 runtime 结算路径;只发一个 terminal,不能杀掉替换后的 Worker,不触发 crash retry。 +- 无 IO 时钟仍随实际 IO 更新;不能把连接 ping 当模型进展;等待用户交互时允许健康检测识别 waiting,但绝对期限按本方案不暂停。 +- `worker.max_lifetime` 到期只标待回收,轮次终结后回收;共享 OCS/Codex 服务按所属 session/run 的隔离能力回收,禁止因一个 session 到龄杀掉全局服务。 +- 文件:`cmd/hotplex/gateway_run.go` 的 OnTerminate 拆分运行释放与逻辑删除副作用;v2 空闲回收不清除尚未过期队列/逻辑附属 Cron。legacy 行为单独保持直至其迁移。 +- v2 附属 Cron 明确绑定逻辑会话:显式删除逻辑会话时清理,自动 Worker 空闲释放和归档不删除;用户显式 `/terminate` 既有附属任务清理语义保持。 +- 完成:持续流式输出仍达绝对期限;安静健康长工具按受限覆盖运行;旧 timer 不作用于新 run;回收不丢队列或误删 Cron。 +- 提交点:运行超时、回收、hooks 与回归。 + +### L3:历史期限与归档,先修复历史失联 + +- 文件:`internal/session/manager.go`、sessions 读写/GC 查询;`internal/gateway/api.go`、`bridge.go`;`internal/eventstore` 的 events/turns 查询。 +- 接受输入时原子 touch last_input_at、向后延长 conversation_expires_at/history_expires_at;新正文写入同事务或可靠补偿更新 last_content_expires_at/history_expires_at,不能用一次 best-effort 更新作为可删证明。 +- 归档投影与 AEP runtime State 分开;会话 GC 在 v2 中先释放运行,再按历史期限处理,禁止保留正文期间物理删除 owner/workspace 索引。 +- 普通用户历史、分页、Worker hydration、Cron 历史统计、管理员事件查询都过滤过期正文,不能只有 GetHistory 过滤。 +- 同步改造 `runEventsGC`:legacy 行按原契约清理,v2 行按持久化 expires_at 条件删除。不得再用全局 created_at + 30 天扫描覆盖 v2 行,否则 180 天策略只在读取侧生效却被旧 GC 提前删除;增加混合策略同库回归。 +- v2 Cron 根行至少覆盖未过期正文;旧 24 小时 term retention 不覆盖内容可见性。无正文的 Cron 可维持短的运行详情期限。 +- 单条正文过期不退休逻辑会话。只有 conversation_expires_at 和最后正文期限均已到达,且没有运行/恢复义务时,才隐藏历史入口并清内容性根字段;控制事实仍需要时保留最小根行。会话仍未过期时保留入口,详情提示“部分/全部正文已过期”,不展示旧全文。 +- 文件:`webchat/lib/api/sessions.ts`、`webchat/app/components/chat/SessionPanel.tsx`、`webchat/lib/adapters/hotplex-runtime-adapter.ts`;仅增加归档标记与正文到期说明,不做 unrelated UI 重构。 +- 完成:60 分钟空闲回收、7 天归档、30 天与 90 天后仍能读取 v2 未到期用户历史并按需续聊;180 天到期边界正确,365 天覆盖配置有效;读取/改标题不续期;过期正文在 GC 尚未执行时也不可见。 +- 提交点:历史读取、索引保护与 UI 契约。 + +### L4:统一删除事务与可靠清理 + +- 文件:`internal/session/manager.go`、store/pg_store、`cleanup_outbox.go`;`internal/gateway/{api,commands}.go`、管理端 adapter。 +- 拟新增:`internal/session/purge.go` 和两张 purge 表及成对迁移。 +- 所有业务删除入口先校验既有 owner/workspace/scope,再调用同一 `RequestPurge`(拟新增),不因 cache hit 改变语义。`DeletePhysical` 降为清理器内部最终步骤,所有公开 adapter 不再直接绕过删除意图。 +- 本地事务:持久标记 DELETED/deleted_at,建立 root/模块任务,结算未派发队列并清正文;事务失败则不声称删除受理成功。 +- 提交事务后 fence 当前 run,等旧转发器退出/排空再清事件正文,阻止晚到 delta 重新写入;跨实例写路径以持久 DELETED 和条件更新为准。 +- 远端删除与慢文件清理异步,不阻塞本地事务或复活会话。未知外部副作用保留安全标记,不自动重发。 +- REST 保持既有删除成功响应兼容,新增可选 job ID/状态;AEP StateDeleted 保持语义并表示本地已不可访问,不承诺远端擦除完成。 +- 拟新增 GET `/api/sessions/{id}/cleanup`,从授权归属快照鉴权;不通过普通 GetHistory 绕过删除屏障。管理端使用其既有 scope 与适用的 workspace 契约。 +- 完成:所有入口、冷/热缓存、重启、并发 resume/delete 结果一致;DELETED 不无限保留内容性数据。 +- 提交点:删除 API、任务、Runner 与双实例回归。 + +### L5:投递正文、证据和辅助数据 GC + +- 文件:`internal/effect/store.go` 及其 SQL 实现、lease/operator/recovery 路径;`internal/cron` occurrence store;`internal/execution/sql_queue_dispatch.go`;gateway 启动接线。 +- 完成投递记录设置不可变 settled_at;正文 GC 使用 terminal/settled 状态和无活动 lease 的事务条件,不能只按 created_at 删除。 +- pending/started/unknown 正文尚有恢复义务时不自动清理;到期仍未收敛时转 blocked 并提醒 operator,不伪造 delivered 或自动 abandon。 +- 明确用户删除撤销的是尚未发送的交付意图;已开始或未知发送先 fence,不能把未知结果改写为 not_sent。安全地清正文后保留去重身份和未知证据。 +- `ErrPayloadNotFound` 保持内容缺失口径,读控制台不因此报事实损坏;恢复不得因为正文缺失重跑 Agent。 +- 事实 90 天 GC 清理已结算投递的详细 `effect_attempts`,并压缩已结算 `execution_inputs` 的诊断明细;保留输入 ID、payload hash、`worker_run_id`、最终状态和 `finished_at`,同时保留 `effects` 与 `cron_occurrences` 的幂等根记录。`failed` delivery + `completed` runtime 仍可能需要迟到收敛,不能压缩。当前没有强制客户端/来源回放期限,不能仅凭年龄删除这些根记录。 +- attempt GC 要求终态、可靠 `settled_at`、无租约且 attempt 已结束;`unknown`、活动租约和 legacy 缺少结算时钟的数据继续保护。后续若要缩短根记录保留,须先定义并强制回放期限,或实现可验证的最小幂等墓碑。 +- 队列 TTL 针对新接受输入按 interactive/Cron 分类计算;已有排队 deadline 不重算。到期反馈复用现有 ACK/runtime 协议,必要新增字段同步协议矩阵。 +- 辅助表:chat_access_events 新策略可保留 30 天;queue_counters 只在逻辑会话彻底退休、无队列且无复用风险时清理;audit identity links 按身份关系业务生命周期处理,不按会话任意级联。 +- 完成:无活跃投递误删、无丢失补偿正文、无重复外发;所有长期保留例外可查询。 +- 提交点:正文与事实清理、队列分级 TTL、辅助表保留规则。 + +### L6:新审计最小化、Worker 能力与调试副本 + +- 文件:`internal/gateway/handler.go`、审计 interaction 写入路径、`internal/audit/{types,store,gc,collector,spill}.go`。 +- v2 新审计默认不写 input content、用户答案原文、工具参数或 metadata 值;保留动作、结果、归属、执行 ID、有限 reason code 等。 +- 如需正文,拟新增独立有期限的 audit payload 表,审计链只存不可变 opaque ref;正文删除不修改链。默认不记录可用于猜测短文本的裸正文 hash。 +- 新审计事实 180 天与 legacy 三年链分开计算;拟新增 `user_activity_v2`、`audit_chain_checkpoints_v2` 和可选 `audit_payloads_v2`,复用既有哈希与 Store 算法,table profile 只允许内置枚举。新链有独立 genesis/epoch,查询聚合两者并显示 epoch;禁止直接修改旧链 retention 后开始 GC。 +- 本批只交付旧明文审计迁移的只读预览和操作设计,不执行旧链重写/销毁;这是 §9 的审批项。 +- 文件:`internal/worker/session_cleanup.go`、四 adapter 及 `internal/worker/noop` 和 registry tests。 +- 清理能力返回支持/不支持/结果未知,而不使用 no-op 假成功;OCS 复用原有实现。Claude 仅精确清理属于 HotPlex 会话的文件;Codex 的临时线程无需声称存在持久副本,持久模式/ACP 依 provider API 返回真实能力。 +- 文件:`internal/worker/acp/trace.go`、gateway 启动清理、Slack/飞书缓存清理器;对 trace 增加时间期限和容量,活跃 writer 与 GC 协调。 +- 不删除外部平台消息、工作目录、生成文件或全局 Agent 配置;如果用户另行要求,再建立独立可评审操作。 +- 完成:新输入不能从长期审计/日志读回;旧审计链仍验证通过;unsupported 在 UI/API 显示为阻塞范围。 +- 提交点:新审计策略一单元,Worker/trace 能力另一单元,避免混合数据迁移。 + +### L7:状态汇总、运维预览、迁移和文档 + +- 已新增只读 Admin API `POST /admin/lifecycle/migration/preview`,显示会话、事件、轮次候选数量、正文总字节、最早目标期限、缺少活动时间/孤立/执行中事实计数和策略版本;不输出正文、凭证、会话 ID 或用户身份。 +- `POST /admin/lifecycle/migration/apply` 是独立显式动作,要求重复确认临时计划 ID,30 分钟后失效;apply 在 serializable 事务中重新校验数据和策略摘要,不把 stale 预览当作写入授权。 +- apply 按 `lifecycle.gc.batch_size` 分批,单类最多 500 条;先推进正文期限,仅在一个会话的全部现存正文期限均达到有效 legacy/v2 期限后才切换该会话到 v2,避免大历史会话的未处理正文继续沿用较短 TTL。 +- 已按现有 sync.Once 规则新增 session GC 的 backlog/oldest-lag gauges,按 `eligible` / `blocked` 汇总,并单独统计 unknown/fenced execution 造成的过期会话阻塞;超过 `lifecycle.gc.max_lag` 时记录低频告警日志,status 查询失败进入固定阶段 Counter。`hotplex.lifecycle.gc.processed` 按固定 `kind` 记录会话、事件/轮次、投递与执行事实、审计事实、trace/media 文件及会话清理项;不同类别单位不同,不跨类求和。provider unsupported 与远端清理结果没有独立指标;所有标签无 session/user 高基数。 +- 文档:`docs/reference/{configuration,admin-api,metrics,aep-protocol,events}.md`,以及实际受影响 SDK/Swagger 生成源和用户说明。只改源文件,生成物按项目目标生成。 +- 先测试库验证、再运行只读预览;实例管理员批准存量 apply 后按 100 行起始批次执行,观察请求延迟、锁等待、IO 和积压后调节,不承诺未测吞吐。 +- 完成:可证明清理范围和延迟,迁移没有意外提前过期;Source/Test/Live 台账分开。 +- 提交点:预览/可观测性与迁移说明;真实运行态执行另行授权。 + +## 6. 关键实现说明 + +### 6.1 起算与续期的单一事实来源 + +```text +accept_first_valid_user_input(now): + 在接受事务中(幂等请求只执行一次): + last_input_at = max(last_input_at, now) + archive_at = max(已有归档 deadline, now + archive_after) + conversation_expires_at = max(已有会话 deadline, now + conversation_retention) + history_expires_at = max(已有值, conversation_expires_at) + 不修改已有正文 expires_at + +write_content(created_at, policy): + content.expires_at = created_at + policy.content_retention + 同事务或可证明的补偿路径更新: + last_content_expires_at = max(已有值, content.expires_at) + history_expires_at = max(已有值, content.expires_at) + 首次创建空会话时: + conversation_expires_at = created_at + conversation_retention(默认 180 天) + history_expires_at = conversation_expires_at + +finish_runtime(now): + runtime_finished_at = now + 只有真正完成新 run 才写;普通更新不修改 + +archive_projection(now): + now >= archive_at 且无正在运行的轮次 + 不删除 sessions,也不删除正文 + +history_read(now): + 鉴权 + 非 DELETED/非 purge 屏障 + 只返回 expires_at > now 的正文 +``` + +业务时间持久化为 UTC;数据库中的同一事务使用一致 now。内存 timer 使用单调时钟;重启从持久 deadline 恢复剩余时间,不能重启续命。`expires_at == now` 视为到期;已有 GC `< cutoff` 的边界变化要成对测试并在说明中列出。 + +记录 created_at 是原始事件时间还是接收时间沿用现行约定;外部未经验证的客户端时间不得作为延长保留期的依据。 + +### 6.2 绝对期限、静默期限和进程年龄 + +- 绝对 timer 从真实轮次开始建立一次;事件到达只更新 LastIO,不重置绝对 timer。 +- `turn_started_at` 取成功投递时写入的一次服务端时间,同时持久化 `turn_deadline_at = turn_started_at + 本 execution 的有效 turn_timeout` 和策略版本。重启读取已存 deadline,不按新配置重算;不得将原有 `started_at` 擅自解释为相同事件。 +- 轮次终结、输入投递失败、stop/reset/delete 都取消本轮 timer;timer 回调验证 frozen run identity 与 lifecycle barrier。 +- 重用长连接 Worker 的下一轮须建立新 execution 的 deadline,不能沿用旧 forwarder 初始化时间。 +- 进程年龄是回收预算,绝对轮次期限是业务上限;两个时钟不能互相替代。 +- 等待审批也计算绝对期限;UI 提示剩余时间。未来若允许暂停审批时间,需要独立评审及总上限,本批不加入。 + +### 6.3 清理任务状态与正文写入屏障 + +拟新增状态响应示例: + +```json +{ + "job_id": "opaque-id", + "status": "blocked", + "local_content": "completed", + "worker_history": "unsupported", + "retained_facts": "dedupe_window", + "legacy_audit_content": "approval_required", + "backup_scope": "external_policy" +} +``` + +该示例不是当前接口。返回结果不包含正文或具体文件路径。 + +删除意图提交后禁止接受、resume、正文写入和 cache hydration。仅拒绝 API 读取不足以防止 late forwarder 再插入内容;写事务必须检查持久 lifecycle/代际,GC 批次也须使用条件删除。 + +真实删除前先收敛队列与当前 run;旧任务失败时不能清除屏障。原生清理完成后仍需保留最小退休标记直到可证明重放窗口关闭,不能只以 outbox 删除就宣布可复用旧身份。 + +显式删除和逻辑会话自然退休均不能把退休根行改回活跃。单条或全部正文过期但 conversation_expires_at 未到达时,会话不退休,允许续聊并明确提示上下文缺失;成功接受新输入按规则延长会话期限。真正退休后,v2 初期要求新建 WebChat 会话或平台新线程,不自动复用旧 thread/session ID;返回“会话保留期已到,请新建会话”的明确说明。未来若需同一平台线程自动创建新逻辑会话,须在现有平台映射上加入可验证代际,单独评审,不能清空墓碑后用旧身份继续。 + +### 6.4 无界 unknown 与保留例外 + +期限到达的 unknown 不能自动重发、自动标成功或自动丢掉去重事实。正文恢复义务转 blocked,通知管理员明确选择撤销或核对回执;若用户显式删除,撤销未发送义务并保留必要未知证据。 + +长期 exception 是受控例外:记录理由、负责人/处理入口、容量指标;正文不是为了保留一个去重键而无限保存。事实终结不等于可以删除唯一业务键,必须核对 scheduled/manual/webhook 的最大重放年龄与入口拒绝规则。 + +### 6.5 存量迁移与策略修订 + +1. 新增字段不删除旧列,不改旧 migration。 +2. 旧 last_input_at 优先取已持久用户 turn/受理事实时间;不得把普通 UpdatedAt 当准确输入时间。证据不足则标记 legacy,保留旧策略。 +3. 旧事件正文原期限是 created_at + 有效 legacy events retention;新增 expires_at 等于该期限,不借迁移改变天数。 + 延长存量用户历史到 180/365 天时另做预览与 apply,仅影响仍存在的正文;已删除消息不能恢复。旧会话缺少可靠输入时间时不把升级时间当作虚构的用户活动;管理员批准后可使用明确标注的迁移保护期。 +4. policy v2 对新数据生效;旧无期限 effect 正文不能直接 backfill 成早已过期并触发自动删除,先保留 legacy,生成预览。 +5. 旧 audit input 明文仍在不可变链。先验证旧链、设计新 epoch 的事实迁移证明;销毁原始链会改变证据形式,需明确批准和备份范围确认。不能 UPDATE detail_json 或随意关触发器。 +6. 缩短期限的 apply 按可审计计划执行;延长已物理删除数据不能恢复,API 必须如实说明。 +7. 清理器启动时在容量限制内补扫;租约过期重新 claim,旧 lease token 不得完成新任务。 + +### 6.6 索引、性能与物理删除边界 + +为内容 `expires_at`、会话 conversation/history/archive deadline、事实 settled_at 与清理任务 next_attempt_at 建索引。每批删除有行数/执行时间预算,多实例争抢用既有条件 claim;保持 PG advisory lock 与 SQLite writer 约束。 + +PostgreSQL DELETE 不立即移除旧版本;SQLite DELETE 也不能自动承诺所有介质副本不可恢复。日常容量依赖 autovacuum/既有维护;不在请求或 GC 中强行执行 VACUUM FULL。强擦除、WAL、备份和快照另设能力与承诺,本批不自动变更数据库全局设置。 + +## 7. 测试方案 + +| 单元 | 既有入口 / 拟新增文件 | 必须断言 | +| --- | --- | --- | +| L1 | `internal/config/effective_events_retention_test.go`;拟新增 `lifecycle_policy_test.go` | 已确认默认值、完整YAML、env覆盖、显式空值/0/负数/溢出拒绝、归档≤会话期限、revision生效、scan/upsert全字段、升级不删旧正文 | +| L2 | `internal/gateway/turn_clock_test.go`、bridge tests;拟新增 `turn_deadline_test.go` | 持续 delta/heartbeat 不延长绝对期限;两轮各自计时;Idle 不触发;旧回调不杀新 run;只有一个 terminal,无 crash 自动重试 | +| L2 | `internal/session/manager_test.go`、`bridge_forward_idle_test.go` | 空闲回收不删历史;24h 年龄仅边界回收;共享服务不被其他会话杀掉;附属 Cron/未过期队列保持 | +| L3 | `internal/gateway/api_test.go`、`api_workspace_session_test.go`;eventstore tests | 7d归档、30d/90d仍可读;180d/365d边界;正文全部到期但逻辑会话有效时可续聊;跨 owner/workspace 拒绝;分页、hydration、Cron 统计一致;并发不删受保护索引 | +| L4 | `manager_test.go`、`cleanup_outbox_test.go`;拟新增 `purge_test.go` | REST/AEP/admin + cache hit/miss 同结果;原子入队;事务失败回退;重复删除幂等;重启不复活;晚到 event 不能再写正文 | +| L4 | 拟新增 `lifecycle_pg_integration_test.go`,`//go:build pg` | 真 PG 两实例 claim、删除与 resume/写入竞争、旧 lease fencing;不能只用 sqlmock | +| L5 | `internal/execution/queue_payload_test.go`、queue tests | 15m/24h/deadline 分类;时间边界;新 TTL 不改旧 queue;领取/取消/过期删正文并保留事实 | +| L5 | `internal/effect/store_test.go`;拟新增 `payload_gc_test.go` | pending/leased/unknown 不误删;终结+7d可删;无正文不重跑 Agent;terminal 事实超90d但重放风险未闭合时保留标记 | +| L6 | `internal/audit/{gc,verify,collector,spill}_test.go` | 新审计无正文;旧链验证不变;删独立正文链仍可验;spill 遵循新策略;旧正文预览零写入 | +| L6 | `internal/worker/session_cleanup_test.go`、各 adapter 测试 | OCS 幂等;Claude 精确归属路径;Codex ephemeral vs persistent;ACP unsupported;不得删除用户目录或全局项目 | +| L7 | 拟新增 WebChat lifecycle/purge 单测与 e2e | 文案区分归档/停止/删除;blocked 不显示已完全擦除;响应 optional 字段兼容;刷新不续期 | + +测试使用 fake clock、channel 和 `require.Eventually`,不用 `time.Sleep` 等待异步。覆盖 deadline 前、等于、后一单位以及重启后剩余时间;不为每个拟新增字段机械复制实现断言。 + +PG 集成测试必须在专用可销毁数据库执行。已有 `delete_terminated_pg_test.go` 和 `sql/migrate_pg_test.go` 会 DROP public schema;新测试优先每例独立 schema。不能把开发/生产数据库的 DSN 提供给这些测试。 + +## 8. 验收标准 + +### 8.1 当前证据(2026-10-08) + +- Source:v2 配置、独立会话/正文期限、轮次绝对 timeout、历史读取过滤、生命周期 GC 退役屏障、持久 cleanup outbox、delivery payload GC、新审计正文最小化、媒体/ACP trace 清理及 WebChat 归档展示均已接入源码;具体边界以配置参考和实现代码为准。 +- Test:`make test-short` 通过;生命周期审计相关 `go test ./internal/audit ./internal/session/sql -race -shuffle=on -count=1` 通过。存量迁移新增 SQLite 回归覆盖双重确认、stale 预览、超批正文等待、策略变化回滚和批大小上限。 +- Test:WebChat `pnpm test`(267 项)、`pnpm lint`、`pnpm exec tsc --noEmit`、`pnpm build` 通过;`make docs-build`、`make docs-lint`、`make build` 通过。 +- Observability:已记录生命周期退役失败阶段计数、成功退役延迟直方图、eligible/blocked 候选积压、最老延迟、unknown/fenced execution 阻塞 gauge 与分类处理量 Counter;`gc.max_lag` 超限会记录告警日志。provider unsupported 与远端清理结果没有独立指标。 +- Remaining:已结算 delivery attempt 与 execution 诊断压缩、独立 v2 审计链及逐条固定期限已实现;幂等根记录在回放窗口未受约束前继续保留。真实 PostgreSQL 生命周期/多实例测试仍未完成。`make quality` 未通过:本机 `golangci-lint` 用 Go 1.26 构建,无法分析需要 Go 1.27 的代码;此前 `make test-short`、`make build` 与 `make docs-lint` 已通过;2026-10-08 分类指标变更后的 race 包测试与 `make docs-lint` 通过。 +- Live:未读取实际实例配置、未查询运行实例数据库、未对真实渠道删除或修改存量数据;真实 PG 生命周期新实现尚未验证。 + +### 8.2 实施后的命令 + +在 `/Users/hrygo/hotplex` 或该仓库授权工作树根目录执行。下面使用可移植原生命令;本机按 AGENTS 的 RTK 路由要求执行。 + +```bash +# 相关单元:每个逻辑单元只跑其影响范围;以下为最终相关 Go 集合。 +go test ./internal/config ./internal/session ./internal/gateway \ + ./internal/execution ./internal/effect ./internal/audit ./internal/eventstore \ + -count=1 -race -shuffle=on + +# Worker 清理能力修改后补 adapter 集合。 +go test ./internal/worker/... -count=1 -race -shuffle=on + +# HOTPLEX_TEST_PG_DSN 须由批准的测试环境预置,目标必须可销毁。 +# 不把 DSN/凭据写入文档、参数输出或提交。 +go test -tags pg ./internal/session \ + -run '^(TestPGStore_DeleteTerminatedEnqueuesCleanupTask|TestLifecyclePG_.*)$' \ + -count=1 -race -p=1 + +# 此路径的现有测试会重建 public schema;仅同一专用测试库,单独执行。 +go test -tags pg ./internal/session/sql \ + -run '^TestMigrations_PG_' -count=1 -p=1 + +cd webchat +npm run test +npm run lint +npx tsc --noEmit +npx playwright test e2e/data-lifecycle.spec.ts --project=chromium +cd .. + +make docs-lint +make quality +make build +``` + +`TestLifecyclePG_*` 与 `e2e/data-lifecycle.spec.ts` 是拟新增,Luna 须实际创建后再运行。任何 PG skip 不计通过;每个迁移应另有对应真实 PG 回归,不能靠老迁移测试替代。项目约束的 make 门禁在 RTK 可用时走 `rtk proxy`,禁止跳 hooks。 + +### 8.3 行为 Checklist + +- [ ] 常规策略下 Worker 空闲释放后,期限内历史仍可查看并安全续聊。 +- [ ] 逻辑会话从最后有效输入起默认保留 180 天,归档不占用常驻 Worker/连接;可选 365 天,分页与按需加载不扫描全部历史。 +- [ ] legacy/v2 同库时旧 30 天 GC 不会提前删除新 180/365 天用户正文;已删除存量不虚假承诺恢复。 +- [ ] 所有 §4.3 参数实际接线;缺省值与用户确认一致,YAML/env 可覆盖;365 天不写成仅有的可选枚举,非法值有明确诊断。 +- [ ] 7 天归档不删除,用户新输入延长会话期限但不延长旧消息;仅正文过期不强制退休仍有效的会话。 +- [ ] 持续输出轮次不能无限延长绝对执行 deadline。 +- [ ] timer、stop、done、reset/delete 竞争只产生一个终态,旧 run 不影响新 run。 +- [ ] 历史读取、分页、恢复注入及统计使用相同正文到期口径。 +- [ ] 新正文存入和历史索引保护原子可靠,GC 不制造保留期内 404。 +- [ ] 所有删除入口、cache hit/miss 及重启后语义一致。 +- [ ] 删除接受后立即禁止正文读取、新输入、恢复和 late writer。 +- [ ] 队列到期有明确结算,正文消失,执行事实仍满足去重。 +- [ ] 未完成投递不丢恢复正文,unknown 不自动重发。 +- [ ] 新审计默认无用户/助手正文;旧不可变审计链仍可验证。 +- [ ] unsupported、blocked 和备份范围如实展示,不能假报完整擦除。 +- [ ] 存量只读预览零写入,缩短期限/旧审计销毁未经批准不能执行。 +- [ ] 超过单批上限的大历史会话先逐批延长所有正文期限,再迁移会话根期限。 +- [ ] SQLite 与真 PG 成对迁移、条件删除和跨实例 lease 测试通过。 +- [ ] 协议变化同步 AEP/SDK/双向语料,文档和生成物按门禁一致。 +- [ ] 正常负载下清理延迟 SLO ≤1 小时;长期异常有指标和告警。 +- [ ] 无他人改动被覆盖;各单元实际 commit、Source/Test/Live 结果可追溯。 + +## 9. 风险与注意事项 + +### 9.1 必须评审的业务与数据决定 + +本方案可以作为实施设计评审稿,但以下不是已批准的运行操作: + +1. 用户已确认本方案数字作为默认值并提供参数覆盖,不再就默认数字重复询问;在实际实例开启 v2、部署和执行存量迁移仍是独立操作。 +2. 新审计事实默认 180 天已确认;组织需要更长期限时配置覆盖,旧三年审计链不自动缩短。 +3. 旧 audit 明文迁移/旧链销毁的证据形式、备份范围和管理员批准。 +4. 存量无 TTL effect、旧 DELETED 数据及未知投递的清理计划。 +5. 用户管理的备份、导出、外部平台消息是否另有受授权删除要求。 + +实施可以先完成代码、测试、legacy 默认和只读预览。上述决定未确认时标为 blocked 的运行操作,不擅自删除数据,也不要求重新确认已经明确批准的范围。 + +### 9.2 真实影响 + +- 绝对 turn timeout 修正会使持续输出的长任务真正达到上限;长任务须显式设置期限。legacy rollout 必须发布行为变更说明。 +- 7 天无输入归档与运行 TTL 不是同一含义;不得直接把旧 `session.retention_period` 改成归档后期限。 +- v2 历史索引延长会增加元数据量;按现有表复用避免双重归属来源,测量后再考虑压缩,不提前分区或引入新服务。 +- 用户正文从 30 天建议延长至 180 天,稳态正文量估算约增加到 6 倍;冷会话释放进程和连接,数据库、备份与检索成本仍需测量,不能据此声称“零资源占用”。 +- 事实保持 90 天需要最小根行,不能沿用“删 sessions 就删 execution”的即刻擦除解释;scope 信息本身也应最小化并有限保留。 +- 共享 Worker 的进程年龄回收不能影响其他用户;没有安全隔离能力时报告 unsupported 并保留原有回收方式。 +- 原生 CLI/provider 可能没有删除 API;保留真实能力限制,不能通过删除用户全局历史目录模拟实现。 +- 旧审计正文不支持普通逐会话擦除;这是数据模型迁移问题,单改 TTL 或关 UPDATE trigger 会损坏审计保证。 +- 物理删除、容量回收、介质不可恢复三种承诺分开;WAL/备份/快照必须纳入实例策略,不能承诺 SQL DELETE 即强擦除。 + +### 9.3 回退 + +- 采用 additive migration 和 legacy/v2 可回退读路径;回退前停止新 GC claim,已写 deleted_at 屏障仍须读取和尊重。 +- 回退程序不得恢复删除/撤销过的投递,不得清除墓碑后接受旧输入。 +- 已删除正文无法通过代码回退恢复;若需要备份恢复,必须在离线测试恢复并应用删除清单后才能对外提供服务。 +- 不自动对运行实例做 schema Down;先检查新字段/任务兼容性及已执行不可逆清理。 +- 发布、服务重启、存量 apply、备份恢复分别按已有授权执行;重启只用项目规定的 `hotplex service restart`。 + +### 9.4 官方参考(2026-10-08 核验) + +以下用于支持原则,不作为本方案具体天数的标准来源: + +- [NIST Privacy Framework 生命周期原则](https://www.nist.gov/privacy-framework/getting-started-0):考虑从收集到销毁的完整生命周期。 +- [OWASP Logging Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html):敏感数据最小化、日志保护及备份/调试副本的保留与销毁。 +- [Kubernetes TTL-after-finished](https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/):运行完成后的资源保留是独立时钟;时间偏差会影响清理。 +- [PostgreSQL Routine Vacuuming](https://www.postgresql.org/docs/current/routine-vacuuming.html):DELETE 与旧版本/磁盘空间回收不同,日常避免 VACUUM FULL。 +- [SQLite secure_delete](https://sqlite.org/pragma.html#pragma_secure_delete):删除残留与安全删除能力、性能和适用限制;本方案不自动开启全局 pragma。 + +## 10. Luna 执行清单 + +1. 核对实施授权、目标分支、HEAD、工作区未提交内容及模块 AGENTS;本会话此前已有 `.dockerignore` 修改和未跟踪 `docker-compose.cliproxy.yml`,仅作历史提示,实际状态重新核实,不能覆盖或提交。 +2. 执行 L0,记录本批独立发现和失败回归;采用用户已确认的默认时长与参数契约,明确 v2 试点范围,存量 apply 保持关闭。 +3. 执行 L1,成对新增字段/策略/迁移;默认新数据使用 v2,升级存量行维持旧期限;验证 legacy 回退与新数据 deadline,原子提交。 +4. 执行 L2,修正绝对期限、运行回收和 hooks;验证冻结 run 与共享 Worker,原子提交。 +5. 执行 L3,修复历史索引保护和统一正文到期读取;验证 UI/恢复/统计,原子提交。 +6. 执行 L4,统一所有删除入口、持久任务和写入屏障;真 PG 并发验证后提交。 +7. 执行 L5,补正文、事实和队列 TTL;先确认去重窗口,unknown 保持安全,按自洽单元提交。 +8. 执行 L6,新审计不采集正文、旧链保持;Worker/trace 能力分别验证提交,禁止顺手处理旧明文。 +9. 执行 L7,交付只读预览、指标和文档;运行必要门禁,按事实记录通过、失败与 skip。 +10. 仅在实例管理员批准后开启 v2 或执行存量计划;先测试库/受控样本,不操作生产或 dev 库替代可销毁测试库。 +11. 最终汇总每单元 commit hash、影响文件、实际测试命令与结果、迁移状态、运行实例适用策略、剩余 blocked/unsupported 范围;代码完成、在线清理完成、旧备份到期分别报告。 + +本轮方案交付不等于以上清单已执行,也不构成业务修改、存量数据删除、远端发送、发布或服务重启授权。 diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index e76094b21..640927872 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -3136,7 +3136,7 @@ "ApiKeyAuth": [] } ], - "description": "Gracefully terminates the worker and physically deletes the session. Ownership check applies.", + "description": "Marks the session deleted, immediately hides it from history, and schedules cleanup. Ownership check applies.", "tags": [ "Gateway API" ], @@ -3257,6 +3257,64 @@ } } }, + "/api/sessions/{id}/cleanup": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns bounded progress for a deleted session without exposing conversation content or provider error text.", + "produces": [ + "application/json" + ], + "tags": [ + "Gateway API" + ], + "summary": "Get session cleanup status", + "parameters": [ + { + "type": "string", + "description": "Session ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/admin.GatewaySessionCleanupStatusResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/admin.ErrorResponse" + } + }, + "403": { + "description": "Ownership required", + "schema": { + "$ref": "#/definitions/admin.ErrorResponse" + } + }, + "404": { + "description": "Cleanup status not found", + "schema": { + "$ref": "#/definitions/admin.ErrorResponse" + } + }, + "503": { + "description": "Cleanup status unavailable", + "schema": { + "$ref": "#/definitions/admin.ErrorResponse" + } + } + } + } + }, "/api/sessions/{id}/events": { "get": { "security": [ @@ -4334,6 +4392,63 @@ } } }, + "admin.GatewaySessionCleanupStatusItem": { + "type": "object", + "properties": { + "attempts": { + "type": "integer", + "example": 2 + }, + "completed_at": { + "type": "string" + }, + "error_code": { + "type": "string", + "example": "cleanup_failed" + }, + "kind": { + "type": "string", + "example": "conversation_content" + }, + "next_attempt_at": { + "type": "string" + }, + "status": { + "type": "string", + "example": "retrying" + } + } + }, + "admin.GatewaySessionCleanupStatusResponse": { + "type": "object", + "properties": { + "completed_at": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/admin.GatewaySessionCleanupStatusItem" + } + }, + "job_id": { + "type": "string" + }, + "requested_at": { + "type": "string" + }, + "session_id": { + "type": "string" + }, + "status": { + "type": "string", + "example": "in_progress" + }, + "updated_at": { + "type": "string" + } + } + }, "admin.GatewaySessionListResponse": { "type": "object", "properties": { diff --git a/internal/admin/admin.go b/internal/admin/admin.go index 91ed258b4..cf24c7b79 100644 --- a/internal/admin/admin.go +++ b/internal/admin/admin.go @@ -172,38 +172,39 @@ func (r *statusRecorder) Unwrap() http.ResponseWriter { } type AdminAPI struct { - log *slog.Logger - cfg ConfigProvider - sm SessionManagerProvider - turnStore TurnStatsProvider - hub HubProvider - bridge BridgeProvider - configWatcher ConfigWatcherProvider - cron CronSchedulerProvider - botLister BotListerProvider - botConfig BotConfigProvider - wsStore session.UserWorkspaceStore // Optional: enables /admin/workspaces console (issue #807); nil when multitenancy/webchat disabled - logCollector LogCollector - akStore APIKeyUserStorer // nil when DB resolver not enabled - keyValidator KeyValidator // nil when not injected - rateLimiter atomic.Value // *simpleRateLimiter - allowedCIDRs atomic.Value // []string - allowedOriginsFn func() []string // returns allowed CORS origins from config - version func() string - newSessionID func() string - restart func() error - restartPrepare func(context.Context) (func() error, func() error, error) - cookieAuth *security.CookieAuth // Optional: enables cookie-session fallback (issue #788 A2) - idp *security.LocalAccountProvider // Optional: paired with cookieAuth - startedAt time.Time - activityService *ActivityService // Optional: enables /admin/activity + /admin/users/{id}/activity (issue #833) - auditCollector *audit.Collector // Optional: emits system.audit_export meta-audit rows (issue #833) - skillsLocator *skills.Locator // Optional: enables /admin/api/skills global skill management (issue #910) - builtinSkills BuiltinSkillsCatalog // Optional: embedded read-only Agent Skills - runtimeExec RuntimeExecutionProvider // Optional: enables /admin/executions fence endpoints (#877); nil → 503 - runtimeNotifier RuntimeEventNotifier // Optional: emits runtime.execution.failed on abandon (#877) - runtimeEffects RuntimeEffectProvider // Optional: enables /admin/effect endpoints; nil → 503 - runtimeQueue RuntimeQueueProvider // Optional: enables /admin input-queue endpoints; nil → 503 + log *slog.Logger + cfg ConfigProvider + sm SessionManagerProvider + turnStore TurnStatsProvider + hub HubProvider + bridge BridgeProvider + configWatcher ConfigWatcherProvider + cron CronSchedulerProvider + botLister BotListerProvider + botConfig BotConfigProvider + wsStore session.UserWorkspaceStore // Optional: enables /admin/workspaces console (issue #807); nil when multitenancy/webchat disabled + logCollector LogCollector + akStore APIKeyUserStorer // nil when DB resolver not enabled + keyValidator KeyValidator // nil when not injected + rateLimiter atomic.Value // *simpleRateLimiter + allowedCIDRs atomic.Value // []string + allowedOriginsFn func() []string // returns allowed CORS origins from config + version func() string + newSessionID func() string + restart func() error + restartPrepare func(context.Context) (func() error, func() error, error) + cookieAuth *security.CookieAuth // Optional: enables cookie-session fallback (issue #788 A2) + idp *security.LocalAccountProvider // Optional: paired with cookieAuth + startedAt time.Time + activityService *ActivityService // Optional: enables /admin/activity + /admin/users/{id}/activity (issue #833) + auditCollector *audit.Collector // Optional: emits system.audit_export meta-audit rows (issue #833) + skillsLocator *skills.Locator // Optional: enables /admin/api/skills global skill management (issue #910) + builtinSkills BuiltinSkillsCatalog // Optional: embedded read-only Agent Skills + runtimeExec RuntimeExecutionProvider // Optional: enables /admin/executions fence endpoints (#877); nil → 503 + runtimeNotifier RuntimeEventNotifier // Optional: emits runtime.execution.failed on abandon (#877) + runtimeEffects RuntimeEffectProvider // Optional: enables /admin/effect endpoints; nil → 503 + runtimeQueue RuntimeQueueProvider // Optional: enables /admin input-queue endpoints; nil → 503 + lifecycleMigration LifecycleMigrationProvider // Optional: explicit legacy retention migration // Execution console (#868). Each view is optional and degrades its own // section to "unavailable" rather than failing the whole projection. consoleExecutions ExecutionReader // Optional: execution list + detail lookup @@ -216,28 +217,29 @@ type AdminAPI struct { } type Deps struct { - Log *slog.Logger - Config ConfigProvider - SessionMgr SessionManagerProvider - TurnStats TurnStatsProvider - Hub HubProvider - Bridge BridgeProvider - ConfigWatcher ConfigWatcherProvider - Cron CronSchedulerProvider - BotLister BotListerProvider - BotConfig BotConfigProvider - WorkspaceStore session.UserWorkspaceStore // Optional: enables /admin/workspaces console (issue #807) - LogCollector LogCollector - Version func() string - NewSessionID func() string - Restart func() error - RestartPrepare func(context.Context) (func() error, func() error, error) - AllowedOriginsFn func() []string // Optional: returns allowed CORS origins; defaults to ["*"] when nil - DB DBExecutor // Optional: enables API key user CRUD + DB resolver - DBResolver cacheInvalidator // Optional: invalidates DBResolver cache after CUD - WriteMu *sqlutil.WriteMu // Optional: serializes SQLite writes; nil-safe, PG-safe - APIKeyStore APIKeyUserStorer // Optional: pre-built store (e.g. PG); overrides DB-based creation - KeyValidator KeyValidator // Optional: syncs DB keys into auth layer for Phase 1 validation + Log *slog.Logger + Config ConfigProvider + SessionMgr SessionManagerProvider + TurnStats TurnStatsProvider + Hub HubProvider + Bridge BridgeProvider + ConfigWatcher ConfigWatcherProvider + Cron CronSchedulerProvider + BotLister BotListerProvider + BotConfig BotConfigProvider + WorkspaceStore session.UserWorkspaceStore // Optional: enables /admin/workspaces console (issue #807) + LogCollector LogCollector + Version func() string + NewSessionID func() string + Restart func() error + RestartPrepare func(context.Context) (func() error, func() error, error) + AllowedOriginsFn func() []string // Optional: returns allowed CORS origins; defaults to ["*"] when nil + DB DBExecutor // Optional: enables API key user CRUD + DB resolver + DBResolver cacheInvalidator // Optional: invalidates DBResolver cache after CUD + WriteMu *sqlutil.WriteMu // Optional: serializes SQLite writes; nil-safe, PG-safe + LifecycleMigration LifecycleMigrationProvider + APIKeyStore APIKeyUserStorer // Optional: pre-built store (e.g. PG); overrides DB-based creation + KeyValidator KeyValidator // Optional: syncs DB keys into auth layer for Phase 1 validation } func New(deps Deps) *AdminAPI { @@ -246,19 +248,20 @@ func New(deps Deps) *AdminAPI { lc = LogRing } a := &AdminAPI{ - log: deps.Log, - cfg: deps.Config, - sm: deps.SessionMgr, - turnStore: deps.TurnStats, - hub: deps.Hub, - bridge: deps.Bridge, - configWatcher: deps.ConfigWatcher, - cron: deps.Cron, - botLister: deps.BotLister, - botConfig: deps.BotConfig, - wsStore: deps.WorkspaceStore, - logCollector: lc, - keyValidator: deps.KeyValidator, + log: deps.Log, + cfg: deps.Config, + sm: deps.SessionMgr, + turnStore: deps.TurnStats, + hub: deps.Hub, + bridge: deps.Bridge, + configWatcher: deps.ConfigWatcher, + cron: deps.Cron, + botLister: deps.BotLister, + botConfig: deps.BotConfig, + wsStore: deps.WorkspaceStore, + logCollector: lc, + keyValidator: deps.KeyValidator, + lifecycleMigration: deps.LifecycleMigration, akStore: func() APIKeyUserStorer { if deps.APIKeyStore != nil { return deps.APIKeyStore diff --git a/internal/admin/audit_service.go b/internal/admin/audit_service.go index 838f0c1e2..75434337d 100644 --- a/internal/admin/audit_service.go +++ b/internal/admin/audit_service.go @@ -186,7 +186,7 @@ func encodeCSV(rows []audit.UserActivity) ([]byte, error) { var buf bytes.Buffer w := csv.NewWriter(&buf) if err := w.Write([]string{ - "id", "ts", "user_id", "user_id_type", "platform", "session_id", + "chain_epoch", "id", "ts", "user_id", "user_id_type", "platform", "session_id", "action", "resource_type", "resource_id", "outcome", "ip", "user_agent", "self_hash", }); err != nil { @@ -194,6 +194,7 @@ func encodeCSV(rows []audit.UserActivity) ([]byte, error) { } for _, r := range rows { if err := w.Write([]string{ + sanitizeCSVCell(r.ChainEpoch), fmt.Sprintf("%d", r.ID), time.UnixMilli(r.Ts).UTC().Format(time.RFC3339Nano), sanitizeCSVCell(r.UserID), diff --git a/internal/admin/audit_service_test.go b/internal/admin/audit_service_test.go index 8e4b420ac..217584e67 100644 --- a/internal/admin/audit_service_test.go +++ b/internal/admin/audit_service_test.go @@ -260,7 +260,7 @@ func TestActivityService_Export_CSV(t *testing.T) { store := &mockAuditStore{ queryFn: func(ctx context.Context, q audit.Query) ([]audit.UserActivity, error) { return []audit.UserActivity{ - {ID: 1, Ts: 1700000000000, UserID: "u1", Action: "auth.login", Outcome: "success"}, + {ID: 1, Ts: 1700000000000, UserID: "u1", Action: "auth.login", Outcome: "success", ChainEpoch: "legacy"}, }, nil }, } @@ -274,8 +274,10 @@ func TestActivityService_Export_CSV(t *testing.T) { records, err := r.ReadAll() require.NoError(t, err) require.GreaterOrEqual(t, len(records), 2) // header + 1 row - require.Equal(t, "id", records[0][0]) - require.Equal(t, "1", records[1][0]) + require.Equal(t, "chain_epoch", records[0][0]) + require.Equal(t, "legacy", records[1][0]) + require.Equal(t, "id", records[0][1]) + require.Equal(t, "1", records[1][1]) } func TestActivityService_Export_DefaultFormat(t *testing.T) { @@ -396,12 +398,13 @@ func TestEncodeCSV(t *testing.T) { records, err := r.ReadAll() require.NoError(t, err) require.Len(t, records, 3) // header + 2 rows - require.Equal(t, "id", records[0][0]) - require.Equal(t, "user_id", records[0][2]) - require.Equal(t, "1", records[1][0]) - require.Equal(t, "u1", records[1][2]) - require.Equal(t, "2", records[2][0]) - require.Equal(t, "u2", records[2][2]) + require.Equal(t, "chain_epoch", records[0][0]) + require.Equal(t, "id", records[0][1]) + require.Equal(t, "user_id", records[0][3]) + require.Equal(t, "1", records[1][1]) + require.Equal(t, "u1", records[1][3]) + require.Equal(t, "2", records[2][1]) + require.Equal(t, "u2", records[2][3]) } // TestEncodeCSV_FormulaInjectionSanitized verifies that attacker-controlled @@ -426,9 +429,9 @@ func TestEncodeCSV_FormulaInjectionSanitized(t *testing.T) { records, err := r.ReadAll() require.NoError(t, err) require.Len(t, records, 2) // header + 1 row - // user_id column index 2; user_agent column index 11 (see encodeCSV header). - require.Equal(t, "'=cmd|'/c calc'!A0", records[1][2], "user_id must be prefixed to neutralize formula") - require.Equal(t, "'@SUM(1+1)*cmd|'/c calc'!A0", records[1][11], "user_agent must be prefixed") + // chain_epoch adds one leading column; user_id is index 3 and user_agent index 12. + require.Equal(t, "'=cmd|'/c calc'!A0", records[1][3], "user_id must be prefixed to neutralize formula") + require.Equal(t, "'@SUM(1+1)*cmd|'/c calc'!A0", records[1][12], "user_agent must be prefixed") } // TestSanitizeCSVCell verifies the cell sanitizer covers all OWASP-listed diff --git a/internal/admin/lifecycle_migration.go b/internal/admin/lifecycle_migration.go new file mode 100644 index 000000000..812aa0917 --- /dev/null +++ b/internal/admin/lifecycle_migration.go @@ -0,0 +1,139 @@ +package admin + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "strings" + "time" + + "github.com/hrygo/hotplex/internal/web" +) + +var ( + ErrLifecycleMigrationConfirmationRequired = errors.New("admin: lifecycle migration confirmation required") + ErrLifecycleMigrationPlanExpired = errors.New("admin: lifecycle migration preview expired") + ErrLifecycleMigrationPlanStale = errors.New("admin: lifecycle migration preview is stale") +) + +// LifecycleMigrationProvider keeps the admin package independent of the +// database-backed lifecycle implementation. +type LifecycleMigrationProvider interface { + Preview(context.Context) (*LifecycleMigrationPreview, error) + Apply(context.Context, string, string) (*LifecycleMigrationApplyResult, error) +} + +type LifecycleMigrationSummary struct { + Count int64 `json:"count"` + Bytes int64 `json:"bytes"` + OldestDeadline *time.Time `json:"oldest_deadline,omitempty"` +} + +type LifecycleMigrationBlocked struct { + LegacySessionsMissingInputClock int64 `json:"legacy_sessions_missing_input_clock"` + OrphanedEvents int64 `json:"orphaned_events"` + OrphanedTurns int64 `json:"orphaned_turns"` + ContentOnSessionsMissingClock int64 `json:"content_on_sessions_missing_input_clock"` + UnknownExecutionRecords int64 `json:"unknown_execution_records"` + ActiveExecutionRecords int64 `json:"active_execution_records"` +} + +type LifecycleMigrationPreview struct { + PlanID string `json:"plan_id"` + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_at"` + PolicyRevision string `json:"policy_revision"` + BatchSize int `json:"batch_size"` + Sessions LifecycleMigrationSummary `json:"sessions"` + Events LifecycleMigrationSummary `json:"events"` + Turns LifecycleMigrationSummary `json:"turns"` + Blocked LifecycleMigrationBlocked `json:"blocked"` +} + +type LifecycleMigrationApplyResult struct { + Sessions int64 `json:"sessions"` + Events int64 `json:"events"` + Turns int64 `json:"turns"` +} + +type lifecycleMigrationApplyRequest struct { + PlanID string `json:"plan_id"` + ConfirmPlanID string `json:"confirm_plan_id"` +} + +// SetLifecycleMigration injects the explicitly invoked legacy retention +// migration surface. Nil disables both endpoints with a 503 response. +func (a *AdminAPI) SetLifecycleMigration(provider LifecycleMigrationProvider) { + a.lifecycleMigration = provider +} + +// HandleLifecycleMigrationPreview creates a read-only aggregate preview. The +// resulting short-lived plan can be applied only through the separate write +// endpoint with its exact plan ID repeated as confirmation. +func (a *AdminAPI) HandleLifecycleMigrationPreview(w http.ResponseWriter, r *http.Request) { + if !requireScope(w, r, ScopeAdminRead) { + return + } + if a.lifecycleMigration == nil { + web.WriteAppError(w, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "lifecycle migration is not available") + return + } + preview, err := a.lifecycleMigration.Preview(r.Context()) + if err != nil { + a.logLifecycleMigrationError("preview", err) + web.WriteAppError(w, http.StatusInternalServerError, "INTERNAL", "failed to preview lifecycle migration") + return + } + respondJSON(w, preview) +} + +// HandleLifecycleMigrationApply applies one bounded batch from a previously +// previewed snapshot after explicit plan-ID confirmation. +func (a *AdminAPI) HandleLifecycleMigrationApply(w http.ResponseWriter, r *http.Request) { + if !requireScope(w, r, ScopeAdminWrite) { + return + } + if a.lifecycleMigration == nil { + web.WriteAppError(w, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "lifecycle migration is not available") + return + } + var request lifecycleMigrationApplyRequest + decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<10)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&request); err != nil { + web.WriteAppError(w, http.StatusBadRequest, "INVALID_REQUEST", "invalid lifecycle migration request") + return + } + if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + web.WriteAppError(w, http.StatusBadRequest, "INVALID_REQUEST", "request must contain a single JSON object") + return + } + request.PlanID = strings.TrimSpace(request.PlanID) + request.ConfirmPlanID = strings.TrimSpace(request.ConfirmPlanID) + if request.PlanID == "" || request.PlanID != request.ConfirmPlanID { + web.WriteAppError(w, http.StatusBadRequest, "CONFIRMATION_REQUIRED", "plan_id and confirm_plan_id must match") + return + } + result, err := a.lifecycleMigration.Apply(r.Context(), request.PlanID, request.ConfirmPlanID) + switch { + case errors.Is(err, ErrLifecycleMigrationConfirmationRequired): + web.WriteAppError(w, http.StatusBadRequest, "CONFIRMATION_REQUIRED", "plan_id and confirm_plan_id must match") + case errors.Is(err, ErrLifecycleMigrationPlanExpired): + web.WriteAppError(w, http.StatusConflict, "PREVIEW_EXPIRED", "lifecycle migration preview expired; create a new preview") + case errors.Is(err, ErrLifecycleMigrationPlanStale): + web.WriteAppError(w, http.StatusConflict, "PREVIEW_STALE", "lifecycle data or policy changed; create a new preview") + case err != nil: + a.logLifecycleMigrationError("apply", err) + web.WriteAppError(w, http.StatusInternalServerError, "INTERNAL", "failed to apply lifecycle migration") + default: + respondJSON(w, result) + } +} + +func (a *AdminAPI) logLifecycleMigrationError(operation string, err error) { + if a.log != nil { + a.log.Error("admin: lifecycle migration "+operation, "err", err) + } +} diff --git a/internal/admin/lifecycle_migration_test.go b/internal/admin/lifecycle_migration_test.go new file mode 100644 index 000000000..dd479976d --- /dev/null +++ b/internal/admin/lifecycle_migration_test.go @@ -0,0 +1,135 @@ +package admin + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +type lifecycleMigrationTestProvider struct { + preview *LifecycleMigrationPreview + previewErr error + applyResult *LifecycleMigrationApplyResult + applyErr error + applyPlanID string + applyConfirm string + applyCallCount int +} + +func (p *lifecycleMigrationTestProvider) Preview(context.Context) (*LifecycleMigrationPreview, error) { + return p.preview, p.previewErr +} + +func (p *lifecycleMigrationTestProvider) Apply(_ context.Context, planID, confirmPlanID string) (*LifecycleMigrationApplyResult, error) { + p.applyCallCount++ + p.applyPlanID = planID + p.applyConfirm = confirmPlanID + return p.applyResult, p.applyErr +} + +func TestLifecycleMigrationPreviewRequiresAdminReadAndReturnsAggregatePlan(t *testing.T) { + t.Parallel() + + createdAt := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + provider := &lifecycleMigrationTestProvider{ + preview: &LifecycleMigrationPreview{ + PlanID: "plan-1", + CreatedAt: createdAt, + ExpiresAt: createdAt.Add(30 * time.Minute), + PolicyRevision: "v2-revision", + BatchSize: 100, + Sessions: LifecycleMigrationSummary{Count: 10}, + Events: LifecycleMigrationSummary{Count: 20, Bytes: 4096}, + }, + } + api := New(Deps{}) + api.SetLifecycleMigration(provider) + + denied := httptest.NewRecorder() + api.HandleLifecycleMigrationPreview(denied, httptest.NewRequest(http.MethodPost, "/admin/lifecycle/migration/preview", nil)) + require.Equal(t, http.StatusForbidden, denied.Code) + + request := withScope(httptest.NewRequest(http.MethodPost, "/admin/lifecycle/migration/preview", nil), ScopeAdminRead) + response := httptest.NewRecorder() + api.HandleLifecycleMigrationPreview(response, request) + require.Equal(t, http.StatusOK, response.Code) + require.Contains(t, response.Body.String(), `"plan_id":"plan-1"`) + require.Contains(t, response.Body.String(), `"bytes":4096`) +} + +func TestLifecycleMigrationApplyRequiresWriteAndExactConfirmation(t *testing.T) { + t.Parallel() + + provider := &lifecycleMigrationTestProvider{ + applyResult: &LifecycleMigrationApplyResult{Sessions: 1, Events: 2, Turns: 3}, + } + api := New(Deps{}) + api.SetLifecycleMigration(provider) + + readOnly := withScope(httptest.NewRequest(http.MethodPost, "/admin/lifecycle/migration/apply", + strings.NewReader(`{"plan_id":"plan-1","confirm_plan_id":"plan-1"}`)), ScopeAdminRead) + readOnlyResponse := httptest.NewRecorder() + api.HandleLifecycleMigrationApply(readOnlyResponse, readOnly) + require.Equal(t, http.StatusForbidden, readOnlyResponse.Code) + require.Zero(t, provider.applyCallCount) + + mismatch := withScope(httptest.NewRequest(http.MethodPost, "/admin/lifecycle/migration/apply", + strings.NewReader(`{"plan_id":"plan-1","confirm_plan_id":"other"}`)), ScopeAdminWrite) + mismatchResponse := httptest.NewRecorder() + api.HandleLifecycleMigrationApply(mismatchResponse, mismatch) + require.Equal(t, http.StatusBadRequest, mismatchResponse.Code) + require.Zero(t, provider.applyCallCount) + + confirmed := withScope(httptest.NewRequest(http.MethodPost, "/admin/lifecycle/migration/apply", + strings.NewReader(`{"plan_id":"plan-1","confirm_plan_id":"plan-1"}`)), ScopeAdminWrite) + confirmedResponse := httptest.NewRecorder() + api.HandleLifecycleMigrationApply(confirmedResponse, confirmed) + require.Equal(t, http.StatusOK, confirmedResponse.Code) + require.Equal(t, "plan-1", provider.applyPlanID) + require.Equal(t, "plan-1", provider.applyConfirm) + require.Contains(t, confirmedResponse.Body.String(), `"events":2`) +} + +func TestLifecycleMigrationApplyMapsExpiredAndRejectsMalformedRequests(t *testing.T) { + t.Parallel() + + provider := &lifecycleMigrationTestProvider{applyErr: ErrLifecycleMigrationPlanExpired} + api := New(Deps{}) + api.SetLifecycleMigration(provider) + + malformed := withScope(httptest.NewRequest(http.MethodPost, "/admin/lifecycle/migration/apply", + strings.NewReader(`{"plan_id":"plan-1","confirm_plan_id":"plan-1","unexpected":true}`)), ScopeAdminWrite) + malformedResponse := httptest.NewRecorder() + api.HandleLifecycleMigrationApply(malformedResponse, malformed) + require.Equal(t, http.StatusBadRequest, malformedResponse.Code) + require.Zero(t, provider.applyCallCount) + + expired := withScope(httptest.NewRequest(http.MethodPost, "/admin/lifecycle/migration/apply", + strings.NewReader(`{"plan_id":"plan-1","confirm_plan_id":"plan-1"}`)), ScopeAdminWrite) + expiredResponse := httptest.NewRecorder() + api.HandleLifecycleMigrationApply(expiredResponse, expired) + require.Equal(t, http.StatusConflict, expiredResponse.Code) + require.Contains(t, expiredResponse.Body.String(), "PREVIEW_EXPIRED") +} + +func TestLifecycleMigrationPreviewUnavailableAndFailuresAreSafe(t *testing.T) { + t.Parallel() + + request := withScope(httptest.NewRequest(http.MethodPost, "/admin/lifecycle/migration/preview", nil), ScopeAdminRead) + unavailable := httptest.NewRecorder() + New(Deps{}).HandleLifecycleMigrationPreview(unavailable, request) + require.Equal(t, http.StatusServiceUnavailable, unavailable.Code) + + api := New(Deps{}) + api.SetLifecycleMigration(&lifecycleMigrationTestProvider{previewErr: errors.New("database details")}) + failed := httptest.NewRecorder() + api.HandleLifecycleMigrationPreview(failed, request) + require.Equal(t, http.StatusInternalServerError, failed.Code) + require.NotContains(t, failed.Body.String(), "database details") +} diff --git a/internal/admin/models.go b/internal/admin/models.go index 236eb4864..bf52aae45 100644 --- a/internal/admin/models.go +++ b/internal/admin/models.go @@ -3,6 +3,8 @@ // in runtime serialization — handlers construct responses via respondJSON(map[string]any{...}). package admin +import "time" + // AppError is the schema-only mirror of web.AppError (internal/web). Defined // locally rather than imported so swaggo can resolve the type during OpenAPI // generation; the runtime envelope is produced by web.WriteAppError. @@ -66,6 +68,27 @@ type GatewaySessionListResponse struct { Platform string `json:"platform" example:"webchat"` } +// GatewaySessionCleanupStatusResponse is the schema for GET /api/sessions/{id}/cleanup. +type GatewaySessionCleanupStatusResponse struct { + JobID string `json:"job_id"` + SessionID string `json:"session_id"` + Status string `json:"status" example:"in_progress"` + RequestedAt time.Time `json:"requested_at"` + UpdatedAt time.Time `json:"updated_at"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + Items []GatewaySessionCleanupStatusItem `json:"items"` +} + +// GatewaySessionCleanupStatusItem is a content-free cleanup component status. +type GatewaySessionCleanupStatusItem struct { + Kind string `json:"kind" example:"conversation_content"` + Status string `json:"status" example:"retrying"` + Attempts int `json:"attempts" example:"2"` + NextAttemptAt *time.Time `json:"next_attempt_at,omitempty"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + ErrorCode string `json:"error_code,omitempty" example:"cleanup_failed"` +} + // GatewayCreateSessionResponse is returned by POST /api/sessions. // Aliased to CreateSessionResponse — same shape, separate name for Gateway API tag grouping. type GatewayCreateSessionResponse = CreateSessionResponse diff --git a/internal/admin/ratelimit_test.go b/internal/admin/ratelimit_test.go index dc3fbdf00..571f3d991 100644 --- a/internal/admin/ratelimit_test.go +++ b/internal/admin/ratelimit_test.go @@ -21,9 +21,12 @@ func TestRateLimiter_Refill(t *testing.T) { rl := NewRateLimiter(100, 1) // 100 req/s, burst 1 require.True(t, rl.Allow(), "initial token") + rl.UpdateRate(0, 1) require.False(t, rl.Allow(), "no tokens") - // Wait for refill (>10ms at 100/s = 1 token per 10ms) + // Re-enable refill after the empty-bucket assertion so scheduler delays + // cannot refill the token between the two immediate checks above. + rl.UpdateRate(100, 1) require.Eventually(t, rl.Allow, 500*time.Millisecond, 10*time.Millisecond, "refilled token") } diff --git a/internal/audit/collector.go b/internal/audit/collector.go index c0da71bab..7f3c04dfe 100644 --- a/internal/audit/collector.go +++ b/internal/audit/collector.go @@ -26,6 +26,7 @@ type CollectorConfig struct { BatchInterval time.Duration SinkTimeout time.Duration SpillBlockTimeout time.Duration + FactsRetention time.Duration } func (c *CollectorConfig) defaults() { @@ -44,6 +45,9 @@ func (c *CollectorConfig) defaults() { if c.SpillBlockTimeout <= 0 { c.SpillBlockTimeout = 5 * time.Second } + if c.FactsRetention <= 0 { + c.FactsRetention = 180 * 24 * time.Hour + } } // Collector is the zero-loss audit event writer. It batches events into @@ -60,6 +64,7 @@ type Collector struct { sinks []AlertSink log *slog.Logger cfg CollectorConfig + epoch string captureC chan *UserActivity closeC chan struct{} @@ -108,6 +113,10 @@ func NewCollector(store Store, spill *SpillFile, sinks []AlertSink, log *slog.Lo if log == nil { log = slog.Default() } + epoch := legacyChainProfile.epoch + if profile, ok := store.(interface{ ChainEpoch() string }); ok { + epoch = profile.ChainEpoch() + } if sinks == nil { sinks = []AlertSink{} } @@ -124,6 +133,7 @@ func NewCollector(store Store, spill *SpillFile, sinks []AlertSink, log *slog.Lo sinks: sinks, log: log, cfg: cfg, + epoch: epoch, captureC: make(chan *UserActivity, cfg.ChannelCap), closeC: make(chan struct{}), sinkWorkers: workers, @@ -170,6 +180,14 @@ func (c *Collector) Enqueue(ctx context.Context, ua *UserActivity) error { if ua == nil { return fmt.Errorf("audit: nil UserActivity") } + if c.epoch == lifecycleChainProfile.epoch { + now := time.Now() + if ua.Ts <= 0 { + ua.Ts = now.UnixMilli() + } + ua.ChainEpoch = c.epoch + ua.ExpiresAt = time.UnixMilli(ua.Ts).Add(c.cfg.FactsRetention).UnixMilli() + } select { case <-c.closeC: return ErrCollectorClosed diff --git a/internal/audit/combined_store.go b/internal/audit/combined_store.go new file mode 100644 index 000000000..f0faff16e --- /dev/null +++ b/internal/audit/combined_store.go @@ -0,0 +1,171 @@ +package audit + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/hrygo/hotplex/internal/dbutil" +) + +// NewCombinedStore returns a query-facing Store that reads the legacy and +// lifecycle-v2 chains together. New writes and maintenance operations go to +// lifecycle; callers that verify or prune the legacy chain must use its +// dedicated Store directly. +func NewCombinedStore(db *sql.DB, dialect dbutil.Dialect, legacy, lifecycle Store) (Store, error) { + if db == nil || legacy == nil || lifecycle == nil { + return nil, fmt.Errorf("audit: combined store requires database and both chain stores") + } + if dialect != legacy.Dialect() || dialect != lifecycle.Dialect() { + return nil, fmt.Errorf("audit: combined store dialect mismatch") + } + return &combinedStore{ + db: db, + dialect: dialect, + legacy: legacy, + lifecycle: lifecycle, + }, nil +} + +type combinedStore struct { + db *sql.DB + dialect dbutil.Dialect + legacy Store + lifecycle Store +} + +func (s *combinedStore) BeginTx(ctx context.Context) (Tx, error) { + return s.lifecycle.BeginTx(ctx) +} + +func (s *combinedStore) Query(ctx context.Context, q Query) ([]UserActivity, error) { + limit := q.Limit + if limit <= 0 { + limit = 100 + } + if limit > 1000 { + limit = 1000 + } + offset := q.Offset + if offset < 0 { + offset = 0 + } + + where, args := buildActivityWhere(q) + statement := fmt.Sprintf( + "SELECT id, ts, user_id, user_id_type, platform, session_id, action, "+ + "resource_type, resource_id, outcome, detail_json, event_ref, "+ + "ip, user_agent, prev_hash, self_hash, ? AS chain_epoch, 0 AS expires_at FROM %s%s "+ + "UNION ALL "+ + "SELECT id, ts, user_id, user_id_type, platform, session_id, action, "+ + "resource_type, resource_id, outcome, detail_json, event_ref, "+ + "ip, user_agent, prev_hash, self_hash, ? AS chain_epoch, expires_at FROM %s%s "+ + "ORDER BY ts DESC, chain_epoch ASC, id DESC LIMIT ? OFFSET ?", + legacyChainProfile.activityTable, where, + lifecycleChainProfile.activityTable, where, + ) + queryArgs := make([]any, 0, len(args)*2+4) + queryArgs = append(queryArgs, legacyChainProfile.epoch) + queryArgs = append(queryArgs, args...) + queryArgs = append(queryArgs, lifecycleChainProfile.epoch) + queryArgs = append(queryArgs, args...) + queryArgs = append(queryArgs, limit, offset) + + rows, err := s.db.QueryContext(ctx, s.dialect.Rebind(statement), queryArgs...) + if err != nil { + return nil, fmt.Errorf("audit: combined query: %w", err) + } + defer func() { _ = rows.Close() }() + + var results []UserActivity + for rows.Next() { + var ua UserActivity + var sessionID, resourceType, resourceID, eventRef, ip, userAgent sql.NullString + if err := rows.Scan( + &ua.ID, &ua.Ts, &ua.UserID, &ua.UserIDType, &ua.Platform, + &sessionID, &ua.Action, &resourceType, &resourceID, + &ua.Outcome, &ua.DetailJSON, &eventRef, &ip, &userAgent, + &ua.PrevHash, &ua.SelfHash, &ua.ChainEpoch, &ua.ExpiresAt, + ); err != nil { + return nil, fmt.Errorf("audit: combined scan: %w", err) + } + ua.SessionID = sessionID.String + ua.ResourceType = resourceType.String + ua.ResourceID = resourceID.String + ua.EventRef = eventRef.String + ua.IP = ip.String + ua.UserAgent = userAgent.String + results = append(results, ua) + } + return results, rows.Err() +} + +func (s *combinedStore) Stats(ctx context.Context, q Query) (ActivityStats, error) { + legacyStats, err := s.legacy.Stats(ctx, q) + if err != nil { + return ActivityStats{}, err + } + lifecycleStats, err := s.lifecycle.Stats(ctx, q) + if err != nil { + return ActivityStats{}, err + } + out := ActivityStats{ + Total: legacyStats.Total + lifecycleStats.Total, + ByOutcome: make(map[string]int64, len(legacyStats.ByOutcome)+len(lifecycleStats.ByOutcome)), + ByPlatform: make(map[string]int64, len(legacyStats.ByPlatform)+len(lifecycleStats.ByPlatform)), + } + for key, value := range legacyStats.ByOutcome { + out.ByOutcome[key] += value + } + for key, value := range lifecycleStats.ByOutcome { + out.ByOutcome[key] += value + } + for key, value := range legacyStats.ByPlatform { + out.ByPlatform[key] += value + } + for key, value := range lifecycleStats.ByPlatform { + out.ByPlatform[key] += value + } + return out, nil +} + +// QueryAsc streams one chain only and is intended for the lifecycle-v2 +// verifier when this wrapper is used outside Gateway wiring. +func (s *combinedStore) QueryAsc(ctx context.Context, fromID int64, limit int) ([]UserActivity, error) { + return s.lifecycle.QueryAsc(ctx, fromID, limit) +} + +func (s *combinedStore) DeleteBefore(ctx context.Context, cutoff time.Time) (int64, error) { + return s.lifecycle.DeleteBefore(ctx, cutoff) +} + +func (s *combinedStore) SaveCheckpoint(ctx context.Context, checkpoint Checkpoint) error { + return s.lifecycle.SaveCheckpoint(ctx, checkpoint) +} + +func (s *combinedStore) LatestCheckpoint(ctx context.Context) (*Checkpoint, error) { + return s.lifecycle.LatestCheckpoint(ctx) +} + +func (s *combinedStore) ListIdentityLinks(ctx context.Context, principalUserID string) ([]IdentityLink, error) { + return s.legacy.ListIdentityLinks(ctx, principalUserID) +} + +func (s *combinedStore) UpsertIdentityLink(ctx context.Context, link IdentityLink) error { + return s.legacy.UpsertIdentityLink(ctx, link) +} + +func (s *combinedStore) DeleteIdentityLink(ctx context.Context, id string) error { + return s.legacy.DeleteIdentityLink(ctx, id) +} + +func (s *combinedStore) Close() error { return s.db.Close() } + +func (s *combinedStore) Dialect() dbutil.Dialect { return s.dialect } + +func (s *combinedStore) ChainEpoch() string { return lifecycleChainProfile.epoch } + +func (s *combinedStore) UsesPersistedExpiry() bool { return true } + +var _ Store = (*combinedStore)(nil) diff --git a/internal/audit/gc.go b/internal/audit/gc.go index f1564f85c..6cb51b59d 100644 --- a/internal/audit/gc.go +++ b/internal/audit/gc.go @@ -8,6 +8,8 @@ import ( "log/slog" "sync" "time" + + "github.com/hrygo/hotplex/internal/observability" ) // GCConfig holds GC tunables. @@ -101,7 +103,14 @@ func (g *GC) Tick(ctx context.Context) (int64, error) { g.mu.Lock() retention := g.cfg.Retention g.mu.Unlock() - cutoff := time.Now().Add(-retention) + cutoff := time.Now() + usesPersistedExpiry := false + if expiryStore, ok := g.store.(interface{ UsesPersistedExpiry() bool }); ok { + usesPersistedExpiry = expiryStore.UsesPersistedExpiry() + } + if !usesPersistedExpiry { + cutoff = cutoff.Add(-retention) + } tx, err := g.store.BeginTx(ctx) if err != nil { @@ -132,5 +141,10 @@ func (g *GC) Tick(ctx context.Context) (int64, error) { "checkpoint_next_id", cp.NextID, "last_self_hash", cp.LastSelfHash, ) + kind := observability.LifecycleGCProcessedAuditFactLegacy + if usesPersistedExpiry { + kind = observability.LifecycleGCProcessedAuditFactV2 + } + observability.RecordLifecycleGCProcessed(ctx, kind, deleted) return deleted, nil } diff --git a/internal/audit/hash.go b/internal/audit/hash.go index acc268ef5..0b3d6cf5f 100644 --- a/internal/audit/hash.go +++ b/internal/audit/hash.go @@ -23,6 +23,7 @@ type hashableFields struct { EventRef string `json:"event_ref"` IP string `json:"ip"` UserAgent string `json:"user_agent"` + ExpiresAt int64 `json:"expires_at,omitempty"` } // ComputeSelfHash returns sha256(prevHash || canonical(ua fields except prev/self_hash)). @@ -31,6 +32,10 @@ func ComputeSelfHash(prevHash string, ua *UserActivity) (string, error) { if ua == nil { return "", fmt.Errorf("audit: nil UserActivity") } + expiresAt := int64(0) + if ua.ChainEpoch == lifecycleChainProfile.epoch { + expiresAt = ua.ExpiresAt + } payload, err := json.Marshal(hashableFields{ Ts: ua.Ts, UserID: ua.UserID, @@ -45,6 +50,7 @@ func ComputeSelfHash(prevHash string, ua *UserActivity) (string, error) { EventRef: ua.EventRef, IP: ua.IP, UserAgent: ua.UserAgent, + ExpiresAt: expiresAt, }) if err != nil { return "", fmt.Errorf("audit: hash marshal: %w", err) diff --git a/internal/audit/hash_test.go b/internal/audit/hash_test.go index 31022e09e..89f5f218b 100644 --- a/internal/audit/hash_test.go +++ b/internal/audit/hash_test.go @@ -22,6 +22,33 @@ func TestComputeSelfHash_GenesisHasStableValue(t *testing.T) { require.Equal(t, h, h2, "hash must be deterministic") } +func TestComputeSelfHashIncludesOnlyLifecycleV2Expiry(t *testing.T) { + t.Parallel() + + legacy := &UserActivity{Ts: 1, UserID: "u", Action: ActionAuthLogin, Outcome: OutcomeSuccess} + legacyHash, err := ComputeSelfHash("", legacy) + require.NoError(t, err) + legacy.ExpiresAt = 1234 + legacyHashWithExpiry, err := ComputeSelfHash("", legacy) + require.NoError(t, err) + require.Equal(t, legacyHash, legacyHashWithExpiry, "legacy hash payload must remain byte-compatible") + + lifecycle := &UserActivity{ + Ts: 1, + UserID: "u", + Action: ActionAuthLogin, + Outcome: OutcomeSuccess, + ChainEpoch: lifecycleChainProfile.epoch, + ExpiresAt: 1234, + } + lifecycleHash, err := ComputeSelfHash("", lifecycle) + require.NoError(t, err) + lifecycle.ExpiresAt++ + lifecycleHashWithChangedExpiry, err := ComputeSelfHash("", lifecycle) + require.NoError(t, err) + require.NotEqual(t, lifecycleHash, lifecycleHashWithChangedExpiry, "lifecycle expiry must be covered by the hash chain") +} + func TestComputeSelfHash_ChangesWithPrev(t *testing.T) { t.Parallel() ua := &UserActivity{Ts: 1700000000000, UserID: "u1", Action: ActionAuthLogin, Outcome: OutcomeSuccess} diff --git a/internal/audit/lifecycle_store_pg_test.go b/internal/audit/lifecycle_store_pg_test.go new file mode 100644 index 000000000..15f1a03f5 --- /dev/null +++ b/internal/audit/lifecycle_store_pg_test.go @@ -0,0 +1,74 @@ +package audit + +import ( + "context" + "log/slog" + "regexp" + "testing" + "time" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/require" + + "github.com/hrygo/hotplex/internal/dbutil" +) + +func TestLifecyclePGStoreUsesSeparateChainTablesAndLock(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New() + require.NoError(t, err) + t.Cleanup(func() { _ = db.Close() }) + + store, err := NewLifecycleStore(db, dbutil.DialectPostgres, nil, slog.Default()) + require.NoError(t, err) + + ctx := context.Background() + mock.ExpectBegin() + mock.ExpectExec(regexp.QuoteMeta("SELECT pg_advisory_xact_lock($1)")). + WithArgs(lifecycleAuditAdvisoryLockKey). + WillReturnResult(sqlmock.NewResult(0, 1)) + tx, err := store.BeginTx(ctx) + require.NoError(t, err) + + mock.ExpectQuery(regexp.QuoteMeta("SELECT self_hash FROM user_activity_v2 ORDER BY id DESC LIMIT 1")). + WillReturnRows(sqlmock.NewRows([]string{"self_hash"})) + tail, err := tx.TailHash(ctx) + require.NoError(t, err) + require.Empty(t, tail) + + mock.ExpectQuery(regexp.QuoteMeta( + "SELECT id, self_hash FROM user_activity_v2 WHERE id < COALESCE((SELECT MIN(id) FROM user_activity_v2 WHERE expires_at >= $1), (SELECT MAX(id)+1 FROM user_activity_v2)) ORDER BY id DESC LIMIT 1", + )).WithArgs(int64(2000)).WillReturnRows(sqlmock.NewRows([]string{"id", "self_hash"})) + id, hash, err := tx.LastRowBefore(ctx, time.UnixMilli(2000)) + require.NoError(t, err) + require.Zero(t, id) + require.Empty(t, hash) + + ua := &UserActivity{ + Ts: 1000, + UserID: "user-v2", + UserIDType: UserIDTypeSystem, + Platform: PlatformAPI, + Action: ActionAuthLogin, + Outcome: OutcomeSuccess, + DetailJSON: `{}`, + ChainEpoch: lifecycleChainProfile.epoch, + ExpiresAt: 2000, + } + ua.SelfHash, err = ComputeSelfHash("", ua) + require.NoError(t, err) + + mock.ExpectExec(regexp.QuoteMeta( + "INSERT INTO user_activity_v2 (ts, user_id, user_id_type, platform, session_id, action, resource_type, resource_id, outcome, detail_json, event_ref, ip, user_agent, prev_hash, self_hash, expires_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)", + )).WithArgs( + ua.Ts, ua.UserID, ua.UserIDType, ua.Platform, ua.SessionID, + ua.Action, ua.ResourceType, ua.ResourceID, ua.Outcome, ua.DetailJSON, ua.EventRef, + ua.IP, ua.UserAgent, ua.PrevHash, ua.SelfHash, ua.ExpiresAt, + ).WillReturnResult(sqlmock.NewResult(1, 1)) + require.NoError(t, tx.Append(ctx, ua)) + mock.ExpectCommit() + require.NoError(t, tx.Commit()) + + require.NoError(t, mock.ExpectationsWereMet()) +} diff --git a/internal/audit/lifecycle_store_test.go b/internal/audit/lifecycle_store_test.go new file mode 100644 index 000000000..2bb1ccde8 --- /dev/null +++ b/internal/audit/lifecycle_store_test.go @@ -0,0 +1,194 @@ +package audit + +import ( + "context" + "log/slog" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/hrygo/hotplex/internal/dbutil" +) + +func TestLifecycleStoreHasIndependentChainAndRetention(t *testing.T) { + t.Parallel() + + legacy, lifecycle := newTestSQLiteLifecycleStores(t) + ctx := context.Background() + oldTs := time.Now().Add(-48 * time.Hour).UnixMilli() + appendTestActivity(t, legacy, oldTs, "legacy") + appendTestActivity(t, lifecycle, oldTs, "current") + + gc := NewGC(lifecycle, GCConfig{Retention: 24 * time.Hour, Interval: time.Hour}, slog.Default()) + deleted, err := gc.Tick(ctx) + require.NoError(t, err) + require.Equal(t, int64(1), deleted) + + legacyRows, err := legacy.Query(ctx, Query{}) + require.NoError(t, err) + require.Len(t, legacyRows, 1) + require.Equal(t, "legacy", legacyRows[0].UserID) + require.Equal(t, "legacy", legacyRows[0].ChainEpoch) + + currentRows, err := lifecycle.Query(ctx, Query{}) + require.NoError(t, err) + require.Empty(t, currentRows) + + legacyCheckpoint, err := legacy.LatestCheckpoint(ctx) + require.NoError(t, err) + require.Nil(t, legacyCheckpoint, "v2 cleanup must not checkpoint or rewrite the legacy chain") + + result, err := NewVerifier(legacy, VerifierConfig{}, slog.Default()).VerifyOnce(ctx) + require.NoError(t, err) + require.Equal(t, int64(0), result.BrokenID) +} + +func TestLifecycleGCStopsAtFirstUnexpiredRow(t *testing.T) { + t.Parallel() + + _, lifecycle := newTestSQLiteLifecycleStores(t) + now := time.Now().UnixMilli() + baseTs := time.Now().Add(-48 * time.Hour).UnixMilli() + appendTestActivityWithExpiry(t, lifecycle, baseTs, now-2*time.Minute.Milliseconds(), "expired-prefix") + appendTestActivityWithExpiry(t, lifecycle, baseTs+1, now+time.Hour.Milliseconds(), "not-yet-expired") + appendTestActivityWithExpiry(t, lifecycle, baseTs+2, now-time.Minute.Milliseconds(), "expired-after-gap") + + gc := NewGC(lifecycle, GCConfig{Retention: 24 * time.Hour, Interval: time.Hour}, slog.Default()) + deleted, err := gc.Tick(context.Background()) + require.NoError(t, err) + require.Equal(t, int64(1), deleted, "only the contiguous expired prefix may be pruned") + + rows, err := lifecycle.QueryAsc(context.Background(), 1, 10) + require.NoError(t, err) + require.Len(t, rows, 2) + require.Equal(t, "not-yet-expired", rows[0].UserID) + require.Equal(t, "expired-after-gap", rows[1].UserID) + + checkpoint, err := lifecycle.LatestCheckpoint(context.Background()) + require.NoError(t, err) + require.NotNil(t, checkpoint) + require.Equal(t, int64(2), checkpoint.NextID) + result, err := NewVerifier(lifecycle, VerifierConfig{}, slog.Default()).VerifyOnce(context.Background()) + require.NoError(t, err) + require.Equal(t, int64(0), result.BrokenID, "prefix cleanup must preserve the surviving chain") +} + +func TestCombinedStoreQueriesBothAuditEpochs(t *testing.T) { + t.Parallel() + + legacy, lifecycle := newTestSQLiteLifecycleStores(t) + appendTestActivity(t, legacy, time.Now().UnixMilli(), "legacy") + appendTestActivity(t, lifecycle, time.Now().Add(time.Millisecond).UnixMilli(), "current") + + combined, err := NewCombinedStore(legacy.db, dbutil.DialectSQLite, legacy, lifecycle) + require.NoError(t, err) + + rows, err := combined.Query(context.Background(), Query{Limit: 10}) + require.NoError(t, err) + require.Len(t, rows, 2) + require.Equal(t, "current", rows[0].UserID) + require.Equal(t, "lifecycle-v2", rows[0].ChainEpoch) + require.Equal(t, "legacy", rows[1].UserID) + require.Equal(t, "legacy", rows[1].ChainEpoch) + + stats, err := combined.Stats(context.Background(), Query{}) + require.NoError(t, err) + require.Equal(t, int64(2), stats.Total) + require.Equal(t, int64(2), stats.ByOutcome[OutcomeSuccess]) +} + +func TestLifecycleCollectorFreezesRetentionDeadlineAtEnqueue(t *testing.T) { + t.Parallel() + + _, lifecycle := newTestSQLiteLifecycleStores(t) + retention := 48 * time.Hour + collector := NewCollector(lifecycle, nil, nil, slog.Default(), CollectorConfig{ + FactsRetention: retention, + }) + + ts := time.Now().UnixMilli() + ua := &UserActivity{Ts: ts, UserID: "user-v2", Action: ActionAuthLogin, Outcome: OutcomeSuccess} + require.NoError(t, collector.Enqueue(context.Background(), ua)) + require.Equal(t, lifecycleChainProfile.epoch, ua.ChainEpoch) + require.Equal(t, time.UnixMilli(ts).Add(retention).UnixMilli(), ua.ExpiresAt) +} + +func newTestSQLiteLifecycleStores(t *testing.T) (*sqliteStore, Store) { + t.Helper() + + legacy := newTestSQLiteStore(t).(*sqliteStore) + _, err := legacy.db.Exec(` +CREATE TABLE user_activity_v2 ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ts INTEGER NOT NULL, + user_id TEXT NOT NULL, + user_id_type TEXT NOT NULL, + platform TEXT NOT NULL, + session_id TEXT, + action TEXT NOT NULL, + resource_type TEXT, + resource_id TEXT, + outcome TEXT NOT NULL, + detail_json TEXT NOT NULL, + event_ref TEXT, + ip TEXT, + user_agent TEXT, + prev_hash TEXT NOT NULL, + self_hash TEXT NOT NULL, + expires_at INTEGER NOT NULL +); +CREATE TABLE audit_chain_checkpoints_v2 ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pruned_at INTEGER NOT NULL, + last_self_hash TEXT NOT NULL, + next_id INTEGER NOT NULL +);`) + require.NoError(t, err) + + lifecycle, err := NewLifecycleStore(legacy.db, dbutil.DialectSQLite, legacy.writeMu, slog.Default()) + require.NoError(t, err) + return legacy, lifecycle +} + +func appendTestActivity(t *testing.T, store Store, ts int64, userID string) { + t.Helper() + + expiry := int64(0) + if provider, ok := store.(interface{ UsesPersistedExpiry() bool }); ok && provider.UsesPersistedExpiry() { + expiry = time.UnixMilli(ts).Add(24 * time.Hour).UnixMilli() + } + appendTestActivityWithExpiry(t, store, ts, expiry, userID) +} + +func appendTestActivityWithExpiry(t *testing.T, store Store, ts, expiry int64, userID string) { + t.Helper() + + ctx := context.Background() + epoch := legacyChainProfile.epoch + if provider, ok := store.(interface{ ChainEpoch() string }); ok { + epoch = provider.ChainEpoch() + } + tx, err := store.BeginTx(ctx) + require.NoError(t, err) + defer func() { _ = tx.Rollback() }() + + tail, err := tx.TailHash(ctx) + require.NoError(t, err) + ua := &UserActivity{ + Ts: ts, + UserID: userID, + UserIDType: UserIDTypeSystem, + Platform: PlatformAPI, + Action: ActionAuthLogin, + Outcome: OutcomeSuccess, + DetailJSON: `{}`, + ChainEpoch: epoch, + ExpiresAt: expiry, + } + ua.PrevHash = tail + ua.SelfHash, err = ComputeSelfHash(tail, ua) + require.NoError(t, err) + require.NoError(t, tx.Append(ctx, ua)) + require.NoError(t, tx.Commit()) +} diff --git a/internal/audit/store.go b/internal/audit/store.go index 6c94f9074..7fcfe6382 100644 --- a/internal/audit/store.go +++ b/internal/audit/store.go @@ -25,6 +25,37 @@ var ( // Picked to avoid collision with other hotplex advisory locks (sentinel: "AUD17"). const AuditAdvisoryLockKey int64 = 819207 +const lifecycleAuditAdvisoryLockKey int64 = AuditAdvisoryLockKey + 1 + +type chainProfile struct { + activityTable string + checkpointTable string + expiryColumn string + expirySelect string + epoch string + advisoryLockKey int64 + expiryDrivenGC bool +} + +var ( + legacyChainProfile = chainProfile{ + activityTable: "user_activity", + checkpointTable: "audit_chain_checkpoints", + expirySelect: "0", + epoch: "legacy", + advisoryLockKey: AuditAdvisoryLockKey, + } + lifecycleChainProfile = chainProfile{ + activityTable: "user_activity_v2", + checkpointTable: "audit_chain_checkpoints_v2", + expiryColumn: "expires_at", + expirySelect: "expires_at", + epoch: "lifecycle-v2", + advisoryLockKey: lifecycleAuditAdvisoryLockKey, + expiryDrivenGC: true, + } +) + // Query holds the by-user activity search parameters. type Query struct { UserID string @@ -110,30 +141,65 @@ type Tx interface { func NewStore(db *sql.DB, dialect dbutil.Dialect, writeMu *sqlutil.WriteMu, log *slog.Logger) (Store, error) { switch dialect { case dbutil.DialectSQLite: - return newSQLiteStore(db, writeMu, log), nil + return newSQLiteStoreWithProfile(db, writeMu, log, legacyChainProfile), nil + case dbutil.DialectPostgres: + return newPGStoreWithProfile(db, log, legacyChainProfile), nil + default: + return nil, fmt.Errorf("audit: unknown dialect %q", dialect) + } +} + +// NewLifecycleStore returns the independent audit chain used by lifecycle-v2 +// writes. The legacy chain remains readable and keeps its configured retention. +func NewLifecycleStore(db *sql.DB, dialect dbutil.Dialect, writeMu *sqlutil.WriteMu, log *slog.Logger) (Store, error) { + switch dialect { + case dbutil.DialectSQLite: + return newSQLiteStoreWithProfile(db, writeMu, log, lifecycleChainProfile), nil case dbutil.DialectPostgres: - return newPGStore(db, log), nil + return newPGStoreWithProfile(db, log, lifecycleChainProfile), nil default: return nil, fmt.Errorf("audit: unknown dialect %q", dialect) } } +func activityInsertStatement(dialect dbutil.Dialect, profile chainProfile) string { + columns := "ts, user_id, user_id_type, platform, session_id, action, resource_type, resource_id, outcome, detail_json, event_ref, ip, user_agent, prev_hash, self_hash" + values := "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?" + if profile.expiryColumn != "" { + columns += ", " + profile.expiryColumn + values += ", ?" + } + return dialect.Rebind(fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", profile.activityTable, columns, values)) +} + +func activityInsertArgs(ua *UserActivity, profile chainProfile) []any { + args := []any{ + ua.Ts, ua.UserID, ua.UserIDType, ua.Platform, ua.SessionID, + ua.Action, ua.ResourceType, ua.ResourceID, ua.Outcome, ua.DetailJSON, ua.EventRef, + ua.IP, ua.UserAgent, ua.PrevHash, ua.SelfHash, + } + if profile.expiryColumn != "" { + args = append(args, ua.ExpiresAt) + } + return args +} + // queryAsc is the shared ascending-order reader backing both dialects' // QueryAsc. It returns rows with id >= fromID in ascending id order, at // most `limit` rows (limit<=0 → empty result). The columns scanned must // match the canonical 16-field user_activity projection used by Query. -func queryAsc(db *sql.DB, d dbutil.Dialect, ctx context.Context, fromID int64, limit int) ([]UserActivity, error) { +func queryAsc(db *sql.DB, d dbutil.Dialect, profile chainProfile, ctx context.Context, fromID int64, limit int) ([]UserActivity, error) { if limit <= 0 { return nil, nil } if limit > 1000 { limit = 1000 } - sqlStr := d.Rebind( - "SELECT id, ts, user_id, user_id_type, platform, session_id, action, " + - "resource_type, resource_id, outcome, detail_json, event_ref, " + - "ip, user_agent, prev_hash, self_hash FROM user_activity" + - " WHERE id >= ? ORDER BY id ASC LIMIT ?") + sqlStr := d.Rebind(fmt.Sprintf( + "SELECT id, ts, user_id, user_id_type, platform, session_id, action, "+ + "resource_type, resource_id, outcome, detail_json, event_ref, "+ + "ip, user_agent, prev_hash, self_hash, %s FROM %s WHERE id >= ? ORDER BY id ASC LIMIT ?", + profile.expirySelect, profile.activityTable)) rows, err := db.QueryContext(ctx, sqlStr, fromID, limit) if err != nil { return nil, fmt.Errorf("audit: query_asc: %w", err) @@ -148,7 +214,7 @@ func queryAsc(db *sql.DB, d dbutil.Dialect, ctx context.Context, fromID int64, l &ua.ID, &ua.Ts, &ua.UserID, &ua.UserIDType, &ua.Platform, &sessionID, &ua.Action, &resourceType, &resourceID, &ua.Outcome, &ua.DetailJSON, &eventRef, &ip, &userAgent, - &ua.PrevHash, &ua.SelfHash, + &ua.PrevHash, &ua.SelfHash, &ua.ExpiresAt, ); err != nil { return nil, fmt.Errorf("audit: query_asc scan: %w", err) } @@ -158,6 +224,7 @@ func queryAsc(db *sql.DB, d dbutil.Dialect, ctx context.Context, fromID int64, l ua.EventRef = eventRef.String ua.IP = ip.String ua.UserAgent = userAgent.String + ua.ChainEpoch = profile.epoch results = append(results, ua) } return results, rows.Err() @@ -172,14 +239,19 @@ type sqliteStore struct { writeMu *sqlutil.WriteMu log *slog.Logger d dbutil.Dialect + profile chainProfile } -func newSQLiteStore(db *sql.DB, writeMu *sqlutil.WriteMu, log *slog.Logger) *sqliteStore { - return &sqliteStore{db: db, writeMu: writeMu, log: log, d: dbutil.DialectSQLite} +func newSQLiteStoreWithProfile(db *sql.DB, writeMu *sqlutil.WriteMu, log *slog.Logger, profile chainProfile) *sqliteStore { + return &sqliteStore{db: db, writeMu: writeMu, log: log, d: dbutil.DialectSQLite, profile: profile} } func (s *sqliteStore) Dialect() dbutil.Dialect { return s.d } +func (s *sqliteStore) ChainEpoch() string { return s.profile.epoch } + +func (s *sqliteStore) UsesPersistedExpiry() bool { return s.profile.expiryDrivenGC } + // BeginTx starts a write transaction. The process-wide writeMu is held for // the entire transaction lifetime to serialize against concurrent GC writes // (SaveCheckpoint/DeleteBefore) and other stores sharing the SQLite file, @@ -215,11 +287,11 @@ func (s *sqliteStore) Query(ctx context.Context, q Query) ([]UserActivity, error offset = 0 } - sqlStr := s.d.Rebind( - "SELECT id, ts, user_id, user_id_type, platform, session_id, action, " + - "resource_type, resource_id, outcome, detail_json, event_ref, " + - "ip, user_agent, prev_hash, self_hash FROM user_activity" + - where + " ORDER BY id DESC LIMIT ? OFFSET ?") + sqlStr := s.d.Rebind(fmt.Sprintf( + "SELECT id, ts, user_id, user_id_type, platform, session_id, action, "+ + "resource_type, resource_id, outcome, detail_json, event_ref, "+ + "ip, user_agent, prev_hash, self_hash, %s FROM %s", + s.profile.expirySelect, s.profile.activityTable) + where + " ORDER BY id DESC LIMIT ? OFFSET ?") args = append(args, limit, offset) rows, err := s.db.QueryContext(ctx, sqlStr, args...) @@ -236,7 +308,7 @@ func (s *sqliteStore) Query(ctx context.Context, q Query) ([]UserActivity, error &ua.ID, &ua.Ts, &ua.UserID, &ua.UserIDType, &ua.Platform, &sessionID, &ua.Action, &resourceType, &resourceID, &ua.Outcome, &ua.DetailJSON, &eventRef, &ip, &userAgent, - &ua.PrevHash, &ua.SelfHash, + &ua.PrevHash, &ua.SelfHash, &ua.ExpiresAt, ); err != nil { return nil, fmt.Errorf("audit: scan: %w", err) } @@ -246,6 +318,7 @@ func (s *sqliteStore) Query(ctx context.Context, q Query) ([]UserActivity, error ua.EventRef = eventRef.String ua.IP = ip.String ua.UserAgent = userAgent.String + ua.ChainEpoch = s.profile.epoch results = append(results, ua) } return results, rows.Err() @@ -402,7 +475,7 @@ func (s *sqliteStore) DeleteIdentityLink(ctx context.Context, id string) error { } func (s *sqliteStore) QueryAsc(ctx context.Context, fromID int64, limit int) ([]UserActivity, error) { - return queryAsc(s.db, s.d, ctx, fromID, limit) + return queryAsc(s.db, s.d, s.profile, ctx, fromID, limit) } // DeleteBefore prunes rows with ts < cutoff, anchored by a checkpoint @@ -472,7 +545,7 @@ func deleteBeforeTx(ctx context.Context, tx Tx, cutoff time.Time) (int64, Checkp func (s *sqliteStore) SaveCheckpoint(ctx context.Context, c Checkpoint) error { return s.writeMu.WithLock(func() error { _, err := s.db.ExecContext(ctx, - s.d.Rebind("INSERT INTO audit_chain_checkpoints (pruned_at, last_self_hash, next_id) VALUES (?, ?, ?)"), + s.d.Rebind(fmt.Sprintf("INSERT INTO %s (pruned_at, last_self_hash, next_id) VALUES (?, ?, ?)", s.profile.checkpointTable)), c.PrunedAt.UnixMilli(), c.LastSelfHash, c.NextID) if err != nil { return fmt.Errorf("audit: save checkpoint: %w", err) @@ -485,7 +558,7 @@ func (s *sqliteStore) LatestCheckpoint(ctx context.Context) (*Checkpoint, error) var c Checkpoint var prunedAtMs int64 err := s.db.QueryRowContext(ctx, - s.d.Rebind("SELECT id, pruned_at, last_self_hash, next_id FROM audit_chain_checkpoints ORDER BY id DESC LIMIT 1"), + s.d.Rebind(fmt.Sprintf("SELECT id, pruned_at, last_self_hash, next_id FROM %s ORDER BY id DESC LIMIT 1", s.profile.checkpointTable)), ).Scan(&c.ID, &prunedAtMs, &c.LastSelfHash, &c.NextID) if errors.Is(err, sql.ErrNoRows) { return nil, nil @@ -500,7 +573,7 @@ func (s *sqliteStore) LatestCheckpoint(ctx context.Context) (*Checkpoint, error) func (s *sqliteStore) Close() error { return s.db.Close() } func (s *sqliteStore) Stats(ctx context.Context, q Query) (ActivityStats, error) { - return activityStats(s.db, s.d, ctx, q) + return activityStats(s.db, s.d, s.profile.activityTable, ctx, q) } // activityStats computes the ActivityStats aggregates (total + per-outcome + @@ -510,7 +583,7 @@ func (s *sqliteStore) Stats(ctx context.Context, q Query) (ActivityStats, error) // keep each query simple and index-friendly; a third COUNT(*) provides total. // The filters in q are applied to every sub-query so the stats reflect the // exact same scoped set the timeline displays. -func activityStats(db queryExecContext, d dbutil.Dialect, ctx context.Context, q Query) (ActivityStats, error) { +func activityStats(db queryExecContext, d dbutil.Dialect, activityTable string, ctx context.Context, q Query) (ActivityStats, error) { where, args := buildActivityWhere(q) stats := ActivityStats{ ByOutcome: make(map[string]int64), @@ -518,7 +591,7 @@ func activityStats(db queryExecContext, d dbutil.Dialect, ctx context.Context, q } // Total count (scoped). - countSQL := d.Rebind("SELECT COUNT(*) FROM user_activity" + where) + countSQL := d.Rebind(fmt.Sprintf("SELECT COUNT(*) FROM %s", activityTable) + where) var total int64 if err := db.QueryRowContext(ctx, countSQL, args...).Scan(&total); err != nil { return stats, fmt.Errorf("audit: stats total: %w", err) @@ -526,7 +599,7 @@ func activityStats(db queryExecContext, d dbutil.Dialect, ctx context.Context, q stats.Total = total // Per-outcome breakdown. - outcomeSQL := d.Rebind("SELECT outcome, COUNT(*) FROM user_activity" + where + " GROUP BY outcome") + outcomeSQL := d.Rebind(fmt.Sprintf("SELECT outcome, COUNT(*) FROM %s", activityTable) + where + " GROUP BY outcome") orows, err := db.QueryContext(ctx, outcomeSQL, args...) if err != nil { return stats, fmt.Errorf("audit: stats by outcome: %w", err) @@ -536,7 +609,7 @@ func activityStats(db queryExecContext, d dbutil.Dialect, ctx context.Context, q } // Per-platform breakdown. - platformSQL := d.Rebind("SELECT platform, COUNT(*) FROM user_activity" + where + " GROUP BY platform") + platformSQL := d.Rebind(fmt.Sprintf("SELECT platform, COUNT(*) FROM %s", activityTable) + where + " GROUP BY platform") prows, err := db.QueryContext(ctx, platformSQL, args...) if err != nil { return stats, fmt.Errorf("audit: stats by platform: %w", err) @@ -591,14 +664,9 @@ func (t *sqliteTx) Append(ctx context.Context, ua *UserActivity) error { if t.done { return ErrStoreClosed } - _, err := t.tx.ExecContext(ctx, t.store.d.Rebind( - "INSERT INTO user_activity (ts, user_id, user_id_type, platform, session_id, "+ - "action, resource_type, resource_id, outcome, detail_json, event_ref, "+ - "ip, user_agent, prev_hash, self_hash) "+ - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), - ua.Ts, ua.UserID, ua.UserIDType, ua.Platform, ua.SessionID, - ua.Action, ua.ResourceType, ua.ResourceID, ua.Outcome, ua.DetailJSON, ua.EventRef, - ua.IP, ua.UserAgent, ua.PrevHash, ua.SelfHash, + _, err := t.tx.ExecContext(ctx, + activityInsertStatement(t.store.d, t.store.profile), + activityInsertArgs(ua, t.store.profile)..., ) if err != nil { return fmt.Errorf("audit: append: %w", err) @@ -623,7 +691,7 @@ func (t *sqliteTx) SaveCheckpoint(ctx context.Context, c Checkpoint) error { return ErrStoreClosed } _, err := t.tx.ExecContext(ctx, t.store.d.Rebind( - "INSERT INTO audit_chain_checkpoints (pruned_at, last_self_hash, next_id) VALUES (?, ?, ?)"), + fmt.Sprintf("INSERT INTO %s (pruned_at, last_self_hash, next_id) VALUES (?, ?, ?)", t.store.profile.checkpointTable)), c.PrunedAt.UnixMilli(), c.LastSelfHash, c.NextID) if err != nil { return fmt.Errorf("audit: tx save checkpoint: %w", err) @@ -637,7 +705,7 @@ func (t *sqliteTx) TailHash(ctx context.Context) (string, error) { } var h sql.NullString err := t.tx.QueryRowContext(ctx, - "SELECT self_hash FROM user_activity ORDER BY id DESC LIMIT 1", + fmt.Sprintf("SELECT self_hash FROM %s ORDER BY id DESC LIMIT 1", t.store.profile.activityTable), ).Scan(&h) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -657,10 +725,18 @@ func (t *sqliteTx) LastRowBefore(ctx context.Context, cutoff time.Time) (int64, } var id int64 var h sql.NullString - err := t.tx.QueryRowContext(ctx, - "SELECT id, self_hash FROM user_activity WHERE ts < ? ORDER BY id DESC LIMIT 1", - cutoff.UnixMilli(), - ).Scan(&id, &h) + var query string + if t.store.profile.expiryDrivenGC { + table := t.store.profile.activityTable + expiryColumn := t.store.profile.expiryColumn + query = fmt.Sprintf( + "SELECT id, self_hash FROM %s WHERE id < COALESCE((SELECT MIN(id) FROM %s WHERE %s >= ?), (SELECT MAX(id)+1 FROM %s)) ORDER BY id DESC LIMIT 1", + table, table, expiryColumn, table, + ) + } else { + query = fmt.Sprintf("SELECT id, self_hash FROM %s WHERE ts < ? ORDER BY id DESC LIMIT 1", t.store.profile.activityTable) + } + err := t.tx.QueryRowContext(ctx, query, cutoff.UnixMilli()).Scan(&id, &h) if errors.Is(err, sql.ErrNoRows) { return 0, "", nil } @@ -674,7 +750,7 @@ func (t *sqliteTx) DeleteByIDLEQ(ctx context.Context, maxID int64) (int64, error if t.done { return 0, ErrStoreClosed } - res, err := t.tx.ExecContext(ctx, "DELETE FROM user_activity WHERE id <= ?", maxID) + res, err := t.tx.ExecContext(ctx, fmt.Sprintf("DELETE FROM %s WHERE id <= ?", t.store.profile.activityTable), maxID) if err != nil { return 0, fmt.Errorf("audit: delete by id: %w", err) } @@ -687,7 +763,7 @@ func (t *sqliteTx) RowCount(ctx context.Context) (int64, error) { return 0, ErrStoreClosed } var n int64 - err := t.tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM user_activity").Scan(&n) + err := t.tx.QueryRowContext(ctx, fmt.Sprintf("SELECT COUNT(*) FROM %s", t.store.profile.activityTable)).Scan(&n) if err != nil { return 0, fmt.Errorf("audit: row count: %w", err) } @@ -717,28 +793,33 @@ func (t *sqliteTx) Rollback() error { // --------------------------------------------------------------------------- type pgStore struct { - db *sql.DB - log *slog.Logger - d dbutil.Dialect + db *sql.DB + log *slog.Logger + d dbutil.Dialect + profile chainProfile } -func newPGStore(db *sql.DB, log *slog.Logger) *pgStore { - return &pgStore{db: db, log: log, d: dbutil.DialectPostgres} +func newPGStoreWithProfile(db *sql.DB, log *slog.Logger, profile chainProfile) *pgStore { + return &pgStore{db: db, log: log, d: dbutil.DialectPostgres, profile: profile} } func (s *pgStore) Dialect() dbutil.Dialect { return s.d } +func (s *pgStore) ChainEpoch() string { return s.profile.epoch } + +func (s *pgStore) UsesPersistedExpiry() bool { return s.profile.expiryDrivenGC } + func (s *pgStore) BeginTx(ctx context.Context) (Tx, error) { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return nil, fmt.Errorf("audit: pg begin tx: %w", err) } // Acquire advisory lock to serialize chain tail writes. - if _, err := tx.ExecContext(ctx, "SELECT pg_advisory_xact_lock($1)", AuditAdvisoryLockKey); err != nil { + if _, err := tx.ExecContext(ctx, "SELECT pg_advisory_xact_lock($1)", s.profile.advisoryLockKey); err != nil { _ = tx.Rollback() return nil, fmt.Errorf("audit: pg advisory lock: %w", err) } - return &pgTx{tx: tx, d: s.d}, nil + return &pgTx{tx: tx, d: s.d, profile: s.profile}, nil } func (s *pgStore) Query(ctx context.Context, q Query) ([]UserActivity, error) { @@ -757,11 +838,11 @@ func (s *pgStore) Query(ctx context.Context, q Query) ([]UserActivity, error) { offset = 0 } - sqlStr := s.d.Rebind( - "SELECT id, ts, user_id, user_id_type, platform, session_id, action, " + - "resource_type, resource_id, outcome, detail_json, event_ref, " + - "ip, user_agent, prev_hash, self_hash FROM user_activity" + - where + " ORDER BY id DESC LIMIT ? OFFSET ?") + sqlStr := s.d.Rebind(fmt.Sprintf( + "SELECT id, ts, user_id, user_id_type, platform, session_id, action, "+ + "resource_type, resource_id, outcome, detail_json, event_ref, "+ + "ip, user_agent, prev_hash, self_hash, %s FROM %s", + s.profile.expirySelect, s.profile.activityTable) + where + " ORDER BY id DESC LIMIT ? OFFSET ?") args = append(args, limit, offset) rows, err := s.db.QueryContext(ctx, sqlStr, args...) @@ -778,7 +859,7 @@ func (s *pgStore) Query(ctx context.Context, q Query) ([]UserActivity, error) { &ua.ID, &ua.Ts, &ua.UserID, &ua.UserIDType, &ua.Platform, &sessionID, &ua.Action, &resourceType, &resourceID, &ua.Outcome, &ua.DetailJSON, &eventRef, &ip, &userAgent, - &ua.PrevHash, &ua.SelfHash, + &ua.PrevHash, &ua.SelfHash, &ua.ExpiresAt, ); err != nil { return nil, fmt.Errorf("audit: pg scan: %w", err) } @@ -788,13 +869,14 @@ func (s *pgStore) Query(ctx context.Context, q Query) ([]UserActivity, error) { ua.EventRef = eventRef.String ua.IP = ip.String ua.UserAgent = userAgent.String + ua.ChainEpoch = s.profile.epoch results = append(results, ua) } return results, rows.Err() } func (s *pgStore) QueryAsc(ctx context.Context, fromID int64, limit int) ([]UserActivity, error) { - return queryAsc(s.db, s.d, ctx, fromID, limit) + return queryAsc(s.db, s.d, s.profile, ctx, fromID, limit) } func (s *pgStore) DeleteBefore(ctx context.Context, cutoff time.Time) (int64, error) { @@ -815,7 +897,7 @@ func (s *pgStore) DeleteBefore(ctx context.Context, cutoff time.Time) (int64, er func (s *pgStore) SaveCheckpoint(ctx context.Context, c Checkpoint) error { _, err := s.db.ExecContext(ctx, - "INSERT INTO audit_chain_checkpoints (pruned_at, last_self_hash, next_id) VALUES ($1, $2, $3)", + fmt.Sprintf("INSERT INTO %s (pruned_at, last_self_hash, next_id) VALUES ($1, $2, $3)", s.profile.checkpointTable), c.PrunedAt.UnixMilli(), c.LastSelfHash, c.NextID) if err != nil { return fmt.Errorf("audit: pg save checkpoint: %w", err) @@ -827,7 +909,7 @@ func (s *pgStore) LatestCheckpoint(ctx context.Context) (*Checkpoint, error) { var c Checkpoint var prunedAtMs int64 err := s.db.QueryRowContext(ctx, - "SELECT id, pruned_at, last_self_hash, next_id FROM audit_chain_checkpoints ORDER BY id DESC LIMIT 1", + fmt.Sprintf("SELECT id, pruned_at, last_self_hash, next_id FROM %s ORDER BY id DESC LIMIT 1", s.profile.checkpointTable), ).Scan(&c.ID, &prunedAtMs, &c.LastSelfHash, &c.NextID) if errors.Is(err, sql.ErrNoRows) { return nil, nil @@ -875,28 +957,24 @@ func (s *pgStore) DeleteIdentityLink(ctx context.Context, id string) error { func (s *pgStore) Close() error { return s.db.Close() } func (s *pgStore) Stats(ctx context.Context, q Query) (ActivityStats, error) { - return activityStats(s.db, s.d, ctx, q) + return activityStats(s.db, s.d, s.profile.activityTable, ctx, q) } // pgTx implements Tx for PostgreSQL with advisory lock serialization. type pgTx struct { - tx *sql.Tx - d dbutil.Dialect - done bool + tx *sql.Tx + d dbutil.Dialect + profile chainProfile + done bool } func (t *pgTx) Append(ctx context.Context, ua *UserActivity) error { if t.done { return ErrStoreClosed } - _, err := t.tx.ExecContext(ctx, t.d.Rebind( - "INSERT INTO user_activity (ts, user_id, user_id_type, platform, session_id, "+ - "action, resource_type, resource_id, outcome, detail_json, event_ref, "+ - "ip, user_agent, prev_hash, self_hash) "+ - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), - ua.Ts, ua.UserID, ua.UserIDType, ua.Platform, ua.SessionID, - ua.Action, ua.ResourceType, ua.ResourceID, ua.Outcome, ua.DetailJSON, ua.EventRef, - ua.IP, ua.UserAgent, ua.PrevHash, ua.SelfHash, + _, err := t.tx.ExecContext(ctx, + activityInsertStatement(t.d, t.profile), + activityInsertArgs(ua, t.profile)..., ) if err != nil { return fmt.Errorf("audit: pg append: %w", err) @@ -910,7 +988,7 @@ func (t *pgTx) TailHash(ctx context.Context) (string, error) { } var h sql.NullString err := t.tx.QueryRowContext(ctx, - "SELECT self_hash FROM user_activity ORDER BY id DESC LIMIT 1", + fmt.Sprintf("SELECT self_hash FROM %s ORDER BY id DESC LIMIT 1", t.profile.activityTable), ).Scan(&h) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -941,7 +1019,7 @@ func (t *pgTx) SaveCheckpoint(ctx context.Context, c Checkpoint) error { return ErrStoreClosed } _, err := t.tx.ExecContext(ctx, - "INSERT INTO audit_chain_checkpoints (pruned_at, last_self_hash, next_id) VALUES ($1, $2, $3)", + fmt.Sprintf("INSERT INTO %s (pruned_at, last_self_hash, next_id) VALUES ($1, $2, $3)", t.profile.checkpointTable), c.PrunedAt.UnixMilli(), c.LastSelfHash, c.NextID) if err != nil { return fmt.Errorf("audit: pg tx save checkpoint: %w", err) @@ -955,10 +1033,18 @@ func (t *pgTx) LastRowBefore(ctx context.Context, cutoff time.Time) (int64, stri } var id int64 var h sql.NullString - err := t.tx.QueryRowContext(ctx, - "SELECT id, self_hash FROM user_activity WHERE ts < $1 ORDER BY id DESC LIMIT 1", - cutoff.UnixMilli(), - ).Scan(&id, &h) + var query string + if t.profile.expiryDrivenGC { + table := t.profile.activityTable + expiryColumn := t.profile.expiryColumn + query = fmt.Sprintf( + "SELECT id, self_hash FROM %s WHERE id < COALESCE((SELECT MIN(id) FROM %s WHERE %s >= $1), (SELECT MAX(id)+1 FROM %s)) ORDER BY id DESC LIMIT 1", + table, table, expiryColumn, table, + ) + } else { + query = fmt.Sprintf("SELECT id, self_hash FROM %s WHERE ts < $1 ORDER BY id DESC LIMIT 1", t.profile.activityTable) + } + err := t.tx.QueryRowContext(ctx, query, cutoff.UnixMilli()).Scan(&id, &h) if errors.Is(err, sql.ErrNoRows) { return 0, "", nil } @@ -972,7 +1058,7 @@ func (t *pgTx) DeleteByIDLEQ(ctx context.Context, maxID int64) (int64, error) { if t.done { return 0, ErrStoreClosed } - res, err := t.tx.ExecContext(ctx, "DELETE FROM user_activity WHERE id <= $1", maxID) + res, err := t.tx.ExecContext(ctx, fmt.Sprintf("DELETE FROM %s WHERE id <= $1", t.profile.activityTable), maxID) if err != nil { return 0, fmt.Errorf("audit: pg delete by id: %w", err) } @@ -985,7 +1071,7 @@ func (t *pgTx) RowCount(ctx context.Context) (int64, error) { return 0, ErrStoreClosed } var n int64 - err := t.tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM user_activity").Scan(&n) + err := t.tx.QueryRowContext(ctx, fmt.Sprintf("SELECT COUNT(*) FROM %s", t.profile.activityTable)).Scan(&n) if err != nil { return 0, fmt.Errorf("audit: pg row count: %w", err) } diff --git a/internal/audit/types.go b/internal/audit/types.go index 07c7cbea9..59ea70b13 100644 --- a/internal/audit/types.go +++ b/internal/audit/types.go @@ -60,22 +60,24 @@ const AnonymousUserID = "anonymous" // UserActivity is the row stored in the user_activity table. // Mirrors the 16-column schema in spec §5.1. type UserActivity struct { - ID int64 `json:"id"` // assigned by DB - Ts int64 `json:"ts"` // Unix ms - UserID string `json:"user_id"` // NOT NULL - UserIDType string `json:"user_id_type"` // NOT NULL - Platform string `json:"platform"` // NOT NULL - SessionID string `json:"session_id"` // empty for admin/api - Action string `json:"action"` // NOT NULL - ResourceType string `json:"resource_type"` // optional - ResourceID string `json:"resource_id"` // optional - Outcome string `json:"outcome"` // NOT NULL: success/failure/denied - DetailJSON string `json:"detail_json"` // NOT NULL: whitelisted fields per §5.9 - EventRef string `json:"event_ref"` // optional: events.id or turns.id - IP string `json:"ip"` // optional - UserAgent string `json:"user_agent"` // optional - PrevHash string `json:"prev_hash"` // NOT NULL: "" for genesis - SelfHash string `json:"self_hash"` // NOT NULL: sha256(PrevHash || canonical(rest)) + ID int64 `json:"id"` // assigned by DB + ChainEpoch string `json:"chain_epoch,omitempty"` // legacy or lifecycle-v2 chain + Ts int64 `json:"ts"` // Unix ms + ExpiresAt int64 `json:"expires_at,omitempty"` // persisted lifecycle-v2 retention deadline, Unix ms + UserID string `json:"user_id"` // NOT NULL + UserIDType string `json:"user_id_type"` // NOT NULL + Platform string `json:"platform"` // NOT NULL + SessionID string `json:"session_id"` // empty for admin/api + Action string `json:"action"` // NOT NULL + ResourceType string `json:"resource_type"` // optional + ResourceID string `json:"resource_id"` // optional + Outcome string `json:"outcome"` // NOT NULL: success/failure/denied + DetailJSON string `json:"detail_json"` // NOT NULL: whitelisted fields per §5.9 + EventRef string `json:"event_ref"` // optional: events.id or turns.id + IP string `json:"ip"` // optional + UserAgent string `json:"user_agent"` // optional + PrevHash string `json:"prev_hash"` // NOT NULL: "" for genesis + SelfHash string `json:"self_hash"` // NOT NULL: sha256(PrevHash || canonical(rest)) } // IdentityLink maps one immutable audit subject (provider + subject) to a diff --git a/internal/config/config.go b/internal/config/config.go index d8d40f567..79f9a2b9c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -83,6 +83,62 @@ func (c *Config) Validate() []string { if c.Session.RetentionPeriod <= 0 { errs = append(errs, "session.retention_period must be positive") } + if c.Worker.TurnTimeout < 0 { + errs = append(errs, "worker.turn_timeout must be non-negative") + } + switch c.Lifecycle.Policy { + case LifecyclePolicyLegacy, LifecyclePolicyV2: + default: + errs = append(errs, "lifecycle.policy must be one of legacy|v2") + } + if c.Lifecycle.Conversation.ArchiveAfter <= 0 { + errs = append(errs, "lifecycle.conversation.archive_after must be positive") + } + if c.Lifecycle.Conversation.RetentionAfterLastInput <= 0 { + errs = append(errs, "lifecycle.conversation.retention_after_last_input must be positive") + } + if c.Lifecycle.Conversation.ArchiveAfter > c.Lifecycle.Conversation.RetentionAfterLastInput { + errs = append(errs, "lifecycle.conversation.archive_after must not exceed retention_after_last_input") + } + if c.Lifecycle.Content.Retention <= 0 { + errs = append(errs, "lifecycle.content.retention must be positive") + } + if c.Lifecycle.EffectPayload.RetentionAfterSettlement <= 0 { + errs = append(errs, "lifecycle.effect_payload.retention_after_settlement must be positive") + } + if c.Lifecycle.Facts.RetentionAfterSettlement <= 0 { + errs = append(errs, "lifecycle.facts.retention_after_settlement must be positive") + } + if c.Lifecycle.Audit.FactsRetention <= 0 { + errs = append(errs, "lifecycle.audit.facts_retention must be positive") + } + if c.Lifecycle.Audit.ContentRetention <= 0 { + errs = append(errs, "lifecycle.audit.content_retention must be positive") + } + if c.Lifecycle.Audit.CaptureContent { + errs = append(errs, "lifecycle.audit.capture_content is unsupported until audit payloads have independent retention storage") + } + if c.Lifecycle.Audit.CaptureContent && c.Lifecycle.Audit.ContentRetention > c.Lifecycle.Content.Retention { + errs = append(errs, "lifecycle.audit.content_retention must not exceed lifecycle.content.retention when audit content capture is enabled") + } + if c.Lifecycle.Media.Retention <= 0 { + errs = append(errs, "lifecycle.media.retention must be positive") + } + if c.Lifecycle.Trace.Retention <= 0 { + errs = append(errs, "lifecycle.trace.retention must be positive") + } + if c.Lifecycle.GC.BatchSize <= 0 { + errs = append(errs, "lifecycle.gc.batch_size must be positive") + } + if c.Lifecycle.GC.Interval <= 0 { + errs = append(errs, "lifecycle.gc.interval must be positive") + } + if c.Lifecycle.GC.MaxLag <= 0 { + errs = append(errs, "lifecycle.gc.max_lag must be positive") + } + if c.Execution.Queue.InteractiveTTL <= 0 { + errs = append(errs, "execution.queue.interactive_ttl must be positive") + } if c.Pool.MaxSize <= 0 { errs = append(errs, "pool.max_size must be positive") } diff --git a/internal/config/config_defaults.go b/internal/config/config_defaults.go index e5760f701..a850c7b43 100644 --- a/internal/config/config_defaults.go +++ b/internal/config/config_defaults.go @@ -53,7 +53,7 @@ func Default() *Config { MaxLifetime: 24 * time.Hour, IdleTimeout: 60 * time.Minute, ExecutionTimeout: 30 * time.Minute, - TurnTimeout: 0, // disabled by default; execution_timeout catches zombies + TurnTimeout: 30 * time.Minute, EnvBlocklist: nil, DefaultWorkDir: filepath.Join(HotplexHome(), "workspace"), PIDDir: filepath.Join(HotplexHome(), ".pids"), @@ -105,6 +105,7 @@ func Default() *Config { PerSession: 20, Global: 1000, MaxPayloadBytes: 64 << 10, + InteractiveTTL: 15 * time.Minute, TTL: 24 * time.Hour, SweepInterval: time.Minute, SweepBatch: 100, @@ -229,6 +230,38 @@ func Default() *Config { {Name: "noop", Type: "noop", Config: map[string]any{}}, }, }, + Lifecycle: LifecycleConfig{ + Policy: "v2", + Conversation: LifecycleConversationConfig{ + ArchiveAfter: 7 * 24 * time.Hour, + RetentionAfterLastInput: 180 * 24 * time.Hour, + }, + Content: LifecycleContentConfig{ + Retention: 180 * 24 * time.Hour, + }, + EffectPayload: LifecycleEffectPayloadConfig{ + RetentionAfterSettlement: 7 * 24 * time.Hour, + }, + Facts: LifecycleFactsConfig{ + RetentionAfterSettlement: 90 * 24 * time.Hour, + }, + Audit: LifecycleAuditConfig{ + CaptureContent: false, + FactsRetention: 180 * 24 * time.Hour, + ContentRetention: 180 * 24 * time.Hour, + }, + Media: LifecycleMediaConfig{ + Retention: 24 * time.Hour, + }, + Trace: LifecycleTraceConfig{ + Retention: 48 * time.Hour, + }, + GC: LifecycleGCConfig{ + BatchSize: 100, + Interval: time.Minute, + MaxLag: time.Hour, + }, + }, } } diff --git a/internal/config/config_loader.go b/internal/config/config_loader.go index 68faf1bae..d9efa2b84 100644 --- a/internal/config/config_loader.go +++ b/internal/config/config_loader.go @@ -70,6 +70,7 @@ func Load(filePath string) (*Config, error) { _ = v.BindEnv("worker.max_lifetime") _ = v.BindEnv("worker.idle_timeout") _ = v.BindEnv("worker.execution_timeout") + _ = v.BindEnv("worker.turn_timeout") _ = v.BindEnv("worker.auto_retry.enabled") _ = v.BindEnv("worker.auto_retry.max_retries") _ = v.BindEnv("worker.claude_code.command") @@ -135,9 +136,24 @@ func Load(filePath string) (*Config, error) { _ = v.BindEnv("execution.queue.per_session") _ = v.BindEnv("execution.queue.global") _ = v.BindEnv("execution.queue.max_payload_bytes") + _ = v.BindEnv("execution.queue.interactive_ttl") _ = v.BindEnv("execution.queue.ttl") _ = v.BindEnv("execution.queue.sweep_interval") _ = v.BindEnv("execution.queue.sweep_batch") + _ = v.BindEnv("lifecycle.policy") + _ = v.BindEnv("lifecycle.conversation.archive_after") + _ = v.BindEnv("lifecycle.conversation.retention_after_last_input") + _ = v.BindEnv("lifecycle.content.retention") + _ = v.BindEnv("lifecycle.effect_payload.retention_after_settlement") + _ = v.BindEnv("lifecycle.facts.retention_after_settlement") + _ = v.BindEnv("lifecycle.audit.capture_content") + _ = v.BindEnv("lifecycle.audit.facts_retention") + _ = v.BindEnv("lifecycle.audit.content_retention") + _ = v.BindEnv("lifecycle.media.retention") + _ = v.BindEnv("lifecycle.trace.retention") + _ = v.BindEnv("lifecycle.gc.batch_size") + _ = v.BindEnv("lifecycle.gc.interval") + _ = v.BindEnv("lifecycle.gc.max_lag") _ = v.BindEnv("audit.enabled") _ = v.BindEnv("audit.retention") _ = v.BindEnv("audit.full_content_retention") @@ -146,6 +162,9 @@ func Load(filePath string) (*Config, error) { _ = v.BindEnv("audit.collector.batch_size") _ = v.BindEnv("audit.collector.spill_dir") + if err := rejectExplicitlyEmptyLifecycleEnv(); err != nil { + return nil, err + } if err := v.Unmarshal(cfg); err != nil { return nil, fmt.Errorf("config: environment override: %w", err) } @@ -157,6 +176,33 @@ func Load(filePath string) (*Config, error) { return cfg, nil } +func rejectExplicitlyEmptyLifecycleEnv() error { + paths := []string{ + "lifecycle.policy", + "lifecycle.conversation.archive_after", + "lifecycle.conversation.retention_after_last_input", + "lifecycle.content.retention", + "lifecycle.effect_payload.retention_after_settlement", + "lifecycle.facts.retention_after_settlement", + "lifecycle.audit.capture_content", + "lifecycle.audit.facts_retention", + "lifecycle.audit.content_retention", + "lifecycle.media.retention", + "lifecycle.trace.retention", + "lifecycle.gc.batch_size", + "lifecycle.gc.interval", + "lifecycle.gc.max_lag", + "execution.queue.interactive_ttl", + } + for _, path := range paths { + envKey := "HOTPLEX_" + strings.ToUpper(strings.ReplaceAll(path, ".", "_")) + if value, ok := os.LookupEnv(envKey); ok && strings.TrimSpace(value) == "" { + return fmt.Errorf("config: %s is explicitly empty", envKey) + } + } + return nil +} + // loadRecursive loads a config file and its ancestors, detecting cycles. // visited tracks file paths already loaded in the current chain; nil on the root call. func loadRecursive(filePath string, visited []string) (*Config, error) { diff --git a/internal/config/config_types.go b/internal/config/config_types.go index 3a6500b90..6fc8757fa 100644 --- a/internal/config/config_types.go +++ b/internal/config/config_types.go @@ -28,6 +28,7 @@ type Config struct { Events EventsConfig `mapstructure:"events"` Audit AuditConfig `mapstructure:"audit"` Execution ExecutionConfig `mapstructure:"execution"` + Lifecycle LifecycleConfig `mapstructure:"lifecycle"` Inherits string `mapstructure:"inherits"` // path to parent config file; "" = no inheritance // ResolvedAPIKeyUsers is the runtime map of expanded API key value → userID. @@ -57,6 +58,9 @@ type ExecutionQueueConfig struct { // MaxPayloadBytes caps one queued input's content, including a native // command invocation's serialized arguments. MaxPayloadBytes int `mapstructure:"max_payload_bytes"` + // InteractiveTTL is the lifetime of an accepted interactive input that + // cannot be dispatched immediately. + InteractiveTTL time.Duration `mapstructure:"interactive_ttl"` // TTL is how long an undispatched input stays dispatchable. TTL time.Duration `mapstructure:"ttl"` // SweepInterval is how often expired inputs are settled. Zero disables the @@ -67,6 +71,70 @@ type ExecutionQueueConfig struct { SweepBatch int `mapstructure:"sweep_batch"` } +// LifecycleConfig defines retention defaults for newly accepted lifecycle data. +// Existing rows keep the policy revision and deadlines assigned when written. +type LifecycleConfig struct { + Policy string `mapstructure:"policy"` + Conversation LifecycleConversationConfig `mapstructure:"conversation"` + Content LifecycleContentConfig `mapstructure:"content"` + EffectPayload LifecycleEffectPayloadConfig `mapstructure:"effect_payload"` + Facts LifecycleFactsConfig `mapstructure:"facts"` + Audit LifecycleAuditConfig `mapstructure:"audit"` + Media LifecycleMediaConfig `mapstructure:"media"` + Trace LifecycleTraceConfig `mapstructure:"trace"` + GC LifecycleGCConfig `mapstructure:"gc"` +} + +const ( + LifecyclePolicyLegacy = "legacy" + LifecyclePolicyV2 = "v2" +) + +// LifecycleConversationConfig defines archive and conversation expiry clocks. +type LifecycleConversationConfig struct { + ArchiveAfter time.Duration `mapstructure:"archive_after"` + RetentionAfterLastInput time.Duration `mapstructure:"retention_after_last_input"` +} + +// LifecycleContentConfig defines per-message body retention. +type LifecycleContentConfig struct { + Retention time.Duration `mapstructure:"retention"` +} + +// LifecycleEffectPayloadConfig defines retention for delivery payload copies. +type LifecycleEffectPayloadConfig struct { + RetentionAfterSettlement time.Duration `mapstructure:"retention_after_settlement"` +} + +// LifecycleFactsConfig defines retention for execution and delivery facts. +type LifecycleFactsConfig struct { + RetentionAfterSettlement time.Duration `mapstructure:"retention_after_settlement"` +} + +// LifecycleAuditConfig defines audit capture behavior and independent retention. +type LifecycleAuditConfig struct { + CaptureContent bool `mapstructure:"capture_content"` + FactsRetention time.Duration `mapstructure:"facts_retention"` + ContentRetention time.Duration `mapstructure:"content_retention"` +} + +// LifecycleMediaConfig defines retention for cached media copies. +type LifecycleMediaConfig struct { + Retention time.Duration `mapstructure:"retention"` +} + +// LifecycleTraceConfig defines retention for diagnostic traces. +type LifecycleTraceConfig struct { + Retention time.Duration `mapstructure:"retention"` +} + +// LifecycleGCConfig controls bounded lifecycle cleanup work. +type LifecycleGCConfig struct { + BatchSize int `mapstructure:"batch_size"` + Interval time.Duration `mapstructure:"interval"` + MaxLag time.Duration `mapstructure:"max_lag"` +} + // MessagingConfig holds messaging platform adapter settings. // Shared defaults (WorkerType, STTConfig, TTSConfig) are set at this level and propagated // to each platform config via propagateMessagingDefaults(). @@ -781,9 +849,9 @@ type SecurityConfig struct { CookieSameSite string `mapstructure:"cookie_same_site"` } -// SessionConfig holds session lifecycle settings. +// SessionConfig holds legacy session lifecycle settings. type SessionConfig struct { - RetentionPeriod time.Duration `mapstructure:"retention_period"` // max session lifetime (default 7d) + RetentionPeriod time.Duration `mapstructure:"retention_period"` // legacy session expiry (default 7d) GCScanInterval time.Duration `mapstructure:"gc_scan_interval"` // GC scan interval (default 1m) MaxConcurrent int `mapstructure:"max_concurrent"` // max concurrent sessions TermRetention time.Duration `mapstructure:"term_retention"` // DB retention for terminated sessions (default 7d) @@ -833,7 +901,7 @@ type CronConfig struct { // EventsConfig holds event and turn retention settings. type EventsConfig struct { - Retention time.Duration `mapstructure:"retention"` // TTL for events + turns, default 720h (30 days) + Retention time.Duration `mapstructure:"retention"` // Fallback TTL for legacy event/turn rows without per-record expiry, default 720h (30 days) } // AuditConfig holds user behavior audit system settings. @@ -845,10 +913,10 @@ type AuditConfig struct { Sinks []AuditSinkConfig `mapstructure:"sinks"` } -// EffectiveEventsRetention computes the events/turns TTL to use at startup. -// Audit keeps its plaintext independently, so full_content_retention never -// extends this short-lived operational copy. A zero events.retention falls back -// to the package default (720h). +// EffectiveEventsRetention computes the fallback TTL for legacy event/turn +// rows that predate persisted per-record expiry. New rows use +// lifecycle.content.retention. Audit keeps its plaintext independently, so +// full_content_retention does not change either event/turn deadline. func EffectiveEventsRetention(cfg *Config) time.Duration { if cfg == nil { return 720 * time.Hour @@ -860,6 +928,22 @@ func EffectiveEventsRetention(cfg *Config) time.Duration { return ev } +// EffectiveTurnTimeout returns the absolute turn limit. Lifecycle v2 preserves +// the historical zero-value configuration as the confirmed 30 minute default; +// legacy policy retains zero as an explicit disabled value. +func EffectiveTurnTimeout(cfg *Config) time.Duration { + if cfg == nil { + return 30 * time.Minute + } + if cfg.Worker.TurnTimeout > 0 { + return cfg.Worker.TurnTimeout + } + if cfg.Lifecycle.Policy == LifecyclePolicyV2 { + return 30 * time.Minute + } + return cfg.Worker.TurnTimeout +} + // AuditCollectorConfig holds audit event collector tuning parameters. type AuditCollectorConfig struct { ChannelCap int `mapstructure:"channel_cap"` diff --git a/internal/config/lifecycle_config_test.go b/internal/config/lifecycle_config_test.go new file mode 100644 index 000000000..82d130c13 --- /dev/null +++ b/internal/config/lifecycle_config_test.go @@ -0,0 +1,235 @@ +package config + +import ( + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestLifecycleConfigDefaults(t *testing.T) { + t.Parallel() + + cfg := Default() + + require.Equal(t, "v2", cfg.Lifecycle.Policy) + require.Equal(t, 7*24*time.Hour, cfg.Lifecycle.Conversation.ArchiveAfter) + require.Equal(t, 180*24*time.Hour, cfg.Lifecycle.Conversation.RetentionAfterLastInput) + require.Equal(t, 180*24*time.Hour, cfg.Lifecycle.Content.Retention) + require.Equal(t, 30*time.Minute, cfg.Worker.TurnTimeout) + require.Equal(t, 7*24*time.Hour, cfg.Lifecycle.EffectPayload.RetentionAfterSettlement) + require.Equal(t, 90*24*time.Hour, cfg.Lifecycle.Facts.RetentionAfterSettlement) + require.False(t, cfg.Lifecycle.Audit.CaptureContent) + require.Equal(t, 180*24*time.Hour, cfg.Lifecycle.Audit.FactsRetention) + require.Equal(t, 180*24*time.Hour, cfg.Lifecycle.Audit.ContentRetention) + require.Equal(t, 24*time.Hour, cfg.Lifecycle.Media.Retention) + require.Equal(t, 48*time.Hour, cfg.Lifecycle.Trace.Retention) + require.Equal(t, 100, cfg.Lifecycle.GC.BatchSize) + require.Equal(t, time.Minute, cfg.Lifecycle.GC.Interval) + require.Equal(t, time.Hour, cfg.Lifecycle.GC.MaxLag) + require.Equal(t, 15*time.Minute, cfg.Execution.Queue.InteractiveTTL) + + require.Empty(t, cfg.Validate()) +} + +func TestEffectiveTurnTimeout(t *testing.T) { + t.Parallel() + + cfg := Default() + require.Equal(t, 30*time.Minute, EffectiveTurnTimeout(cfg)) + + cfg.Worker.TurnTimeout = 42 * time.Minute + require.Equal(t, 42*time.Minute, EffectiveTurnTimeout(cfg)) + + cfg.Worker.TurnTimeout = 0 + cfg.Lifecycle.Policy = LifecyclePolicyV2 + require.Equal(t, 30*time.Minute, EffectiveTurnTimeout(cfg)) + + cfg.Lifecycle.Policy = LifecyclePolicyLegacy + require.Zero(t, EffectiveTurnTimeout(cfg)) +} + +func TestLifecycleConfigValidation(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + mutate func(*Config) + wantErr string + }{ + { + name: "unknown policy", + mutate: func(cfg *Config) { + cfg.Lifecycle.Policy = "future" + }, + wantErr: "lifecycle.policy", + }, + { + name: "zero archive duration", + mutate: func(cfg *Config) { + cfg.Lifecycle.Conversation.ArchiveAfter = 0 + }, + wantErr: "lifecycle.conversation.archive_after", + }, + { + name: "archive after session expiration", + mutate: func(cfg *Config) { + cfg.Lifecycle.Conversation.ArchiveAfter = 181 * 24 * time.Hour + }, + wantErr: "lifecycle.conversation.archive_after", + }, + { + name: "zero conversation retention", + mutate: func(cfg *Config) { + cfg.Lifecycle.Conversation.RetentionAfterLastInput = 0 + }, + wantErr: "lifecycle.conversation.retention_after_last_input", + }, + { + name: "zero message retention", + mutate: func(cfg *Config) { + cfg.Lifecycle.Content.Retention = 0 + }, + wantErr: "lifecycle.content.retention", + }, + { + name: "zero effect payload retention", + mutate: func(cfg *Config) { + cfg.Lifecycle.EffectPayload.RetentionAfterSettlement = 0 + }, + wantErr: "lifecycle.effect_payload.retention_after_settlement", + }, + { + name: "zero execution fact retention", + mutate: func(cfg *Config) { + cfg.Lifecycle.Facts.RetentionAfterSettlement = 0 + }, + wantErr: "lifecycle.facts.retention_after_settlement", + }, + { + name: "audit payload capture requires separate storage", + mutate: func(cfg *Config) { + cfg.Lifecycle.Audit.CaptureContent = true + }, + wantErr: "lifecycle.audit.capture_content is unsupported", + }, + { + name: "audit payload exceeds chat body retention", + mutate: func(cfg *Config) { + cfg.Lifecycle.Audit.CaptureContent = true + cfg.Lifecycle.Audit.ContentRetention = 181 * 24 * time.Hour + }, + wantErr: "lifecycle.audit.content_retention", + }, + { + name: "zero media retention", + mutate: func(cfg *Config) { + cfg.Lifecycle.Media.Retention = 0 + }, + wantErr: "lifecycle.media.retention", + }, + { + name: "zero trace retention", + mutate: func(cfg *Config) { + cfg.Lifecycle.Trace.Retention = 0 + }, + wantErr: "lifecycle.trace.retention", + }, + { + name: "zero gc batch", + mutate: func(cfg *Config) { + cfg.Lifecycle.GC.BatchSize = 0 + }, + wantErr: "lifecycle.gc.batch_size", + }, + { + name: "zero gc interval", + mutate: func(cfg *Config) { + cfg.Lifecycle.GC.Interval = 0 + }, + wantErr: "lifecycle.gc.interval", + }, + { + name: "zero gc lag target", + mutate: func(cfg *Config) { + cfg.Lifecycle.GC.MaxLag = 0 + }, + wantErr: "lifecycle.gc.max_lag", + }, + { + name: "zero interactive queue ttl", + mutate: func(cfg *Config) { + cfg.Execution.Queue.InteractiveTTL = 0 + }, + wantErr: "execution.queue.interactive_ttl", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + cfg := Default() + tt.mutate(cfg) + + errs := strings.Join(cfg.Validate(), "\n") + require.Contains(t, errs, tt.wantErr) + }) + } +} + +func TestLoad_LifecycleEnvironmentOverrides(t *testing.T) { + t.Setenv("HOTPLEX_LIFECYCLE_CONVERSATION_RETENTION_AFTER_LAST_INPUT", "8760h") + t.Setenv("HOTPLEX_LIFECYCLE_CONTENT_RETENTION", "8760h") + t.Setenv("HOTPLEX_EXECUTION_QUEUE_INTERACTIVE_TTL", "45m") + t.Setenv("HOTPLEX_WORKER_TURN_TIMEOUT", "42m") + + cfg, err := Load("") + require.NoError(t, err) + require.Equal(t, 365*24*time.Hour, cfg.Lifecycle.Conversation.RetentionAfterLastInput) + require.Equal(t, 365*24*time.Hour, cfg.Lifecycle.Content.Retention) + require.Equal(t, 45*time.Minute, cfg.Execution.Queue.InteractiveTTL) + require.Equal(t, 42*time.Minute, cfg.Worker.TurnTimeout) + require.Empty(t, cfg.Validate()) +} + +func TestLoad_LifecycleEmptyEnvironmentOverrideIsRejected(t *testing.T) { + t.Setenv("HOTPLEX_LIFECYCLE_CONVERSATION_RETENTION_AFTER_LAST_INPUT", "") + + _, err := Load("") + require.ErrorContains(t, err, "HOTPLEX_LIFECYCLE_CONVERSATION_RETENTION_AFTER_LAST_INPUT") + require.ErrorContains(t, err, "explicitly empty") +} + +func TestLoad_LifecycleYAMLOverrides(t *testing.T) { + t.Parallel() + + configPath := filepath.Join(t.TempDir(), "config.yaml") + configData := []byte("lifecycle:\n policy: legacy\n conversation:\n archive_after: 72h\n retention_after_last_input: 8760h\n content:\n retention: 8760h\nexecution:\n queue:\n interactive_ttl: 45m\n") + require.NoError(t, os.WriteFile(configPath, configData, 0o600)) + + cfg, err := Load(configPath) + require.NoError(t, err) + require.Equal(t, "legacy", cfg.Lifecycle.Policy) + require.Equal(t, 72*time.Hour, cfg.Lifecycle.Conversation.ArchiveAfter) + require.Equal(t, 365*24*time.Hour, cfg.Lifecycle.Conversation.RetentionAfterLastInput) + require.Equal(t, 365*24*time.Hour, cfg.Lifecycle.Content.Retention) + require.Equal(t, 45*time.Minute, cfg.Execution.Queue.InteractiveTTL) + require.Empty(t, cfg.Validate()) +} + +func TestLoad_RepositoryConfigHasLifecycleDefaults(t *testing.T) { + t.Parallel() + + cfg, err := loadRecursive(filepath.Join("..", "..", "configs", "config.yaml"), nil) + require.NoError(t, err) + require.Equal(t, LifecyclePolicyV2, cfg.Lifecycle.Policy) + require.Equal(t, 7*24*time.Hour, cfg.Lifecycle.Conversation.ArchiveAfter) + require.Equal(t, 180*24*time.Hour, cfg.Lifecycle.Conversation.RetentionAfterLastInput) + require.Equal(t, 180*24*time.Hour, cfg.Lifecycle.Content.Retention) + require.Equal(t, 15*time.Minute, cfg.Execution.Queue.InteractiveTTL) + require.Empty(t, cfg.Validate()) +} diff --git a/internal/cron/executor.go b/internal/cron/executor.go index 1ddc54e7a..0fbdf1d05 100644 --- a/internal/cron/executor.go +++ b/internal/cron/executor.go @@ -300,7 +300,15 @@ func (e *Executor) Execute( func (e *Executor) terminateSession(sessionKey string) { termCtx, cancel := context.WithTimeout(context.Background(), base.GracefulShutdownTimeout) defer cancel() - if termErr := e.sm.Transition(termCtx, sessionKey, events.StateTerminated); termErr != nil { + var termErr error + if reasoned, ok := e.sm.(interface { + TransitionWithReason(context.Context, string, events.SessionState, string) error + }); ok { + termErr = reasoned.TransitionWithReason(termCtx, sessionKey, events.StateTerminated, "cron_complete") + } else { + termErr = e.sm.Transition(termCtx, sessionKey, events.StateTerminated) + } + if termErr != nil { e.log.Warn("cron executor: failed to terminate session", "session_id", sessionKey, "err", termErr) } } diff --git a/internal/cron/executor_test.go b/internal/cron/executor_test.go index 195262091..4b0e6059d 100644 --- a/internal/cron/executor_test.go +++ b/internal/cron/executor_test.go @@ -33,6 +33,7 @@ type mockSessionStateChecker struct { workers map[string]worker.Worker defaultWorker worker.Worker defaultSession *session.SessionInfo + lastTermReason string } func (m *mockSessionStateChecker) Get(_ context.Context, id string) (*session.SessionInfo, error) { @@ -56,6 +57,11 @@ func (m *mockSessionStateChecker) Transition(_ context.Context, _ string, _ even return nil } +func (m *mockSessionStateChecker) TransitionWithReason(_ context.Context, _ string, _ events.SessionState, reason string) error { + m.lastTermReason = reason + return nil +} + // mockWorker implements worker.Worker for testing with minimal stubs. type mockWorker struct { inputErr error @@ -238,6 +244,17 @@ func TestExecutor_Execute_Success(t *testing.T) { require.NotEmpty(t, gotKey) } +func TestExecutor_TerminateSessionUsesCronCompletionReason(t *testing.T) { + t.Parallel() + + sm := &mockSessionStateChecker{} + e := newTestExecutor(&mockBridge{}, sm) + + e.terminateSession("cron-session") + + require.Equal(t, "cron_complete", sm.lastTermReason) +} + // TestExecutor_Execute_NormalizesEmptyPlatformToCron verifies that a pure // scheduled task (no messaging delivery target) records the "cron" platform on // the session it starts, so the session.create audit row is populated instead diff --git a/internal/effect/attempt_gc.go b/internal/effect/attempt_gc.go new file mode 100644 index 000000000..e3940576e --- /dev/null +++ b/internal/effect/attempt_gc.go @@ -0,0 +1,90 @@ +package effect + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/hrygo/hotplex/internal/dbutil" +) + +// AttemptGCStore exposes bounded cleanup of detailed send-attempt evidence. +// The effect row remains as the idempotency marker after its attempts expire. +type AttemptGCStore interface { + DeleteSettledAttempts(ctx context.Context, settledBefore time.Time, limit int) (int64, error) +} + +// Only finished attempts belonging to old, settled terminal effects are +// eligible. A missing settlement clock, live lease, nonterminal state, or +// unfinished attempt keeps the evidence intact. +const deleteSettledAttemptsSQL = `DELETE FROM effect_attempts + WHERE attempt_id IN ( + SELECT a.attempt_id + FROM effect_attempts a + JOIN effects e ON e.effect_id = a.effect_id + WHERE a.finished_at IS NOT NULL + AND e.status IN ('delivered', 'failed', 'reconciled_succeeded', + 'reconciled_failed', 'fenced') + AND e.settled_at IS NOT NULL + AND e.settled_at <= ? + AND e.lease_until IS NULL + AND e.owner_instance_id = '' + AND NOT EXISTS ( + SELECT 1 FROM effect_attempts pending + WHERE pending.effect_id = e.effect_id + AND pending.finished_at IS NULL + ) + ORDER BY a.finished_at, a.attempt_id + LIMIT ? + )` + +func deleteSettledAttempts( + ctx context.Context, tx *sql.Tx, dialect dbutil.Dialect, settledBefore time.Time, limit int, +) (int64, error) { + if limit <= 0 { + return 0, nil + } + res, err := tx.ExecContext(ctx, dialect.Rebind(deleteSettledAttemptsSQL), + settledBefore.UnixMilli(), limit) + if err != nil { + return 0, fmt.Errorf("effect: expire settled attempts: %w", err) + } + deleted, err := res.RowsAffected() + if err != nil { + return 0, fmt.Errorf("effect: expired attempt rows: %w", err) + } + return deleted, nil +} + +func (s *SQLiteStore) DeleteSettledAttempts( + ctx context.Context, settledBefore time.Time, limit int, +) (int64, error) { + if limit <= 0 { + return 0, nil + } + var deleted int64 + err := s.writeMu.WithLock(func() error { + return s.inTx(ctx, func(tx *sql.Tx) error { + var err error + deleted, err = deleteSettledAttempts(ctx, tx, dbutil.DialectSQLite, settledBefore, limit) + return err + }) + }) + return deleted, err +} + +func (s *PGStore) DeleteSettledAttempts( + ctx context.Context, settledBefore time.Time, limit int, +) (int64, error) { + if limit <= 0 { + return 0, nil + } + var deleted int64 + err := s.inTx(ctx, func(tx *sql.Tx) error { + var err error + deleted, err = deleteSettledAttempts(ctx, tx, s.db.Dialect(), settledBefore, limit) + return err + }) + return deleted, err +} diff --git a/internal/effect/attempt_gc_pg_test.go b/internal/effect/attempt_gc_pg_test.go new file mode 100644 index 000000000..d9560d627 --- /dev/null +++ b/internal/effect/attempt_gc_pg_test.go @@ -0,0 +1,37 @@ +package effect + +import ( + "context" + "regexp" + "testing" + "time" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/require" + + "github.com/hrygo/hotplex/internal/dbutil" +) + +func TestDeleteSettledAttemptsPostgresRebindsAndUsesTransaction(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New() + require.NoError(t, err) + t.Cleanup(func() { _ = db.Close() }) + + cutoff := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC) + mock.ExpectBegin() + tx, err := db.BeginTx(context.Background(), nil) + require.NoError(t, err) + mock.ExpectExec(regexp.QuoteMeta(dbutil.DialectPostgres.Rebind(deleteSettledAttemptsSQL))). + WithArgs(cutoff.UnixMilli(), 25). + WillReturnResult(sqlmock.NewResult(0, 3)) + + deleted, err := deleteSettledAttempts(context.Background(), tx, dbutil.DialectPostgres, cutoff, 25) + require.NoError(t, err) + require.EqualValues(t, 3, deleted) + + mock.ExpectCommit() + require.NoError(t, tx.Commit()) + require.NoError(t, mock.ExpectationsWereMet()) +} diff --git a/internal/effect/attempt_gc_test.go b/internal/effect/attempt_gc_test.go new file mode 100644 index 000000000..0bb07772f --- /dev/null +++ b/internal/effect/attempt_gc_test.go @@ -0,0 +1,95 @@ +package effect_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestDeleteSettledAttemptsKeepsUnresolvedEvidenceAndIdempotency(t *testing.T) { + t.Parallel() + + store, db := newStore(t) + ctx := context.Background() + base := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC) + + settledPlan := validPlan() + settledPlan.OccurrenceID = "occ-settled" + settledPlan.ExecutionID = "exec-settled" + settled, _, err := store.PlanOnce(ctx, settledPlan, base) + require.NoError(t, err) + settledAt := base.Add(2 * time.Hour) + claim, err := store.ClaimSend(ctx, claimFor(settled.EffectID, 0, base.Add(time.Hour))) + require.NoError(t, err) + require.NoError(t, store.CompleteSend(ctx, completeFor(claim, settledAt))) + + unknownPlan := validPlan() + unknownPlan.OccurrenceID = "occ-unknown" + unknownPlan.ExecutionID = "exec-unknown" + unknown, _, err := store.PlanOnce(ctx, unknownPlan, base) + require.NoError(t, err) + unknownClaimAt := base.Add(3 * time.Hour) + _, err = store.ClaimSend(ctx, claimFor(unknown.EffectID, 0, unknownClaimAt)) + require.NoError(t, err) + expired, err := store.ExpireLeases(ctx, unknownClaimAt.Add(time.Hour), 10) + require.NoError(t, err) + require.Len(t, expired, 1) + + activePlan := validPlan() + activePlan.OccurrenceID = "occ-active" + activePlan.ExecutionID = "exec-active" + active, _, err := store.PlanOnce(ctx, activePlan, base) + require.NoError(t, err) + _, err = store.ClaimSend(ctx, claimFor(active.EffectID, 0, base.Add(5*time.Hour))) + require.NoError(t, err) + + legacyPlan := validPlan() + legacyPlan.OccurrenceID = "occ-legacy" + legacyPlan.ExecutionID = "exec-legacy" + legacy, _, err := store.PlanOnce(ctx, legacyPlan, base) + require.NoError(t, err) + legacyClaim, err := store.ClaimSend(ctx, claimFor(legacy.EffectID, 0, base.Add(6*time.Hour))) + require.NoError(t, err) + require.NoError(t, store.CompleteSend(ctx, completeFor(legacyClaim, base.Add(7*time.Hour)))) + _, err = db.Exec(`UPDATE effects SET settled_at = NULL WHERE effect_id = ?`, legacy.EffectID) + require.NoError(t, err) + + openAttemptPlan := validPlan() + openAttemptPlan.OccurrenceID = "occ-open-attempt" + openAttemptPlan.ExecutionID = "exec-open-attempt" + openAttempt, _, err := store.PlanOnce(ctx, openAttemptPlan, base) + require.NoError(t, err) + _, err = store.ClaimSend(ctx, claimFor(openAttempt.EffectID, 0, base.Add(8*time.Hour))) + require.NoError(t, err) + // Guard against inconsistent or legacy terminal rows that still have an + // attempt open: the detailed evidence must not be partially discarded. + _, err = db.Exec(`UPDATE effects + SET status = 'delivered', settled_at = ?, owner_instance_id = '', lease_until = NULL + WHERE effect_id = ?`, settledAt.UnixMilli(), openAttempt.EffectID) + require.NoError(t, err) + + n, err := store.DeleteSettledAttempts(ctx, settledAt.Add(-time.Nanosecond), 10) + require.NoError(t, err) + require.Zero(t, n, "attempt evidence must remain until the full post-settlement retention elapses") + + cutoff := base.Add(400 * 24 * time.Hour) + n, err = store.DeleteSettledAttempts(ctx, cutoff, 10) + require.NoError(t, err) + require.EqualValues(t, 1, n) + + attempts, err := store.ListAttempts(ctx, settled.EffectID) + require.NoError(t, err) + require.Empty(t, attempts) + _, err = store.GetByKey(ctx, settledPlan.OccurrenceID, settledPlan.DeliveryOrdinal, settledPlan.TargetRevision) + require.NoError(t, err, "the effect identity remains to prevent duplicate delivery") + + for _, effectID := range []string{ + unknown.EffectID, active.EffectID, legacy.EffectID, openAttempt.EffectID, + } { + attempts, err := store.ListAttempts(ctx, effectID) + require.NoError(t, err) + require.Len(t, attempts, 1, "unresolved, active, or legacy evidence without a settlement clock is protected") + } +} diff --git a/internal/effect/effect.go b/internal/effect/effect.go index 0e5851371..4c47017ed 100644 --- a/internal/effect/effect.go +++ b/internal/effect/effect.go @@ -127,6 +127,10 @@ type Effect struct { CreatedAtMs int64 UpdatedAtMs int64 + // SettledAtMs is set when the effect reaches a terminal, non-retryable + // outcome. Unresolved and legacy rows keep it nil, which protects their + // recovery payload from time-based cleanup. + SettledAtMs *int64 } // BusinessKey is the stable identity of an effect. @@ -244,9 +248,6 @@ const insertEffectSQL = `INSERT INTO effects VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(occurrence_id, delivery_ordinal, target_revision) DO NOTHING` -const selectPayloadSHASQL = `SELECT content_sha256 FROM effect_payloads - WHERE occurrence_id = ? AND execution_id = ?` - // PlanWithPayload writes the content snapshot and the planned effect in the // CALLER's transaction. The caller commits. // @@ -300,6 +301,38 @@ func (p *Planner) PlanWithPayload(ctx context.Context, tx Tx, plan Plan, now tim if err != nil { return nil, false, fmt.Errorf("effect: insert payload: %w", err) } + payloadInserted, err := rowsAffected(payloadRes) + if err != nil { + return nil, false, err + } + if payloadInserted == 0 { + var recordedPayloadID, recordedSHA string + var contentBytes int64 + row := tx.QueryRowContext(ctx, p.dialect.Rebind( + `SELECT payload_id, content_sha256, content_bytes FROM effect_payloads + WHERE occurrence_id = ? AND execution_id = ?`), + plan.OccurrenceID, plan.ExecutionID) + switch err := row.Scan(&recordedPayloadID, &recordedSHA, &contentBytes); { + case errors.Is(err, sql.ErrNoRows): + return nil, false, fmt.Errorf("%w: payload %s/%s is absent", + ErrPayloadEffectMismatch, plan.OccurrenceID, plan.ExecutionID) + case err != nil: + return nil, false, fmt.Errorf("effect: verify existing payload: %w", err) + case contentBytes == 0: + // The payload identity row remains after its body expires. Reusing + // that row prevents a duplicate trigger from recreating old content. + return nil, false, ErrPayloadNotFound + case recordedSHA != contentSHA: + return nil, false, fmt.Errorf( + "%w: ledger holds a different payload for execution %s", + ErrPayloadEffectMismatch, plan.ExecutionID) + default: + // Multiple target revisions may share one execution snapshot. The + // new effect must reference the already committed payload row. + payload.PayloadID = recordedPayloadID + e.PayloadID = recordedPayloadID + } + } effectRes, err := tx.ExecContext(ctx, p.dialect.Rebind(insertEffectSQL), e.EffectID, e.OccurrenceID, e.DeliveryOrdinal, e.TargetRevision, e.Attempt, @@ -313,37 +346,11 @@ func (p *Planner) PlanWithPayload(ctx context.Context, tx Tx, plan Plan, now tim if err != nil { return nil, false, fmt.Errorf("effect: insert effect: %w", err) } - - payloadInserted, err := rowsAffected(payloadRes) - if err != nil { - return nil, false, err - } effectInserted, err := rowsAffected(effectRes) if err != nil { return nil, false, err } - // An effect can never be committed without its payload — both writes are in - // this one transaction. The remaining hazard is the opposite one: the - // payload may already exist from an earlier commit for the same execution - // while this plan carries different content. The ledger's copy wins, so - // verify it rather than assume. - if payloadInserted == 0 { - var recordedSHA string - row := tx.QueryRowContext(ctx, p.dialect.Rebind(selectPayloadSHASQL), - plan.OccurrenceID, plan.ExecutionID) - switch err := row.Scan(&recordedSHA); { - case errors.Is(err, sql.ErrNoRows): - return nil, false, fmt.Errorf("%w: payload %s/%s is absent", - ErrPayloadEffectMismatch, plan.OccurrenceID, plan.ExecutionID) - case err != nil: - return nil, false, fmt.Errorf("effect: verify existing payload: %w", err) - case recordedSHA != contentSHA: - return nil, false, fmt.Errorf( - "%w: ledger holds a different payload for execution %s", - ErrPayloadEffectMismatch, plan.ExecutionID) - } - } return e, effectInserted > 0, nil } diff --git a/internal/effect/effect_test.go b/internal/effect/effect_test.go index b94af0394..661619987 100644 --- a/internal/effect/effect_test.go +++ b/internal/effect/effect_test.go @@ -52,6 +52,7 @@ CREATE TABLE effects ( evidence_ref TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, + settled_at INTEGER, UNIQUE(occurrence_id, delivery_ordinal, target_revision) ); CREATE TABLE effect_attempts ( diff --git a/internal/effect/lease.go b/internal/effect/lease.go index faf88e012..5e1135555 100644 --- a/internal/effect/lease.go +++ b/internal/effect/lease.go @@ -279,6 +279,7 @@ const claimSendSQL = `UPDATE effects SET status = 'started', owner_instance_id = ?, lease_until = ?, + settled_at = NULL, lease_version = lease_version + 1, updated_at = ? WHERE effect_id = ? @@ -291,6 +292,7 @@ const claimRetrySQL = `UPDATE effects SET lease_until = ?, lease_version = lease_version + 1, next_attempt_at = NULL, + settled_at = NULL, updated_at = ? WHERE effect_id = ? AND status = 'started' @@ -418,6 +420,8 @@ const applyCompletionSQL = `UPDATE effects SET evidence_ref = ?, owner_instance_id = '', lease_until = NULL, + settled_at = CASE WHEN ? IN ('delivered', 'failed', 'reconciled_succeeded', + 'reconciled_failed', 'fenced') THEN ? ELSE NULL END, next_attempt_at = ?, updated_at = ? WHERE effect_id = ? @@ -457,7 +461,7 @@ func completeSend( res, err = tx.ExecContext(ctx, dialect.Rebind(applyCompletionSQL), status, c.ErrorCode, c.Reason, c.ProviderRef, c.EvidenceRef, - nextAttemptAt, c.Now.UnixMilli(), c.EffectID, c.Attempt) + status, c.Now.UnixMilli(), nextAttemptAt, c.Now.UnixMilli(), c.EffectID, c.Attempt) if err != nil { return fmt.Errorf("effect: apply completion: %w", err) } @@ -477,6 +481,7 @@ const expireEffectSQL = `UPDATE effects SET reason = 'send lease expired; the commit is unproven', owner_instance_id = '', lease_until = NULL, + settled_at = NULL, updated_at = ? WHERE effect_id = ? AND status = 'started' diff --git a/internal/effect/operator.go b/internal/effect/operator.go index dbc60ae2f..a1da66c5e 100644 --- a/internal/effect/operator.go +++ b/internal/effect/operator.go @@ -108,6 +108,7 @@ const operatorUpdateSQL = `UPDATE effects SET owner_instance_id = '', lease_until = NULL, next_attempt_at = NULL, + settled_at = CASE WHEN ? = 'planned' THEN NULL ELSE ? END, updated_at = ? WHERE effect_id = ? AND status = ?` @@ -131,7 +132,7 @@ func (s *SQLiteStore) ApplyOperatorAction(ctx context.Context, req OperatorActio res, err := s.db.ExecContext(ctx, dbutil.DialectSQLite.Rebind(operatorUpdateSQL), target, errorCode, operatorReasonPrefix+req.Reason, req.EvidenceRef, - req.Now.UnixMilli(), req.EffectID, req.ExpectedStatus) + target, req.Now.UnixMilli(), req.Now.UnixMilli(), req.EffectID, req.ExpectedStatus) if err != nil { return nil, fmt.Errorf("effect: operator action: %w", err) } @@ -162,7 +163,7 @@ func (s *PGStore) ApplyOperatorAction(ctx context.Context, req OperatorActionReq res, err := s.db.ExecContext(ctx, s.db.Dialect().Rebind(operatorUpdateSQL), target, errorCode, operatorReasonPrefix+req.Reason, req.EvidenceRef, - req.Now.UnixMilli(), req.EffectID, req.ExpectedStatus) + target, req.Now.UnixMilli(), req.Now.UnixMilli(), req.EffectID, req.ExpectedStatus) if err != nil { return nil, fmt.Errorf("effect: operator action: %w", err) } diff --git a/internal/effect/payload_gc.go b/internal/effect/payload_gc.go new file mode 100644 index 000000000..8ee9bbee3 --- /dev/null +++ b/internal/effect/payload_gc.go @@ -0,0 +1,92 @@ +package effect + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/hrygo/hotplex/internal/dbutil" +) + +// PayloadGCStore exposes the narrowly scoped retention operation for delivery +// bodies without widening Store and every delivery adapter's test double. +type PayloadGCStore interface { + DeleteExpiredPayloads(ctx context.Context, settledBefore time.Time, limit int) (int64, error) +} + +// Payload bodies are blanked in place rather than deleting identity rows. +// Keeping the unique occurrence/execution row prevents a late duplicate trigger +// from recreating expired content. settledBefore is the latest settlement time +// still eligible for deletion (now minus the configured post-settlement TTL). +const deleteExpiredPayloadsSQL = `UPDATE effect_payloads + SET content = '', content_bytes = 0 + WHERE content_bytes > 0 + AND payload_id IN ( + SELECT p.payload_id + FROM effect_payloads p + WHERE p.content_bytes > 0 + AND EXISTS ( + SELECT 1 FROM effects e WHERE e.payload_id = p.payload_id + ) + AND NOT EXISTS ( + SELECT 1 FROM effects e + WHERE e.payload_id = p.payload_id + AND (e.status IN ('planned', 'started', 'unknown') + OR e.settled_at IS NULL + OR e.lease_until IS NOT NULL + OR e.next_attempt_at IS NOT NULL) + ) + AND NOT EXISTS ( + SELECT 1 FROM effects e + WHERE e.payload_id = p.payload_id AND e.settled_at > ? + ) + ORDER BY p.created_at, p.payload_id + LIMIT ? + )` + +func (s *SQLiteStore) DeleteExpiredPayloads( + ctx context.Context, settledBefore time.Time, limit int, +) (int64, error) { + if limit <= 0 { + return 0, nil + } + var deleted int64 + err := s.writeMu.WithLock(func() error { + return s.inTx(ctx, func(tx *sql.Tx) error { + res, err := tx.ExecContext(ctx, dbutil.DialectSQLite.Rebind(deleteExpiredPayloadsSQL), + settledBefore.UnixMilli(), limit) + if err != nil { + return fmt.Errorf("effect: expire payloads: %w", err) + } + deleted, err = res.RowsAffected() + if err != nil { + return fmt.Errorf("effect: expired payload rows: %w", err) + } + return nil + }) + }) + return deleted, err +} + +func (s *PGStore) DeleteExpiredPayloads( + ctx context.Context, settledBefore time.Time, limit int, +) (int64, error) { + if limit <= 0 { + return 0, nil + } + var deleted int64 + err := s.inTx(ctx, func(tx *sql.Tx) error { + res, err := tx.ExecContext(ctx, s.db.Dialect().Rebind(deleteExpiredPayloadsSQL), + settledBefore.UnixMilli(), limit) + if err != nil { + return fmt.Errorf("effect: expire payloads: %w", err) + } + deleted, err = res.RowsAffected() + if err != nil { + return fmt.Errorf("effect: expired payload rows: %w", err) + } + return nil + }) + return deleted, err +} diff --git a/internal/effect/payload_gc_test.go b/internal/effect/payload_gc_test.go new file mode 100644 index 000000000..8375e4e19 --- /dev/null +++ b/internal/effect/payload_gc_test.go @@ -0,0 +1,121 @@ +package effect_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/hrygo/hotplex/internal/effect" +) + +func TestDeleteExpiredPayloadsWaitsForEveryDeliveryToSettle(t *testing.T) { + t.Parallel() + + store, _ := newStore(t) + ctx := context.Background() + base := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC) + + firstPlan := validPlan() + first, created, err := store.PlanOnce(ctx, firstPlan, base) + require.NoError(t, err) + require.True(t, created) + + secondPlan := firstPlan + secondPlan.TargetRevision = "rev-b" + second, created, err := store.PlanOnce(ctx, secondPlan, base.Add(time.Minute)) + require.NoError(t, err) + require.True(t, created) + require.Equal(t, first.PayloadID, second.PayloadID) + + firstClaimAt := base.Add(time.Hour) + firstClaim, err := store.ClaimSend(ctx, effect.ClaimRequest{ + EffectID: first.EffectID, OwnerInstanceID: "instance-a", + LeaseUntil: firstClaimAt.Add(time.Hour), ExpectedAttempt: 0, Now: firstClaimAt, + }) + require.NoError(t, err) + firstSettledAt := base.Add(24 * time.Hour) + require.NoError(t, store.CompleteSend(ctx, completeFor(firstClaim, firstSettledAt))) + + // The first target is complete, but the second still has a delivery + // obligation. The shared snapshot must remain available for it. + n, err := store.DeleteExpiredPayloads(ctx, base.Add(100*24*time.Hour), 10) + require.NoError(t, err) + require.Zero(t, n) + _, err = store.GetPayload(ctx, first.PayloadID) + require.NoError(t, err) + + secondClaimAt := base.Add(48 * time.Hour) + secondClaim, err := store.ClaimSend(ctx, effect.ClaimRequest{ + EffectID: second.EffectID, OwnerInstanceID: "instance-b", + LeaseUntil: secondClaimAt.Add(time.Hour), ExpectedAttempt: 0, Now: secondClaimAt, + }) + require.NoError(t, err) + secondSettledAt := base.Add(72 * time.Hour) + require.NoError(t, store.CompleteSend(ctx, completeFor(secondClaim, secondSettledAt))) + + // Expiry starts at the latest settlement among all effects that share the + // payload. Before that deadline, the snapshot is still recoverable. + n, err = store.DeleteExpiredPayloads(ctx, secondSettledAt.Add(-time.Nanosecond), 10) + require.NoError(t, err) + require.Zero(t, n) + + // At the deadline, remove the body while retaining the idempotency row. + n, err = store.DeleteExpiredPayloads(ctx, secondSettledAt, 10) + require.NoError(t, err) + require.EqualValues(t, 1, n) + _, err = store.GetPayload(ctx, first.PayloadID) + require.ErrorIs(t, err, effect.ErrPayloadNotFound) + + // A replay after expiry must not recreate the old body or produce a new + // sendable effect under another target revision. + replay := firstPlan + replay.TargetRevision = "rev-c" + _, _, err = store.PlanOnce(ctx, replay, base.Add(100*24*time.Hour)) + require.ErrorIs(t, err, effect.ErrPayloadNotFound) +} + +func TestDeleteExpiredPayloadsPreservesUnknownAndLegacyTerminalEffects(t *testing.T) { + t.Parallel() + + store, db := newStore(t) + ctx := context.Background() + base := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC) + planned, _, err := store.PlanOnce(ctx, validPlan(), base) + require.NoError(t, err) + + claimAt := base.Add(time.Hour) + _, err = store.ClaimSend(ctx, effect.ClaimRequest{ + EffectID: planned.EffectID, OwnerInstanceID: "instance-a", + LeaseUntil: claimAt.Add(time.Minute), ExpectedAttempt: 0, Now: claimAt, + }) + require.NoError(t, err) + _, err = store.ExpireLeases(ctx, claimAt.Add(time.Hour), 10) + require.NoError(t, err) + + // unknown is unresolved evidence, so age alone cannot delete its recovery + // content. + n, err := store.DeleteExpiredPayloads(ctx, base.Add(365*24*time.Hour), 10) + require.NoError(t, err) + require.Zero(t, n) + _, err = store.GetPayload(ctx, planned.PayloadID) + require.NoError(t, err) + + settledAt := base.Add(400 * 24 * time.Hour) + _, err = store.ReconcileUnknown(ctx, effect.ReconcileRequest{ + EffectID: planned.EffectID, Outcome: effect.ReconcileFailed, + ExpectedStatus: effect.StatusUnknown, Reason: "provider evidence confirmed no delivery", + Now: settledAt, + }) + require.NoError(t, err) + + // Terminal rows created before settled_at was introduced have no reliable + // settlement clock. Keep their body until a separate migration preview + // establishes a safe deadline. + _, err = db.Exec(`UPDATE effects SET settled_at = NULL WHERE effect_id = ?`, planned.EffectID) + require.NoError(t, err) + n, err = store.DeleteExpiredPayloads(ctx, settledAt.Add(365*24*time.Hour), 10) + require.NoError(t, err) + require.Zero(t, n) +} diff --git a/internal/effect/reconcile.go b/internal/effect/reconcile.go index d4ad4feee..4bdf32871 100644 --- a/internal/effect/reconcile.go +++ b/internal/effect/reconcile.go @@ -80,6 +80,7 @@ const reconcileUpdateSQL = `UPDATE effects SET owner_instance_id = '', lease_until = NULL, next_attempt_at = NULL, + settled_at = ?, updated_at = ? WHERE effect_id = ? AND status = ?` @@ -115,6 +116,7 @@ const fenceUpdateSQL = `UPDATE effects SET owner_instance_id = '', lease_until = NULL, next_attempt_at = NULL, + settled_at = COALESCE(settled_at, ?), updated_at = ? WHERE effect_id = ? AND status = ?` @@ -130,7 +132,7 @@ func applyReconcile(ctx context.Context, exec func(context.Context, string, ...a errorCode := "reconciled_" + string(req.Outcome) n, err := exec(ctx, reconcileUpdateSQL, string(target), errorCode, req.Reason, req.ProviderRef, req.EvidenceRef, - req.Now.UnixMilli(), req.EffectID, string(req.ExpectedStatus)) + req.Now.UnixMilli(), req.Now.UnixMilli(), req.EffectID, string(req.ExpectedStatus)) if err != nil { return fmt.Errorf("effect: reconcile: %w", err) } @@ -146,7 +148,7 @@ func applyFence(ctx context.Context, exec func(context.Context, string, ...any) } n, err := exec(ctx, fenceUpdateSQL, string(StatusFenced), "fenced", "fenced: "+req.Reason, - req.Now.UnixMilli(), req.EffectID, string(req.ExpectedStatus)) + req.Now.UnixMilli(), req.Now.UnixMilli(), req.EffectID, string(req.ExpectedStatus)) if err != nil { return fmt.Errorf("effect: fence: %w", err) } diff --git a/internal/effect/store.go b/internal/effect/store.go index a72858a2c..06233df31 100644 --- a/internal/effect/store.go +++ b/internal/effect/store.go @@ -76,7 +76,7 @@ const effectColumns = `effect_id, occurrence_id, delivery_ordinal, target_revisi session_id, execution_id, worker_run_id, payload_id, payload_sha256, target_kind, target_ref, status, error_code, reason, owner_instance_id, lease_until, lease_version, provider_ref, evidence_ref, - created_at, updated_at` + created_at, updated_at, settled_at` const payloadColumns = `payload_id, occurrence_id, execution_id, worker_run_id, content, content_bytes, content_sha256, created_at` @@ -89,13 +89,14 @@ func scanEffect(row rowScanner) (*Effect, error) { var ( e Effect leaseTill sql.NullInt64 + settledAt sql.NullInt64 ) err := row.Scan( &e.EffectID, &e.OccurrenceID, &e.DeliveryOrdinal, &e.TargetRevision, &e.Attempt, &e.SessionID, &e.ExecutionID, &e.WorkerRunID, &e.PayloadID, &e.PayloadSHA256, &e.TargetKind, &e.TargetRef, &e.Status, &e.ErrorCode, &e.Reason, &e.OwnerInstanceID, &leaseTill, &e.LeaseVersion, &e.ProviderRef, &e.EvidenceRef, - &e.CreatedAtMs, &e.UpdatedAtMs, + &e.CreatedAtMs, &e.UpdatedAtMs, &settledAt, ) if errors.Is(err, sql.ErrNoRows) { return nil, ErrEffectNotFound @@ -107,6 +108,10 @@ func scanEffect(row rowScanner) (*Effect, error) { v := leaseTill.Int64 e.LeaseUntilMs = &v } + if settledAt.Valid { + v := settledAt.Int64 + e.SettledAtMs = &v + } return &e, nil } @@ -122,5 +127,8 @@ func scanPayload(row rowScanner) (*Payload, error) { if err != nil { return nil, fmt.Errorf("effect: scan payload: %w", err) } + if p.ContentBytes == 0 { + return nil, ErrPayloadNotFound + } return &p, nil } diff --git a/internal/eventstore/pg_store.go b/internal/eventstore/pg_store.go index 4835e3730..f99db7195 100644 --- a/internal/eventstore/pg_store.go +++ b/internal/eventstore/pg_store.go @@ -15,17 +15,19 @@ import ( // pgStore implements EventStore + TurnQuerier using PostgreSQL. type pgStore struct { - db *dbutil.DB - dialect dbutil.Dialect - sql map[string]string // Rebound query cache (PG $N placeholders) - log *slog.Logger + db *dbutil.DB + dialect dbutil.Dialect + sql map[string]string // Rebound query cache (PG $N placeholders) + log *slog.Logger + retention RetentionPolicy } // pgEventTx is a PostgreSQL-backed transaction for batch event and turn writes. type pgEventTx struct { - tx *sql.Tx - sql map[string]string // Reference to pgStore's rebound queries - dialect dbutil.Dialect + tx *sql.Tx + sql map[string]string // Reference to pgStore's rebound queries + dialect dbutil.Dialect + retention RetentionPolicy } // Interface checks. @@ -38,12 +40,20 @@ var ( // are rebound to PG $N placeholders. The turns.insert query gets an additional // RETURNING id clause since PG does not support LastInsertId. func NewPGStore(db *dbutil.DB, log *slog.Logger) *pgStore { + return NewPGStoreWithRetention(db, log, RetentionPolicy{}) +} + +// NewPGStoreWithRetention creates a PostgreSQL store with explicit content +// retention. Existing rows retain their zero per-record deadline and use the +// legacy window. +func NewPGStoreWithRetention(db *dbutil.DB, log *slog.Logger, retention RetentionPolicy) *pgStore { d := db.Dialect() s := &pgStore{ - db: db, - dialect: d, - sql: make(map[string]string, len(queries)), - log: log, + db: db, + dialect: d, + sql: make(map[string]string, len(queries)), + log: log, + retention: retention.normalized(), } for k, v := range queries { s.sql[k] = d.Rebind(v) @@ -59,11 +69,15 @@ func NewPGStore(db *dbutil.DB, log *slog.Logger) *pgStore { // --------------------------------------------------------------------------- func (s *pgStore) Append(ctx context.Context, event *StoredEvent) error { - ctx, cancel := withDefaultTimeout(ctx) - defer cancel() - _, err := s.db.ExecContext(ctx, s.sql["insert"], - event.SessionID, event.Seq, event.Type, event.Data, event.Direction, event.Source, event.CreatedAt) + tx, err := s.BeginTx(ctx) if err != nil { + return err + } + if err := tx.Append(ctx, event); err != nil { + _ = tx.Rollback() + return err + } + if err := tx.Commit(); err != nil { return fmt.Errorf("eventstore: append: %w", err) } return nil @@ -74,13 +88,14 @@ func (s *pgStore) BeginTx(ctx context.Context) (EventTx, error) { if err != nil { return nil, fmt.Errorf("eventstore: begin tx: %w", err) } - return &pgEventTx{tx: tx, sql: s.sql, dialect: s.dialect}, nil + return &pgEventTx{tx: tx, sql: s.sql, dialect: s.dialect, retention: s.retention}, nil } func (s *pgStore) QueryBySession(ctx context.Context, sessionID string, cursor int64, dir CursorDirection, limit int) (*EventPage, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - return queryBySession(ctx, s.db, s.sql, sessionID, cursor, dir, limit) + nowMS, legacyCutoffMS := s.retention.readWindow(time.Now()) + return queryBySession(ctx, s.db, s.sql, sessionID, cursor, dir, limit, nowMS, legacyCutoffMS) } func (s *pgStore) DeleteBySession(ctx context.Context, sessionID string) error { @@ -93,10 +108,31 @@ func (s *pgStore) DeleteBySession(ctx context.Context, sessionID string) error { return nil } -func (s *pgStore) DeleteExpired(ctx context.Context, cutoff time.Time) (int64, error) { +// DeleteConversationBySession atomically removes both event payloads and +// materialized turns so a partial purge cannot leave a readable copy behind. +func (s *pgStore) DeleteConversationBySession(ctx context.Context, sessionID string) error { + ctx, cancel := withDefaultTimeout(ctx) + defer cancel() + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("eventstore: begin conversation deletion: %w", err) + } + defer func() { _ = tx.Rollback() }() + for _, query := range []string{s.sql["delete_by_session"], s.sql["turns.delete_by_session"]} { + if _, err := tx.ExecContext(ctx, query, sessionID); err != nil { + return fmt.Errorf("eventstore: delete conversation: %w", err) + } + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("eventstore: commit conversation deletion: %w", err) + } + return nil +} + +func (s *pgStore) DeleteExpired(ctx context.Context, legacyCutoff time.Time) (int64, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - res, err := s.db.ExecContext(ctx, s.sql["delete_expired"], cutoff.UnixMilli()) + res, err := s.db.ExecContext(ctx, s.sql["delete_expired"], time.Now().UnixMilli(), legacyCutoff.UnixMilli()) if err != nil { return 0, fmt.Errorf("eventstore: delete expired: %w", err) } @@ -113,11 +149,24 @@ func (s *pgStore) Close() error { // --------------------------------------------------------------------------- func (t *pgEventTx) Append(ctx context.Context, event *StoredEvent) error { - _, err := t.tx.ExecContext(ctx, t.sql["insert"], - event.SessionID, event.Seq, event.Type, event.Data, event.Direction, event.Source, event.CreatedAt) + if err := ensureSessionWritable(ctx, t.tx, event.SessionID, t.dialect.Rebind, true); err != nil { + return err + } + expiresAt, err := contentExpiryForSession( + ctx, t.tx, t.sql["lifecycle.get_content_policy"], t.retention, + event.SessionID, event.CreatedAt, event.ExpiresAt, + ) + if err != nil { + return fmt.Errorf("eventstore: resolve content expiry: %w", err) + } + _, err = t.tx.ExecContext(ctx, t.sql["insert"], + event.SessionID, event.Seq, event.Type, event.Data, event.Direction, event.Source, event.CreatedAt, expiresAt) if err != nil { return fmt.Errorf("eventstore: tx append: %w", err) } + if err := updateSessionContentDeadline(ctx, t.tx, t.sql["lifecycle.update_session_content_deadline"], event.SessionID, expiresAt); err != nil { + return fmt.Errorf("eventstore: tx update session content deadline: %w", err) + } return nil } @@ -139,21 +188,35 @@ func (t *pgEventTx) QueryRowContext(ctx context.Context, query string, args ...a } func (t *pgEventTx) AppendTurn(ctx context.Context, turn *TurnWriteRequest) error { + if err := ensureSessionWritable(ctx, t.tx, turn.SessionID, t.dialect.Rebind, true); err != nil { + return err + } var successVal any if turn.Success != nil { successVal = t.dialect.BoolValue(*turn.Success) } + expiresAt, err := contentExpiryForSession( + ctx, t.tx, t.sql["lifecycle.get_content_policy"], t.retention, + turn.SessionID, turn.CreatedAt, turn.ExpiresAt, + ) + if err != nil { + return fmt.Errorf("eventstore: resolve turn content expiry: %w", err) + } + var id int64 - err := t.tx.QueryRowContext(ctx, t.sql["turns.insert"], + err = t.tx.QueryRowContext(ctx, t.sql["turns.insert"], turn.SessionID, nullableClientMessageID(turn.ClientMessageID), turn.Generation, turn.TurnNum, turn.Seq, turn.Role, turn.Content, turn.Platform, turn.UserID, turn.Model, successVal, turn.Source, turn.ToolsJSON, turn.ToolCount, turn.TokensInput, turn.TokensCacheWrite, turn.TokensCacheRead, turn.TokensOut, - turn.DurationMs, turn.CostUSD, turn.CreatedAt, + turn.DurationMs, turn.CostUSD, turn.CreatedAt, expiresAt, ).Scan(&id) if err != nil { return fmt.Errorf("eventstore: tx append turn: %w", err) } + if err := updateSessionContentDeadline(ctx, t.tx, t.sql["lifecycle.update_session_content_deadline"], turn.SessionID, expiresAt); err != nil { + return fmt.Errorf("eventstore: tx update session content deadline: %w", err) + } return nil } @@ -172,7 +235,8 @@ func (t *pgEventTx) Rollback() error { func (s *pgStore) QueryTurns(ctx context.Context, sessionID string, limit, offset int) ([]*TurnRecord, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - rows, err := s.db.QueryContext(ctx, s.sql["turns.query_with_gen"], sessionID, sessionID, limit, offset) + nowMS, legacyCutoffMS := s.retention.readWindow(time.Now()) + rows, err := s.db.QueryContext(ctx, s.sql["turns.query_with_gen"], sessionID, sessionID, nowMS, legacyCutoffMS, limit, offset) if err != nil { return nil, fmt.Errorf("eventstore: query turns: %w", err) } @@ -183,7 +247,8 @@ func (s *pgStore) QueryTurns(ctx context.Context, sessionID string, limit, offse func (s *pgStore) QueryTurnsBefore(ctx context.Context, sessionID string, beforeID int64, limit int) ([]*TurnRecord, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - rows, err := s.db.QueryContext(ctx, s.sql["turns.query_before"], sessionID, beforeID, limit) + nowMS, legacyCutoffMS := s.retention.readWindow(time.Now()) + rows, err := s.db.QueryContext(ctx, s.sql["turns.query_before"], sessionID, beforeID, nowMS, legacyCutoffMS, limit) if err != nil { return nil, fmt.Errorf("eventstore: query turns before: %w", err) } @@ -200,7 +265,8 @@ func (s *pgStore) QueryTurnsBefore(ctx context.Context, sessionID string, before func (s *pgStore) QueryLatestTurns(ctx context.Context, sessionID string, limit int) ([]*TurnRecord, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - rows, err := s.db.QueryContext(ctx, s.sql["turns.query_latest"], sessionID, sessionID, limit) + nowMS, legacyCutoffMS := s.retention.readWindow(time.Now()) + rows, err := s.db.QueryContext(ctx, s.sql["turns.query_latest"], sessionID, sessionID, nowMS, legacyCutoffMS, limit) if err != nil { return nil, fmt.Errorf("eventstore: query latest turns: %w", err) } @@ -217,7 +283,8 @@ func (s *pgStore) QueryLatestTurns(ctx context.Context, sessionID string, limit func (s *pgStore) QueryTurnStats(ctx context.Context, sessionID string) (*TurnStats, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - rows, err := s.db.QueryContext(ctx, s.sql["turns.stats_with_gen"], sessionID, sessionID) + nowMS, legacyCutoffMS := s.retention.readWindow(time.Now()) + rows, err := s.db.QueryContext(ctx, s.sql["turns.stats_with_gen"], sessionID, sessionID, nowMS, legacyCutoffMS) if err != nil { return nil, fmt.Errorf("eventstore: query turn stats: %w", err) } @@ -261,10 +328,10 @@ func (s *pgStore) LatestSeq(ctx context.Context, sessionID string) (int64, error return seq, nil } -func (s *pgStore) DeleteExpiredTurns(ctx context.Context, cutoff time.Time) (int64, error) { +func (s *pgStore) DeleteExpiredTurns(ctx context.Context, legacyCutoff time.Time) (int64, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - res, err := s.db.ExecContext(ctx, s.sql["turns.delete_expired"], cutoff.UnixMilli()) + res, err := s.db.ExecContext(ctx, s.sql["turns.delete_expired"], time.Now().UnixMilli(), legacyCutoff.UnixMilli()) if err != nil { return 0, fmt.Errorf("eventstore: delete expired turns: %w", err) } diff --git a/internal/eventstore/pg_store_test.go b/internal/eventstore/pg_store_test.go index 34fe909a0..477b8d750 100644 --- a/internal/eventstore/pg_store_test.go +++ b/internal/eventstore/pg_store_test.go @@ -5,6 +5,7 @@ import ( "log/slog" "regexp" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/require" @@ -21,8 +22,10 @@ func newEventPGMock(t *testing.T) (*pgStore, sqlmock.Sqlmock, func()) { pg := dbutil.DialectPostgres sqlMap := map[string]string{ - "insert": pg.Rebind("INSERT INTO events (session_id, seq, type, data, direction, source, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)"), - "query_latest": pg.Rebind("SELECT id, session_id, seq, type, data, direction, source, created_at FROM events WHERE session_id = ? ORDER BY id DESC LIMIT ?"), + "insert": pg.Rebind("INSERT INTO events (session_id, seq, type, data, direction, source, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"), + "query_latest": pg.Rebind("SELECT id, session_id, seq, type, data, direction, source, created_at, expires_at FROM events WHERE session_id = ? AND ((expires_at > 0 AND expires_at > ?) OR (expires_at = 0 AND created_at > ?)) ORDER BY id DESC LIMIT ?"), + "delete_by_session": pg.Rebind(queries["delete_by_session"]), + "turns.delete_by_session": pg.Rebind(queries["turns.delete_by_session"]), } store := &pgStore{ @@ -39,7 +42,25 @@ func newEventPGMock(t *testing.T) (*pgStore, sqlmock.Sqlmock, func()) { } func eventColumns() []string { - return []string{"id", "session_id", "seq", "type", "data", "direction", "source", "created_at"} + return []string{"id", "session_id", "seq", "type", "data", "direction", "source", "created_at", "expires_at"} +} + +func expectPGSessionWriteBarrier(mock sqlmock.Sqlmock, sessionID string, state string, found bool, hasPurgeJob bool) { + stateQuery := dbutil.DialectPostgres.Rebind(`SELECT state FROM sessions WHERE id = ? FOR UPDATE`) + stateRows := sqlmock.NewRows([]string{"state"}) + if found { + stateRows.AddRow(state) + } + mock.ExpectQuery(regexp.QuoteMeta(stateQuery)).WithArgs(sessionID).WillReturnRows(stateRows) + if found { + return + } + purgeQuery := dbutil.DialectPostgres.Rebind(`SELECT EXISTS( + SELECT 1 FROM session_purge_jobs WHERE session_id = ? + )`) + mock.ExpectQuery(regexp.QuoteMeta(purgeQuery)). + WithArgs(sessionID). + WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(hasPurgeJob)) } func TestPGEventStore_AppendEvent(t *testing.T) { @@ -58,16 +79,109 @@ func TestPGEventStore_AppendEvent(t *testing.T) { } q := dbutil.DialectPostgres.Rebind( - "INSERT INTO events (session_id, seq, type, data, direction, source, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)") + "INSERT INTO events (session_id, seq, type, data, direction, source, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") + mock.ExpectBegin() + expectPGSessionWriteBarrier(mock, event.SessionID, "", false, false) mock.ExpectExec(regexp.QuoteMeta(q)). - WithArgs(event.SessionID, event.Seq, event.Type, event.Data, event.Direction, event.Source, event.CreatedAt). + WithArgs(event.SessionID, event.Seq, event.Type, event.Data, event.Direction, event.Source, event.CreatedAt, int64(0)). WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectCommit() err := store.Append(context.Background(), event) require.NoError(t, err) } +func TestPGEventStore_AppendAssignsExpiryAndAdvancesSessionDeadline(t *testing.T) { + t.Parallel() + store, mock, cleanup := newEventPGMock(t) + defer cleanup() + store.retention = RetentionPolicy{Content: 24 * time.Hour, Legacy: 30 * 24 * time.Hour} + store.sql["lifecycle.get_content_policy"] = dbutil.DialectPostgres.Rebind(queries["lifecycle.get_content_policy"]) + store.sql["lifecycle.update_session_content_deadline"] = + dbutil.DialectPostgres.Rebind(queries["lifecycle.update_session_content_deadline"]) + + event := &StoredEvent{ + SessionID: "sess-1", + Seq: 1, + Type: "message", + Data: []byte(`{"content":"hello"}`), + Direction: "out", + Source: "normal", + CreatedAt: 1_000_000, + } + expiresAt := event.CreatedAt + (24 * time.Hour).Milliseconds() + insertQuery := dbutil.DialectPostgres.Rebind(queries["insert"]) + updateQuery := dbutil.DialectPostgres.Rebind(queries["lifecycle.update_session_content_deadline"]) + policyQuery := dbutil.DialectPostgres.Rebind(queries["lifecycle.get_content_policy"]) + deadline := time.UnixMilli(expiresAt).UTC() + + mock.ExpectBegin() + expectPGSessionWriteBarrier(mock, event.SessionID, "running", true, false) + mock.ExpectQuery(regexp.QuoteMeta(policyQuery)). + WithArgs(event.SessionID). + WillReturnRows(sqlmock.NewRows([]string{"lifecycle_policy"}).AddRow("v2")) + mock.ExpectExec(regexp.QuoteMeta(insertQuery)). + WithArgs(event.SessionID, event.Seq, event.Type, event.Data, event.Direction, event.Source, event.CreatedAt, expiresAt). + WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec(regexp.QuoteMeta(updateQuery)). + WithArgs(deadline, deadline, deadline, deadline, event.SessionID). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectCommit() + + require.NoError(t, store.Append(context.Background(), event)) +} + +func TestPGEventStore_RejectsPurgeTombstone(t *testing.T) { + t.Parallel() + store, mock, cleanup := newEventPGMock(t) + defer cleanup() + event := &StoredEvent{ + SessionID: "sess-purged", Seq: 1, Type: "message", + Data: []byte(`{}`), Direction: "out", Source: SourceNormal, CreatedAt: 1000, + } + + mock.ExpectBegin() + expectPGSessionWriteBarrier(mock, event.SessionID, "", false, true) + mock.ExpectRollback() + + require.ErrorIs(t, store.Append(context.Background(), event), ErrSessionDeleted) +} + +func TestPGEventStore_DeleteConversationAtomic(t *testing.T) { + t.Parallel() + store, mock, cleanup := newEventPGMock(t) + defer cleanup() + sessionID := "sess-purge" + deleteEvents := regexp.QuoteMeta(store.sql["delete_by_session"]) + deleteTurns := regexp.QuoteMeta(store.sql["turns.delete_by_session"]) + + mock.ExpectBegin() + mock.ExpectExec(deleteEvents).WithArgs(sessionID).WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectExec(deleteTurns).WithArgs(sessionID).WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectCommit() + + require.NoError(t, store.DeleteConversationBySession(context.Background(), sessionID)) +} + +func TestPGEventStore_DeleteConversationRollsBackTogether(t *testing.T) { + t.Parallel() + store, mock, cleanup := newEventPGMock(t) + defer cleanup() + sessionID := "sess-purge" + + mock.ExpectBegin() + mock.ExpectExec(regexp.QuoteMeta(store.sql["delete_by_session"])). + WithArgs(sessionID). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectExec(regexp.QuoteMeta(store.sql["turns.delete_by_session"])). + WithArgs(sessionID). + WillReturnError(sqlmock.ErrCancelled) + mock.ExpectRollback() + + require.Error(t, store.DeleteConversationBySession(context.Background(), sessionID)) +} + func TestPGEventStore_QueryBySession_Latest(t *testing.T) { t.Parallel() store, mock, cleanup := newEventPGMock(t) @@ -77,13 +191,15 @@ func TestPGEventStore_QueryBySession_Latest(t *testing.T) { fetchLimit := 201 q := dbutil.DialectPostgres.Rebind( - "SELECT id, session_id, seq, type, data, direction, source, created_at FROM events WHERE session_id = ? ORDER BY id DESC LIMIT ?") + "SELECT id, session_id, seq, type, data, direction, source, created_at, expires_at FROM events WHERE session_id = ? AND ((expires_at > 0 AND expires_at > ?) OR (expires_at = 0 AND created_at > ?)) ORDER BY id DESC LIMIT ?") rows := sqlmock.NewRows(eventColumns()). - AddRow(int64(2), sessionID, int64(2), "message.delta", []byte(`{"c":"b"}`), "out", "normal", int64(2000)). - AddRow(int64(1), sessionID, int64(1), "message.delta", []byte(`{"c":"a"}`), "out", "normal", int64(1000)) + AddRow(int64(2), sessionID, int64(2), "message.delta", []byte(`{"c":"b"}`), "out", "normal", int64(2000), int64(0)). + AddRow(int64(1), sessionID, int64(1), "message.delta", []byte(`{"c":"a"}`), "out", "normal", int64(1000), int64(0)) - mock.ExpectQuery(regexp.QuoteMeta(q)).WithArgs(sessionID, fetchLimit).WillReturnRows(rows) + mock.ExpectQuery(regexp.QuoteMeta(q)). + WithArgs(sessionID, sqlmock.AnyArg(), sqlmock.AnyArg(), fetchLimit). + WillReturnRows(rows) page, err := store.QueryBySession(context.Background(), sessionID, 0, CursorLatest, 200) require.NoError(t, err) diff --git a/internal/eventstore/query_shared.go b/internal/eventstore/query_shared.go index 66c94b0c5..750c09a91 100644 --- a/internal/eventstore/query_shared.go +++ b/internal/eventstore/query_shared.go @@ -3,9 +3,13 @@ package eventstore import ( "context" "database/sql" + "errors" "fmt" "log/slog" "slices" + "time" + + "github.com/hrygo/hotplex/pkg/events" ) // queryExecer abstracts the read-side query capability shared by *sql.DB @@ -17,10 +21,56 @@ type queryExecer interface { QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row } +type commandExecer interface { + ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) +} + +func rebindEventSQL(query string) string { return query } + +func ensureSessionWritable(ctx context.Context, tx *sql.Tx, sessionID string, rebind func(string) string, lockRow bool) error { + stateQuery := `SELECT state FROM sessions WHERE id = ?` + if lockRow { + stateQuery += ` FOR UPDATE` + } + var state sql.NullString + err := tx.QueryRowContext(ctx, rebind(stateQuery), sessionID).Scan(&state) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("eventstore: check session write barrier: %w", err) + } + if state.Valid && state.String == string(events.StateDeleted) { + return ErrSessionDeleted + } + if state.Valid { + return nil + } + + var hasPurgeJob bool + err = tx.QueryRowContext(ctx, rebind(`SELECT EXISTS( + SELECT 1 FROM session_purge_jobs WHERE session_id = ? + )`), sessionID).Scan(&hasPurgeJob) + if err != nil { + return fmt.Errorf("eventstore: check session purge barrier: %w", err) + } + if hasPurgeJob { + return ErrSessionDeleted + } + return nil +} + // queryBySession is the dialect-agnostic cursor pagination implementation // shared by SQLiteStore and pgStore. The caller supplies its own query map // (the package-level `queries` for SQLite, the rebound `s.sql` for Postgres). -func queryBySession(ctx context.Context, qe queryExecer, q map[string]string, sessionID string, cursor int64, dir CursorDirection, limit int) (*EventPage, error) { +func queryBySession( + ctx context.Context, + qe queryExecer, + q map[string]string, + sessionID string, + cursor int64, + dir CursorDirection, + limit int, + nowMS int64, + legacyCutoffMS int64, +) (*EventPage, error) { if limit <= 0 { limit = 200 } @@ -35,11 +85,11 @@ func queryBySession(ctx context.Context, qe queryExecer, q map[string]string, se var err error switch dir { case CursorAfter: - rows, err = qe.QueryContext(ctx, q["query_after"], sessionID, cursor, fetchLimit) + rows, err = qe.QueryContext(ctx, q["query_after"], sessionID, cursor, nowMS, legacyCutoffMS, fetchLimit) case CursorBefore: - rows, err = qe.QueryContext(ctx, q["query_before"], sessionID, cursor, fetchLimit) + rows, err = qe.QueryContext(ctx, q["query_before"], sessionID, cursor, nowMS, legacyCutoffMS, fetchLimit) default: // CursorLatest - rows, err = qe.QueryContext(ctx, q["query_latest"], sessionID, fetchLimit) + rows, err = qe.QueryContext(ctx, q["query_latest"], sessionID, nowMS, legacyCutoffMS, fetchLimit) } if err != nil { return nil, fmt.Errorf("eventstore: query: %w", err) @@ -75,7 +125,7 @@ func queryBySession(ctx context.Context, qe queryExecer, q map[string]string, se page.HasOlder = hasMore default: var exists int - err := qe.QueryRowContext(ctx, q["has_older"], sessionID, page.OldestID).Scan(&exists) + err := qe.QueryRowContext(ctx, q["has_older"], sessionID, page.OldestID, nowMS, legacyCutoffMS).Scan(&exists) page.HasOlder = err == nil && exists == 1 } } @@ -83,6 +133,45 @@ func queryBySession(ctx context.Context, qe queryExecer, q map[string]string, se return page, nil } +func updateSessionContentDeadline(ctx context.Context, exec commandExecer, query, sessionID string, expiresAt int64) error { + if expiresAt <= 0 { + return nil + } + deadline := time.UnixMilli(expiresAt).UTC() + _, err := exec.ExecContext(ctx, query, deadline, deadline, deadline, deadline, sessionID) + return err +} + +func contentExpiryForSession( + ctx context.Context, + tx *sql.Tx, + policyQuery string, + retention RetentionPolicy, + sessionID string, + createdAt int64, + existingExpiry int64, +) (int64, error) { + if existingExpiry > 0 { + return existingExpiry, nil + } + if retention.Content <= 0 || createdAt <= 0 { + return 0, nil + } + + var policy string + err := tx.QueryRowContext(ctx, policyQuery, sessionID).Scan(&policy) + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } + if err != nil { + return 0, err + } + if policy != "v2" { + return 0, nil + } + return retention.expiry(createdAt, 0), nil +} + // turnScanDest returns the scan destination slice for a turn row. It is shared // by scanTurns (SQLite) and scanTurnsPG (Postgres) so the 23-column projection // order cannot drift between backends. successPtr is dialect-specific diff --git a/internal/eventstore/sql/queries/events.delete_expired.sql b/internal/eventstore/sql/queries/events.delete_expired.sql index ee057bc6e..4b610e7dc 100644 --- a/internal/eventstore/sql/queries/events.delete_expired.sql +++ b/internal/eventstore/sql/queries/events.delete_expired.sql @@ -1 +1,3 @@ -DELETE FROM events WHERE created_at < ? +DELETE FROM events +WHERE (expires_at > 0 AND expires_at <= ?) + OR (expires_at = 0 AND created_at < ?) diff --git a/internal/eventstore/sql/queries/events.has_older.sql b/internal/eventstore/sql/queries/events.has_older.sql index 33d48bb74..35885f945 100644 --- a/internal/eventstore/sql/queries/events.has_older.sql +++ b/internal/eventstore/sql/queries/events.has_older.sql @@ -1 +1,5 @@ -SELECT 1 FROM events WHERE session_id = ? AND id < ? LIMIT 1 +SELECT 1 +FROM events +WHERE session_id = ? AND id < ? + AND ((expires_at > 0 AND expires_at > ?) OR (expires_at = 0 AND created_at > ?)) +LIMIT 1 diff --git a/internal/eventstore/sql/queries/events.insert.sql b/internal/eventstore/sql/queries/events.insert.sql index cb4a0a141..786d9f5e0 100644 --- a/internal/eventstore/sql/queries/events.insert.sql +++ b/internal/eventstore/sql/queries/events.insert.sql @@ -1,2 +1,2 @@ -INSERT INTO events (session_id, seq, type, data, direction, source, created_at) -VALUES (?, ?, ?, ?, ?, ?, ?) +INSERT INTO events (session_id, seq, type, data, direction, source, created_at, expires_at) +VALUES (?, ?, ?, ?, ?, ?, ?, ?) diff --git a/internal/eventstore/sql/queries/events.query_after.sql b/internal/eventstore/sql/queries/events.query_after.sql index 9eede9510..d9c7c1474 100644 --- a/internal/eventstore/sql/queries/events.query_after.sql +++ b/internal/eventstore/sql/queries/events.query_after.sql @@ -1,5 +1,6 @@ -SELECT id, session_id, seq, type, data, direction, source, created_at +SELECT id, session_id, seq, type, data, direction, source, created_at, expires_at FROM events WHERE session_id = ? AND id > ? + AND ((expires_at > 0 AND expires_at > ?) OR (expires_at = 0 AND created_at > ?)) ORDER BY id ASC LIMIT ? diff --git a/internal/eventstore/sql/queries/events.query_before.sql b/internal/eventstore/sql/queries/events.query_before.sql index 82e5a5b77..9d4df4e63 100644 --- a/internal/eventstore/sql/queries/events.query_before.sql +++ b/internal/eventstore/sql/queries/events.query_before.sql @@ -1,5 +1,6 @@ -SELECT id, session_id, seq, type, data, direction, source, created_at +SELECT id, session_id, seq, type, data, direction, source, created_at, expires_at FROM events WHERE session_id = ? AND id < ? + AND ((expires_at > 0 AND expires_at > ?) OR (expires_at = 0 AND created_at > ?)) ORDER BY id DESC LIMIT ? diff --git a/internal/eventstore/sql/queries/events.query_latest.sql b/internal/eventstore/sql/queries/events.query_latest.sql index 417c02dcb..c3caa40c9 100644 --- a/internal/eventstore/sql/queries/events.query_latest.sql +++ b/internal/eventstore/sql/queries/events.query_latest.sql @@ -4,8 +4,9 @@ -- seq DESC would bury low-seq new events under the LIMIT. Mirrors -- turns.query_latest's ORDER BY t.id DESC. Correct because flushBoth now sends -- deltas in firstSeq order, so insertion order (id) matches logical seq order. -SELECT id, session_id, seq, type, data, direction, source, created_at +SELECT id, session_id, seq, type, data, direction, source, created_at, expires_at FROM events WHERE session_id = ? + AND ((expires_at > 0 AND expires_at > ?) OR (expires_at = 0 AND created_at > ?)) ORDER BY id DESC LIMIT ? diff --git a/internal/eventstore/sql/queries/lifecycle.get_content_policy.sql b/internal/eventstore/sql/queries/lifecycle.get_content_policy.sql new file mode 100644 index 000000000..c0e09fb72 --- /dev/null +++ b/internal/eventstore/sql/queries/lifecycle.get_content_policy.sql @@ -0,0 +1,3 @@ +SELECT lifecycle_policy +FROM sessions +WHERE id = ? diff --git a/internal/eventstore/sql/queries/lifecycle.update_session_content_deadline.sql b/internal/eventstore/sql/queries/lifecycle.update_session_content_deadline.sql new file mode 100644 index 000000000..566d2d61f --- /dev/null +++ b/internal/eventstore/sql/queries/lifecycle.update_session_content_deadline.sql @@ -0,0 +1,13 @@ +UPDATE sessions +SET + last_content_expires_at = CASE + WHEN last_content_expires_at IS NULL OR last_content_expires_at < ? THEN ? + ELSE last_content_expires_at + END, + history_expires_at = CASE + WHEN history_expires_at IS NULL OR history_expires_at < ? THEN ? + ELSE history_expires_at + END +WHERE id = ? + AND lifecycle_policy = 'v2' + AND deleted_at IS NULL diff --git a/internal/eventstore/sql/queries/turns.delete_by_session.sql b/internal/eventstore/sql/queries/turns.delete_by_session.sql new file mode 100644 index 000000000..573d38a35 --- /dev/null +++ b/internal/eventstore/sql/queries/turns.delete_by_session.sql @@ -0,0 +1 @@ +DELETE FROM turns WHERE session_id = ? diff --git a/internal/eventstore/sql/queries/turns.delete_expired.sql b/internal/eventstore/sql/queries/turns.delete_expired.sql index b200735a9..67c4fe9cc 100644 --- a/internal/eventstore/sql/queries/turns.delete_expired.sql +++ b/internal/eventstore/sql/queries/turns.delete_expired.sql @@ -1 +1,3 @@ -DELETE FROM turns WHERE created_at < ? +DELETE FROM turns +WHERE (expires_at > 0 AND expires_at <= ?) + OR (expires_at = 0 AND created_at < ?) diff --git a/internal/eventstore/sql/queries/turns.insert.sql b/internal/eventstore/sql/queries/turns.insert.sql index 83f82690b..bd5cf56b1 100644 --- a/internal/eventstore/sql/queries/turns.insert.sql +++ b/internal/eventstore/sql/queries/turns.insert.sql @@ -1,5 +1,5 @@ INSERT INTO turns (session_id, client_message_id, generation, turn_num, seq, role, content, platform, user_id, model, success, source, tools_json, tool_count, tokens_input, tokens_cache_write, tokens_cache_read, tokens_out, - duration_ms, cost_usd, created_at) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + duration_ms, cost_usd, created_at, expires_at) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) diff --git a/internal/eventstore/sql/queries/turns.query_before.sql b/internal/eventstore/sql/queries/turns.query_before.sql index d5b763cbe..db3ceae59 100644 --- a/internal/eventstore/sql/queries/turns.query_before.sql +++ b/internal/eventstore/sql/queries/turns.query_before.sql @@ -5,5 +5,6 @@ SELECT id, session_id, client_message_id, generation, turn_num, seq, role, conte tokens_out, duration_ms, cost_usd, created_at FROM turns WHERE session_id = ? AND id < ? + AND ((expires_at > 0 AND expires_at > ?) OR (expires_at = 0 AND created_at > ?)) ORDER BY id DESC LIMIT ? diff --git a/internal/eventstore/sql/queries/turns.query_latest.sql b/internal/eventstore/sql/queries/turns.query_latest.sql index 8c12233d7..6e358e9c4 100644 --- a/internal/eventstore/sql/queries/turns.query_latest.sql +++ b/internal/eventstore/sql/queries/turns.query_latest.sql @@ -12,5 +12,6 @@ SELECT t.id, t.session_id, t.client_message_id, t.generation, t.turn_num, t.seq, t.tokens_out, t.duration_ms, t.cost_usd, t.created_at FROM turns t, latest_gen lg WHERE t.session_id = ? AND t.generation = lg.gen + AND ((t.expires_at > 0 AND t.expires_at > ?) OR (t.expires_at = 0 AND t.created_at > ?)) ORDER BY t.id DESC LIMIT ? diff --git a/internal/eventstore/sql/queries/turns.query_with_gen.sql b/internal/eventstore/sql/queries/turns.query_with_gen.sql index d2166f783..9f186167a 100644 --- a/internal/eventstore/sql/queries/turns.query_with_gen.sql +++ b/internal/eventstore/sql/queries/turns.query_with_gen.sql @@ -8,5 +8,6 @@ SELECT t.id, t.session_id, t.client_message_id, t.generation, t.turn_num, t.seq, t.tokens_out, t.duration_ms, t.cost_usd, t.created_at FROM turns t, latest_gen lg WHERE t.session_id = ? AND t.generation = lg.gen + AND ((t.expires_at > 0 AND t.expires_at > ?) OR (t.expires_at = 0 AND t.created_at > ?)) ORDER BY t.id ASC LIMIT ? OFFSET ? diff --git a/internal/eventstore/sql/queries/turns.stats_with_gen.sql b/internal/eventstore/sql/queries/turns.stats_with_gen.sql index e3162c940..ced41031a 100644 --- a/internal/eventstore/sql/queries/turns.stats_with_gen.sql +++ b/internal/eventstore/sql/queries/turns.stats_with_gen.sql @@ -8,4 +8,5 @@ SELECT lg.gen, t.turn_num, t.seq, t.success, t.source, t.tokens_out, t.duration_ms, t.cost_usd, t.model, t.created_at FROM turns t, latest_gen lg WHERE t.session_id = ? AND t.generation = lg.gen AND t.role = 'assistant' + AND ((t.expires_at > 0 AND t.expires_at > ?) OR (t.expires_at = 0 AND t.created_at > ?)) ORDER BY t.id ASC diff --git a/internal/eventstore/store.go b/internal/eventstore/store.go index 3322251dd..174e688be 100644 --- a/internal/eventstore/store.go +++ b/internal/eventstore/store.go @@ -77,9 +77,46 @@ const ( CursorBefore ) -var ErrNotFound = errors.New("eventstore: no events found") +var ( + ErrNotFound = errors.New("eventstore: no events found") + ErrSessionDeleted = errors.New("eventstore: session has been deleted") +) const defaultTimeout = 5 * time.Second +const defaultLegacyContentRetention = 720 * time.Hour + +// RetentionPolicy separates immutable deadlines assigned to newly written +// content from the fallback window used for rows written before those +// deadlines were persisted. +type RetentionPolicy struct { + Content time.Duration + Legacy time.Duration +} + +func (p RetentionPolicy) normalized() RetentionPolicy { + if p.Content < 0 { + p.Content = 0 + } + if p.Legacy <= 0 { + p.Legacy = defaultLegacyContentRetention + } + return p +} + +func (p RetentionPolicy) expiry(createdAt, existingExpiry int64) int64 { + if existingExpiry > 0 { + return existingExpiry + } + if p.Content <= 0 || createdAt <= 0 { + return 0 + } + return createdAt + p.Content.Milliseconds() +} + +func (p RetentionPolicy) readWindow(now time.Time) (nowMS, legacyCutoffMS int64) { + p = p.normalized() + return now.UnixMilli(), now.Add(-p.Legacy).UnixMilli() +} // withDefaultTimeout wraps ctx with a 5s timeout if it has no deadline. func withDefaultTimeout(ctx context.Context) (context.Context, context.CancelFunc) { @@ -160,6 +197,7 @@ type StoredEvent struct { Direction string `json:"direction"` Source string `json:"source"` CreatedAt int64 `json:"created_at"` + ExpiresAt int64 `json:"-"` } // EventPage is a page of events with pagination metadata. @@ -191,8 +229,9 @@ type EventStore interface { // DeleteBySession removes all events for a session. DeleteBySession(ctx context.Context, sessionID string) error - // DeleteExpired removes events older than the cutoff. - DeleteExpired(ctx context.Context, cutoff time.Time) (int64, error) + // DeleteExpired removes expired per-record events and legacy rows older + // than the supplied fallback cutoff. + DeleteExpired(ctx context.Context, legacyCutoff time.Time) (int64, error) // Close flushes pending writes and closes the database (if owned). Close() error @@ -232,7 +271,7 @@ type TurnQuerier interface { QueryTurnStats(ctx context.Context, sessionID string) (*TurnStats, error) LatestGeneration(ctx context.Context, sessionID string) (int64, error) LatestTurnNum(ctx context.Context, sessionID string, generation int64) (int, error) - DeleteExpiredTurns(ctx context.Context, cutoff time.Time) (int64, error) + DeleteExpiredTurns(ctx context.Context, legacyCutoff time.Time) (int64, error) // LatestSeq returns the maximum event seq persisted for a session, or 0 if // none. Used to hydrate the in-memory SeqGen on resume/reconnect so new // events do not collide with the prior seq range (issue #879). @@ -241,9 +280,10 @@ type TurnQuerier interface { // SQLiteStore implements EventStore using a shared SQLite database connection. type SQLiteStore struct { - db *sql.DB - ownsDB bool // true only when opened independently (tests); false when sharing session store DB. - writeMu *sqlutil.WriteMu + db *sql.DB + ownsDB bool // true only when opened independently (tests); false when sharing session store DB. + writeMu *sqlutil.WriteMu + retention RetentionPolicy } var _ EventStore = (*SQLiteStore)(nil) @@ -251,7 +291,13 @@ var _ EventStore = (*SQLiteStore)(nil) // NewSQLiteStore creates an event store using a shared *sql.DB. // The schema is managed by the session store goose migrations (002_events_table.sql). func NewSQLiteStore(db *sql.DB, writeMu *sqlutil.WriteMu) *SQLiteStore { - return &SQLiteStore{db: db, ownsDB: false, writeMu: writeMu} + return NewSQLiteStoreWithRetention(db, writeMu, RetentionPolicy{}) +} + +// NewSQLiteStoreWithRetention creates a store with explicit content retention. +// Existing rows retain their zero per-record deadline and use the legacy window. +func NewSQLiteStoreWithRetention(db *sql.DB, writeMu *sqlutil.WriteMu, retention RetentionPolicy) *SQLiteStore { + return &SQLiteStore{db: db, ownsDB: false, writeMu: writeMu, retention: retention.normalized()} } // NewIndependentStore opens its own DB for testing. @@ -263,20 +309,22 @@ func NewIndependentStore(dbPath string) (*SQLiteStore, error) { // Apply same pragmas as production for test fidelity. _, _ = db.Exec("PRAGMA journal_mode=WAL") _, _ = db.Exec("PRAGMA busy_timeout=5000") - return &SQLiteStore{db: db, ownsDB: true}, nil + return &SQLiteStore{db: db, ownsDB: true, retention: RetentionPolicy{}.normalized()}, nil } func (s *SQLiteStore) Append(ctx context.Context, event *StoredEvent) error { - ctx, cancel := withDefaultTimeout(ctx) - defer cancel() - return s.writeMu.WithLock(func() error { - _, err := s.db.ExecContext(ctx, queries["insert"], - event.SessionID, event.Seq, event.Type, event.Data, event.Direction, event.Source, event.CreatedAt) - if err != nil { - return fmt.Errorf("eventstore: append: %w", err) - } - return nil - }) + tx, err := s.BeginTx(ctx) + if err != nil { + return err + } + if err := tx.Append(ctx, event); err != nil { + _ = tx.Rollback() + return err + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("eventstore: append: %w", err) + } + return nil } func (s *SQLiteStore) BeginTx(ctx context.Context) (EventTx, error) { @@ -295,14 +343,15 @@ func (s *SQLiteStore) BeginTx(ctx context.Context) (EventTx, error) { // The cancel must NOT run here: this context drives the returned // transaction, so cancelling it on return would abort the transaction // before the caller's first statement. It is released with the tx instead. - return &sqliteTx{tx: tx, writeMu: s.writeMu, cancel: cancel}, nil + return &sqliteTx{tx: tx, writeMu: s.writeMu, cancel: cancel, retention: s.retention}, nil } type sqliteTx struct { - tx *sql.Tx - writeMu *sqlutil.WriteMu - released bool - cancel context.CancelFunc + tx *sql.Tx + writeMu *sqlutil.WriteMu + released bool + cancel context.CancelFunc + retention RetentionPolicy } func (t *sqliteTx) release() { @@ -334,15 +383,31 @@ func (t *sqliteTx) QueryRowContext(ctx context.Context, query string, args ...an } func (t *sqliteTx) Append(ctx context.Context, event *StoredEvent) error { - _, err := t.tx.ExecContext(ctx, queries["insert"], - event.SessionID, event.Seq, event.Type, event.Data, event.Direction, event.Source, event.CreatedAt) + if err := ensureSessionWritable(ctx, t.tx, event.SessionID, rebindEventSQL, false); err != nil { + return err + } + expiresAt, err := contentExpiryForSession( + ctx, t.tx, queries["lifecycle.get_content_policy"], t.retention, + event.SessionID, event.CreatedAt, event.ExpiresAt, + ) + if err != nil { + return fmt.Errorf("eventstore: resolve content expiry: %w", err) + } + _, err = t.tx.ExecContext(ctx, queries["insert"], + event.SessionID, event.Seq, event.Type, event.Data, event.Direction, event.Source, event.CreatedAt, expiresAt) if err != nil { return fmt.Errorf("eventstore: tx append: %w", err) } + if err := updateSessionContentDeadline(ctx, t.tx, queries["lifecycle.update_session_content_deadline"], event.SessionID, expiresAt); err != nil { + return fmt.Errorf("eventstore: tx update session content deadline: %w", err) + } return nil } func (t *sqliteTx) AppendTurn(ctx context.Context, turn *TurnWriteRequest) error { + if err := ensureSessionWritable(ctx, t.tx, turn.SessionID, rebindEventSQL, false); err != nil { + return err + } var successVal any if turn.Success != nil { if *turn.Success { @@ -351,14 +416,24 @@ func (t *sqliteTx) AppendTurn(ctx context.Context, turn *TurnWriteRequest) error successVal = 0 } } - _, err := t.tx.ExecContext(ctx, queries["turns.insert"], + expiresAt, err := contentExpiryForSession( + ctx, t.tx, queries["lifecycle.get_content_policy"], t.retention, + turn.SessionID, turn.CreatedAt, turn.ExpiresAt, + ) + if err != nil { + return fmt.Errorf("eventstore: resolve turn content expiry: %w", err) + } + _, err = t.tx.ExecContext(ctx, queries["turns.insert"], turn.SessionID, nullableClientMessageID(turn.ClientMessageID), turn.Generation, turn.TurnNum, turn.Seq, turn.Role, turn.Content, turn.Platform, turn.UserID, turn.Model, successVal, turn.Source, turn.ToolsJSON, turn.ToolCount, turn.TokensInput, turn.TokensCacheWrite, turn.TokensCacheRead, turn.TokensOut, - turn.DurationMs, turn.CostUSD, turn.CreatedAt) + turn.DurationMs, turn.CostUSD, turn.CreatedAt, expiresAt) if err != nil { return fmt.Errorf("eventstore: tx append turn: %w", err) } + if err := updateSessionContentDeadline(ctx, t.tx, queries["lifecycle.update_session_content_deadline"], turn.SessionID, expiresAt); err != nil { + return fmt.Errorf("eventstore: tx update session content deadline: %w", err) + } return nil } @@ -377,7 +452,8 @@ func (t *sqliteTx) Rollback() error { func (s *SQLiteStore) QueryBySession(ctx context.Context, sessionID string, cursor int64, dir CursorDirection, limit int) (*EventPage, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - return queryBySession(ctx, s.db, queries, sessionID, cursor, dir, limit) + nowMS, legacyCutoffMS := s.retention.readWindow(time.Now()) + return queryBySession(ctx, s.db, queries, sessionID, cursor, dir, limit, nowMS, legacyCutoffMS) } func (s *SQLiteStore) DeleteBySession(ctx context.Context, sessionID string) error { @@ -392,12 +468,31 @@ func (s *SQLiteStore) DeleteBySession(ctx context.Context, sessionID string) err }) } -func (s *SQLiteStore) DeleteExpired(ctx context.Context, cutoff time.Time) (int64, error) { +// DeleteConversationBySession atomically removes both event payloads and +// materialized turns so a partial purge cannot leave a readable copy behind. +func (s *SQLiteStore) DeleteConversationBySession(ctx context.Context, sessionID string) error { + tx, err := s.BeginTx(ctx) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + for _, query := range []string{queries["delete_by_session"], queries["turns.delete_by_session"]} { + if _, err := tx.ExecContext(ctx, query, sessionID); err != nil { + return fmt.Errorf("eventstore: delete conversation: %w", err) + } + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("eventstore: commit conversation deletion: %w", err) + } + return nil +} + +func (s *SQLiteStore) DeleteExpired(ctx context.Context, legacyCutoff time.Time) (int64, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() var rowsAffected int64 err := s.writeMu.WithLock(func() error { - res, err := s.db.ExecContext(ctx, queries["delete_expired"], cutoff.UnixMilli()) + res, err := s.db.ExecContext(ctx, queries["delete_expired"], time.Now().UnixMilli(), legacyCutoff.UnixMilli()) if err != nil { return fmt.Errorf("eventstore: delete expired: %w", err) } @@ -419,7 +514,8 @@ func (s *SQLiteStore) Close() error { func (s *SQLiteStore) QueryTurns(ctx context.Context, sessionID string, limit, offset int) ([]*TurnRecord, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - rows, err := s.db.QueryContext(ctx, queries["turns.query_with_gen"], sessionID, sessionID, limit, offset) + nowMS, legacyCutoffMS := s.retention.readWindow(time.Now()) + rows, err := s.db.QueryContext(ctx, queries["turns.query_with_gen"], sessionID, sessionID, nowMS, legacyCutoffMS, limit, offset) if err != nil { return nil, fmt.Errorf("eventstore: query turns: %w", err) } @@ -432,7 +528,8 @@ func (s *SQLiteStore) QueryTurns(ctx context.Context, sessionID string, limit, o func (s *SQLiteStore) QueryTurnsBefore(ctx context.Context, sessionID string, beforeID int64, limit int) ([]*TurnRecord, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - rows, err := s.db.QueryContext(ctx, queries["turns.query_before"], sessionID, beforeID, limit) + nowMS, legacyCutoffMS := s.retention.readWindow(time.Now()) + rows, err := s.db.QueryContext(ctx, queries["turns.query_before"], sessionID, beforeID, nowMS, legacyCutoffMS, limit) if err != nil { return nil, fmt.Errorf("eventstore: query turns before: %w", err) } @@ -455,7 +552,8 @@ func (s *SQLiteStore) QueryTurnsBefore(ctx context.Context, sessionID string, be func (s *SQLiteStore) QueryLatestTurns(ctx context.Context, sessionID string, limit int) ([]*TurnRecord, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - rows, err := s.db.QueryContext(ctx, queries["turns.query_latest"], sessionID, sessionID, limit) + nowMS, legacyCutoffMS := s.retention.readWindow(time.Now()) + rows, err := s.db.QueryContext(ctx, queries["turns.query_latest"], sessionID, sessionID, nowMS, legacyCutoffMS, limit) if err != nil { return nil, fmt.Errorf("eventstore: query latest turns: %w", err) } @@ -474,7 +572,8 @@ func (s *SQLiteStore) QueryLatestTurns(ctx context.Context, sessionID string, li func (s *SQLiteStore) QueryTurnStats(ctx context.Context, sessionID string) (*TurnStats, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() - rows, err := s.db.QueryContext(ctx, queries["turns.stats_with_gen"], sessionID, sessionID) + nowMS, legacyCutoffMS := s.retention.readWindow(time.Now()) + rows, err := s.db.QueryContext(ctx, queries["turns.stats_with_gen"], sessionID, sessionID, nowMS, legacyCutoffMS) if err != nil { return nil, fmt.Errorf("eventstore: query turn stats: %w", err) } @@ -521,13 +620,14 @@ func (s *SQLiteStore) LatestSeq(ctx context.Context, sessionID string) (int64, e return seq, nil } -// DeleteExpiredTurns removes turns older than the cutoff by created_at. -func (s *SQLiteStore) DeleteExpiredTurns(ctx context.Context, cutoff time.Time) (int64, error) { +// DeleteExpiredTurns removes expired per-record turns and legacy rows older +// than the supplied fallback cutoff. +func (s *SQLiteStore) DeleteExpiredTurns(ctx context.Context, legacyCutoff time.Time) (int64, error) { ctx, cancel := withDefaultTimeout(ctx) defer cancel() var rowsAffected int64 err := s.writeMu.WithLock(func() error { - res, err := s.db.ExecContext(ctx, queries["turns.delete_expired"], cutoff.UnixMilli()) + res, err := s.db.ExecContext(ctx, queries["turns.delete_expired"], time.Now().UnixMilli(), legacyCutoff.UnixMilli()) if err != nil { return fmt.Errorf("eventstore: delete expired turns: %w", err) } @@ -541,7 +641,7 @@ func scanEvents(rows *sql.Rows) ([]*StoredEvent, error) { var events []*StoredEvent for rows.Next() { var e StoredEvent - if err := rows.Scan(&e.ID, &e.SessionID, &e.Seq, &e.Type, &e.Data, &e.Direction, &e.Source, &e.CreatedAt); err != nil { + if err := rows.Scan(&e.ID, &e.SessionID, &e.Seq, &e.Type, &e.Data, &e.Direction, &e.Source, &e.CreatedAt, &e.ExpiresAt); err != nil { return nil, fmt.Errorf("eventstore: scan: %w", err) } events = append(events, &e) diff --git a/internal/eventstore/store_test.go b/internal/eventstore/store_test.go index 62f8c26c7..a07b18cde 100644 --- a/internal/eventstore/store_test.go +++ b/internal/eventstore/store_test.go @@ -2,6 +2,7 @@ package eventstore import ( "context" + "database/sql" "encoding/json" "errors" "log/slog" @@ -39,7 +40,21 @@ func newTestStore(t *testing.T) *SQLiteStore { direction TEXT NOT NULL DEFAULT 'outbound', source TEXT NOT NULL DEFAULT 'normal' CHECK(source IN ('normal', 'crash', 'timeout', 'fresh_start')), - created_at INTEGER NOT NULL + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL DEFAULT 0 + )`) + require.NoError(t, err) + _, err = store.db.Exec(`CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + state TEXT NOT NULL DEFAULT '', + lifecycle_policy TEXT, + last_content_expires_at DATETIME, + history_expires_at DATETIME, + deleted_at DATETIME + )`) + require.NoError(t, err) + _, err = store.db.Exec(`CREATE TABLE IF NOT EXISTS session_purge_jobs ( + session_id TEXT PRIMARY KEY )`) require.NoError(t, err) return store @@ -147,6 +162,37 @@ func TestSQLiteStore_DeleteBySession(t *testing.T) { require.ErrorIs(t, err, ErrNotFound) } +func TestSQLiteStore_RejectsAppendsAfterSessionDeletion(t *testing.T) { + t.Parallel() + store := newTestStore(t) + ctx := context.Background() + + _, err := store.db.ExecContext(ctx, `INSERT INTO sessions (id, state) VALUES (?, 'deleted')`, "sess-deleted") + require.NoError(t, err) + err = store.Append(ctx, &StoredEvent{ + SessionID: "sess-deleted", Seq: 1, Type: "message", + Data: raw(`{"content":"late"}`), Direction: "outbound", + Source: SourceNormal, CreatedAt: time.Now().UnixMilli(), + }) + require.ErrorIs(t, err, ErrSessionDeleted) + + // The purge job remains as a durable tombstone after session metadata is + // physically removed, so a delayed writer cannot recreate conversation data. + _, err = store.db.ExecContext(ctx, `DELETE FROM sessions WHERE id = ?`, "sess-deleted") + require.NoError(t, err) + _, err = store.db.ExecContext(ctx, `INSERT INTO session_purge_jobs (session_id) VALUES (?)`, "sess-deleted") + require.NoError(t, err) + err = store.Append(ctx, &StoredEvent{ + SessionID: "sess-deleted", Seq: 2, Type: "message", + Data: raw(`{"content":"late"}`), Direction: "outbound", + Source: SourceNormal, CreatedAt: time.Now().UnixMilli(), + }) + require.ErrorIs(t, err, ErrSessionDeleted) + + _, err = store.QueryBySession(ctx, "sess-deleted", 0, CursorLatest, 10) + require.ErrorIs(t, err, ErrNotFound) +} + func TestSQLiteStore_LatestSeq(t *testing.T) { t.Parallel() store := newTestStore(t) @@ -213,6 +259,85 @@ func TestSQLiteStore_DeleteExpired(t *testing.T) { require.Equal(t, int64(2), page.Events[0].Seq) } +func TestSQLiteStore_PerRecordContentExpiry(t *testing.T) { + t.Parallel() + store := newTestStore(t) + store.retention = RetentionPolicy{ + Content: 10 * 24 * time.Hour, + Legacy: 30 * 24 * time.Hour, + } + ctx := context.Background() + now := time.Now().Truncate(time.Millisecond) + + _, err := store.db.ExecContext(ctx, + `INSERT INTO sessions (id, lifecycle_policy) VALUES (?, 'v2')`, "s1") + require.NoError(t, err) + _, err = store.db.ExecContext(ctx, + `INSERT INTO sessions (id, lifecycle_policy) VALUES (?, 'legacy')`, "s-legacy") + require.NoError(t, err) + + // New rows receive an immutable deadline from their creation time. + require.NoError(t, store.Append(ctx, &StoredEvent{ + SessionID: "s1", Seq: 1, Type: "message", Data: json.RawMessage(`{}`), + Direction: "outbound", Source: SourceNormal, CreatedAt: now.Add(-11 * 24 * time.Hour).UnixMilli(), + })) + require.NoError(t, store.Append(ctx, &StoredEvent{ + SessionID: "s1", Seq: 2, Type: "message", Data: json.RawMessage(`{}`), + Direction: "outbound", Source: SourceNormal, CreatedAt: now.Add(-9 * 24 * time.Hour).UnixMilli(), + })) + + // Rows that predate the per-record policy keep the legacy zero deadline. + _, err = store.db.ExecContext(ctx, `INSERT INTO events + (session_id, seq, type, data, direction, source, created_at, expires_at) + VALUES (?, ?, ?, ?, ?, ?, ?, 0)`, + "s1", 3, "message", []byte(`{}`), "outbound", SourceNormal, now.Add(-31*24*time.Hour).UnixMilli()) + require.NoError(t, err) + _, err = store.db.ExecContext(ctx, `INSERT INTO events + (session_id, seq, type, data, direction, source, created_at, expires_at) + VALUES (?, ?, ?, ?, ?, ?, ?, 0)`, + "s1", 4, "message", []byte(`{}`), "outbound", SourceNormal, now.Add(-29*24*time.Hour).UnixMilli()) + require.NoError(t, err) + + var expiresAt int64 + require.NoError(t, store.db.QueryRowContext(ctx, + `SELECT expires_at FROM events WHERE session_id = ? AND seq = 2`, "s1").Scan(&expiresAt)) + require.Equal(t, now.Add(time.Hour*24).UnixMilli(), expiresAt) + contentExpiresAt := expiresAt + + require.NoError(t, store.Append(ctx, &StoredEvent{ + SessionID: "s-legacy", Seq: 1, Type: "message", Data: json.RawMessage(`{}`), + Direction: "outbound", Source: SourceNormal, CreatedAt: now.UnixMilli(), + })) + require.NoError(t, store.db.QueryRowContext(ctx, + `SELECT expires_at FROM events WHERE session_id = ? AND seq = 1`, "s-legacy").Scan(&expiresAt)) + require.Zero(t, expiresAt, "legacy sessions must keep the legacy retention window") + + var lastContentExpiry, historyExpiry sql.NullTime + require.NoError(t, store.db.QueryRowContext(ctx, + `SELECT last_content_expires_at, history_expires_at FROM sessions WHERE id = ?`, "s1"). + Scan(&lastContentExpiry, &historyExpiry)) + require.True(t, lastContentExpiry.Valid) + require.True(t, historyExpiry.Valid) + require.WithinDuration(t, time.UnixMilli(contentExpiresAt), lastContentExpiry.Time, time.Millisecond) + require.WithinDuration(t, time.UnixMilli(contentExpiresAt), historyExpiry.Time, time.Millisecond) + + // Reconfiguring retention does not rewrite deadlines already assigned. + store.retention.Content = 24 * time.Hour + page, err := store.QueryBySession(ctx, "s1", 0, CursorLatest, 10) + require.NoError(t, err) + require.Len(t, page.Events, 2) + require.Equal(t, []int64{2, 4}, []int64{page.Events[0].Seq, page.Events[1].Seq}) + + deleted, err := store.DeleteExpired(ctx, now.Add(-30*24*time.Hour)) + require.NoError(t, err) + require.Equal(t, int64(2), deleted) + + page, err = store.QueryBySession(ctx, "s1", 0, CursorLatest, 10) + require.NoError(t, err) + require.Len(t, page.Events, 2) + require.Equal(t, []int64{2, 4}, []int64{page.Events[0].Seq, page.Events[1].Seq}) +} + func TestSQLiteStore_Transaction(t *testing.T) { store := newTestStore(t) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) @@ -379,8 +504,20 @@ func newTestStoreWithWriteMu(t *testing.T) *SQLiteStore { direction TEXT NOT NULL DEFAULT 'outbound', source TEXT NOT NULL DEFAULT 'normal' CHECK(source IN ('normal', 'crash', 'timeout', 'fresh_start')), - created_at INTEGER NOT NULL DEFAULT 0 + created_at INTEGER NOT NULL DEFAULT 0, + expires_at INTEGER NOT NULL DEFAULT 0 )`) require.NoError(t, err) + _, err = store.db.Exec(`CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + state TEXT NOT NULL DEFAULT '', + lifecycle_policy TEXT, + last_content_expires_at DATETIME, + history_expires_at DATETIME, + deleted_at DATETIME + )`) + require.NoError(t, err) + _, err = store.db.Exec(`CREATE TABLE IF NOT EXISTS session_purge_jobs (session_id TEXT PRIMARY KEY)`) + require.NoError(t, err) return store } diff --git a/internal/eventstore/turn_write.go b/internal/eventstore/turn_write.go index 81e9c9a14..885f131b0 100644 --- a/internal/eventstore/turn_write.go +++ b/internal/eventstore/turn_write.go @@ -31,6 +31,7 @@ type TurnWriteRequest struct { DurationMs int64 CostUSD float64 CreatedAt int64 // Unix ms + ExpiresAt int64 // Unix ms; zero assigns the configured new-content deadline. } func nullableClientMessageID(id string) any { diff --git a/internal/eventstore/turns_table_test.go b/internal/eventstore/turns_table_test.go index cdcd7fd7f..addb5a5a5 100644 --- a/internal/eventstore/turns_table_test.go +++ b/internal/eventstore/turns_table_test.go @@ -54,7 +54,8 @@ func newTestStoreWithTurnsTable(t *testing.T) *SQLiteStore { tokens_out INTEGER NOT NULL DEFAULT 0, duration_ms INTEGER NOT NULL DEFAULT 0, cost_usd REAL NOT NULL DEFAULT 0.0, - created_at INTEGER NOT NULL + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL DEFAULT 0 )`) require.NoError(t, err) @@ -66,6 +67,73 @@ func newTestStoreWithTurnsTable(t *testing.T) *SQLiteStore { return store } +func TestSQLiteStore_DeleteConversationBySession(t *testing.T) { + t.Parallel() + store := newTestStoreWithTurnsTable(t) + ctx := testCtx(t) + require.NoError(t, store.Append(ctx, &StoredEvent{ + SessionID: "sess-purge", Seq: 1, Type: "message", + Data: json.RawMessage(`{"content":"private"}`), Direction: "outbound", + Source: SourceNormal, CreatedAt: time.Now().UnixMilli(), + })) + _, err := store.db.ExecContext(ctx, + `INSERT INTO turns (session_id, turn_num, role, content, created_at) VALUES (?, ?, ?, ?, ?)`, + "sess-purge", 1, "user", "private", time.Now().UnixMilli()) + require.NoError(t, err) + + require.NoError(t, store.DeleteConversationBySession(ctx, "sess-purge")) + + var eventCount, turnCount int + require.NoError(t, store.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM events WHERE session_id = ?`, "sess-purge").Scan(&eventCount)) + require.NoError(t, store.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM turns WHERE session_id = ?`, "sess-purge").Scan(&turnCount)) + require.Zero(t, eventCount) + require.Zero(t, turnCount) +} + +func TestSQLiteStore_DeleteConversationBySessionRollsBackTogether(t *testing.T) { + t.Parallel() + store := newTestStoreWithTurnsTable(t) + ctx := testCtx(t) + require.NoError(t, store.Append(ctx, &StoredEvent{ + SessionID: "sess-purge-rollback", Seq: 1, Type: "message", + Data: json.RawMessage(`{"content":"private"}`), Direction: "outbound", + Source: SourceNormal, CreatedAt: time.Now().UnixMilli(), + })) + _, err := store.db.ExecContext(ctx, + `INSERT INTO turns (session_id, turn_num, role, content, created_at) VALUES (?, ?, ?, ?, ?)`, + "sess-purge-rollback", 1, "user", "private", time.Now().UnixMilli()) + require.NoError(t, err) + _, err = store.db.ExecContext(ctx, `CREATE TRIGGER reject_turn_purge BEFORE DELETE ON turns + BEGIN SELECT RAISE(ABORT, 'blocked'); END`) + require.NoError(t, err) + + require.Error(t, store.DeleteConversationBySession(ctx, "sess-purge-rollback")) + + var eventCount, turnCount int + require.NoError(t, store.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM events WHERE session_id = ?`, "sess-purge-rollback").Scan(&eventCount)) + require.NoError(t, store.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM turns WHERE session_id = ?`, "sess-purge-rollback").Scan(&turnCount)) + require.Equal(t, 1, eventCount) + require.Equal(t, 1, turnCount) +} + +func TestSQLiteStore_RejectsTurnAppendAfterSessionDeletion(t *testing.T) { + t.Parallel() + store := newTestStoreWithTurnsTable(t) + ctx := testCtx(t) + + _, err := store.db.ExecContext(ctx, `INSERT INTO sessions (id, state) VALUES (?, 'deleted')`, "sess-deleted-turn") + require.NoError(t, err) + + tx, err := store.BeginTx(ctx) + require.NoError(t, err) + err = tx.AppendTurn(ctx, &TurnWriteRequest{ + SessionID: "sess-deleted-turn", Generation: 1, TurnNum: 1, Role: "user", + Content: "late", CreatedAt: time.Now().UnixMilli(), + }) + require.ErrorIs(t, err, ErrSessionDeleted) + require.NoError(t, tx.Rollback()) +} + // ─── TURN-001: turns table creation ───────────────────────────────────────── func TestTurnsTable_Schema(t *testing.T) { @@ -596,6 +664,63 @@ func TestTurnsTable_DeleteExpiredTurns(t *testing.T) { require.Equal(t, "recent", records[0].Content) } +func TestTurnsTable_PerRecordContentExpiry(t *testing.T) { + t.Parallel() + store := newTestStoreWithTurnsTable(t) + store.retention = RetentionPolicy{ + Content: 10 * 24 * time.Hour, + Legacy: 30 * 24 * time.Hour, + } + ctx := testCtx(t) + now := time.Now().Truncate(time.Millisecond) + + _, err := store.db.ExecContext(ctx, + `INSERT INTO sessions (id, lifecycle_policy) VALUES (?, 'v2')`, "s-exp") + require.NoError(t, err) + + tx, err := store.BeginTx(ctx) + require.NoError(t, err) + require.NoError(t, tx.AppendTurn(ctx, &TurnWriteRequest{ + SessionID: "s-exp", Generation: 1, TurnNum: 1, Role: RoleUser, + Content: "expired", Source: SourceNormal, CreatedAt: now.Add(-11 * 24 * time.Hour).UnixMilli(), + })) + require.NoError(t, tx.AppendTurn(ctx, &TurnWriteRequest{ + SessionID: "s-exp", Generation: 1, TurnNum: 2, Role: RoleUser, + Content: "live", Source: SourceNormal, CreatedAt: now.Add(-9 * 24 * time.Hour).UnixMilli(), + })) + require.NoError(t, tx.Commit()) + + _, err = store.db.ExecContext(ctx, `INSERT INTO turns + (session_id, generation, turn_num, role, content, created_at, expires_at) + VALUES (?, 1, 3, 'user', 'legacy-expired', ?, 0)`, + "s-exp", now.Add(-31*24*time.Hour).UnixMilli()) + require.NoError(t, err) + _, err = store.db.ExecContext(ctx, `INSERT INTO turns + (session_id, generation, turn_num, role, content, created_at, expires_at) + VALUES (?, 1, 4, 'user', 'legacy-live', ?, 0)`, + "s-exp", now.Add(-29*24*time.Hour).UnixMilli()) + require.NoError(t, err) + + var expiresAt int64 + require.NoError(t, store.db.QueryRowContext(ctx, + `SELECT expires_at FROM turns WHERE session_id = ? AND turn_num = 2`, "s-exp").Scan(&expiresAt)) + require.Equal(t, now.Add(24*time.Hour).UnixMilli(), expiresAt) + + records, err := store.QueryTurns(ctx, "s-exp", 10, 0) + require.NoError(t, err) + require.Len(t, records, 2) + require.Equal(t, []string{"live", "legacy-live"}, []string{records[0].Content, records[1].Content}) + + deleted, err := store.DeleteExpiredTurns(ctx, now.Add(-30*24*time.Hour)) + require.NoError(t, err) + require.Equal(t, int64(2), deleted) + + records, err = store.QueryTurns(ctx, "s-exp", 10, 0) + require.NoError(t, err) + require.Len(t, records, 2) + require.Equal(t, []string{"live", "legacy-live"}, []string{records[0].Content, records[1].Content}) +} + // ─── TURN-020: TurnRecord ID type ────────────────────────────────────────── func TestTurnsTable_TurnRecordIDInt64(t *testing.T) { diff --git a/internal/execution/lifecycle_gc.go b/internal/execution/lifecycle_gc.go new file mode 100644 index 000000000..fecb15990 --- /dev/null +++ b/internal/execution/lifecycle_gc.go @@ -0,0 +1,89 @@ +package execution + +import ( + "context" + "fmt" + "time" +) + +// LifecycleFactsGCStore exposes bounded compaction of settled execution +// details while preserving the client message key and payload fingerprint. +type LifecycleFactsGCStore interface { + CompactSettledFacts(ctx context.Context, finishedBefore time.Time, limit int) (int64, error) +} + +const compactSettledFactsSQL = `UPDATE execution_inputs + SET error_code = '', + owner_instance_id = '', + lease_until = 0, + runtime_error_code = '', + started_at = NULL, + fence_created_at = NULL, + turn_started_at = NULL, + turn_deadline_at = NULL, + turn_policy_revision = '' + WHERE execution_id IN ( + SELECT e.execution_id + FROM execution_inputs e + WHERE e.status IN ('delivered', 'failed') + AND e.runtime_status IN ('completed', 'failed') + AND e.finished_at IS NOT NULL + AND e.finished_at <= ? + AND e.fence_reason = '' + AND NOT (e.status = 'failed' AND e.runtime_status = 'completed') + AND NOT EXISTS ( + SELECT 1 FROM execution_queue q + WHERE q.execution_id = e.execution_id + ) + AND ( + e.error_code <> '' + OR e.owner_instance_id <> '' + OR e.lease_until <> 0 + OR e.runtime_error_code <> '' + OR e.started_at IS NOT NULL + OR e.fence_created_at IS NOT NULL + OR e.turn_started_at IS NOT NULL + OR e.turn_deadline_at IS NOT NULL + OR e.turn_policy_revision <> '' + ) + ORDER BY e.finished_at, e.execution_id + LIMIT ? + )` + +// CompactSettledFacts removes operational details after their retention window. +// It preserves the idempotency key, payload hash, final delivery/runtime states, +// and finished_at so a late duplicate is still recognized and facts are not +// mistaken for missing input. +func (s *SQLStore) CompactSettledFacts( + ctx context.Context, finishedBefore time.Time, limit int, +) (int64, error) { + if limit <= 0 { + return 0, nil + } + ctx, cancel := withTimeout(ctx) + defer cancel() + + var compacted int64 + err := s.withWriteLock(func() error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("execution: begin lifecycle fact compaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + + result, err := tx.ExecContext(ctx, s.rebind(compactSettledFactsSQL), + finishedBefore.UnixMilli(), limit) + if err != nil { + return fmt.Errorf("execution: compact settled facts: %w", err) + } + compacted, err = result.RowsAffected() + if err != nil { + return fmt.Errorf("execution: compacted fact rows: %w", err) + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("execution: commit lifecycle fact compaction: %w", err) + } + return nil + }) + return compacted, err +} diff --git a/internal/execution/lifecycle_gc_pg_test.go b/internal/execution/lifecycle_gc_pg_test.go new file mode 100644 index 000000000..43b779613 --- /dev/null +++ b/internal/execution/lifecycle_gc_pg_test.go @@ -0,0 +1,36 @@ +package execution + +import ( + "context" + "regexp" + "testing" + "time" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/require" + + "github.com/hrygo/hotplex/internal/dbutil" +) + +func TestCompactSettledFactsPostgresRebindsAndUsesTransaction(t *testing.T) { + t.Parallel() + + mockDB, mock, err := sqlmock.New() + require.NoError(t, err) + t.Cleanup(func() { _ = mockDB.Close() }) + + store, err := NewSQLStore(context.Background(), mockDB, dbutil.DialectPostgres, nil, nil) + require.NoError(t, err) + + cutoff := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC) + mock.ExpectBegin() + mock.ExpectExec(regexp.QuoteMeta(dbutil.DialectPostgres.Rebind(compactSettledFactsSQL))). + WithArgs(cutoff.UnixMilli(), 25). + WillReturnResult(sqlmock.NewResult(0, 3)) + mock.ExpectCommit() + + compacted, err := store.CompactSettledFacts(context.Background(), cutoff, 25) + require.NoError(t, err) + require.EqualValues(t, 3, compacted) + require.NoError(t, mock.ExpectationsWereMet()) +} diff --git a/internal/execution/lifecycle_gc_test.go b/internal/execution/lifecycle_gc_test.go new file mode 100644 index 000000000..8d83f6f03 --- /dev/null +++ b/internal/execution/lifecycle_gc_test.go @@ -0,0 +1,122 @@ +package execution + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestCompactSettledFactsPreservesIdempotencyAndProtectsUnresolvedRows(t *testing.T) { + t.Parallel() + + store, sessionStore := newTestSQLStore(t) + ctx := context.Background() + oldFinishedAt := time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC) + + settled, _, err := store.Accept(ctx, testAcceptReq("session-1", "message-settled", "payload-hash")) + require.NoError(t, err) + require.NoError(t, store.MarkRunning(ctx, settled.ExecutionID, testOwner, testRun)) + require.NoError(t, store.SetDelivery(ctx, settled.ExecutionID, testOwner, StatusDelivered, "")) + require.NoError(t, store.FinishRuntime(ctx, settled.ExecutionID, testRun, RuntimeCompleted, "")) + _, err = sessionStore.DB().ExecContext(ctx, `UPDATE execution_inputs SET + created_at = ?, updated_at = ?, finished_at = ?, delivered_at = ?, + error_code = 'old-delivery-error', owner_instance_id = 'old-owner', + worker_run_id = 'old-run', lease_until = 123, runtime_error_code = 'old-runtime-error', + started_at = ?, fence_created_at = ?, turn_started_at = ?, turn_deadline_at = ?, + turn_policy_revision = 'old-policy' + WHERE execution_id = ?`, + oldFinishedAt.UnixMilli(), oldFinishedAt.UnixMilli(), oldFinishedAt.UnixMilli(), + oldFinishedAt.UnixMilli(), oldFinishedAt.UnixMilli(), oldFinishedAt.UnixMilli(), + oldFinishedAt.UnixMilli(), oldFinishedAt.Add(time.Hour).UnixMilli(), settled.ExecutionID) + require.NoError(t, err) + + ensureSession(t, sessionStore, "session-unknown") + unknown, _, err := store.Accept(ctx, testAcceptReq("session-unknown", "message-unknown", "unknown-hash")) + require.NoError(t, err) + require.NoError(t, store.MarkRunning(ctx, unknown.ExecutionID, testOwner, testRun)) + require.NoError(t, store.FinishRuntime(ctx, unknown.ExecutionID, testRun, RuntimeUnknown, "")) + _, err = sessionStore.DB().ExecContext(ctx, + `UPDATE execution_inputs SET finished_at = ? WHERE execution_id = ?`, + oldFinishedAt.UnixMilli(), unknown.ExecutionID) + require.NoError(t, err) + + ensureSession(t, sessionStore, "session-active") + active, _, err := store.Accept(ctx, testAcceptReq("session-active", "message-active", "active-hash")) + require.NoError(t, err) + _, err = sessionStore.DB().ExecContext(ctx, + `UPDATE execution_inputs SET created_at = ?, updated_at = ? WHERE execution_id = ?`, + oldFinishedAt.UnixMilli(), oldFinishedAt.UnixMilli(), active.ExecutionID) + require.NoError(t, err) + + ensureSession(t, sessionStore, "session-legacy") + legacy, _, err := store.Accept(ctx, testAcceptReq("session-legacy", "message-legacy", "legacy-hash")) + require.NoError(t, err) + require.NoError(t, store.MarkRunning(ctx, legacy.ExecutionID, testOwner, testRun)) + require.NoError(t, store.SetDelivery(ctx, legacy.ExecutionID, testOwner, StatusDelivered, "")) + require.NoError(t, store.FinishRuntime(ctx, legacy.ExecutionID, testRun, RuntimeCompleted, "")) + _, err = sessionStore.DB().ExecContext(ctx, `UPDATE execution_inputs SET + created_at = ?, updated_at = ?, finished_at = NULL, owner_instance_id = 'legacy-owner', + worker_run_id = 'legacy-run' WHERE execution_id = ?`, + oldFinishedAt.UnixMilli(), oldFinishedAt.UnixMilli(), legacy.ExecutionID) + require.NoError(t, err) + + ensureSession(t, sessionStore, "session-late-convergence") + lateConvergence, _, err := store.Accept(ctx, + testAcceptReq("session-late-convergence", "message-late-convergence", "late-hash")) + require.NoError(t, err) + require.NoError(t, store.MarkRunning(ctx, lateConvergence.ExecutionID, testOwner, testRun)) + require.NoError(t, store.SetDelivery(ctx, lateConvergence.ExecutionID, testOwner, StatusFailed, "late-ack")) + require.NoError(t, + store.FinishRuntime(ctx, lateConvergence.ExecutionID, testRun, RuntimeCompleted, "")) + _, err = sessionStore.DB().ExecContext(ctx, `UPDATE execution_inputs SET + created_at = ?, updated_at = ?, finished_at = ? + WHERE execution_id = ?`, + oldFinishedAt.UnixMilli(), oldFinishedAt.UnixMilli(), oldFinishedAt.UnixMilli(), + lateConvergence.ExecutionID) + require.NoError(t, err) + + compacted, err := store.CompactSettledFacts(ctx, oldFinishedAt, 1) + require.NoError(t, err) + require.EqualValues(t, 1, compacted, "the batch size bounds work") + + stored, err := store.getByClientMessage(ctx, "session-1", "message-settled") + require.NoError(t, err) + require.Equal(t, settled.ExecutionID, stored.ExecutionID) + require.Equal(t, "payload-hash", stored.PayloadHash) + require.Equal(t, StatusDelivered, stored.Status) + require.Equal(t, RuntimeCompleted, stored.RuntimeStatus) + require.Empty(t, stored.ErrorCode) + require.Empty(t, stored.OwnerInstanceID) + require.Equal(t, "old-run", stored.WorkerRunID, + "the worker run ID remains to recognize late terminal events for this execution") + require.Empty(t, stored.RuntimeErrorCode) + require.Nil(t, stored.StartedAt) + require.Nil(t, stored.TurnStartedAt) + require.Nil(t, stored.TurnDeadlineAt) + require.NotNil(t, stored.FinishedAt, "settlement clock remains available") + + duplicate, wasDuplicate, err := store.Accept(ctx, testAcceptReq("session-1", "message-settled", "payload-hash")) + require.NoError(t, err) + require.True(t, wasDuplicate, "compaction must not reopen the client idempotency key") + require.Equal(t, settled.ExecutionID, duplicate.ExecutionID) + + for _, input := range []struct { + sessionID string + messageID string + }{ + {sessionID: "session-unknown", messageID: "message-unknown"}, + {sessionID: "session-active", messageID: "message-active"}, + {sessionID: "session-legacy", messageID: "message-legacy"}, + {sessionID: "session-late-convergence", messageID: "message-late-convergence"}, + } { + record, err := store.getByClientMessage(ctx, input.sessionID, input.messageID) + require.NoError(t, err) + require.NotEmpty(t, record.OwnerInstanceID, "unresolved, active, or no-clock rows remain unchanged") + if input.messageID == "message-late-convergence" { + require.Equal(t, StatusFailed, record.Status) + require.Equal(t, RuntimeCompleted, record.RuntimeStatus) + } + } +} diff --git a/internal/execution/queue_test.go b/internal/execution/queue_test.go index bccf768b7..0b68202b6 100644 --- a/internal/execution/queue_test.go +++ b/internal/execution/queue_test.go @@ -71,6 +71,21 @@ func TestAcceptQueued_WritesQueuedExecutionAndDurableEntry(t *testing.T) { require.Equal(t, entry, stored) } +func TestAcceptQueued_RejectsRetiredSession(t *testing.T) { + t.Parallel() + + ctx := context.Background() + store, sessionStore := newTestSQLStore(t) + expired := time.Now().Add(-time.Hour) + _, err := sessionStore.DB().ExecContext(ctx, + `UPDATE sessions SET lifecycle_policy = ?, conversation_expires_at = ? WHERE id = ?`, + config.LifecyclePolicyV2, expired, "session-1") + require.NoError(t, err) + + _, _, _, err = store.AcceptQueued(ctx, queuedReq("session-1", "message-after-expiry"), QueueLimits{}) + require.ErrorIs(t, err, ErrSessionExpired) +} + // TestAcceptQueued_DuplicateReturnsOriginalWithoutConsumingCapacity proves a // retried message costs nothing: no second queue row, no second ordinal, and // the capacity budget it would have consumed stays available. diff --git a/internal/execution/sql_console_test.go b/internal/execution/sql_console_test.go index cc5240004..080f5a965 100644 --- a/internal/execution/sql_console_test.go +++ b/internal/execution/sql_console_test.go @@ -146,7 +146,15 @@ func TestListRecent_TimeWindow(t *testing.T) { ctx := context.Background() store, _ := newTestSQLStore(t) created := acceptN(t, store, "session-1", 2) - pivot := created[0].CreatedAt + // Accept timestamps are millisecond precision; pin this test's fixture + // explicitly so both executions do not accidentally share a clock tick. + pivot := int64(1_700_000_000_000) + for i, record := range created { + _, err := store.db.ExecContext(ctx, + `UPDATE execution_inputs SET created_at = ?, updated_at = ? WHERE execution_id = ?`, + pivot+int64(i), pivot+int64(i), record.ExecutionID) + require.NoError(t, err) + } after, err := store.ListRecent(ctx, ListFilter{SinceMs: pivot}) require.NoError(t, err) diff --git a/internal/execution/sql_queue.go b/internal/execution/sql_queue.go index 552f1b101..98d863dcb 100644 --- a/internal/execution/sql_queue.go +++ b/internal/execution/sql_queue.go @@ -83,6 +83,23 @@ func (s *SQLStore) AcceptQueued( ctx, cancel := withTimeout(ctx) defer cancel() + // Preserve idempotency for an already accepted input even if its session + // has since been retired. This read has no write side effect. + existing, err := s.getByClientMessage(ctx, request.SessionID, request.ClientMessageID) + if err == nil { + if existing.PayloadHash != request.PayloadHash { + return existing, nil, true, ErrPayloadConflict + } + entry, entryErr := s.queueEntryByExecution(ctx, s.db, existing.ExecutionID) + if entryErr != nil && !errors.Is(entryErr, ErrNotFound) { + return nil, nil, true, entryErr + } + return existing, entry, true, nil + } + if !errors.Is(err, ErrNotFound) { + return nil, nil, false, err + } + var ( stored *Record entry *QueueEntry @@ -92,7 +109,7 @@ func (s *SQLStore) AcceptQueued( // state instead of being guessed. uniqueRace bool ) - err := s.withWriteLock(func() error { + err = s.withWriteLock(func() error { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("execution: begin queue accept: %w", err) @@ -102,9 +119,19 @@ func (s *SQLStore) AcceptQueued( return e } - // Input idempotency is checked first, inside the transaction, so a - // retry of the same message never consumes queue capacity or an - // ordinal. + // The global budget row is written first and unconditionally: that write + // is the cross-instance lock for the global limit. A lock-free COUNT + // would let two gateways both see room and both insert. + if _, err := tx.ExecContext(ctx, + `UPDATE execution_queue_budget SET used = used WHERE budget_id = 1`); err != nil { + return fail(fmt.Errorf("execution: lock queue budget: %w", err)) + } + if err := s.lockAcceptableSession(ctx, tx, request.SessionID); err != nil { + return fail(err) + } + + // Recheck input idempotency inside the transaction before capacity or + // ordinal writes. A duplicate never consumes queue capacity or a slot. existing, err := s.getByClientMessageFrom(ctx, tx, request.SessionID, request.ClientMessageID) if err == nil { if existing.PayloadHash != request.PayloadHash { @@ -130,14 +157,6 @@ func (s *SQLStore) AcceptQueued( if !errors.Is(err, ErrNotFound) { return fail(err) } - - // The global budget row is written first and unconditionally: that write - // is the cross-instance lock for the global limit. A lock-free COUNT - // would let two gateways both see room and both insert. - if _, err := tx.ExecContext(ctx, - `UPDATE execution_queue_budget SET used = used WHERE budget_id = 1`); err != nil { - return fail(fmt.Errorf("execution: lock queue budget: %w", err)) - } var depth int64 if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM execution_queue`).Scan(&depth); err != nil { @@ -149,8 +168,9 @@ func (s *SQLStore) AcceptQueued( // Then the per-session ordinal allocator. Locking it is what serialises // concurrent enqueues for one session, so the depth check below cannot - // be overtaken by a sibling transaction. The order (budget, then - // session) is fixed so two enqueues can never take them in reverse. + // be overtaken by a sibling transaction. The order (budget, session + // lifecycle row, then session counter) is fixed so enqueues never take + // those locks in reverse. if _, err := tx.ExecContext(ctx, s.rebind(` INSERT INTO execution_queue_counters (session_id, next_seq) VALUES (?, 1) diff --git a/internal/execution/sql_store.go b/internal/execution/sql_store.go index 868752225..d7c2b9d5f 100644 --- a/internal/execution/sql_store.go +++ b/internal/execution/sql_store.go @@ -71,7 +71,8 @@ const executionColumns = `execution_id, session_id, client_message_id, payload_h error_code, created_at, updated_at, delivered_at, owner_instance_id, worker_run_id, lease_until, runtime_status, runtime_error_code, started_at, finished_at, fence_reason, - fence_version, fence_created_at` + fence_version, fence_created_at, turn_started_at, turn_deadline_at, + turn_policy_revision` // ListRecent bounds. A caller that passes no limit gets a page, not a table // dump: the operator console is a browsing surface, and "return everything" @@ -86,12 +87,13 @@ func (s *SQLStore) scanRecord(sc interface { }) (*Record, error) { r := new(Record) var deliveredAt, startedAt, finishedAt, fenceCreatedAt sql.NullInt64 + var turnStartedAt, turnDeadlineAt sql.NullInt64 err := sc.Scan( &r.ExecutionID, &r.SessionID, &r.ClientMessageID, &r.PayloadHash, &r.Status, &r.ErrorCode, &r.CreatedAt, &r.UpdatedAt, &deliveredAt, &r.OwnerInstanceID, &r.WorkerRunID, &r.LeaseUntil, &r.RuntimeStatus, &r.RuntimeErrorCode, &startedAt, &finishedAt, &r.FenceReason, - &r.FenceVersion, &fenceCreatedAt, + &r.FenceVersion, &fenceCreatedAt, &turnStartedAt, &turnDeadlineAt, &r.TurnPolicyRevision, ) if err != nil { return nil, err @@ -108,6 +110,12 @@ func (s *SQLStore) scanRecord(sc interface { if fenceCreatedAt.Valid { r.FenceCreatedAt = &fenceCreatedAt.Int64 } + if turnStartedAt.Valid { + r.TurnStartedAt = &turnStartedAt.Int64 + } + if turnDeadlineAt.Valid { + r.TurnDeadlineAt = &turnDeadlineAt.Int64 + } return r, nil } @@ -123,6 +131,37 @@ func (s *SQLStore) fenceEntryBump(setsFence bool) string { fence_created_at = CASE WHEN fence_reason = '' THEN ` + s.dbNowMillisExpr() + ` ELSE fence_created_at END` } +func (s *SQLStore) lockAcceptableSession(ctx context.Context, tx *sql.Tx, sessionID string) error { + result, err := tx.ExecContext(ctx, s.rebind(`UPDATE sessions SET state = state WHERE id = ?`), sessionID) + if err != nil { + return fmt.Errorf("execution: lock session for input acceptance: %w", err) + } + rows, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("execution: session lock rows affected: %w", err) + } + if rows == 0 { + return ErrSessionExpired + } + + var state, lifecyclePolicy string + var deletedAt, conversationExpiresAt sql.NullTime + if err := tx.QueryRowContext(ctx, s.rebind(`SELECT state, lifecycle_policy, deleted_at, conversation_expires_at + FROM sessions WHERE id = ?`), sessionID).Scan(&state, &lifecyclePolicy, &deletedAt, &conversationExpiresAt); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return ErrSessionExpired + } + return fmt.Errorf("execution: read session lifecycle for input acceptance: %w", err) + } + if state == "deleted" || deletedAt.Valid { + return ErrSessionExpired + } + if lifecyclePolicy == "v2" && conversationExpiresAt.Valid && !conversationExpiresAt.Time.After(time.Now()) { + return ErrSessionExpired + } + return nil +} + func (s *SQLStore) Accept(ctx context.Context, request AcceptRequest) (*Record, bool, error) { if request.SessionID == "" || request.ClientMessageID == "" || request.PayloadHash == "" { return nil, false, errors.New("execution: session id, client message id, and payload hash are required") @@ -137,6 +176,19 @@ func (s *SQLStore) Accept(ctx context.Context, request AcceptRequest) (*Record, ctx, cancel := withTimeout(ctx) defer cancel() + // Retried client IDs keep their original idempotency result even when the + // session has since been retired. This read creates no new obligation. + existing, err := s.getByClientMessage(ctx, request.SessionID, request.ClientMessageID) + if err == nil { + if existing.PayloadHash != request.PayloadHash { + return existing, true, ErrPayloadConflict + } + return existing, true, nil + } + if !errors.Is(err, ErrNotFound) { + return nil, false, err + } + now := time.Now().UnixMilli() record := &Record{ ExecutionID: "exec_" + uuid.NewString(), @@ -152,8 +204,20 @@ func (s *SQLStore) Accept(ctx context.Context, request AcceptRequest) (*Record, } var inserted bool - err := s.withWriteLock(func() error { - result, err := s.db.ExecContext(ctx, s.rebind(` + err = s.withWriteLock(func() error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("execution: begin input acceptance: %w", err) + } + fail := func(e error) error { + _ = tx.Rollback() + return e + } + if err := s.lockAcceptableSession(ctx, tx, request.SessionID); err != nil { + return fail(err) + } + + result, err := tx.ExecContext(ctx, s.rebind(` INSERT INTO execution_inputs (execution_id, session_id, client_message_id, payload_hash, status, error_code, created_at, updated_at, owner_instance_id, worker_run_id, lease_until, @@ -165,16 +229,16 @@ func (s *SQLStore) Accept(ctx context.Context, request AcceptRequest) (*Record, record.OwnerInstanceID, record.WorkerRunID, int64(LeaseTTL)*1000, record.RuntimeStatus) if err != nil { if s.dialect.IsUniqueViolation(err) { - return ErrSessionBusy + return fail(ErrSessionBusy) } - return fmt.Errorf("execution: accept input: %w", err) + return fail(fmt.Errorf("execution: accept input: %w", err)) } rows, err := result.RowsAffected() if err != nil { - return fmt.Errorf("execution: accept rows affected: %w", err) + return fail(fmt.Errorf("execution: accept rows affected: %w", err)) } inserted = rows == 1 - return nil + return tx.Commit() }) if err != nil { return nil, false, err @@ -187,7 +251,7 @@ func (s *SQLStore) Accept(ctx context.Context, request AcceptRequest) (*Record, return stored, false, nil } - existing, err := s.getByClientMessage(ctx, request.SessionID, request.ClientMessageID) + existing, err = s.getByClientMessage(ctx, request.SessionID, request.ClientMessageID) if err != nil { return nil, false, err } @@ -352,6 +416,72 @@ func (s *SQLStore) MarkRunning(ctx context.Context, executionID, ownerID, worker return nil } +func (s *SQLStore) SetTurnDeadline( + ctx context.Context, + executionID, ownerID string, + startedAt, deadlineAt int64, + policyRevision string, +) error { + if executionID == "" || ownerID == "" || startedAt <= 0 || deadlineAt <= startedAt || policyRevision == "" { + return errors.New("execution: valid execution, owner, turn timestamps, and policy revision are required") + } + + ctx, cancel := withTimeout(ctx) + defer cancel() + + now := time.Now().UnixMilli() + var rows int64 + err := s.withWriteLock(func() error { + result, err := s.db.ExecContext(ctx, s.rebind(` + UPDATE execution_inputs + SET turn_started_at = COALESCE(turn_started_at, ?), + turn_deadline_at = COALESCE(turn_deadline_at, ?), + turn_policy_revision = CASE + WHEN turn_policy_revision = '' THEN ? + ELSE turn_policy_revision + END, + updated_at = ? + WHERE execution_id = ? AND owner_instance_id = ? AND runtime_status = ? + AND ( + turn_deadline_at IS NULL OR + (turn_started_at = ? AND turn_deadline_at = ? AND turn_policy_revision = ?) + )`), + startedAt, deadlineAt, policyRevision, now, + executionID, ownerID, RuntimeRunning, + startedAt, deadlineAt, policyRevision) + if err != nil { + return fmt.Errorf("execution: set turn deadline: %w", err) + } + rows, _ = result.RowsAffected() + return nil + }) + if err != nil { + return err + } + if rows > 0 { + return nil + } + + record, err := s.getByID(ctx, executionID) + if err != nil { + return err + } + if record.OwnerInstanceID != ownerID { + return ErrOwnerMismatch + } + if record.TurnStartedAt != nil && + record.TurnDeadlineAt != nil && + *record.TurnStartedAt == startedAt && + *record.TurnDeadlineAt == deadlineAt && + record.TurnPolicyRevision == policyRevision { + return nil + } + if record.TurnDeadlineAt != nil { + return ErrTurnDeadlineConflict + } + return fmt.Errorf("execution: set turn deadline: runtime_status is %q, expected running", record.RuntimeStatus) +} + func (s *SQLStore) FinishRuntime(ctx context.Context, executionID, workerRunID string, status RuntimeStatus, errorCode string) error { if executionID == "" || workerRunID == "" { return errors.New("execution: execution id and worker run id are required") diff --git a/internal/execution/sql_store_test.go b/internal/execution/sql_store_test.go index 9b8bd6309..aff9c3f29 100644 --- a/internal/execution/sql_store_test.go +++ b/internal/execution/sql_store_test.go @@ -79,6 +79,110 @@ func TestSQLStore_AcceptIsIdempotentAndRejectsPayloadConflict(t *testing.T) { require.True(t, duplicate) } +func TestSQLStore_AcceptRejectsNewInputAfterSessionRetirement(t *testing.T) { + t.Parallel() + store, sessionStore := newTestSQLStore(t) + ctx := context.Background() + now := time.Now() + _, err := sessionStore.DB().ExecContext(ctx, + `UPDATE sessions SET state = ?, deleted_at = ? WHERE id = ?`, + string(events.StateDeleted), now, "session-1") + require.NoError(t, err) + + _, _, err = store.Accept(ctx, testAcceptReq("session-1", "message-after-delete", "hash")) + require.ErrorIs(t, err, ErrSessionExpired) +} + +func TestSQLStore_AcceptKeepsExistingDuplicateIdempotentAfterSessionRetirement(t *testing.T) { + t.Parallel() + store, sessionStore := newTestSQLStore(t) + ctx := context.Background() + request := testAcceptReq("session-1", "message-before-delete", "hash") + first, duplicate, err := store.Accept(ctx, request) + require.NoError(t, err) + require.False(t, duplicate) + + _, err = sessionStore.DB().ExecContext(ctx, + `UPDATE sessions SET state = ?, deleted_at = ? WHERE id = ?`, + string(events.StateDeleted), time.Now(), "session-1") + require.NoError(t, err) + + retry, duplicate, err := store.Accept(ctx, request) + require.NoError(t, err) + require.True(t, duplicate) + require.Equal(t, first.ExecutionID, retry.ExecutionID) +} + +func TestSQLStore_AcceptAndLifecycleRetirementAreSerialized(t *testing.T) { + t.Parallel() + store, sessionStore := newTestSQLStore(t) + ctx := context.Background() + now := time.Now() + expired := now.Add(-time.Hour) + _, err := sessionStore.DB().ExecContext(ctx, + `UPDATE sessions SET state = ?, lifecycle_policy = ?, lifecycle_policy_revision = ?, + conversation_expires_at = ?, history_expires_at = ? WHERE id = ?`, + string(events.StateTerminated), config.LifecyclePolicyV2, "revision-1", + expired, expired, "session-1") + require.NoError(t, err) + + start := make(chan struct{}) + var wg sync.WaitGroup + var accepted *Record + var acceptErr error + var retired *session.SessionInfo + var retireErr error + wg.Add(2) + go func() { + defer wg.Done() + <-start + accepted, _, acceptErr = store.Accept(ctx, testAcceptReq("session-1", "race-message", "hash")) + }() + go func() { + defer wg.Done() + <-start + retired, retireErr = sessionStore.RetireExpiredLifecycleSession(ctx, "session-1", now) + }() + close(start) + wg.Wait() + + require.NoError(t, retireErr) + if acceptErr == nil { + require.NotNil(t, accepted) + require.Nil(t, retired, "an accepted input must prevent concurrent retirement") + return + } + require.ErrorIs(t, acceptErr, ErrSessionExpired) + require.NotNil(t, retired, "if retirement wins, input acceptance must be rejected") +} + +func TestSQLStore_TurnDeadlineFieldsRoundTrip(t *testing.T) { + t.Parallel() + + store, _ := newTestSQLStore(t) + ctx := t.Context() + record, _, err := store.Accept(ctx, testAcceptReq("session-1", "turn-deadline", "hash")) + require.NoError(t, err) + require.Nil(t, record.TurnStartedAt) + require.Nil(t, record.TurnDeadlineAt) + require.Empty(t, record.TurnPolicyRevision) + + startedAt := time.Now().UnixMilli() + deadlineAt := startedAt + int64(30*time.Minute/time.Millisecond) + _, err = store.db.ExecContext(ctx, + `UPDATE execution_inputs + SET turn_started_at = ?, turn_deadline_at = ?, turn_policy_revision = ? + WHERE execution_id = ?`, + startedAt, deadlineAt, "policy-rev-1", record.ExecutionID) + require.NoError(t, err) + + got, err := store.ByID(ctx, record.ExecutionID) + require.NoError(t, err) + require.Equal(t, &startedAt, got.TurnStartedAt) + require.Equal(t, &deadlineAt, got.TurnDeadlineAt) + require.Equal(t, "policy-rev-1", got.TurnPolicyRevision) +} + func TestSQLStore_ConcurrentAcceptCreatesOneExecution(t *testing.T) { t.Parallel() store, _ := newTestSQLStore(t) @@ -149,6 +253,34 @@ func TestSQLStore_SetStatusIsIdempotentAndTerminal(t *testing.T) { require.Equal(t, first.DeliveredAt, stored.DeliveredAt) } +func TestSQLStore_SetTurnDeadlineIsImmutable(t *testing.T) { + t.Parallel() + + store, _ := newTestSQLStore(t) + ctx := context.Background() + record, _, err := store.Accept(ctx, testAcceptReq("session-1", "turn-deadline", "hash")) + require.NoError(t, err) + require.NoError(t, store.MarkRunning(ctx, record.ExecutionID, testOwner, testRun)) + + startedAt := time.Now().UnixMilli() + deadlineAt := startedAt + (30 * time.Minute).Milliseconds() + revision := "worker.turn_timeout=30m0s" + require.NoError(t, store.SetTurnDeadline(ctx, record.ExecutionID, testOwner, startedAt, deadlineAt, revision)) + + stored, err := store.getByID(ctx, record.ExecutionID) + require.NoError(t, err) + require.Equal(t, startedAt, *stored.TurnStartedAt) + require.Equal(t, deadlineAt, *stored.TurnDeadlineAt) + require.Equal(t, revision, stored.TurnPolicyRevision) + + require.NoError(t, store.SetTurnDeadline(ctx, record.ExecutionID, testOwner, startedAt, deadlineAt, revision), + "repeating the persisted snapshot must be idempotent") + require.ErrorIs(t, store.SetTurnDeadline(ctx, record.ExecutionID, testOwner, startedAt, deadlineAt+1, revision), + ErrTurnDeadlineConflict, "a later configuration must not extend an existing execution") + require.ErrorIs(t, store.SetTurnDeadline(ctx, record.ExecutionID, "other-owner", startedAt, deadlineAt, revision), + ErrOwnerMismatch) +} + func TestSQLStore_ConvergeDeliveryFailedConverges(t *testing.T) { t.Parallel() store, _ := newTestSQLStore(t) diff --git a/internal/execution/store.go b/internal/execution/store.go index c43444835..834f98e1e 100644 --- a/internal/execution/store.go +++ b/internal/execution/store.go @@ -99,6 +99,9 @@ var ( ErrNotFound = errors.New("execution: record not found") // ErrPayloadConflict means a client message ID was reused with different data. ErrPayloadConflict = errors.New("execution: client message id reused with different payload") + // ErrSessionExpired means a new input cannot be accepted because the + // session is retired or its v2 conversation deadline has passed. + ErrSessionExpired = errors.New("execution: session expired") // ErrSessionBusy means the session already has an active (pending/running or // fenced) execution and cannot accept a new one until the current one // terminates or the fence is cleared. @@ -112,6 +115,9 @@ var ( ErrLeaseExpired = errors.New("execution: lease expired") // ErrRunMismatch means a conditional update did not match the expected worker_run_id. ErrRunMismatch = errors.New("execution: worker run mismatch") + // ErrTurnDeadlineConflict means an execution already has a different + // immutable turn deadline. + ErrTurnDeadlineConflict = errors.New("execution: turn deadline conflict") // ErrFenceConflict means a conditional fence action did not match: the // record is missing, no longer fenced, or its fence_version moved on // (another operator or gateway instance acted first). The caller must @@ -173,6 +179,11 @@ type Record struct { // FenceCreatedAt is the millisecond timestamp at which the current fence // was raised; nil when the execution was never fenced. FenceCreatedAt *int64 + // TurnStartedAt and TurnDeadlineAt are immutable per-turn timestamps in + // Unix milliseconds. They remain NULL for legacy executions. + TurnStartedAt *int64 + TurnDeadlineAt *int64 + TurnPolicyRevision string } // AcceptRequest describes an input that must be durably accepted before dispatch. @@ -218,6 +229,11 @@ type Store interface { // the worker_run_id. Called after active gate passes, before Worker.Input. MarkRunning(ctx context.Context, executionID, ownerID, workerRunID string) error + // SetTurnDeadline stores the immutable absolute execution budget after the + // worker run is bound. Repeating the same snapshot is idempotent; a + // different deadline for the same execution is rejected. + SetTurnDeadline(ctx context.Context, executionID, ownerID string, startedAt, deadlineAt int64, policyRevision string) error + // FinishRuntime sets the terminal runtime status (completed/failed) for an // execution matching executionID and workerRunID. Sets finished_at, releases // the active gate, and stops lease renewal for this execution. Late Done diff --git a/internal/gateway/api.go b/internal/gateway/api.go index cbde53a6e..91ae763de 100644 --- a/internal/gateway/api.go +++ b/internal/gateway/api.go @@ -11,9 +11,8 @@ import ( "os/exec" "strconv" "strings" - "time" - "github.com/hrygo/hotplex/internal/audit" + "github.com/hrygo/hotplex/internal/admin" "github.com/hrygo/hotplex/internal/config" "github.com/hrygo/hotplex/internal/eventstore" "github.com/hrygo/hotplex/internal/messaging" @@ -35,21 +34,14 @@ type apiSM interface { } type GatewayAPI struct { - auth *security.Authenticator - sm apiSM - bridge SessionStarter - cfgStore *config.ConfigStore - turnsStore eventstore.TurnQuerier - eventStore EventStoreReader - wsStore WorkspaceReader // WebChat 多租户 workspace 归属校验(spec ①);nil = 未启用 - log *slog.Logger - auditCollector *audit.Collector // issue #833 P1; nil = audit disabled -} - -// SetAuditCollector injects the audit collector for session.delete instrumentation. -// Nil disables audit emission (no-op). -func (g *GatewayAPI) SetAuditCollector(c *audit.Collector) { - g.auditCollector = c + auth *security.Authenticator + sm apiSM + bridge SessionStarter + cfgStore *config.ConfigStore + turnsStore eventstore.TurnQuerier + eventStore EventStoreReader + wsStore WorkspaceReader // WebChat 多租户 workspace 归属校验(spec ①);nil = 未启用 + log *slog.Logger } // EventStoreReader defines the subset of EventStore needed by the events API. @@ -91,6 +83,10 @@ func (g *GatewayAPI) authorizeSession(w http.ResponseWriter, r *http.Request) (s writeAppError(w, http.StatusNotFound, "NOT_FOUND", "not found") return "", nil, false } + if si.State == events.StateDeleted || si.DeletedAt != nil { + writeAppError(w, http.StatusNotFound, "NOT_FOUND", "not found") + return "", nil, false + } if si.UserID != userID { writeAppError(w, http.StatusForbidden, "FORBIDDEN", "ownership required") return "", nil, false @@ -340,13 +336,12 @@ func (g *GatewayAPI) CreateSession(w http.ResponseWriter, r *http.Request) { // Idempotency check: if session exists and is active, just return it. if si, err := g.sm.Get(r.Context(), id); err == nil { - if si.State != events.StateDeleted { + if si.State != events.StateDeleted && si.DeletedAt == nil { respondJSON(w, map[string]string{"session_id": id}) return } - // If it's deleted, we must physically remove it before re-creating - // to avoid StateMachine transition errors and primary key conflicts. - _ = g.sm.DeletePhysical(r.Context(), id) + writeAppError(w, http.StatusGone, "SESSION_DELETED", "session is deleted; create a new session") + return } startParams := worker.SessionStartParams{ @@ -396,10 +391,10 @@ func (g *GatewayAPI) GetSession(w http.ResponseWriter, r *http.Request) { respondJSON(w, si) } -// DeleteSession terminates and deletes a session. +// DeleteSession marks a session deleted and schedules durable cleanup. // // @Summary Delete session -// @Description Gracefully terminates the worker and physically deletes the session. Ownership check applies. +// @Description Marks the session deleted, immediately hides it from history, and schedules cleanup. Ownership check applies. // @Tags Gateway API // @Security ApiKeyAuth // @Param id path string true "Session ID" @@ -410,56 +405,88 @@ func (g *GatewayAPI) GetSession(w http.ResponseWriter, r *http.Request) { // @Failure 500 {object} admin.ErrorResponse "Failed to delete session" // @Router /api/sessions/{id} [delete] func (g *GatewayAPI) DeleteSession(w http.ResponseWriter, r *http.Request) { - id, si, ok := g.authorizeSession(w, r) + id, _, ok := g.authorizeSession(w, r) if !ok { return } - // Gracefully terminate the worker through the state machine before deleting. - // Transition sends SIGTERM → wait → SIGKILL and releases pool quota. - if err := g.sm.Transition(r.Context(), id, events.StateTerminated); err != nil { - g.log.Debug("gateway: pre-delete transition skipped", "session_id", id, "err", err) - } - - if err := g.sm.DeletePhysical(r.Context(), id); err != nil { + ctx := session.WithDeleteAuditRequest(r.Context(), r.RemoteAddr, r.UserAgent()) + if err := g.sm.Delete(ctx, id); err != nil { g.log.Error("gateway: delete session failed", "session_id", id, "method", r.Method, "path", r.URL.Path, "err", err) - g.emitSessionDeleteAudit(r, si, id, audit.OutcomeFailure, err.Error()) writeAppError(w, http.StatusInternalServerError, "INTERNAL", "failed to delete session") return } - g.emitSessionDeleteAudit(r, si, id, audit.OutcomeSuccess, "") w.WriteHeader(http.StatusNoContent) } -// emitSessionDeleteAudit enqueues a session.delete audit event. -// Uses context.Background() so audit never blocks on request cancellation. -func (g *GatewayAPI) emitSessionDeleteAudit(r *http.Request, si *session.SessionInfo, sessionID, outcome, errMsg string) { - if g.auditCollector == nil { +// GetSessionCleanupStatus returns non-content cleanup progress for a deleted +// session. It authorizes through the purge job's immutable ownership snapshot. +// +// @Summary Get session cleanup status +// @Description Returns bounded progress for a deleted session without exposing conversation content or provider error text. +// @Tags Gateway API +// @Produce json +// @Security ApiKeyAuth +// @Param id path string true "Session ID" +// @Success 200 {object} admin.GatewaySessionCleanupStatusResponse +// @Failure 401 {object} admin.ErrorResponse "Unauthorized" +// @Failure 403 {object} admin.ErrorResponse "Ownership required" +// @Failure 404 {object} admin.ErrorResponse "Cleanup status not found" +// @Failure 503 {object} admin.ErrorResponse "Cleanup status unavailable" +// @Router /api/sessions/{id}/cleanup [get] +func (g *GatewayAPI) GetSessionCleanupStatus(w http.ResponseWriter, r *http.Request) { + userID, _, _, err := g.auth.AuthenticateRequest(r) + if err != nil { + writeAppError(w, http.StatusUnauthorized, "UNAUTHORIZED", "unauthorized") return } - // #848: carry the unified identity keys so REST deletes correlate with WS/ - // cron session events. errMsg (if any) is merged into the same payload. - // #866: stamp the effective-spec fingerprint when a snapshot was bound. - fields := session.AuditDetailFields(si.BotID, string(si.WorkerType), si.EffectiveIdentity()) - si.SpecSnapshot.StampMetadata(fields) - if errMsg != "" { - fields["error"] = errMsg - } - b, _ := json.Marshal(fields) - _ = g.auditCollector.Enqueue(context.Background(), &audit.UserActivity{ - Ts: time.Now().UnixMilli(), - UserID: si.UserID, - UserIDType: audit.UserIDTypePlatform, - Platform: si.Platform, - SessionID: sessionID, - Action: audit.ActionSessionDelete, - ResourceType: "session", - ResourceID: sessionID, - Outcome: outcome, - DetailJSON: string(b), - IP: r.RemoteAddr, - UserAgent: r.UserAgent(), - }) + id := r.PathValue("id") + if id == "" { + writeAppError(w, http.StatusBadRequest, "BAD_REQUEST", "session id required") + return + } + reader, ok := g.sm.(session.PurgeStatusReader) + if !ok { + writeAppError(w, http.StatusServiceUnavailable, "PURGE_STATUS_UNAVAILABLE", "cleanup status unavailable") + return + } + status, err := reader.GetPurgeStatus(r.Context(), id, userID) + if errors.Is(err, session.ErrPurgeNotFound) { + writeAppError(w, http.StatusNotFound, "NOT_FOUND", "cleanup status not found") + return + } + if err != nil { + g.log.Error("gateway: load session cleanup status failed", "session_id", id, "err", err) + writeAppError(w, http.StatusServiceUnavailable, "PURGE_STATUS_UNAVAILABLE", "cleanup status unavailable") + return + } + if status.WorkspaceID != "" && g.wsStore != nil { + ws, wsErr := g.wsStore.GetWorkspaceByID(r.Context(), status.WorkspaceID) + if wsErr != nil || ws.OwnerUserID != userID { + writeAppError(w, http.StatusForbidden, "WORKSPACE_FORBIDDEN", "ownership required") + return + } + } + response := admin.GatewaySessionCleanupStatusResponse{ + JobID: status.JobID, + SessionID: status.SessionID, + Status: string(status.Status), + RequestedAt: status.RequestedAt, + UpdatedAt: status.UpdatedAt, + CompletedAt: status.CompletedAt, + Items: make([]admin.GatewaySessionCleanupStatusItem, 0, len(status.Items)), + } + for _, item := range status.Items { + response.Items = append(response.Items, admin.GatewaySessionCleanupStatusItem{ + Kind: item.Kind, + Status: string(item.Status), + Attempts: item.Attempts, + NextAttemptAt: item.NextAttemptAt, + CompletedAt: item.CompletedAt, + ErrorCode: item.ErrorCode, + }) + } + respondJSON(w, response) } // SwitchWorkDir changes the working directory for a session. diff --git a/internal/gateway/api_test.go b/internal/gateway/api_test.go index a6ca88486..63d48e439 100644 --- a/internal/gateway/api_test.go +++ b/internal/gateway/api_test.go @@ -59,6 +59,14 @@ func (m *mockAPISM) Get(_ context.Context, id string) (*session.SessionInfo, err return args.Get(0).(*session.SessionInfo), args.Error(1) } +func (m *mockAPISM) GetPurgeStatus(ctx context.Context, sessionID, userID string) (*session.PurgeStatus, error) { + args := m.Called(ctx, sessionID, userID) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*session.PurgeStatus), args.Error(1) +} + func (m *mockAPISM) GetWorker(id string) worker.Worker { args := m.Called(id) if args.Get(0) == nil { @@ -206,6 +214,18 @@ func (m *mockTurnsStore) LatestSeq(ctx context.Context, sessionID string) (int64 return args.Get(0).(int64), args.Error(1) } +type mockAPIEventStore struct { + mock.Mock +} + +func (m *mockAPIEventStore) QueryBySession(ctx context.Context, sessionID string, cursor int64, dir eventstore.CursorDirection, limit int) (*eventstore.EventPage, error) { + args := m.Called(ctx, sessionID, cursor, dir, limit) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*eventstore.EventPage), args.Error(1) +} + // ─── Test helpers ─────────────────────────────────────────────────────────────── func newTestAuth(t *testing.T) *security.Authenticator { @@ -268,8 +288,10 @@ func setupMux(api *GatewayAPI) *http.ServeMux { mux.HandleFunc("POST /api/sessions", api.CreateSession) mux.HandleFunc("GET /api/sessions/{id}", api.GetSession) mux.HandleFunc("DELETE /api/sessions/{id}", api.DeleteSession) + mux.HandleFunc("GET /api/sessions/{id}/cleanup", api.GetSessionCleanupStatus) mux.HandleFunc("POST /api/sessions/{id}/cd", api.SwitchWorkDir) mux.HandleFunc("GET /api/sessions/{id}/history", api.GetHistory) + mux.HandleFunc("GET /api/sessions/{id}/events", api.GetEvents) mux.HandleFunc("GET /api/workers", api.ListWorkers) return mux } @@ -380,7 +402,7 @@ func TestCreateSession_IdempotentActiveSession(t *testing.T) { bridge.AssertNotCalled(t, "StartSession", mock.Anything) } -func TestCreateSession_DeletedSessionRecreated(t *testing.T) { +func TestCreateSession_DeletedSessionCannotBeReused(t *testing.T) { t.Parallel() sm := new(mockAPISM) bridge := new(mockAPIBridge) @@ -388,15 +410,13 @@ func TestCreateSession_DeletedSessionRecreated(t *testing.T) { deleted := &session.SessionInfo{ID: "deleted-id", State: events.StateDeleted} sm.On("Get", mock.Anything).Return(deleted, nil) - sm.On("DeletePhysical", mock.Anything, mock.Anything).Return(nil) - bridge.On("StartSession", mock.Anything, mock.Anything).Return(nil) w := httptest.NewRecorder() api.CreateSession(w, authedReq("POST", "/api/sessions?workspace_id=ws-test&client_session_id=test-csid&title=test", nil)) - require.Equal(t, http.StatusOK, w.Code) - sm.AssertCalled(t, "DeletePhysical", mock.Anything, mock.Anything) - bridge.AssertExpectations(t) + require.Equal(t, http.StatusGone, w.Code) + sm.AssertNotCalled(t, "DeletePhysical", mock.Anything, mock.Anything) + bridge.AssertNotCalled(t, "StartSession", mock.Anything, mock.Anything) } func TestCreateSession_BridgeError(t *testing.T) { @@ -537,42 +557,39 @@ func TestCreateSession_WorkDirFromWorkspace(t *testing.T) { // ─── DeleteSession tests ──────────────────────────────────────────────────────── -func TestDeleteSession_GracefulTermination(t *testing.T) { +func TestDeleteSession_UsesLifecycleDelete(t *testing.T) { t.Parallel() sm := new(mockAPISM) bridge := new(mockAPIBridge) api := newTestAPI(t, sm, bridge) sm.On("Get", "sess-1").Return(&session.SessionInfo{ID: "sess-1", UserID: "anonymous"}, nil) - sm.On("Transition", mock.Anything, "sess-1", events.StateTerminated).Return(nil) - sm.On("DeletePhysical", mock.Anything, "sess-1").Return(nil) + sm.On("Delete", mock.Anything, "sess-1").Return(nil) mux := setupMux(api) w := httptest.NewRecorder() mux.ServeHTTP(w, authedReq("DELETE", "/api/sessions/sess-1", nil)) require.Equal(t, http.StatusNoContent, w.Code) - sm.AssertCalled(t, "Transition", mock.Anything, "sess-1", events.StateTerminated) - sm.AssertCalled(t, "DeletePhysical", mock.Anything, "sess-1") + sm.AssertCalled(t, "Delete", mock.Anything, "sess-1") + sm.AssertNotCalled(t, "DeletePhysical", mock.Anything, "sess-1") } -func TestDeleteSession_TransitionFailsStillDeletes(t *testing.T) { +func TestDeleteSession_DeleteFailureReturnsError(t *testing.T) { t.Parallel() sm := new(mockAPISM) bridge := new(mockAPIBridge) api := newTestAPI(t, sm, bridge) sm.On("Get", "sess-2").Return(&session.SessionInfo{ID: "sess-2", UserID: "anonymous"}, nil) - sm.On("Transition", mock.Anything, "sess-2", events.StateTerminated).Return(errTestBridge) - sm.On("DeletePhysical", mock.Anything, "sess-2").Return(nil) + sm.On("Delete", mock.Anything, "sess-2").Return(errTestBridge) mux := setupMux(api) w := httptest.NewRecorder() mux.ServeHTTP(w, authedReq("DELETE", "/api/sessions/sess-2", nil)) - // Transition failure is tolerated; delete still proceeds - require.Equal(t, http.StatusNoContent, w.Code) - sm.AssertCalled(t, "DeletePhysical", mock.Anything, "sess-2") + require.Equal(t, http.StatusInternalServerError, w.Code) + sm.AssertCalled(t, "Delete", mock.Anything, "sess-2") } func TestDeleteSession_MissingID(t *testing.T) { @@ -589,6 +606,46 @@ func TestDeleteSession_MissingID(t *testing.T) { require.Equal(t, http.StatusNotFound, w.Code) } +func TestGetSessionCleanupStatusUsesOwnershipSnapshot(t *testing.T) { + t.Parallel() + sm := new(mockAPISM) + bridge := new(mockAPIBridge) + status := &session.PurgeStatus{ + JobID: "purge-1", SessionID: "sess-deleted", + Status: session.PurgeJobInProgress, WorkspaceID: "ws-test", + Items: []session.PurgeItemStatus{{ + Kind: session.PurgeItemConversationContent, Status: session.PurgeItemPending, + }}, + } + sm.On("GetPurgeStatus", mock.Anything, "sess-deleted", "anonymous").Return(status, nil).Once() + api := newTestAPIWithWorkspace(t, sm, bridge, ownedWorkspaceMock("anonymous", "/tmp/project")) + w := httptest.NewRecorder() + setupMux(api).ServeHTTP(w, authedReq("GET", "/api/sessions/sess-deleted/cleanup", nil)) + + require.Equal(t, http.StatusOK, w.Code, w.Body.String()) + var body map[string]any + require.NoError(t, json.NewDecoder(w.Body).Decode(&body)) + require.Equal(t, "purge-1", body["job_id"]) + require.Equal(t, "in_progress", body["status"]) + require.NotContains(t, body, "workspace_id", + "the persisted authorization snapshot is not part of the public response") + sm.AssertExpectations(t) +} + +func TestGetSessionCleanupStatusHidesOtherOwners(t *testing.T) { + t.Parallel() + sm := new(mockAPISM) + bridge := new(mockAPIBridge) + sm.On("GetPurgeStatus", mock.Anything, "sess-private", "anonymous"). + Return(nil, session.ErrPurgeNotFound).Once() + api := newTestAPI(t, sm, bridge) + w := httptest.NewRecorder() + setupMux(api).ServeHTTP(w, authedReq("GET", "/api/sessions/sess-private/cleanup", nil)) + + require.Equal(t, http.StatusNotFound, w.Code, w.Body.String()) + sm.AssertExpectations(t) +} + // ─── ListSessions tests ───────────────────────────────────────────────────────── func TestListSessions(t *testing.T) { @@ -776,6 +833,53 @@ func TestGetHistory_Success(t *testing.T) { require.False(t, resp.HasMore) } +func TestGetHistory_DeletedSessionIsHidden(t *testing.T) { + t.Parallel() + sm := new(mockAPISM) + bridge := new(mockAPIBridge) + ts := new(mockTurnsStore) + api := newTestAPIWithTurns(t, sm, bridge, ts) + + sm.On("Get", "sess-deleted").Return(&session.SessionInfo{ + ID: "sess-deleted", UserID: "anonymous", State: events.StateDeleted, + }, nil) + + mux := setupMux(api) + w := httptest.NewRecorder() + mux.ServeHTTP(w, authedReq("GET", "/api/sessions/sess-deleted/history", nil)) + + require.Equal(t, http.StatusNotFound, w.Code) + ts.AssertNotCalled(t, "QueryLatestTurns", mock.Anything, mock.Anything, mock.Anything) +} + +func TestGetHistory_ExpiredSessionStillUsesPerRecordExpiry(t *testing.T) { + t.Parallel() + sm := new(mockAPISM) + bridge := new(mockAPIBridge) + ts := new(mockTurnsStore) + api := newTestAPIWithTurns(t, sm, bridge, ts) + + expiredAt := time.Now().Add(-time.Second) + sm.On("Get", "sess-1").Return(&session.SessionInfo{ + ID: "sess-1", UserID: "anonymous", HistoryExpiresAt: &expiredAt, + }, nil) + ts.On("QueryLatestTurns", mock.Anything, "sess-1", 51).Return(nil, eventstore.ErrNotFound) + + mux := setupMux(api) + w := httptest.NewRecorder() + mux.ServeHTTP(w, authedReq("GET", "/api/sessions/sess-1/history", nil)) + + require.Equal(t, http.StatusOK, w.Code) + var resp struct { + Records []any `json:"records"` + HasMore bool `json:"has_more"` + } + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + require.Empty(t, resp.Records) + require.False(t, resp.HasMore) + ts.AssertExpectations(t) +} + func TestGetHistory_ReturnsClientMessageID(t *testing.T) { t.Parallel() sm := new(mockAPISM) @@ -864,6 +968,36 @@ func TestGetHistory_NoRecords(t *testing.T) { require.False(t, resp.HasMore) } +func TestGetEvents_ExpiredSessionStillUsesPerRecordExpiry(t *testing.T) { + t.Parallel() + sm := new(mockAPISM) + bridge := new(mockAPIBridge) + api := newTestAPI(t, sm, bridge) + eventReader := new(mockAPIEventStore) + api.eventStore = eventReader + + expiredAt := time.Now().Add(-time.Second) + sm.On("Get", "sess-1").Return(&session.SessionInfo{ + ID: "sess-1", UserID: "anonymous", HistoryExpiresAt: &expiredAt, + }, nil) + eventReader.On("QueryBySession", mock.Anything, "sess-1", int64(0), eventstore.CursorLatest, 200). + Return(nil, eventstore.ErrNotFound) + + mux := setupMux(api) + w := httptest.NewRecorder() + mux.ServeHTTP(w, authedReq("GET", "/api/sessions/sess-1/events", nil)) + + require.Equal(t, http.StatusOK, w.Code) + var resp struct { + Events []any `json:"events"` + HasOlder bool `json:"has_older"` + } + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + require.Empty(t, resp.Events) + require.False(t, resp.HasOlder) + eventReader.AssertExpectations(t) +} + func TestGetHistory_Unauthorized(t *testing.T) { t.Parallel() sm := new(mockAPISM) diff --git a/internal/gateway/bridge.go b/internal/gateway/bridge.go index e1f4ea3d8..062e5870d 100644 --- a/internal/gateway/bridge.go +++ b/internal/gateway/bridge.go @@ -171,6 +171,12 @@ type workerRunLifecycle struct { terminalCommitted atomic.Bool done chan struct{} conn worker.SessionConn + + turnTimeoutMu sync.Mutex + turnTimeoutTimer *time.Timer + turnTimeoutDeadline time.Time + turnTimeoutGeneration uint64 + turnTimeoutHandler func(uint64) } func newWorkerRunLifecycle(conn worker.SessionConn) *workerRunLifecycle { @@ -1494,25 +1500,61 @@ func (b *Bridge) deliverInputReplayForSession(ctx context.Context, sessionID str } replay = validated } - if replay.Skill == nil { - if replay.Content == "" { - return nil + if replay.Skill == nil && replay.Content == "" { + return nil + } + + runID := "" + if sessionID != "" { + restoredDeadline := false + if b.executionStore != nil { + record, err := b.executionStore.OpenBySession(ctx, sessionID) + if err == nil && record.TurnStartedAt != nil && record.TurnDeadlineAt != nil { + var ok bool + runID, ok = b.CurrentWorkerRunID(sessionID) + if !ok { + return errWorkerRunChanged + } + if err := b.RecordTurnDeadlineForRun(sessionID, runID, *record.TurnStartedAt, *record.TurnDeadlineAt); err != nil { + return fmt.Errorf("bridge: restore turn deadline: %w", err) + } + restoredDeadline = true + } else if err != nil && !errors.Is(err, execution.ErrNotFound) { + return fmt.Errorf("bridge: load turn deadline: %w", err) + } } - return w.Input(ctx, replay.Content, nil) - } - if invoker, ok := worker.AsNativeInvoker(w); ok { - return invoker.InvokeNativeCommand(ctx, *replay.Skill) - } - // The replacement Worker lacks any native command path — typically - // because worker_type changed between the crash and recovery. The - // invocation must NOT be replayed as an ordinary prompt: that would turn - // "execute the Skill" into "let the model guess the Skill" (plan - // constraint #7; NativeCommandInvoker contract: "Workers that cannot - // execute a native command must not receive the invocation as an ordinary - // prompt"). Fail loudly instead of silently degrading the semantics. - b.log.Warn("bridge: native Skill replay rejected, replacement worker lacks native command invoker", - "worker_type", w.Type(), "skill", replay.Skill.Name) - return fmt.Errorf("%w: worker %s cannot replay native Skill %q as text", worker.ErrSkillNotSupported, w.Type(), replay.Skill.Name) + if !restoredDeadline { + if b.turnTimeout > 0 { + var ok bool + runID, ok = b.CurrentWorkerRunID(sessionID) + if !ok { + return errWorkerRunChanged + } + if _, _, _, err := b.RecordTurnStartForRun(sessionID, runID); err != nil { + return fmt.Errorf("bridge: arm replay turn deadline: %w", err) + } + } + } + } + + var err error + if replay.Skill == nil { + err = w.Input(ctx, replay.Content, nil) + } else if invoker, ok := worker.AsNativeInvoker(w); ok { + err = invoker.InvokeNativeCommand(ctx, *replay.Skill) + } else { + // The replacement Worker lacks any native command path — typically + // because worker_type changed between the crash and recovery. The + // invocation must NOT be replayed as an ordinary prompt: that would turn + // "execute the Skill" into "let the model guess the Skill". + b.log.Warn("bridge: native Skill replay rejected, replacement worker lacks native command invoker", + "worker_type", w.Type(), "skill", replay.Skill.Name) + err = fmt.Errorf("%w: worker %s cannot replay native Skill %q as text", worker.ErrSkillNotSupported, w.Type(), replay.Skill.Name) + } + if err != nil && sessionID != "" && runID != "" { + b.ClearTurnStartForRun(sessionID, runID) + } + return err } // firstNonEmpty returns the first non-empty string from the given values. diff --git a/internal/gateway/bridge_forward.go b/internal/gateway/bridge_forward.go index c76764612..e421c3dec 100644 --- a/internal/gateway/bridge_forward.go +++ b/internal/gateway/bridge_forward.go @@ -61,7 +61,6 @@ type forwardContext struct { lastError *events.ErrorData pendingError *events.Envelope turnTimerFired atomic.Bool - turnTimer *time.Timer lifecycle *workerRunLifecycle // flog carries trace_id from the forwardEvents OTel span. Helpers must // use fc.flog (not b.log) to keep log lines correlatable with the span. @@ -235,38 +234,45 @@ func (b *Bridge) forwardEvents(fb forwarderBinding, sessionID string, opts forwa } } - if b.turnTimeout > 0 { - fc.turnTimer = time.AfterFunc(b.turnTimeout, func() { - releaseEvent, admitted := fc.lifecycle.beginEvent() - if !admitted { - return - } - defer releaseEvent() - if !fc.turnTimerFired.CompareAndSwap(false, true) { - return - } - if !fc.claimTerminal() { - return - } - flog.Warn("bridge: turn timeout exceeded, terminating worker", - "session_id", sessionID, "worker_type", workerType, "turn_timeout", b.turnTimeout) - b.sendError(sessionID, events.ErrCodeTurnTimeout, "Turn exceeded %v time limit and was terminated.", b.turnTimeout) - acc := b.getOrInitAccum(sessionID, fc.workDir, fc.startTime) - b.captureSyntheticEvent(syntheticTurnParams{ - SessionID: sessionID, - Reason: "turn_timeout", - Message: fmt.Sprintf("Turn exceeded %v time limit", b.turnTimeout), - Source: eventstore.SourceTimeout, - Platform: fc.sessPlatform, - Owner: fc.sessOwner, - Model: acc.ModelName, - Generation: acc.Generation.Load(), - TurnNum: int(acc.TurnCount.Load()), - }) - _ = w.Terminate(context.Background()) + fc.lifecycle.setTurnTimeoutHandler(func(generation uint64) { + releaseEvent, admitted := fc.lifecycle.beginEvent() + if !admitted { + return + } + defer releaseEvent() + if !fc.lifecycle.turnTimeoutCurrent(generation) { + return + } + if !fc.turnTimerFired.CompareAndSwap(false, true) { + return + } + if !fc.claimTerminal() { + return + } + fc.lifecycle.stopTurnTimeout() + acc := b.getOrInitAccum(sessionID, fc.workDir, fc.startTime) + acc.clearTurnStart() + b.finishTurnTTFT(sessionID, "timeout") + fc.turnText.Reset() + fc.hasRealText = false + flog.Warn("bridge: turn timeout exceeded, terminating worker", + "session_id", sessionID, "worker_type", workerType, "turn_timeout", b.turnTimeout) + if err := w.Terminate(context.Background()); err != nil { + flog.Warn("bridge: terminate worker after turn timeout", "session_id", sessionID, "err", err) + } + b.finishTurnTimeout(sessionID, fc, syntheticTurnParams{ + SessionID: sessionID, + Reason: "turn_timeout", + Message: fmt.Sprintf("Turn exceeded %v time limit", b.turnTimeout), + Source: eventstore.SourceTimeout, + Platform: fc.sessPlatform, + Owner: fc.sessOwner, + Model: acc.ModelName, + Generation: acc.Generation.Load(), + TurnNum: int(acc.TurnCount.Load()), }) - defer fc.turnTimer.Stop() - } + }) + defer fc.lifecycle.clearTurnTimeoutHandler() // Event source: the frozen Conn captured at launch (Fix A). forwardEvents // MUST NOT call w.Conn() here — doing so would re-read the mutable field and @@ -497,9 +503,6 @@ func (b *Bridge) processForwardedEvent(env *events.Envelope, w worker.Worker, op } } - if fc.turnTimer != nil && !fc.turnTimerFired.Load() { - fc.turnTimer.Reset(b.turnTimeout) - } if fc.turnTimerFired.Load() { return } @@ -589,6 +592,10 @@ func (b *Bridge) processForwardedEvent(env *events.Envelope, w worker.Worker, op return // continue — retry produces new events on recv } + if env.Event.Type == events.Done || env.Event.Type == events.Error { + fc.lifecycle.stopTurnTimeout() + } + if env.Event.Type == events.Done && isDone && !doneData.Success && fc.pendingError != nil { // A failed turn with a worker Error has one user-visible terminal: the // Error. The Done already owns the bridge fence, so sending it first @@ -801,6 +808,51 @@ func (b *Bridge) emitRuntimeFactOnDone(sessionID string, fact *pendingRuntimeFac _ = b.hub.SendToSession(context.Background(), rtEnv) } +// finishTurnTimeout closes the durable runtime execution before publishing its +// terminal events. A timeout is an ordinary failed execution: releasing the +// active gate lets the queued head proceed after the Worker has been stopped. +// The error and runtime fact are sent in sequence order; the synthetic Done is +// persisted for history but remains hidden from the client. +func (b *Bridge) finishTurnTimeout(sessionID string, fc *forwardContext, turn syntheticTurnParams) { + if b.hub == nil { + return + } + + if b.collector != nil { + releaseSeq, ok := b.hub.BeginSeqOperation(sessionID) + if !ok { + return + } + defer releaseSeq() + order := b.hub.SeqOrderLock(sessionID) + order.Lock() + defer order.Unlock() + } + + done := events.NewEnvelope(aep.NewID(), sessionID, 0, events.Done, events.DoneData{ + Success: false, + Reason: turn.Reason, + }) + done.OwnerID = fc.sessOwner + runtimeFact := b.finishRuntimeOnDone(sessionID, fc, done) + + errEnv := events.NewEnvelope(aep.NewID(), sessionID, 0, events.Error, events.ErrorData{ + Code: events.ErrCodeTurnTimeout, + Message: fmt.Sprintf("Turn exceeded %v time limit and was terminated.", b.turnTimeout), + }) + errEnv.OwnerID = fc.sessOwner + if b.collector != nil { + errEnv.Seq = b.hub.NextSeqHeld(sessionID) + } + if err := b.hub.SendToSession(context.Background(), errEnv); err != nil { + b.flogOf(fc).Warn("bridge: send turn timeout error", "session_id", sessionID, "err", err) + } + b.captureEvent(sessionID, errEnv.Seq, errEnv.Event.Type, errEnv.Event.Data, b.flogOf(fc)) + + b.captureSyntheticEventHeld(turn) + b.emitRuntimeFactOnDone(sessionID, runtimeFact) +} + // extractTurnContent extracts message/reasoning content for turn tracking. func (b *Bridge) extractTurnContent(env *events.Envelope, fc *forwardContext) (deltaContent, reasoningContent string) { switch env.Event.Type { @@ -877,9 +929,6 @@ func (b *Bridge) accumulateStats(env *events.Envelope, w worker.Worker, opts for b.emitToolCallAudit(fc, &tc) } case events.Done: - if fc.turnTimer != nil { - fc.turnTimer.Stop() - } acc := b.getOrInitAccum(sessionID, opts.workDir, fc.startTime) success := false if dd, ok := asDoneData(env.Event.Data); ok { @@ -1489,12 +1538,7 @@ func (b *Bridge) captureSyntheticEvent(p syntheticTurnParams) { if b.collector == nil { return } - data, err := json.Marshal(map[string]any{ - "success": false, - "reason": p.Reason, - "message": p.Message, - "synthetic": true, - }) + data, err := marshalSyntheticEvent(p) if err != nil { return } @@ -1504,25 +1548,62 @@ func (b *Bridge) captureSyntheticEvent(p syntheticTurnParams) { data, p.Source, func(seq int64) { - sFalse := false - b.collector.CaptureTurn(&eventstore.TurnWriteRequest{ - SessionID: p.SessionID, - Generation: p.Generation, - TurnNum: p.TurnNum, - Seq: seq, - Role: eventstore.RoleAssistant, - Content: p.Message, - Platform: p.Platform, - UserID: p.Owner, - Model: p.Model, - Source: p.Source, - Success: &sFalse, - CreatedAt: time.Now().UnixMilli(), - }) + b.captureSyntheticTurn(p, seq) }, ) } +// captureSyntheticEventHeld is the sequence-lease variant used when a caller +// already owns the Hub barrier and publish-order lock. +func (b *Bridge) captureSyntheticEventHeld(p syntheticTurnParams) { + if b.collector == nil { + return + } + data, err := marshalSyntheticEvent(p) + if err != nil { + return + } + b.nextSeqAndCaptureHeld( + p.SessionID, + events.Done, + data, + p.Source, + func(seq int64) { + b.captureSyntheticTurn(p, seq) + }, + ) +} + +func marshalSyntheticEvent(p syntheticTurnParams) ([]byte, error) { + return json.Marshal(map[string]any{ + "success": false, + "reason": p.Reason, + "message": p.Message, + "synthetic": true, + }) +} + +func (b *Bridge) captureSyntheticTurn(p syntheticTurnParams, seq int64) { + if b.collector == nil { + return + } + sFalse := false + b.collector.CaptureTurn(&eventstore.TurnWriteRequest{ + SessionID: p.SessionID, + Generation: p.Generation, + TurnNum: p.TurnNum, + Seq: seq, + Role: eventstore.RoleAssistant, + Content: p.Message, + Platform: p.Platform, + UserID: p.Owner, + Model: p.Model, + Source: p.Source, + Success: &sFalse, + CreatedAt: time.Now().UnixMilli(), + }) +} + // captureAssistantTurn writes an assistant turn record from the done event path. func (b *Bridge) captureAssistantTurn(sessionID string, seq int64, acc *sessionAccumulator, content, owner, platform string, timestamp int64) { if b.collector == nil { diff --git a/internal/gateway/bridge_forward_seq_test.go b/internal/gateway/bridge_forward_seq_test.go index e12ee8ffe..bafbe08ef 100644 --- a/internal/gateway/bridge_forward_seq_test.go +++ b/internal/gateway/bridge_forward_seq_test.go @@ -41,7 +41,35 @@ func newCollectorBridgeForSeqTest(t *testing.T, sessionID string, knownSeq int64 direction TEXT NOT NULL DEFAULT 'outbound', source TEXT NOT NULL DEFAULT 'normal' CHECK(source IN ('normal', 'crash', 'timeout', 'fresh_start')), - created_at INTEGER NOT NULL + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL DEFAULT 0 + )`) + require.NoError(t, err) + createEventStoreSessionBarrierSchema(t, db) + _, err = db.Exec(`CREATE TABLE IF NOT EXISTS turns ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id TEXT NOT NULL, + client_message_id TEXT, + generation INTEGER NOT NULL DEFAULT 1, + turn_num INTEGER NOT NULL, + seq INTEGER NOT NULL DEFAULT 0, + role TEXT NOT NULL, + content TEXT NOT NULL DEFAULT '', + platform TEXT NOT NULL DEFAULT '', + user_id TEXT NOT NULL DEFAULT '', + model TEXT NOT NULL DEFAULT '', + success INTEGER, + source TEXT NOT NULL DEFAULT 'normal', + tools_json TEXT, + tool_count INTEGER NOT NULL DEFAULT 0, + tokens_input INTEGER NOT NULL DEFAULT 0, + tokens_cache_write INTEGER NOT NULL DEFAULT 0, + tokens_cache_read INTEGER NOT NULL DEFAULT 0, + tokens_out INTEGER NOT NULL DEFAULT 0, + duration_ms INTEGER NOT NULL DEFAULT 0, + cost_usd REAL NOT NULL DEFAULT 0.0, + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL DEFAULT 0 )`) require.NoError(t, err) @@ -134,6 +162,48 @@ func TestProcessForwardedEvent_NoCollectorPreservesWorkerSeq(t *testing.T) { "Hub SeqGen must not advance when collector is disabled and worker provided a non-zero seq") } +func TestFinishTurnTimeoutPersistsFailureAndAllocatesOrderedEvents(t *testing.T) { + t.Parallel() + + const sessionID = "turn-timeout-terminal" + b, eventStore := newCollectorBridgeForSeqTest(t, sessionID, 0) + execStore := &fakeExecutionStore{ + openRecord: &execution.Record{ExecutionID: "execution-1"}, + } + b.executionStore = execStore + b.turnTimeout = 30 * time.Minute + fc := &forwardContext{ + sessionID: sessionID, + workerRunID: "worker-run-1", + sessOwner: "owner-1", + } + + b.finishTurnTimeout(sessionID, fc, syntheticTurnParams{ + SessionID: sessionID, + Reason: "turn_timeout", + Message: "Turn exceeded 30m time limit", + Source: eventstore.SourceTimeout, + Owner: "owner-1", + }) + + require.Equal(t, execution.RuntimeFailed, execStore.finishStatus) + require.Equal(t, "worker-run-1", execStore.finishRunID) + require.Equal(t, int64(3), b.hub.NextSeqPeek(sessionID), + "timeout Error, persisted synthetic Done, and runtime failure fact must allocate in order") + require.NoError(t, b.collector.Flush()) + page, err := eventStore.QueryBySession(t.Context(), sessionID, 0, eventstore.CursorLatest, 10) + require.NoError(t, err) + require.Len(t, page.Events, 2) + require.Equal(t, []string{string(events.Error), string(events.Done)}, []string{ + page.Events[0].Type, + page.Events[1].Type, + }) + require.Equal(t, []int64{1, 2}, []int64{ + page.Events[0].Seq, + page.Events[1].Seq, + }) +} + // TestProcessForwardedEvent_DoneArrivesAfterTheRuntimeFactItOvertakes pins the // client-visible order AND seq invariant for the terminal pair: the Done // arrives first and the terminal runtime fact follows it, with seq increasing diff --git a/internal/gateway/bridge_worker.go b/internal/gateway/bridge_worker.go index 895e333d9..1065316a5 100644 --- a/internal/gateway/bridge_worker.go +++ b/internal/gateway/bridge_worker.go @@ -217,6 +217,99 @@ func (b *Bridge) createAndLaunchWorker(params workerLaunchParams, startFn worker // before the Worker becomes observable through forwarding. A concurrent // first-writer mismatch fences this Worker instead of allowing it to run above // the already-established session ceiling. +func (lifecycle *workerRunLifecycle) setTurnTimeoutHandler(handler func(uint64)) { + if lifecycle == nil { + return + } + lifecycle.turnTimeoutMu.Lock() + defer lifecycle.turnTimeoutMu.Unlock() + lifecycle.turnTimeoutHandler = handler + if handler != nil && !lifecycle.turnTimeoutDeadline.IsZero() { + lifecycle.scheduleTurnTimeoutLocked(lifecycle.turnTimeoutGeneration, lifecycle.turnTimeoutDeadline) + } +} + +func (lifecycle *workerRunLifecycle) armTurnTimeout(deadline time.Time) uint64 { + if lifecycle == nil || deadline.IsZero() { + return 0 + } + lifecycle.turnTimeoutMu.Lock() + defer lifecycle.turnTimeoutMu.Unlock() + lifecycle.turnTimeoutGeneration++ + lifecycle.turnTimeoutDeadline = deadline + if lifecycle.turnTimeoutTimer != nil { + lifecycle.turnTimeoutTimer.Stop() + lifecycle.turnTimeoutTimer = nil + } + if lifecycle.turnTimeoutHandler != nil { + lifecycle.scheduleTurnTimeoutLocked(lifecycle.turnTimeoutGeneration, deadline) + } + return lifecycle.turnTimeoutGeneration +} + +func (lifecycle *workerRunLifecycle) scheduleTurnTimeoutLocked(generation uint64, deadline time.Time) { + if lifecycle.turnTimeoutTimer != nil { + lifecycle.turnTimeoutTimer.Stop() + } + delay := time.Until(deadline) + if delay < 0 { + delay = 0 + } + lifecycle.turnTimeoutTimer = time.AfterFunc(delay, func() { + lifecycle.turnTimeoutMu.Lock() + if lifecycle.turnTimeoutGeneration != generation || lifecycle.turnTimeoutDeadline.IsZero() { + lifecycle.turnTimeoutMu.Unlock() + return + } + handler := lifecycle.turnTimeoutHandler + lifecycle.turnTimeoutTimer = nil + lifecycle.turnTimeoutMu.Unlock() + if handler != nil { + handler(generation) + } + }) +} + +func (lifecycle *workerRunLifecycle) turnTimeoutCurrent(generation uint64) bool { + if lifecycle == nil { + return false + } + lifecycle.turnTimeoutMu.Lock() + defer lifecycle.turnTimeoutMu.Unlock() + return generation != 0 && + generation == lifecycle.turnTimeoutGeneration && + !lifecycle.turnTimeoutDeadline.IsZero() +} + +func (lifecycle *workerRunLifecycle) stopTurnTimeout() { + if lifecycle == nil { + return + } + lifecycle.turnTimeoutMu.Lock() + defer lifecycle.turnTimeoutMu.Unlock() + lifecycle.turnTimeoutGeneration++ + lifecycle.turnTimeoutDeadline = time.Time{} + if lifecycle.turnTimeoutTimer != nil { + lifecycle.turnTimeoutTimer.Stop() + lifecycle.turnTimeoutTimer = nil + } +} + +func (lifecycle *workerRunLifecycle) clearTurnTimeoutHandler() { + if lifecycle == nil { + return + } + lifecycle.turnTimeoutMu.Lock() + defer lifecycle.turnTimeoutMu.Unlock() + lifecycle.turnTimeoutGeneration++ + lifecycle.turnTimeoutDeadline = time.Time{} + lifecycle.turnTimeoutHandler = nil + if lifecycle.turnTimeoutTimer != nil { + lifecycle.turnTimeoutTimer.Stop() + lifecycle.turnTimeoutTimer = nil + } +} + func (b *Bridge) capturePermissionCeiling(ctx context.Context, sessionID string, w worker.Worker) error { reporter, ok := w.(worker.PermissionCeilingReporter) if !ok { @@ -339,15 +432,73 @@ func (b *Bridge) LaunchPlanApplied(sessionID string) bool { // user's input to the worker. Safe to call when the accumulator does not yet // exist (it is created on-demand). Turn-Integrity Fix D. func (b *Bridge) RecordTurnStart(sessionID string) { - b.getOrInitAccum(sessionID, "", time.Now()).recordTurnStart(time.Now()) + _, _, _, _ = b.RecordTurnStartForRun(sessionID, "") +} + +// RecordTurnStartForRun records the input-path start stamp and arms the +// current worker run's absolute timeout. The returned values are the exact +// deadline snapshot to persist with the durable execution record. +func (b *Bridge) RecordTurnStartForRun(sessionID, expectedRunID string) (startedAt, deadlineAt int64, policyRevision string, err error) { + started := time.Now() + acc := b.getOrInitAccum(sessionID, "", started) + if expectedRunID != "" { + binding, ok := b.currentWorkerRunBinding(sessionID, expectedRunID) + if !ok || binding.lifecycle == nil { + return 0, 0, "", errWorkerRunChanged + } + acc.recordTurnStart(started) + startedAt = started.UnixMilli() + if b.turnTimeout > 0 { + deadline := started.Add(b.turnTimeout) + binding.lifecycle.armTurnTimeout(deadline) + return startedAt, deadline.UnixMilli(), turnTimeoutRevision(b.turnTimeout), nil + } + return startedAt, 0, "", nil + } + + acc.recordTurnStart(started) + startedAt = started.UnixMilli() + if b.turnTimeout <= 0 { + return startedAt, 0, "", nil + } + if binding, ok := b.currentWorkerRunBinding(sessionID, ""); ok && binding.lifecycle != nil { + deadline := started.Add(b.turnTimeout) + binding.lifecycle.armTurnTimeout(deadline) + return startedAt, deadline.UnixMilli(), turnTimeoutRevision(b.turnTimeout), nil + } + return startedAt, 0, "", nil +} + +func turnTimeoutRevision(timeout time.Duration) string { + return fmt.Sprintf("worker.turn_timeout=%s", timeout) +} + +func (b *Bridge) RecordTurnDeadlineForRun(sessionID, expectedRunID string, startedAt, deadlineAt int64) error { + if startedAt <= 0 || deadlineAt <= startedAt { + return errors.New("bridge: valid persisted turn deadline is required") + } + binding, ok := b.currentWorkerRunBinding(sessionID, expectedRunID) + if !ok || binding.lifecycle == nil { + return errWorkerRunChanged + } + b.getOrInitAccum(sessionID, "", time.UnixMilli(startedAt)).recordTurnStart(time.UnixMilli(startedAt)) + binding.lifecycle.armTurnTimeout(time.UnixMilli(deadlineAt)) + return nil } // ClearTurnStart clears the current turn's start stamp. Called by the input // path when delivery to the worker failed, so the subsequent Done does not // bill a turn that never started. Turn-Integrity Fix D. func (b *Bridge) ClearTurnStart(sessionID string) { + b.ClearTurnStartForRun(sessionID, "") +} + +func (b *Bridge) ClearTurnStartForRun(sessionID, expectedRunID string) { acc := b.getOrInitAccum(sessionID, "", time.Now()) acc.clearTurnStart() + if binding, ok := b.currentWorkerRunBinding(sessionID, expectedRunID); ok && binding.lifecycle != nil { + binding.lifecycle.stopTurnTimeout() + } } // consumeTurnStartMs reads and clears the current turn's start stamp. Called by diff --git a/internal/gateway/eventstore_test_helpers_test.go b/internal/gateway/eventstore_test_helpers_test.go new file mode 100644 index 000000000..f6e4bf1b1 --- /dev/null +++ b/internal/gateway/eventstore_test_helpers_test.go @@ -0,0 +1,23 @@ +package gateway + +import ( + "database/sql" + "testing" + + "github.com/stretchr/testify/require" +) + +func createEventStoreSessionBarrierSchema(t *testing.T, db *sql.DB) { + t.Helper() + + _, err := db.Exec(`CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + state TEXT NOT NULL DEFAULT '' + )`) + require.NoError(t, err) + + _, err = db.Exec(`CREATE TABLE IF NOT EXISTS session_purge_jobs ( + session_id TEXT PRIMARY KEY + )`) + require.NoError(t, err) +} diff --git a/internal/gateway/handler.go b/internal/gateway/handler.go index 8133fc248..fb748ae80 100644 --- a/internal/gateway/handler.go +++ b/internal/gateway/handler.go @@ -150,21 +150,15 @@ func (h *Handler) SetAuditCollector(ac *audit.Collector) { h.auditCollector = ac } -// emitAudit enqueues a non-blocking message.inbound audit event. No-op when collector is nil. -func (h *Handler) emitAudit(outcome, userID, platform, sessionID, content string) { +// emitAudit enqueues a non-blocking, bodyless message.inbound audit event. +// The final value is intentionally discarded so callers cannot persist text. +func (h *Handler) emitAudit(outcome, userID, platform, sessionID, _ string) { if h.auditCollector == nil { return } if userID == "" { userID = audit.AnonymousUserID } - detailJSON := "{}" - if content != "" { - detail := map[string]any{"content": content} - if bytes, err := json.Marshal(detail); err == nil { - detailJSON = string(bytes) - } - } _ = h.auditCollector.Enqueue(context.Background(), &audit.UserActivity{ Ts: time.Now().UnixMilli(), UserID: userID, @@ -173,13 +167,19 @@ func (h *Handler) emitAudit(outcome, userID, platform, sessionID, content string SessionID: sessionID, Action: audit.ActionMessageInbound, Outcome: outcome, - DetailJSON: detailJSON, + DetailJSON: "{}", }) } -// emitInteractionAudit enqueues a non-blocking interaction response audit event -// (permission.response / question.response / elicitation.response). -func (h *Handler) emitInteractionAudit(userID, platform, sessionID string, eventType events.Kind, data any, content string) { +// emitInteractionAudit enqueues a non-blocking, bodyless interaction response +// audit event (permission.response / question.response / elicitation.response). +// Free-form response content is intentionally discarded. +func (h *Handler) emitInteractionAudit( + userID, platform, sessionID string, + eventType events.Kind, + data any, + _ string, +) { if h.auditCollector == nil { return } @@ -198,67 +198,33 @@ func (h *Handler) emitInteractionAudit(userID, platform, sessionID string, event action = audit.ActionPermissionRequest resourceType = "permission" var reqID string - var toolName string - var description string - var args []string if prd, ok := data.(events.PermissionRequestData); ok { reqID = prd.ID - toolName = prd.ToolName - description = prd.Description - args = prd.Args } else if prdPtr, ok := data.(*events.PermissionRequestData); ok && prdPtr != nil { reqID = prdPtr.ID - toolName = prdPtr.ToolName - description = prdPtr.Description - args = prdPtr.Args } else if m, ok := data.(map[string]any); ok { reqID, _ = m["id"].(string) if reqID == "" { reqID, _ = m["request_id"].(string) } - toolName, _ = m["tool_name"].(string) - if toolName == "" { - toolName, _ = m["tool"].(string) - } - description, _ = m["description"].(string) - if rawArgs, ok := m["args"].([]string); ok { - args = rawArgs - } else if rawArgsAny, ok := m["args"].([]any); ok { - for _, a := range rawArgsAny { - if s, ok := a.(string); ok { - args = append(args, s) - } - } - } } resourceID = reqID if reqID != "" { detailMap["id"] = reqID } - if toolName != "" { - detailMap["tool_name"] = toolName - } - if description != "" { - detailMap["description"] = description - } - if len(args) > 0 { - detailMap["args"] = args - } outcome = audit.OutcomeSuccess case events.PermissionResponse: action = audit.ActionPermissionResponse resourceType = "permission" var allowed = true - var reason string var reqID string if prd, ok := data.(events.PermissionResponseData); ok { reqID = prd.ID allowed = prd.Allowed - reason = prd.Reason } else if m, ok := data.(map[string]any); ok { reqID, _ = m["id"].(string) if reqID == "" { @@ -267,7 +233,6 @@ func (h *Handler) emitInteractionAudit(userID, platform, sessionID string, event if a, ok := m["allowed"].(bool); ok { allowed = a } - reason, _ = m["reason"].(string) } resourceID = reqID @@ -275,12 +240,6 @@ func (h *Handler) emitInteractionAudit(userID, platform, sessionID string, event detailMap["id"] = reqID } detailMap["allowed"] = allowed - if reason != "" { - detailMap["reason"] = reason - } - if content != "" { - detailMap["content"] = content - } if allowed { outcome = audit.OutcomeSuccess } else { @@ -291,26 +250,17 @@ func (h *Handler) emitInteractionAudit(userID, platform, sessionID string, event action = audit.ActionQuestionResponse resourceType = "question" var reqID string - var answers any if qrd, ok := data.(events.QuestionResponseData); ok { reqID = qrd.ID - answers = qrd.Answers } else if m, ok := data.(map[string]any); ok { reqID, _ = m["id"].(string) - answers = m["answers"] } resourceID = reqID if reqID != "" { detailMap["id"] = reqID } - if answers != nil { - detailMap["answers"] = answers - } - if content != "" { - detailMap["content"] = content - } outcome = audit.OutcomeSuccess case events.ElicitationResponse: @@ -318,16 +268,13 @@ func (h *Handler) emitInteractionAudit(userID, platform, sessionID string, event resourceType = "elicitation" var reqID string var act string - var elicitContent any if erd, ok := data.(events.ElicitationResponseData); ok { reqID = erd.ID act = erd.Action - elicitContent = erd.Content } else if m, ok := data.(map[string]any); ok { reqID, _ = m["id"].(string) act, _ = m["action"].(string) - elicitContent = m["content"] } resourceID = reqID @@ -335,12 +282,10 @@ func (h *Handler) emitInteractionAudit(userID, platform, sessionID string, event detailMap["id"] = reqID } if act != "" { - detailMap["action"] = act - } - if elicitContent != nil { - detailMap["content"] = elicitContent - } else if content != "" { - detailMap["content"] = content + switch act { + case "accept", "decline", "cancel": + detailMap["action"] = act + } } if act == "decline" || act == "cancel" { outcome = audit.OutcomeDenied @@ -1093,6 +1038,9 @@ func (h *Handler) handleSupplementOnBusy(ctx context.Context, env *events.Envelo case qerr != nil && errors.Is(qerr, execution.ErrPayloadConflict): return h.sendErrorf(ctx, env, events.ErrCodeInvalidMessage, "client message id %q was already used with different input", clientID) + case qerr != nil && errors.Is(qerr, execution.ErrSessionExpired): + return h.sendErrorf(ctx, env, events.ErrCodeSessionExpired, + "session expired; create a new session to continue") } // Full, oversized or otherwise unusable: fall through to the volatile // buffer, which is still better than refusing the input outright. @@ -1323,9 +1271,15 @@ func (h *Handler) deliverToWorkerWithBusyHandling(ctx context.Context, env *even h.cancelRetryIfNeeded(env.SessionID) return h.sendErrorf(ctx, env, events.ErrCodeSessionNotFound, "session not found") } + if si.State == events.StateDeleted || si.DeletedAt != nil { + return h.sendErrorf(ctx, env, events.ErrCodeSessionExpired, "session expired; create a new session to continue") + } execRecord, duplicate, err := h.acceptInputExecutionWithRetry(ctx, env) if err != nil { + if errors.Is(err, execution.ErrSessionExpired) { + return h.sendErrorf(ctx, env, events.ErrCodeSessionExpired, "session expired; create a new session to continue") + } if errors.Is(err, execution.ErrPayloadConflict) { observability.ExecutionConflict().Add(ctx, 1) return h.sendErrorf(ctx, env, events.ErrCodeInvalidMessage, @@ -1406,6 +1360,19 @@ func (h *Handler) deliverToWorkerWithBusyHandling(ctx context.Context, env *even h.bridge.finishTurnTTFT(env.SessionID, string(status)) } } + if recorder, ok := h.sm.(SessionInputLifecycleRecorder); ok { + acceptedAt := inputReceivedAt + if execRecord != nil && execRecord.CreatedAt > 0 { + acceptedAt = time.UnixMilli(execRecord.CreatedAt) + } + if err := recorder.RecordInputAccepted(ctx, env.SessionID, acceptedAt); err != nil { + h.log.Error("gateway: persist session input lifecycle failed", + "err", err, "session_id", env.SessionID) + finishOutcome(execution.StatusFailed, events.ErrCodeInternalError) + h.emitAudit(audit.OutcomeFailure, env.OwnerID, si.Platform, env.SessionID, content) + return h.sendErrorf(ctx, env, events.ErrCodeInternalError, "session lifecycle update failed") + } + } // Fence recovery may have replaced the Worker and transitioned a TERMINATED // session back to RUNNING while accepting this input. Re-read state so the // fresh Worker is not immediately replaced by the normal resume path below. @@ -1515,7 +1482,7 @@ func (h *Handler) deliverToWorkerWithBusyHandling(ctx context.Context, env *even if mrErr := h.executionStore.MarkRunning(ctx, execRecord.ExecutionID, h.ownerInstanceID, workerRunID); mrErr != nil { h.log.Warn("gateway: mark execution running failed", "err", mrErr, "session_id", env.SessionID, observability.KeyExecutionID, execRecord.ExecutionID) - h.finishRuntimeWithoutDispatch(ctx, execRecord, persistedRunID, events.ErrCodeInternalError) + h.finishRuntimeWithoutDispatch(ctx, execRecord, persistedRunID) finishOutcome(execution.StatusFailed, events.ErrCodeInternalError) return h.sendErrorf(ctx, env, events.ErrCodeInternalError, "execution dispatch registration failed") } @@ -1532,17 +1499,44 @@ func (h *Handler) deliverToWorkerWithBusyHandling(ctx context.Context, env *even } if h.bridge != nil && workerRunBound { if err := h.bridge.beginWorkerRunTurn(ctx, env.SessionID, workerRunID); err != nil { - h.finishRuntimeWithoutDispatch(ctx, execRecord, workerRunID, events.ErrCodeInternalError) + h.finishRuntimeWithoutDispatch(ctx, execRecord, workerRunID) finishOutcome(execution.StatusFailed, events.ErrCodeInternalError) return h.sendErrorf(ctx, env, events.ErrCodeInternalError, "worker turn admission failed") } } - // Stamp the turn start immediately before delivery so the Done-bound timer - // measures only this turn's processing, excluding inter-turn idle - // (Turn-Integrity spec RC-4 / Fix D). + // Stamp and arm the absolute turn deadline immediately before delivery. + // Worker output must not extend this deadline; only a new accepted input + // starts a new turn clock. + var turnStartedAt, turnDeadlineAt int64 + var turnPolicyRevision string if h.bridge != nil { - h.bridge.RecordTurnStart(env.SessionID) + turnStartedAt, turnDeadlineAt, turnPolicyRevision, err = + h.bridge.RecordTurnStartForRun(env.SessionID, workerRunID) + if err != nil { + h.finishRuntimeWithoutDispatch(ctx, execRecord, workerRunID) + finishOutcome(execution.StatusFailed, events.ErrCodeInternalError) + return h.sendErrorf(ctx, env, events.ErrCodeInternalError, "worker turn deadline setup failed") + } + } else { + turnStartedAt = time.Now().UnixMilli() + } + if execRecord != nil && h.executionStore != nil && turnDeadlineAt > 0 { + if err := h.executionStore.SetTurnDeadline( + ctx, + execRecord.ExecutionID, + h.ownerInstanceID, + turnStartedAt, + turnDeadlineAt, + turnPolicyRevision, + ); err != nil { + if h.bridge != nil { + h.bridge.ClearTurnStartForRun(env.SessionID, workerRunID) + } + h.finishRuntimeWithoutDispatch(ctx, execRecord, workerRunID) + finishOutcome(execution.StatusFailed, events.ErrCodeInternalError) + return h.sendErrorf(ctx, env, events.ErrCodeInternalError, "worker turn deadline persistence failed") + } } // A new primary turn begins: clear the previous turn's stop claim so this @@ -1613,7 +1607,7 @@ func (h *Handler) deliverToWorkerWithBusyHandling(ctx context.Context, env *even cancel() if catalogErr != nil { if h.bridge != nil { - h.bridge.ClearTurnStart(env.SessionID) + h.bridge.ClearTurnStartForRun(env.SessionID, workerRunID) } finishOutcome(execution.StatusFailed, events.ErrCodeInternalError) h.emitAudit(audit.OutcomeFailure, env.OwnerID, si.Platform, env.SessionID, content) @@ -1640,7 +1634,7 @@ func (h *Handler) deliverToWorkerWithBusyHandling(ctx context.Context, env *even } if !advertised { if h.bridge != nil { - h.bridge.ClearTurnStart(env.SessionID) + h.bridge.ClearTurnStartForRun(env.SessionID, workerRunID) } finishOutcome(execution.StatusFailed, events.ErrCodeNotSupported) h.emitAudit(audit.OutcomeFailure, env.OwnerID, si.Platform, env.SessionID, content) @@ -1651,7 +1645,7 @@ func (h *Handler) deliverToWorkerWithBusyHandling(ctx context.Context, env *even invoker, ok := worker.AsNativeInvoker(w) if !ok { if h.bridge != nil { - h.bridge.ClearTurnStart(env.SessionID) + h.bridge.ClearTurnStartForRun(env.SessionID, workerRunID) } finishOutcome(execution.StatusFailed, events.ErrCodeNotSupported) h.emitAudit(audit.OutcomeFailure, env.OwnerID, si.Platform, env.SessionID, content) @@ -1711,7 +1705,7 @@ func (h *Handler) deliverToWorkerWithBusyHandling(ctx context.Context, env *even // Input never reached the worker: clear the turn start so a later Done // (or crash-cleanup) cannot bill idle time to a turn that never ran. if h.bridge != nil { - h.bridge.ClearTurnStart(env.SessionID) + h.bridge.ClearTurnStartForRun(env.SessionID, workerRunID) } code := classifyWorkerError(inputErr) finishOutcome(execution.StatusFailed, code) @@ -1893,12 +1887,13 @@ func (h *Handler) finishInputExecution(ctx context.Context, record *execution.Re return nil } -func (h *Handler) finishRuntimeWithoutDispatch(ctx context.Context, record *execution.Record, workerRunID string, code events.ErrorCode) { +func (h *Handler) finishRuntimeWithoutDispatch(ctx context.Context, record *execution.Record, workerRunID string) { if h.executionStore == nil || record == nil || workerRunID == "" { return } err := h.executionStore.FinishRuntime( - context.WithoutCancel(ctx), record.ExecutionID, workerRunID, execution.RuntimeFailed, string(code), + context.WithoutCancel(ctx), record.ExecutionID, workerRunID, execution.RuntimeFailed, + string(events.ErrCodeInternalError), ) if err == nil || h.repairer == nil { return @@ -1908,7 +1903,7 @@ func (h *Handler) finishRuntimeWithoutDispatch(ctx context.Context, record *exec WorkerRunID: workerRunID, Kind: execution.RepairRuntime, Status: string(execution.RuntimeFailed), - ErrorCode: string(code), + ErrorCode: string(events.ErrCodeInternalError), }) } @@ -2061,6 +2056,13 @@ type SessionReader interface { GetWorker(id string) worker.Worker } +// SessionInputLifecycleRecorder persists retention deadlines for a newly +// accepted input. It remains optional for adapters and test doubles that do +// not own lifecycle persistence. +type SessionInputLifecycleRecorder interface { + RecordInputAccepted(ctx context.Context, id string, acceptedAt time.Time) error +} + // SessionLifecycle provides session creation and deletion. type SessionLifecycle interface { CreateWithBot(ctx context.Context, id, userID, botID, botName string, wt worker.WorkerType, allowedTools []string, platform string, platformKey map[string]string, workspaceID, workDir, title, clientKey string) (*session.SessionInfo, error) diff --git a/internal/gateway/handler_test.go b/internal/gateway/handler_test.go index a9a05388d..de5cd108c 100644 --- a/internal/gateway/handler_test.go +++ b/internal/gateway/handler_test.go @@ -135,10 +135,9 @@ func (tx *captureAuditTx) Rollback() error { return nil } -// TestHandler_ReceivedEventLogExcludesBodyAndAuditRetainsContent catches a -// regression that creates an unmanaged plaintext log copy while preserving -// the governed audit copy. -func TestHandler_ReceivedEventLogExcludesBodyAndAuditRetainsContent(t *testing.T) { +// TestHandler_ReceivedEventLogAndAuditExcludeBody ensures neither operational +// logs nor audit facts create a second plaintext copy of chat content. +func TestHandler_ReceivedEventLogAndAuditExcludeBody(t *testing.T) { t.Parallel() var logs bytes.Buffer @@ -176,9 +175,10 @@ func TestHandler_ReceivedEventLogExcludesBodyAndAuditRetainsContent(t *testing.T activities, err := store.Query(context.Background(), audit.Query{}) require.NoError(t, err) - var detail map[string]string + var detail map[string]any require.NoError(t, json.Unmarshal([]byte(activities[0].DetailJSON), &detail)) - require.Equal(t, secret, detail["content"]) + require.NotContains(t, activities[0].DetailJSON, secret) + require.NotContains(t, detail, "content") } // mockHandlerSM is a mock session manager for handler tests. @@ -1338,9 +1338,11 @@ func newBridgeWithCollector(t *testing.T) (*Bridge, *eventstore.SQLiteStore) { direction TEXT NOT NULL DEFAULT 'outbound', source TEXT NOT NULL DEFAULT 'normal' CHECK(source IN ('normal', 'crash', 'timeout', 'fresh_start')), - created_at INTEGER NOT NULL + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL DEFAULT 0 )`) require.NoError(t, err) + createEventStoreSessionBarrierSchema(t, db) _, err = db.Exec(`CREATE TABLE IF NOT EXISTS turns ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, @@ -1363,7 +1365,8 @@ func newBridgeWithCollector(t *testing.T) (*Bridge, *eventstore.SQLiteStore) { tokens_out INTEGER NOT NULL DEFAULT 0, duration_ms INTEGER NOT NULL DEFAULT 0, cost_usd REAL NOT NULL DEFAULT 0.0, - created_at INTEGER NOT NULL + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL DEFAULT 0 )`) require.NoError(t, err) diff --git a/internal/gateway/input_execution_test.go b/internal/gateway/input_execution_test.go index 5f07ef9d1..f1103f397 100644 --- a/internal/gateway/input_execution_test.go +++ b/internal/gateway/input_execution_test.go @@ -22,25 +22,29 @@ import ( var errFakeQueueUnsupported = errors.New("fake execution store: queue unsupported") type fakeExecutionStore struct { - mu sync.Mutex - record *execution.Record - duplicate bool - acceptErr error - statusErr error - lastAccept execution.AcceptRequest - status execution.Status - errorCode string - statusCalls int - openRecord *execution.Record - activeRecord *execution.Record // when set, ActiveBySession reports the gate as held - activeResults []*execution.Record // optional ordered ActiveBySession results; nil entry means ErrNotFound - activeCalls int - markRunID string - markErr error - finishRunID string - finishStatus execution.RuntimeStatus - finishErr error - latestErr error + mu sync.Mutex + record *execution.Record + duplicate bool + acceptErr error + statusErr error + lastAccept execution.AcceptRequest + status execution.Status + errorCode string + statusCalls int + openRecord *execution.Record + activeRecord *execution.Record // when set, ActiveBySession reports the gate as held + activeResults []*execution.Record // optional ordered ActiveBySession results; nil entry means ErrNotFound + activeCalls int + markRunID string + markErr error + turnStartedAt int64 + turnDeadline int64 + turnRevision string + turnDeadlineErr error + finishRunID string + finishStatus execution.RuntimeStatus + finishErr error + latestErr error } func (s *fakeExecutionStore) Accept(_ context.Context, req execution.AcceptRequest) (*execution.Record, bool, error) { @@ -74,6 +78,14 @@ func (s *fakeExecutionStore) MarkRunning(_ context.Context, _ string, _ string, s.markRunID = runID return s.markErr } +func (s *fakeExecutionStore) SetTurnDeadline(_ context.Context, _ string, _ string, startedAt, deadlineAt int64, policyRevision string) error { + s.mu.Lock() + defer s.mu.Unlock() + s.turnStartedAt = startedAt + s.turnDeadline = deadlineAt + s.turnRevision = policyRevision + return s.turnDeadlineErr +} func (s *fakeExecutionStore) FinishRuntime(_ context.Context, _ string, runID string, status execution.RuntimeStatus, _ string) error { s.mu.Lock() defer s.mu.Unlock() @@ -220,6 +232,22 @@ func newExecutionHandler(t *testing.T, store execution.Store, workerInputError e return &Handler{log: testLogger(t), hub: hub, sm: sm, executionStore: store}, sm, w, conn } +type lifecycleInputRecord struct { + sessionID string + acceptedAt time.Time +} + +type recordingLifecycleInputSM struct { + *mockInputSM + records []lifecycleInputRecord + err error +} + +func (m *recordingLifecycleInputSM) RecordInputAccepted(_ context.Context, sessionID string, acceptedAt time.Time) error { + m.records = append(m.records, lifecycleInputRecord{sessionID: sessionID, acceptedAt: acceptedAt}) + return m.err +} + func testExecutionRecord(status execution.Status) *execution.Record { return &execution.Record{ ExecutionID: "exec_test", @@ -247,6 +275,45 @@ func requireInputAcks(t *testing.T, conn *mockPlatformConn, statuses ...events.E } } +func TestInputExecution_RecordsLifecycleForAcceptedInput(t *testing.T) { + t.Parallel() + record := testExecutionRecord(execution.StatusAccepted) + record.CreatedAt = time.Now().UTC().Add(-time.Minute).Truncate(time.Millisecond).UnixMilli() + h, baseSM, _, _ := newExecutionHandler(t, &fakeExecutionStore{record: record}, nil) + lifecycleSM := &recordingLifecycleInputSM{mockInputSM: baseSM} + h.sm = lifecycleSM + + env := inputEnvelope("s-exec", "hello") + env.ID = "evt-client-1" + require.NoError(t, h.handleInput(context.Background(), env)) + + require.Equal(t, []lifecycleInputRecord{{ + sessionID: "s-exec", acceptedAt: time.UnixMilli(record.CreatedAt), + }}, lifecycleSM.records) +} + +func TestInputExecution_LifecyclePersistenceFailurePreventsWorkerDispatch(t *testing.T) { + t.Parallel() + store := &fakeExecutionStore{record: testExecutionRecord(execution.StatusAccepted)} + h, baseSM, w, _ := newExecutionHandler(t, store, nil) + lifecycleSM := &recordingLifecycleInputSM{ + mockInputSM: baseSM, + err: errors.New("lifecycle store unavailable"), + } + h.sm = lifecycleSM + + env := inputEnvelope("s-exec", "hello") + env.ID = "evt-client-1" + err := h.handleInput(context.Background(), env) + + require.ErrorContains(t, err, "lifecycle") + status, code, calls := store.snapshot() + require.Equal(t, execution.StatusFailed, status) + require.Equal(t, string(events.ErrCodeInternalError), code) + require.Equal(t, 1, calls) + w.AssertNotCalled(t, "Input", mock.Anything, mock.Anything, mock.Anything) +} + func TestInputExecution_DeliveredAckAfterWorkerAccepts(t *testing.T) { t.Parallel() store := &fakeExecutionStore{record: testExecutionRecord(execution.StatusAccepted)} @@ -474,6 +541,8 @@ func TestInputExecution_DuplicateReplaysAckWithoutWorkerCall(t *testing.T) { t.Parallel() store := &fakeExecutionStore{record: testExecutionRecord(execution.StatusDelivered), duplicate: true} h, sm, w, conn := newExecutionHandler(t, store, nil) + lifecycleSM := &recordingLifecycleInputSM{mockInputSM: sm} + h.sm = lifecycleSM retryCancel := make(chan struct{}) h.bridge = &Bridge{retryCancel: map[string]chan struct{}{"s-exec": retryCancel}} env := inputEnvelope("s-exec", "hello") @@ -482,6 +551,7 @@ func TestInputExecution_DuplicateReplaysAckWithoutWorkerCall(t *testing.T) { require.NoError(t, h.handleInput(context.Background(), env)) requireInputAcks(t, conn, events.ExecutionStatusDelivered) require.True(t, conn.envelopes()[0].Event.Data.(events.InputAckData).Duplicate) + require.Empty(t, lifecycleSM.records, "duplicate delivery must not renew session retention") _, _, calls := store.snapshot() require.Zero(t, calls) sm.AssertExpectations(t) diff --git a/internal/gateway/queue.go b/internal/gateway/queue.go index 4646ec7bd..97416d95e 100644 --- a/internal/gateway/queue.go +++ b/internal/gateway/queue.go @@ -69,11 +69,15 @@ func (h *Handler) queueEnabled() bool { func (h *Handler) queueLimits() execution.QueueLimits { cfg := h.queueConfig() + ttl := cfg.TTL + if cfg.InteractiveTTL > 0 { + ttl = cfg.InteractiveTTL + } return execution.QueueLimits{ PerSession: cfg.PerSession, Global: cfg.Global, MaxPayloadBytes: cfg.MaxPayloadBytes, - TTL: cfg.TTL, + TTL: ttl, } } diff --git a/internal/gateway/queue_test.go b/internal/gateway/queue_test.go index 63f9e375b..8f98dee0c 100644 --- a/internal/gateway/queue_test.go +++ b/internal/gateway/queue_test.go @@ -156,6 +156,26 @@ func TestEnqueueBusyInput_ACKsQueuedDurably(t *testing.T) { require.Equal(t, "later please", payload.Content) } +func TestEnqueueBusyInput_UsesInteractiveTTL(t *testing.T) { + t.Parallel() + + h, _, _, store, _ := newQueueHandler(t, "s-exec") + cfg := config.Default() + cfg.Execution.Queue.Enabled = true + cfg.Execution.Queue.InteractiveTTL = 15 * time.Minute + cfg.Execution.Queue.TTL = 24 * time.Hour + h.configProvider = func() *config.Config { return cfg } + + record, queued, err := h.EnqueueBusyInput(context.Background(), + queuedInputEnv("s-exec", "m1", "later please"), "later please", nil) + require.NoError(t, err) + require.True(t, queued) + + entry, err := store.QueueByExecution(context.Background(), record.ExecutionID) + require.NoError(t, err) + require.Equal(t, int64((15*time.Minute)/time.Millisecond), entry.ExpiresAt-entry.EnqueuedAt) +} + // TestEnqueueBusyInput_DuplicateIsOneQueueEntry proves a retried submission // does not double the queue, and that a different payload under the same // client message id is refused rather than quietly queued as a second input. diff --git a/internal/gateway/system_input.go b/internal/gateway/system_input.go index f7a2ca249..05b3cbc30 100644 --- a/internal/gateway/system_input.go +++ b/internal/gateway/system_input.go @@ -73,6 +73,9 @@ func (h *Handler) DispatchSystemInput(ctx context.Context, req cron.SystemInputR record, duplicate, err := h.acceptInputExecution(ctx, env) if err != nil { + if errors.Is(err, execution.ErrSessionExpired) { + return out, fmt.Errorf("%w: session %s has expired", ErrSystemInputRejected, req.SessionID) + } if errors.Is(err, execution.ErrPayloadConflict) { return out, fmt.Errorf("%w: occurrence %s already used with different input", ErrSystemInputRejected, req.OccurrenceID) @@ -132,11 +135,25 @@ func (h *Handler) DispatchSystemInput(ctx context.Context, req cron.SystemInputR } if h.bridge != nil { - h.bridge.RecordTurnStart(req.SessionID) + startedAt, deadlineAt, policyRevision, err := h.bridge.RecordTurnStartForRun(req.SessionID, workerRunID) + if err != nil { + return fail(events.ErrCodeInternalError, fmt.Errorf("turn deadline setup: %w", err)) + } + if deadlineAt > 0 { + if err := h.executionStore.SetTurnDeadline( + ctx, record.ExecutionID, h.ownerInstanceID, startedAt, deadlineAt, policyRevision, + ); err != nil { + h.bridge.ClearTurnStartForRun(req.SessionID, workerRunID) + return fail(events.ErrCodeInternalError, fmt.Errorf("turn deadline persistence: %w", err)) + } + } } h.stopFence.BeginTurn(req.SessionID, workerRunID, record.ExecutionID) if err := w.Input(ctx, req.Content, nil); err != nil { + if h.bridge != nil { + h.bridge.ClearTurnStartForRun(req.SessionID, workerRunID) + } return fail(events.ErrCodeInternalError, fmt.Errorf("worker input: %w", err)) } diff --git a/internal/gateway/tool_audit.go b/internal/gateway/tool_audit.go index baed39d8e..87e6d8020 100644 --- a/internal/gateway/tool_audit.go +++ b/internal/gateway/tool_audit.go @@ -7,7 +7,6 @@ import ( "encoding/json" "strings" "time" - "unicode/utf8" "github.com/hrygo/hotplex/internal/audit" "github.com/hrygo/hotplex/pkg/events" @@ -30,10 +29,6 @@ var sensitiveToolNames = map[string]bool{ "web_search": true, } -// toolInputPreviewLimit caps the non-sensitive tool input preview length -// (UTF-8 runes). Sensitive tools store full input (after sanitization). -const toolInputPreviewLimit = 200 - // sensitiveInputPatterns match secret-like substrings inside tool inputs so // they are masked before the input is stored in the audit trail (spec §5.9: // "forbidden to log credentials, API key plaintext"). Matches are replaced @@ -56,20 +51,6 @@ func maskSensitiveInput(s string) string { return audit.MaskSensitiveText(s) } -// renderToolInput converts a tool's Input map to a stable JSON string for the -// audit detail. Returns "" when the input is empty/unrenderable. -func renderToolInput(input map[string]any) string { - if len(input) == 0 { - return "" - } - sanitized, _ := audit.SanitizeValue(input).(map[string]any) - b, err := json.Marshal(sanitized) - if err != nil { - return "" - } - return string(b) -} - // sha256Hex returns the hex-encoded sha256 of s. Used for non-sensitive tool // input fingerprinting (spec §5.3: non-sensitive stores summary + sha256). func sha256Hex(s string) string { @@ -77,26 +58,17 @@ func sha256Hex(s string) string { return hex.EncodeToString(h[:]) } -// truncateRunes returns the first n UTF-8 runes of s, suffixed with "…" when -// truncation occurs. -func truncateRunes(s string, n int) string { - if utf8.RuneCountInString(s) <= n { - return s - } - r := []rune(s) - return string(r[:n]) + "…" -} - -// emitToolCallAudit enqueues a tool.call audit event. Non-blocking and safe to -// call from the forward path; no-op when auditCollector is nil. Outcome is -// success (see note on ToolCall branch in bridge_forward.go re: failure scope). +// emitToolCallAudit enqueues a bodyless tool.call audit event. Non-blocking and +// safe to call from the forward path; no-op when auditCollector is nil. +// Outcome is success (see note on ToolCall branch in bridge_forward.go re: +// failure scope). // Attribution: UserID comes from fc.sessOwner (resolved earlier via // sm.Get → OwnerID||UserID, same identity space as message.inbound's // env.OwnerID). UserIDType is "platform" per spec §5.4 tool.call backtracking // (session_id → sessions.user_id). func (b *Bridge) emitToolCallAudit(fc *forwardContext, tc *events.ToolCallData) { c := b.auditCollector - if c == nil { + if c == nil || tc == nil { return } userID := fc.sessOwner @@ -119,43 +91,37 @@ func (b *Bridge) emitToolCallAudit(fc *forwardContext, tc *events.ToolCallData) _ = c.Enqueue(context.Background(), ua) } -// buildToolCallDetail constructs the whitelisted detail_json for a tool.call -// audit row. Per spec §5.3: -// - Sensitive tools → full input (after secret masking), no sha256 needed. -// - Non-sensitive tools → input sha256 + truncated preview. -// -// Always records: name, success (true at emit time), kind (if present), title. +// buildToolCallDetail constructs the whitelisted, bodyless detail_json for a +// tool.call audit row. Tool names are bounded identifiers because the upstream +// worker controls this value; free-form names could otherwise carry user text. func buildToolCallDetail(tc *events.ToolCallData) string { - rawInput := renderToolInput(tc.Input) - sensitive := sensitiveToolNames[strings.ToLower(tc.Name)] + name := "unknown" + if tc != nil { + name = safeAuditToolName(tc.Name) + } d := map[string]any{ - "name": tc.Name, + "name": name, "success": true, // tool was invoked; failure correlation is P3 } - if tc.Kind != "" { - d["kind"] = tc.Kind - } - if tc.Title != "" { - d["title"] = audit.MaskSensitiveText(tc.Title) - } - switch { - case sensitive: - // Full input, secrets masked (spec §5.3 sensitive-behavior full store). - masked := maskSensitiveInput(rawInput) - d["input"] = masked - d["sensitive"] = true - case rawInput != "": - // Summary + sha256 (spec §5.3 default). - d["input_sha256"] = sha256Hex(rawInput) - d["input_preview"] = truncateRunes(rawInput, toolInputPreviewLimit) - default: - // No input payload (e.g. parameterless tool). - d["input_sha256"] = "" - } b, _ := json.Marshal(d) return string(b) } +func safeAuditToolName(name string) string { + name = strings.TrimSpace(name) + if name == "" || len(name) > 64 { + return "unknown" + } + for _, r := range name { + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || + (r >= '0' && r <= '9') || r == '_' || r == '-' || r == '.' || r == ':' { + continue + } + return "unknown" + } + return name +} + // emitPermissionRequestAudit enqueues a permission.request audit event when // an outgoing PermissionRequest is forwarded to a client. func (b *Bridge) emitPermissionRequestAudit(fc *forwardContext, pr *events.PermissionRequestData) { @@ -168,14 +134,7 @@ func (b *Bridge) emitPermissionRequestAudit(fc *forwardContext, pr *events.Permi userID = audit.AnonymousUserID } - detailMap := map[string]any{ - "id": pr.ID, - "tool_name": pr.ToolName, - "description": pr.Description, - } - if len(pr.Args) > 0 { - detailMap["args"] = pr.Args - } + detailMap := map[string]any{"id": pr.ID} detailBytes, err := json.Marshal(detailMap) var detailStr string diff --git a/internal/gateway/tool_audit_test.go b/internal/gateway/tool_audit_test.go index fcc2fbcec..21e8b059f 100644 --- a/internal/gateway/tool_audit_test.go +++ b/internal/gateway/tool_audit_test.go @@ -91,35 +91,30 @@ func gwTestCollector(t *testing.T) (*audit.Collector, func(t *testing.T) []audit return c, query } -// TestBuildToolCallDetail_SensitiveTool verifies sensitive tools (Bash) store -// full input with secrets masked (spec §5.3 sensitive-behavior full store). -func TestBuildToolCallDetail_SensitiveTool(t *testing.T) { +// TestBuildToolCallDetail_OmitsInput verifies tool input is not copied into +// audit facts, including for tools that can contain commands or file contents. +func TestBuildToolCallDetail_OmitsInput(t *testing.T) { t.Parallel() - // NOTE: uses an obviously-fake placeholder body (not a real key) so GitHub - // push protection does not flag this test fixture. The mask regex matches - // the hpk_ prefix regardless of the body. tc := events.ToolCallData{ ID: "tc1", Name: "Bash", Input: map[string]any{ - "command": `export API_KEY="hpk_FAKEPLACEHOLDER1234" && curl https://example.com`, + "command": `echo PRIVATE_COMMAND_BODY`, }, } detail := buildToolCallDetail(&tc) var d map[string]any require.NoError(t, json.Unmarshal([]byte(detail), &d)) require.Equal(t, "Bash", d["name"]) - require.Equal(t, true, d["sensitive"]) - // Full input present (not just a sha256/preview pair). - input, ok := d["input"].(string) - require.True(t, ok, "sensitive tool should store full 'input'") - // The structurally sensitive API_KEY field must be fully redacted. - require.NotContains(t, input, "FAKEPLACEHOLDER1234", "raw secret must be masked") - require.Contains(t, input, audit.RedactedValue) + require.Equal(t, true, d["success"]) + require.NotContains(t, d, "input") + require.NotContains(t, d, "input_sha256") + require.NotContains(t, d, "input_preview") + require.NotContains(t, detail, "PRIVATE_COMMAND_BODY") } -// TestBuildToolCallDetail_NonSensitiveTool verifies non-sensitive tools store -// a sha256 + truncated preview, never the full input. +// TestBuildToolCallDetail_NonSensitiveTool verifies non-sensitive tool input +// is omitted instead of retaining a preview or a guessable content hash. func TestBuildToolCallDetail_NonSensitiveTool(t *testing.T) { t.Parallel() tc := events.ToolCallData{ @@ -133,17 +128,10 @@ func TestBuildToolCallDetail_NonSensitiveTool(t *testing.T) { var d map[string]any require.NoError(t, json.Unmarshal([]byte(detail), &d)) require.Equal(t, "Read", d["name"]) - require.Nil(t, d["sensitive"], "non-sensitive tool must not mark itself sensitive") - require.Contains(t, d, "input_sha256") - require.Contains(t, d, "input_preview") - // input_preview must NOT contain the raw serialized input verbatim — it is - // truncated JSON. For this tiny input it fits, but the field must be the - // preview, not "input". - preview, _ := d["input_preview"].(string) - require.Contains(t, preview, "/etc/hosts") - // sha256 must be a 64-char hex string. - sha, _ := d["input_sha256"].(string) - require.Len(t, sha, 64) + require.NotContains(t, d, "input") + require.NotContains(t, d, "input_sha256") + require.NotContains(t, d, "input_preview") + require.NotContains(t, detail, "/etc/hosts") } func TestBuildToolCallDetail_NonSensitiveToolRedactsCredentials(t *testing.T) { @@ -162,17 +150,19 @@ func TestBuildToolCallDetail_NonSensitiveToolRedactsCredentials(t *testing.T) { detail := buildToolCallDetail(&tc) require.NotContains(t, detail, "FAKE_PREVIEW_TOKEN_123456") require.NotContains(t, detail, "FAKE_PRIVATE_BODY") - require.Contains(t, detail, "[REDACTED]") + require.NotContains(t, detail, "config.json") + require.NotContains(t, detail, "private_key") + require.NotContains(t, detail, "input") } -func TestBuildToolCallDetail_LowercaseBashIsSensitive(t *testing.T) { +func TestBuildToolCallDetail_LowercaseBashOmitsInput(t *testing.T) { t.Parallel() tc := events.ToolCallData{ID: "tc-bash", Name: "bash", Input: map[string]any{"command": "pwd"}} detail := buildToolCallDetail(&tc) var d map[string]any require.NoError(t, json.Unmarshal([]byte(detail), &d)) - require.Equal(t, true, d["sensitive"]) - require.Contains(t, d, "input") + require.NotContains(t, d, "input") + require.NotContains(t, detail, "pwd") } // TestBuildToolCallDetail_NoInput covers parameterless tools. @@ -183,13 +173,12 @@ func TestBuildToolCallDetail_NoInput(t *testing.T) { var d map[string]any require.NoError(t, json.Unmarshal([]byte(detail), &d)) require.Equal(t, "ListTools", d["name"]) - require.Contains(t, d, "input_sha256") + require.NotContains(t, d, "input_sha256") require.NotContains(t, d, "input_preview") require.NotContains(t, d, "input") } -// TestBuildToolCallDetail_ACPExtFields verifies kind/title are recorded when -// present (ACP worker extension fields). +// TestBuildToolCallDetail_ACPExtFields verifies metadata values are omitted. func TestBuildToolCallDetail_ACPExtFields(t *testing.T) { t.Parallel() tc := events.ToolCallData{ @@ -202,8 +191,9 @@ func TestBuildToolCallDetail_ACPExtFields(t *testing.T) { detail := buildToolCallDetail(&tc) var d map[string]any require.NoError(t, json.Unmarshal([]byte(detail), &d)) - require.Equal(t, "edit: main.go", d["title"]) - require.Equal(t, "edit", d["kind"]) + require.NotContains(t, d, "title") + require.NotContains(t, d, "kind") + require.NotContains(t, detail, "main.go") } // TestMaskSensitiveInput covers the PII redaction patterns (spec §5.9). @@ -294,7 +284,7 @@ func TestEmitToolCallAudit_Enqueues(t *testing.T) { sessPlatform: "feishu", sessOwner: "ou_testuser", } - tc := events.ToolCallData{ID: "tc-audit-1", Name: "Bash", Input: map[string]any{"command": "ls"}} + tc := events.ToolCallData{ID: "tc-audit-1", Name: "Bash", Input: map[string]any{"command": "PRIVATE_TOOL_BODY"}} b.emitToolCallAudit(fc, &tc) @@ -311,7 +301,9 @@ func TestEmitToolCallAudit_Enqueues(t *testing.T) { require.Equal(t, "tool", r.ResourceType) require.Equal(t, "tc-audit-1", r.ResourceID) require.Contains(t, r.DetailJSON, "Bash") - require.Contains(t, r.DetailJSON, `"sensitive":true`) + require.NotContains(t, r.DetailJSON, "PRIVATE_TOOL_BODY") + require.NotContains(t, r.DetailJSON, `"input"`) + require.NotContains(t, r.DetailJSON, `"input_sha256"`) } // TestEmitToolCallAudit_AnonymousFallback verifies empty owner falls back to @@ -384,9 +376,9 @@ func TestEmitPermissionRequestAudit(t *testing.T) { require.Equal(t, "sess-req-1", r.SessionID) require.Equal(t, "permission", r.ResourceType) require.Equal(t, "req-pr-1", r.ResourceID) - require.Contains(t, r.DetailJSON, `"tool_name":"Bash"`) - require.Contains(t, r.DetailJSON, "Execute shell script") - require.Contains(t, r.DetailJSON, "ls") + require.NotContains(t, r.DetailJSON, "Bash") + require.NotContains(t, r.DetailJSON, "Execute shell script") + require.NotContains(t, r.DetailJSON, "ls") } func TestEmitInteractionAudit_PermissionResponse(t *testing.T) { @@ -411,7 +403,7 @@ func TestEmitInteractionAudit_PermissionResponse(t *testing.T) { require.Equal(t, "permission", r.ResourceType) require.Equal(t, "req-p1", r.ResourceID) require.Contains(t, r.DetailJSON, `"allowed":true`) - require.Contains(t, r.DetailJSON, "approved by user") + require.NotContains(t, r.DetailJSON, "approved by user") // Test Allowed = false (Denied) h.emitInteractionAudit("u-1", "webchat", "sess-p2", events.PermissionResponse, map[string]any{ @@ -425,7 +417,7 @@ func TestEmitInteractionAudit_PermissionResponse(t *testing.T) { require.Equal(t, audit.ActionPermissionResponse, r2.Action) require.Equal(t, audit.OutcomeDenied, r2.Outcome) require.Contains(t, r2.DetailJSON, `"allowed":false`) - require.Contains(t, r2.DetailJSON, "user denied") + require.NotContains(t, r2.DetailJSON, "user denied") } func TestEmitInteractionAudit_QuestionResponse(t *testing.T) { @@ -444,7 +436,7 @@ func TestEmitInteractionAudit_QuestionResponse(t *testing.T) { require.Equal(t, audit.OutcomeSuccess, r.Outcome) require.Equal(t, "question", r.ResourceType) require.Equal(t, "req-q1", r.ResourceID) - require.Contains(t, r.DetailJSON, "Option A") + require.NotContains(t, r.DetailJSON, "Option A") } func TestEmitInteractionAudit_ElicitationResponse(t *testing.T) { @@ -453,8 +445,9 @@ func TestEmitInteractionAudit_ElicitationResponse(t *testing.T) { h := &Handler{auditCollector: c} h.emitInteractionAudit("u-3", "slack", "sess-e1", events.ElicitationResponse, events.ElicitationResponseData{ - ID: "req-e1", - Action: "decline", + ID: "req-e1", + Action: "decline", + Content: map[string]any{"answer": "PRIVATE_ANSWER"}, }, "") require.Eventually(t, func() bool { return len(query(t)) >= 1 }, 2*time.Second, 5*time.Millisecond) @@ -464,4 +457,5 @@ func TestEmitInteractionAudit_ElicitationResponse(t *testing.T) { require.Equal(t, "elicitation", r.ResourceType) require.Equal(t, "req-e1", r.ResourceID) require.Contains(t, r.DetailJSON, `"action":"decline"`) + require.NotContains(t, r.DetailJSON, "PRIVATE_ANSWER") } diff --git a/internal/gateway/turn_deadline_test.go b/internal/gateway/turn_deadline_test.go new file mode 100644 index 000000000..54e13bf44 --- /dev/null +++ b/internal/gateway/turn_deadline_test.go @@ -0,0 +1,80 @@ +package gateway + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestWorkerRunLifecycle_TurnTimeoutUsesAbsoluteDeadline(t *testing.T) { + t.Parallel() + + lifecycle := newWorkerRunLifecycle(nil) + fired := make(chan uint64, 1) + lifecycle.setTurnTimeoutHandler(func(generation uint64) { + fired <- generation + }) + t.Cleanup(lifecycle.clearTurnTimeoutHandler) + + firstDeadline := time.Now().Add(300 * time.Millisecond) + firstGeneration := lifecycle.armTurnTimeout(firstDeadline) + secondDeadline := time.Now().Add(80 * time.Millisecond) + secondGeneration := lifecycle.armTurnTimeout(secondDeadline) + require.Greater(t, secondGeneration, firstGeneration) + + select { + case generation := <-fired: + require.Equal(t, secondGeneration, generation, "a replaced turn deadline must not fire") + case <-time.After(time.Second): + t.Fatal("turn deadline did not fire") + } +} + +func TestWorkerRunLifecycle_StopTurnTimeoutFencesCallback(t *testing.T) { + t.Parallel() + + lifecycle := newWorkerRunLifecycle(nil) + fired := make(chan struct{}, 1) + lifecycle.setTurnTimeoutHandler(func(uint64) { + fired <- struct{}{} + }) + t.Cleanup(lifecycle.clearTurnTimeoutHandler) + + lifecycle.armTurnTimeout(time.Now().Add(50 * time.Millisecond)) + lifecycle.stopTurnTimeout() + + select { + case <-fired: + t.Fatal("stopped turn deadline fired") + case <-time.After(100 * time.Millisecond): + } +} + +func TestBridge_RestoredTurnDeadlineFiresWhenCurrentTimeoutIsDisabled(t *testing.T) { + t.Parallel() + + b := &Bridge{} + worker := &mockBridgeWorker{workerType: "test"} + sm := &mockBridgeSM{} + sm.On("GetWorker", "session-1").Return(worker).Once() + b.sm = sm + binding := b.bindWorkerRun("session-1", worker, "run-1", launchPlan{}) + fired := make(chan uint64, 1) + binding.lifecycle.setTurnTimeoutHandler(func(generation uint64) { + fired <- generation + }) + t.Cleanup(binding.lifecycle.clearTurnTimeoutHandler) + + startedAt := time.Now().Add(-time.Minute).UnixMilli() + deadlineAt := time.Now().Add(80 * time.Millisecond).UnixMilli() + require.NoError(t, b.RecordTurnDeadlineForRun("session-1", "run-1", startedAt, deadlineAt)) + + select { + case generation := <-fired: + require.True(t, binding.lifecycle.turnTimeoutCurrent(generation)) + case <-time.After(time.Second): + t.Fatal("persisted turn deadline was not restored") + } + sm.AssertExpectations(t) +} diff --git a/internal/lifecycle/migration.go b/internal/lifecycle/migration.go new file mode 100644 index 000000000..e324ca89a --- /dev/null +++ b/internal/lifecycle/migration.go @@ -0,0 +1,869 @@ +package lifecycle + +import ( + "context" + "crypto/sha256" + "database/sql" + "encoding/hex" + "errors" + "fmt" + "hash" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/google/uuid" + + "github.com/hrygo/hotplex/internal/dbutil" +) + +const ( + migrationPreviewTTL = 30 * time.Minute + maxMigrationPlans = 16 + maxMigrationBatchSize = 500 +) + +var ( + ErrConfirmationRequired = errors.New("lifecycle: migration plan confirmation required") + ErrPreviewExpired = errors.New("lifecycle: migration preview expired or unavailable") + ErrStalePreview = errors.New("lifecycle: migration preview is stale") +) + +// RetentionPolicy is the v2 policy used to migrate eligible legacy records. +// LegacyContentRetention preserves the effective expiry used before per-record +// deadlines were introduced. +type RetentionPolicy struct { + ArchiveAfter time.Duration + ConversationRetention time.Duration + ContentRetention time.Duration + LegacyContentRetention time.Duration + BatchSize int +} + +// MigrationSummary describes the records that would receive a new lifecycle +// policy or an extended explicit deadline. Bytes counts stored content bytes +// for message bodies; session metadata deliberately reports zero bytes. +type MigrationSummary struct { + Count int64 `json:"count"` + Bytes int64 `json:"bytes"` + OldestDeadline *time.Time `json:"oldest_deadline,omitempty"` +} + +// MigrationBlocked contains aggregate-only facts excluded from migration. +type MigrationBlocked struct { + LegacySessionsMissingInputClock int64 `json:"legacy_sessions_missing_input_clock"` + OrphanedEvents int64 `json:"orphaned_events"` + OrphanedTurns int64 `json:"orphaned_turns"` + ContentOnSessionsMissingInputClock int64 `json:"content_on_sessions_missing_input_clock"` + UnknownExecutionRecords int64 `json:"unknown_execution_records"` + ActiveExecutionRecords int64 `json:"active_execution_records"` +} + +// MigrationPreview contains no session identifiers, message text, payloads, +// or credentials. Plan IDs bind the subsequent apply to an in-memory snapshot. +type MigrationPreview struct { + PlanID string `json:"plan_id"` + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_at"` + PolicyRevision string `json:"policy_revision"` + BatchSize int `json:"batch_size"` + Sessions MigrationSummary `json:"sessions"` + Events MigrationSummary `json:"events"` + Turns MigrationSummary `json:"turns"` + Blocked MigrationBlocked `json:"blocked"` +} + +// MigrationApplyResult reports how many rows in each bounded batch were +// updated. It intentionally contains no row identifiers. +type MigrationApplyResult struct { + Sessions int64 `json:"sessions"` + Events int64 `json:"events"` + Turns int64 `json:"turns"` +} + +type migrationPolicySource func() RetentionPolicy +type migrationNow func() time.Time + +// MigrationService previews and applies a short-lived, explicitly confirmed +// migration from legacy lifecycle clocks to v2 deadlines. +type MigrationService struct { + db *sql.DB + dialect dbutil.Dialect + policy migrationPolicySource + now migrationNow + + mu sync.Mutex + plans map[string]*migrationPlan +} + +type migrationPlan struct { + preview MigrationPreview + digest string + policy RetentionPolicy + rows migrationSnapshot +} + +type migrationSnapshot struct { + digest string + events []contentMigration + turns []contentMigration + preview MigrationPreview +} + +type sessionMigration struct { + id string + state string + lastInput time.Time + lifecycleRevision string + oldArchive sql.NullTime + oldConversationExpiry sql.NullTime + oldLastContentExpiry sql.NullTime + oldHistoryExpiry sql.NullTime + archive time.Time + conversationExpiry time.Time + lastContentExpiry time.Time + historyExpiry time.Time +} + +type contentMigration struct { + id int64 + sessionID string + createdAtMS int64 + oldExpiryMS int64 + newExpiryMS int64 +} + +type migrationQueryer interface { + QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) + QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row +} + +// NewMigrationService constructs a migration service. Configuration is read +// for each preview and apply so a policy reload invalidates old plans. +func NewMigrationService( + db *sql.DB, + dialect dbutil.Dialect, + policy func() RetentionPolicy, + now func() time.Time, +) *MigrationService { + if now == nil { + now = time.Now + } + return &MigrationService{ + db: db, + dialect: dialect, + policy: policy, + now: now, + plans: make(map[string]*migrationPlan), + } +} + +// Preview creates an aggregate-only report and a bounded, short-lived plan. +func (s *MigrationService) Preview(ctx context.Context) (*MigrationPreview, error) { + if s == nil || s.db == nil || s.policy == nil { + return nil, errors.New("lifecycle: migration service is unavailable") + } + policy := normalizeMigrationPolicy(s.policy()) + if err := validateRetentionPolicy(policy); err != nil { + return nil, err + } + + tx, err := s.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("lifecycle: begin migration preview: %w", err) + } + defer func() { _ = tx.Rollback() }() + + snapshot, err := s.readSnapshot(ctx, tx, policy) + if err != nil { + return nil, err + } + if err := tx.Commit(); err != nil { + return nil, fmt.Errorf("lifecycle: finish migration preview: %w", err) + } + + now := s.now().UTC() + snapshot.preview.PlanID = uuid.NewString() + snapshot.preview.CreatedAt = now + snapshot.preview.ExpiresAt = now.Add(migrationPreviewTTL) + + s.mu.Lock() + defer s.mu.Unlock() + s.prunePlansLocked(now) + if len(s.plans) >= maxMigrationPlans { + s.removeOldestPlanLocked() + } + s.plans[snapshot.preview.PlanID] = &migrationPlan{ + preview: snapshot.preview, + digest: snapshot.digest, + policy: policy, + rows: snapshot, + } + preview := snapshot.preview + return &preview, nil +} + +// Apply requires the preview's exact plan ID to be submitted twice. It +// revalidates the entire aggregate snapshot in a serializable transaction and +// then updates at most BatchSize rows per data type. +func (s *MigrationService) Apply( + ctx context.Context, + planID string, + confirmPlanID string, +) (*MigrationApplyResult, error) { + if strings.TrimSpace(planID) == "" || planID != confirmPlanID { + return nil, ErrConfirmationRequired + } + if s == nil || s.db == nil || s.policy == nil { + return nil, errors.New("lifecycle: migration service is unavailable") + } + + s.mu.Lock() + defer s.mu.Unlock() + now := s.now().UTC() + s.prunePlansLocked(now) + plan, ok := s.plans[planID] + if !ok || !now.Before(plan.preview.ExpiresAt) { + delete(s.plans, planID) + return nil, ErrPreviewExpired + } + + policy := normalizeMigrationPolicy(s.policy()) + if err := validateRetentionPolicy(policy); err != nil { + return nil, err + } + if policy != plan.policy { + delete(s.plans, planID) + return nil, ErrStalePreview + } + + tx, err := s.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable}) + if err != nil { + return nil, fmt.Errorf("lifecycle: begin migration apply: %w", err) + } + rollback := func(cause error) (*MigrationApplyResult, error) { + _ = tx.Rollback() + return nil, cause + } + + current, err := s.readSnapshot(ctx, tx, policy) + if err != nil { + return rollback(err) + } + if current.digest != plan.digest { + delete(s.plans, planID) + return rollback(ErrStalePreview) + } + + result, err := applySnapshot(ctx, tx, s.dialect, policy, plan.rows) + if err != nil { + if errors.Is(err, ErrStalePreview) { + delete(s.plans, planID) + } + return rollback(err) + } + if normalizeMigrationPolicy(s.policy()) != policy { + delete(s.plans, planID) + return rollback(ErrStalePreview) + } + if err := tx.Commit(); err != nil { + return nil, fmt.Errorf("lifecycle: commit migration apply: %w", err) + } + delete(s.plans, planID) + return result, nil +} + +func (s *MigrationService) readSnapshot( + ctx context.Context, + q migrationQueryer, + policy RetentionPolicy, +) (migrationSnapshot, error) { + snapshot := migrationSnapshot{ + preview: MigrationPreview{ + PolicyRevision: retentionPolicyRevision(policy), + BatchSize: policy.BatchSize, + }, + } + digest := sha256.New() + writeDigest(digest, snapshot.preview.PolicyRevision) + + if err := readBlocked(ctx, q, s.dialect, &snapshot.preview.Blocked, digest); err != nil { + return migrationSnapshot{}, err + } + if err := readSessionCandidates(ctx, q, s.dialect, policy, &snapshot.preview.Sessions, digest); err != nil { + return migrationSnapshot{}, err + } + if err := readContentCandidates(ctx, q, s.dialect, policy, "events", `length(CAST(data AS BLOB))`, `octet_length(data)`, &snapshot.preview.Events, &snapshot.events, digest); err != nil { + return migrationSnapshot{}, err + } + if err := readContentCandidates(ctx, q, s.dialect, policy, "turns", + `length(CAST(content AS BLOB)) + length(CAST(COALESCE(tools_json, '') AS BLOB))`, + `octet_length(content) + octet_length(COALESCE(tools_json, ''))`, + &snapshot.preview.Turns, &snapshot.turns, digest); err != nil { + return migrationSnapshot{}, err + } + + snapshot.digest = hex.EncodeToString(digest.Sum(nil)) + return snapshot, nil +} + +func readBlocked( + ctx context.Context, + q migrationQueryer, + dialect dbutil.Dialect, + blocked *MigrationBlocked, + digest hash.Hash, +) error { + counts := []struct { + query string + dst *int64 + name string + }{ + { + query: `SELECT COUNT(*) FROM sessions + WHERE lifecycle_policy = 'legacy' + AND state <> 'deleted' AND deleted_at IS NULL AND last_input_at IS NULL`, + dst: &blocked.LegacySessionsMissingInputClock, + name: "missing_input_clock", + }, + { + query: `SELECT COUNT(*) FROM events e LEFT JOIN sessions s ON s.id = e.session_id + WHERE s.id IS NULL`, + dst: &blocked.OrphanedEvents, + name: "orphaned_events", + }, + { + query: `SELECT COUNT(*) FROM turns t LEFT JOIN sessions s ON s.id = t.session_id + WHERE s.id IS NULL`, + dst: &blocked.OrphanedTurns, + name: "orphaned_turns", + }, + { + query: `SELECT + (SELECT COUNT(*) FROM events e JOIN sessions s ON s.id = e.session_id + WHERE s.lifecycle_policy = 'legacy' AND s.state <> 'deleted' + AND s.deleted_at IS NULL AND s.last_input_at IS NULL) + + + (SELECT COUNT(*) FROM turns t JOIN sessions s ON s.id = t.session_id + WHERE s.lifecycle_policy = 'legacy' AND s.state <> 'deleted' + AND s.deleted_at IS NULL AND s.last_input_at IS NULL)`, + dst: &blocked.ContentOnSessionsMissingInputClock, + name: "content_missing_clock", + }, + { + query: `SELECT COUNT(*) FROM execution_inputs + WHERE status = 'unknown' OR runtime_status = 'unknown' OR fence_reason <> ''`, + dst: &blocked.UnknownExecutionRecords, + name: "unknown_execution_records", + }, + { + query: `SELECT COUNT(*) FROM execution_inputs + WHERE runtime_status IN ('pending', 'running') OR fence_reason <> ''`, + dst: &blocked.ActiveExecutionRecords, + name: "active_execution_records", + }, + } + for _, item := range counts { + if err := q.QueryRowContext(ctx, dialect.Rebind(item.query)).Scan(item.dst); err != nil { + return fmt.Errorf("lifecycle: count %s: %w", item.name, err) + } + writeDigest(digest, item.name, strconv.FormatInt(*item.dst, 10)) + } + return nil +} + +func readSessionCandidates( + ctx context.Context, + q migrationQueryer, + dialect dbutil.Dialect, + policy RetentionPolicy, + summary *MigrationSummary, + digest hash.Hash, +) error { + rows, err := q.QueryContext(ctx, dialect.Rebind(` + SELECT id, state, last_input_at, lifecycle_policy_revision, + archive_at, conversation_expires_at, last_content_expires_at, history_expires_at + FROM sessions + WHERE lifecycle_policy = 'legacy' + AND state <> 'deleted' AND deleted_at IS NULL AND last_input_at IS NOT NULL + ORDER BY last_input_at ASC, id ASC`)) + if err != nil { + return fmt.Errorf("lifecycle: list legacy sessions: %w", err) + } + defer func() { _ = rows.Close() }() + + for rows.Next() { + var candidate sessionMigration + if err := rows.Scan( + &candidate.id, + &candidate.state, + &candidate.lastInput, + &candidate.lifecycleRevision, + &candidate.oldArchive, + &candidate.oldConversationExpiry, + &candidate.oldLastContentExpiry, + &candidate.oldHistoryExpiry, + ); err != nil { + return fmt.Errorf("lifecycle: scan legacy session: %w", err) + } + candidate.lastInput = candidate.lastInput.UTC() + candidate.archive = maxTime(candidate.oldArchive, candidate.lastInput.Add(policy.ArchiveAfter)) + candidate.conversationExpiry = maxTime(candidate.oldConversationExpiry, candidate.lastInput.Add(policy.ConversationRetention)) + writeDigest(digest, + "session", + candidate.id, + candidate.state, + candidate.lastInput.Format(time.RFC3339Nano), + candidate.lifecycleRevision, + nullTimeString(candidate.oldArchive), + nullTimeString(candidate.oldConversationExpiry), + nullTimeString(candidate.oldLastContentExpiry), + nullTimeString(candidate.oldHistoryExpiry), + ) + summary.Count++ + updateOldestDeadline(summary, candidate.conversationExpiry) + } + if err := rows.Err(); err != nil { + return fmt.Errorf("lifecycle: iterate legacy sessions: %w", err) + } + if err := rows.Close(); err != nil { + return fmt.Errorf("lifecycle: close legacy sessions: %w", err) + } + return nil +} + +func readContentCandidates( + ctx context.Context, + q migrationQueryer, + dialect dbutil.Dialect, + policy RetentionPolicy, + table string, + sqliteBytesExpr string, + postgresBytesExpr string, + summary *MigrationSummary, + selected *[]contentMigration, + digest hash.Hash, +) error { + byteExpr := sqliteBytesExpr + if dialect == dbutil.DialectPostgres { + byteExpr = postgresBytesExpr + } + query := `SELECT c.id, c.session_id, c.created_at, c.expires_at, ` + byteExpr + ` + FROM ` + table + ` c + JOIN sessions s ON s.id = c.session_id + WHERE s.lifecycle_policy = 'legacy' + AND s.state <> 'deleted' AND s.deleted_at IS NULL AND s.last_input_at IS NOT NULL + ORDER BY c.id ASC` + rows, err := q.QueryContext(ctx, dialect.Rebind(query)) + if err != nil { + return fmt.Errorf("lifecycle: list legacy %s: %w", table, err) + } + defer func() { _ = rows.Close() }() + for rows.Next() { + var id, createdAtMS, expiryMS, byteCount int64 + var sessionID string + if err := rows.Scan(&id, &sessionID, &createdAtMS, &expiryMS, &byteCount); err != nil { + return fmt.Errorf("lifecycle: scan legacy %s: %w", table, err) + } + newExpiryMS := migratedContentExpiry(createdAtMS, expiryMS, policy) + needsUpdate := expiryMS == 0 || expiryMS < createdAtMS+durationMillis(policy.ContentRetention) + writeDigest(digest, + table, + strconv.FormatInt(id, 10), + sessionID, + strconv.FormatInt(createdAtMS, 10), + strconv.FormatInt(expiryMS, 10), + strconv.FormatInt(byteCount, 10), + strconv.FormatInt(newExpiryMS, 10), + ) + if needsUpdate { + summary.Count++ + summary.Bytes += byteCount + updateOldestDeadline(summary, time.UnixMilli(newExpiryMS).UTC()) + if len(*selected) < policy.BatchSize { + *selected = append(*selected, contentMigration{ + id: id, + sessionID: sessionID, + createdAtMS: createdAtMS, + oldExpiryMS: expiryMS, + newExpiryMS: newExpiryMS, + }) + } + } + } + if err := rows.Err(); err != nil { + return fmt.Errorf("lifecycle: iterate legacy %s: %w", table, err) + } + if err := rows.Close(); err != nil { + return fmt.Errorf("lifecycle: close legacy %s: %w", table, err) + } + + return nil +} + +func readSelectedSessionContentExpiry( + ctx context.Context, + q migrationQueryer, + dialect dbutil.Dialect, + policy RetentionPolicy, + sessions []sessionMigration, +) error { + if len(sessions) == 0 { + return nil + } + ids := make([]string, 0, len(sessions)) + for _, candidate := range sessions { + ids = append(ids, candidate.id) + } + sort.Strings(ids) + placeholders := make([]string, len(ids)) + for i := range ids { + placeholders[i] = "(?)" + } + query := `WITH selected_sessions(session_id) AS (VALUES ` + strings.Join(placeholders, ",") + `), + content AS ( + SELECT e.session_id, ` + effectiveContentExpiry("e") + ` AS expiry_ms + FROM events e JOIN selected_sessions s ON s.session_id = e.session_id + UNION ALL + SELECT t.session_id, ` + effectiveContentExpiry("t") + ` AS expiry_ms + FROM turns t JOIN selected_sessions s ON s.session_id = t.session_id + ) + SELECT session_id, MAX(expiry_ms) FROM content GROUP BY session_id` + // Session IDs appear once in the CTE, followed by each branch's duration + // placeholders. Keeping the placeholder count bounded also supports older + // SQLite builds with a 999-variable limit. + durationArgs := durationArgs(policy) + orderedArgs := make([]any, 0, len(ids)+len(durationArgs)*2) + for _, id := range ids { + orderedArgs = append(orderedArgs, id) + } + orderedArgs = append(orderedArgs, durationArgs...) + orderedArgs = append(orderedArgs, durationArgs...) + rows, err := q.QueryContext(ctx, dialect.Rebind(query), orderedArgs...) + if err != nil { + return fmt.Errorf("lifecycle: summarize selected session content: %w", err) + } + defer func() { _ = rows.Close() }() + latest := make(map[string]time.Time, len(sessions)) + for rows.Next() { + var sessionID string + var expiryMS int64 + if err := rows.Scan(&sessionID, &expiryMS); err != nil { + return fmt.Errorf("lifecycle: scan selected session content: %w", err) + } + latest[sessionID] = time.UnixMilli(expiryMS).UTC() + } + if err := rows.Err(); err != nil { + return fmt.Errorf("lifecycle: iterate selected session content: %w", err) + } + if err := rows.Close(); err != nil { + return fmt.Errorf("lifecycle: close selected session content: %w", err) + } + for i := range sessions { + if expiry, ok := latest[sessions[i].id]; ok { + sessions[i].lastContentExpiry = expiry + } + } + return nil +} + +func applySnapshot( + ctx context.Context, + tx *sql.Tx, + dialect dbutil.Dialect, + policy RetentionPolicy, + snapshot migrationSnapshot, +) (*MigrationApplyResult, error) { + result := &MigrationApplyResult{} + revision := retentionPolicyRevision(policy) + for _, candidate := range snapshot.events { + updated, err := tx.ExecContext(ctx, dialect.Rebind(` + UPDATE events SET expires_at = ? + WHERE id = ? AND session_id = ? AND created_at = ? AND expires_at = ?`), + candidate.newExpiryMS, candidate.id, candidate.sessionID, candidate.createdAtMS, candidate.oldExpiryMS, + ) + if err != nil { + return nil, fmt.Errorf("lifecycle: migrate event deadline: %w", err) + } + rows, err := updated.RowsAffected() + if err != nil { + return nil, fmt.Errorf("lifecycle: event rows affected: %w", err) + } + if rows != 1 { + return nil, ErrStalePreview + } + result.Events++ + } + for _, candidate := range snapshot.turns { + updated, err := tx.ExecContext(ctx, dialect.Rebind(` + UPDATE turns SET expires_at = ? + WHERE id = ? AND session_id = ? AND created_at = ? AND expires_at = ?`), + candidate.newExpiryMS, candidate.id, candidate.sessionID, candidate.createdAtMS, candidate.oldExpiryMS, + ) + if err != nil { + return nil, fmt.Errorf("lifecycle: migrate turn deadline: %w", err) + } + rows, err := updated.RowsAffected() + if err != nil { + return nil, fmt.Errorf("lifecycle: turn rows affected: %w", err) + } + if rows != 1 { + return nil, ErrStalePreview + } + result.Turns++ + } + + // Do not switch a session to v2 until every existing content row already + // has an explicit deadline at least as long as the effective legacy/v2 + // retention. Otherwise a bounded content batch could leave old rows subject + // to the shorter legacy TTL after the session root has moved to v2. + sessions, err := readReadySessionCandidates(ctx, tx, dialect, policy) + if err != nil { + return nil, err + } + if err := readSelectedSessionContentExpiry(ctx, tx, dialect, policy, sessions); err != nil { + return nil, err + } + for _, candidate := range sessions { + candidate.archive = maxTime(candidate.oldArchive, candidate.lastInput.Add(policy.ArchiveAfter)) + candidate.conversationExpiry = maxTime(candidate.oldConversationExpiry, candidate.lastInput.Add(policy.ConversationRetention)) + candidate.lastContentExpiry = maxTime(candidate.oldLastContentExpiry, candidate.lastContentExpiry) + candidate.historyExpiry = maxTime(candidate.oldHistoryExpiry, candidate.conversationExpiry) + candidate.historyExpiry = maxTime( + sql.NullTime{Time: candidate.lastContentExpiry, Valid: !candidate.lastContentExpiry.IsZero()}, + candidate.historyExpiry, + ) + updated, err := tx.ExecContext(ctx, dialect.Rebind(` + UPDATE sessions + SET lifecycle_policy = 'v2', + lifecycle_policy_revision = ?, + archive_at = ?, + conversation_expires_at = ?, + last_content_expires_at = ?, + history_expires_at = ? + WHERE id = ? AND lifecycle_policy = 'legacy' + AND state = ? AND last_input_at = ? AND deleted_at IS NULL`), + revision, + candidate.archive, + candidate.conversationExpiry, + nullableTime(candidate.lastContentExpiry), + candidate.historyExpiry, + candidate.id, + candidate.state, + candidate.lastInput, + ) + if err != nil { + return nil, fmt.Errorf("lifecycle: migrate session: %w", err) + } + rows, err := updated.RowsAffected() + if err != nil { + return nil, fmt.Errorf("lifecycle: session rows affected: %w", err) + } + if rows != 1 { + return nil, ErrStalePreview + } + result.Sessions++ + } + return result, nil +} + +func readReadySessionCandidates( + ctx context.Context, + q migrationQueryer, + dialect dbutil.Dialect, + policy RetentionPolicy, +) ([]sessionMigration, error) { + // For a content row, migratedContentExpiry preserves the later of the + // configured legacy and v2 deadlines. A session is ready only when all + // existing event/turn rows have reached at least that effective deadline. + contentRetention := maxDuration(policy.ContentRetention, policy.LegacyContentRetention) + contentRetentionMS := durationMillis(contentRetention) + rows, err := q.QueryContext(ctx, dialect.Rebind(` + SELECT s.id, s.state, s.last_input_at, s.lifecycle_policy_revision, + s.archive_at, s.conversation_expires_at, s.last_content_expires_at, s.history_expires_at + FROM sessions s + WHERE s.lifecycle_policy = 'legacy' + AND s.state <> 'deleted' AND s.deleted_at IS NULL AND s.last_input_at IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM events e + WHERE e.session_id = s.id AND e.expires_at < e.created_at + ? + ) + AND NOT EXISTS ( + SELECT 1 FROM turns t + WHERE t.session_id = s.id AND t.expires_at < t.created_at + ? + ) + ORDER BY s.last_input_at ASC, s.id ASC + LIMIT ?`), + contentRetentionMS, contentRetentionMS, policy.BatchSize) + if err != nil { + return nil, fmt.Errorf("lifecycle: list sessions with migrated content: %w", err) + } + defer func() { _ = rows.Close() }() + + sessions := make([]sessionMigration, 0, policy.BatchSize) + for rows.Next() { + var candidate sessionMigration + if err := rows.Scan( + &candidate.id, + &candidate.state, + &candidate.lastInput, + &candidate.lifecycleRevision, + &candidate.oldArchive, + &candidate.oldConversationExpiry, + &candidate.oldLastContentExpiry, + &candidate.oldHistoryExpiry, + ); err != nil { + return nil, fmt.Errorf("lifecycle: scan session with migrated content: %w", err) + } + candidate.lastInput = candidate.lastInput.UTC() + sessions = append(sessions, candidate) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("lifecycle: iterate sessions with migrated content: %w", err) + } + if err := rows.Close(); err != nil { + return nil, fmt.Errorf("lifecycle: close sessions with migrated content: %w", err) + } + return sessions, nil +} + +func maxDuration(left, right time.Duration) time.Duration { + if left > right { + return left + } + return right +} + +func effectiveContentExpiry(alias string) string { + return `CASE + WHEN ` + alias + `.expires_at > 0 THEN + CASE WHEN ` + alias + `.expires_at > ` + alias + `.created_at + ? THEN ` + alias + `.expires_at + ELSE ` + alias + `.created_at + ? END + WHEN ` + alias + `.created_at + ? > ` + alias + `.created_at + ? THEN ` + alias + `.created_at + ? + ELSE ` + alias + `.created_at + ? END` +} + +func durationArgs(policy RetentionPolicy) []any { + content := durationMillis(policy.ContentRetention) + legacy := durationMillis(policy.LegacyContentRetention) + return []any{content, content, legacy, content, legacy, content} +} + +func migratedContentExpiry(createdAtMS, oldExpiryMS int64, policy RetentionPolicy) int64 { + target := createdAtMS + durationMillis(policy.ContentRetention) + if oldExpiryMS > 0 { + if oldExpiryMS > target { + return oldExpiryMS + } + return target + } + legacy := createdAtMS + durationMillis(policy.LegacyContentRetention) + if legacy > target { + return legacy + } + return target +} + +func validateRetentionPolicy(policy RetentionPolicy) error { + switch { + case policy.ArchiveAfter <= 0: + return errors.New("lifecycle: archive retention must be positive") + case policy.ConversationRetention <= 0: + return errors.New("lifecycle: conversation retention must be positive") + case policy.ArchiveAfter > policy.ConversationRetention: + return errors.New("lifecycle: archive retention must not exceed conversation retention") + case policy.ContentRetention <= 0: + return errors.New("lifecycle: content retention must be positive") + case policy.LegacyContentRetention <= 0: + return errors.New("lifecycle: legacy content retention must be positive") + case policy.BatchSize <= 0: + return errors.New("lifecycle: migration batch size must be positive") + case policy.BatchSize > maxMigrationBatchSize: + return fmt.Errorf("lifecycle: migration batch size must not exceed %d", maxMigrationBatchSize) + } + return nil +} + +func normalizeMigrationPolicy(policy RetentionPolicy) RetentionPolicy { + if policy.BatchSize > maxMigrationBatchSize { + policy.BatchSize = maxMigrationBatchSize + } + return policy +} + +func retentionPolicyRevision(policy RetentionPolicy) string { + input := fmt.Sprintf( + "v2|archive=%d|conversation=%d|content=%d|legacy=%d|batch=%d", + policy.ArchiveAfter, + policy.ConversationRetention, + policy.ContentRetention, + policy.LegacyContentRetention, + policy.BatchSize, + ) + sum := sha256.Sum256([]byte(input)) + return "v2-" + hex.EncodeToString(sum[:8]) +} + +func durationMillis(duration time.Duration) int64 { + return duration.Milliseconds() +} + +func maxTime(old sql.NullTime, target time.Time) time.Time { + if old.Valid && old.Time.After(target) { + return old.Time.UTC() + } + return target.UTC() +} + +func nullableTime(value time.Time) any { + if value.IsZero() { + return nil + } + return value.UTC() +} + +func nullTimeString(value sql.NullTime) string { + if !value.Valid { + return "" + } + return value.Time.UTC().Format(time.RFC3339Nano) +} + +func updateOldestDeadline(summary *MigrationSummary, candidate time.Time) { + candidate = candidate.UTC() + if summary.OldestDeadline == nil || candidate.Before(*summary.OldestDeadline) { + deadline := candidate + summary.OldestDeadline = &deadline + } +} + +func writeDigest(digest hash.Hash, values ...string) { + for _, value := range values { + _, _ = digest.Write([]byte(value)) + _, _ = digest.Write([]byte{0}) + } +} + +func (s *MigrationService) prunePlansLocked(now time.Time) { + for id, plan := range s.plans { + if !now.Before(plan.preview.ExpiresAt) { + delete(s.plans, id) + } + } +} + +func (s *MigrationService) removeOldestPlanLocked() { + var oldestID string + var oldestTime time.Time + for id, plan := range s.plans { + if oldestID == "" || plan.preview.CreatedAt.Before(oldestTime) { + oldestID = id + oldestTime = plan.preview.CreatedAt + } + } + if oldestID != "" { + delete(s.plans, oldestID) + } +} diff --git a/internal/lifecycle/migration_test.go b/internal/lifecycle/migration_test.go new file mode 100644 index 000000000..95d1dc914 --- /dev/null +++ b/internal/lifecycle/migration_test.go @@ -0,0 +1,341 @@ +package lifecycle + +import ( + "context" + "database/sql" + "encoding/json" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/hrygo/hotplex/internal/dbutil" + "github.com/hrygo/hotplex/internal/sqlutil" +) + +func TestMigrationPreviewApplyExtendsOnlyEligibleLegacyRecords(t *testing.T) { + t.Parallel() + + db := newMigrationTestDB(t) + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + lastInput := now.Add(-60 * 24 * time.Hour) + contentCreated := now.Add(-60 * 24 * time.Hour).UnixMilli() + _, err := db.Exec(` + INSERT INTO sessions + (id, lifecycle_policy, lifecycle_policy_revision, last_input_at, state, title) + VALUES + ('eligible', 'legacy', '', ?, 'terminated', 'old chat'), + ('ambiguous', 'legacy', '', NULL, 'terminated', 'unknown clock'), + ('deleted', 'legacy', '', ?, 'deleted', 'deleted chat')`, + lastInput, lastInput) + require.NoError(t, err) + _, err = db.Exec(` + INSERT INTO events (id, session_id, created_at, expires_at, data) + VALUES + (1, 'eligible', ?, 0, 'secret event'), + (2, 'ambiguous', ?, 0, 'ambiguous body'), + (3, 'missing-session', ?, 0, 'orphan body')`, + contentCreated, contentCreated, contentCreated) + require.NoError(t, err) + _, err = db.Exec(` + INSERT INTO turns (id, session_id, created_at, expires_at, content, tools_json) + VALUES + (1, 'eligible', ?, 0, 'secret turn', NULL), + (2, 'ambiguous', ?, 0, 'ambiguous turn', NULL)`, + contentCreated, contentCreated) + require.NoError(t, err) + _, err = db.Exec(`INSERT INTO execution_inputs (id, session_id, status, runtime_status, fence_reason) + VALUES ('unknown-1', 'eligible', 'unknown', 'unknown', '')`) + require.NoError(t, err) + + service := NewMigrationService(db, dbutil.DialectSQLite, func() RetentionPolicy { + return RetentionPolicy{ + ArchiveAfter: 7 * 24 * time.Hour, + ConversationRetention: 180 * 24 * time.Hour, + ContentRetention: 180 * 24 * time.Hour, + LegacyContentRetention: 30 * 24 * time.Hour, + BatchSize: 100, + } + }, func() time.Time { return now }) + + preview, err := service.Preview(context.Background()) + require.NoError(t, err) + require.EqualValues(t, 1, preview.Sessions.Count) + require.EqualValues(t, 1, preview.Events.Count) + require.EqualValues(t, 1, preview.Turns.Count) + require.EqualValues(t, 1, preview.Blocked.LegacySessionsMissingInputClock) + require.EqualValues(t, 1, preview.Blocked.OrphanedEvents) + require.EqualValues(t, 2, preview.Blocked.ContentOnSessionsMissingInputClock) + require.EqualValues(t, 1, preview.Blocked.UnknownExecutionRecords) + + previewJSON, err := json.Marshal(preview) + require.NoError(t, err) + require.NotContains(t, string(previewJSON), "secret event") + require.NotContains(t, string(previewJSON), "secret turn") + require.NotContains(t, string(previewJSON), "eligible") + + _, err = service.Apply(context.Background(), preview.PlanID, "wrong-plan") + require.ErrorIs(t, err, ErrConfirmationRequired) + + result, err := service.Apply(context.Background(), preview.PlanID, preview.PlanID) + require.NoError(t, err) + require.EqualValues(t, 1, result.Sessions) + require.EqualValues(t, 1, result.Events) + require.EqualValues(t, 1, result.Turns) + + var policy, revision string + var migratedInput, archiveAt, conversationExpiresAt, historyExpiresAt time.Time + err = db.QueryRow(` + SELECT lifecycle_policy, lifecycle_policy_revision, last_input_at, archive_at, + conversation_expires_at, history_expires_at + FROM sessions WHERE id = 'eligible'`). + Scan(&policy, &revision, &migratedInput, &archiveAt, &conversationExpiresAt, &historyExpiresAt) + require.NoError(t, err) + require.Equal(t, "v2", policy) + require.NotEmpty(t, revision) + require.True(t, migratedInput.Equal(lastInput), "migration must not invent new user activity") + require.True(t, archiveAt.Equal(lastInput.Add(7*24*time.Hour))) + require.True(t, conversationExpiresAt.Equal(lastInput.Add(180*24*time.Hour))) + require.True(t, historyExpiresAt.Equal(conversationExpiresAt)) + + var eventExpiry, turnExpiry int64 + require.NoError(t, db.QueryRow(`SELECT expires_at FROM events WHERE id = 1`).Scan(&eventExpiry)) + require.NoError(t, db.QueryRow(`SELECT expires_at FROM turns WHERE id = 1`).Scan(&turnExpiry)) + require.Equal(t, contentCreated+int64((180*24*time.Hour)/time.Millisecond), eventExpiry) + require.Equal(t, eventExpiry, turnExpiry) + + var ambiguousPolicy string + require.NoError(t, db.QueryRow(`SELECT lifecycle_policy FROM sessions WHERE id = 'ambiguous'`).Scan(&ambiguousPolicy)) + require.Equal(t, "legacy", ambiguousPolicy) + var orphanExpiry int64 + require.NoError(t, db.QueryRow(`SELECT expires_at FROM events WHERE id = 3`).Scan(&orphanExpiry)) + require.Zero(t, orphanExpiry, "orphaned content must not be revived") +} + +func TestMigrationApplyRejectsStalePreviewWithoutPartialChanges(t *testing.T) { + t.Parallel() + + db := newMigrationTestDB(t) + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + lastInput := now.Add(-60 * 24 * time.Hour) + _, err := db.Exec(` + INSERT INTO sessions (id, lifecycle_policy, last_input_at, state) + VALUES ('eligible', 'legacy', ?, 'terminated')`, lastInput) + require.NoError(t, err) + + service := NewMigrationService(db, dbutil.DialectSQLite, func() RetentionPolicy { + return RetentionPolicy{ + ArchiveAfter: 7 * 24 * time.Hour, + ConversationRetention: 180 * 24 * time.Hour, + ContentRetention: 180 * 24 * time.Hour, + LegacyContentRetention: 30 * 24 * time.Hour, + BatchSize: 100, + } + }, func() time.Time { return now }) + + preview, err := service.Preview(context.Background()) + require.NoError(t, err) + _, err = db.Exec(`UPDATE sessions SET last_input_at = ? WHERE id = 'eligible'`, lastInput.Add(time.Hour)) + require.NoError(t, err) + + _, err = service.Apply(context.Background(), preview.PlanID, preview.PlanID) + require.ErrorIs(t, err, ErrStalePreview) + + var policy string + require.NoError(t, db.QueryRow(`SELECT lifecycle_policy FROM sessions WHERE id = 'eligible'`).Scan(&policy)) + require.Equal(t, "legacy", policy) +} + +func TestMigrationApplyRejectsPolicyChangeBeforeCommit(t *testing.T) { + t.Parallel() + + db := newMigrationTestDB(t) + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + lastInput := now.Add(-60 * 24 * time.Hour) + _, err := db.Exec(`INSERT INTO sessions (id, lifecycle_policy, last_input_at, state) VALUES ('eligible', 'legacy', ?, 'terminated')`, lastInput) + require.NoError(t, err) + + basePolicy := RetentionPolicy{ + ArchiveAfter: 7 * 24 * time.Hour, + ConversationRetention: 180 * 24 * time.Hour, + ContentRetention: 180 * 24 * time.Hour, + LegacyContentRetention: 30 * 24 * time.Hour, + BatchSize: 100, + } + policyCalls := 0 + service := NewMigrationService(db, dbutil.DialectSQLite, func() RetentionPolicy { + policyCalls++ + if policyCalls >= 3 { + changed := basePolicy + changed.ContentRetention += 24 * time.Hour + return changed + } + return basePolicy + }, func() time.Time { return now }) + + preview, err := service.Preview(context.Background()) + require.NoError(t, err) + _, err = service.Apply(context.Background(), preview.PlanID, preview.PlanID) + require.ErrorIs(t, err, ErrStalePreview) + + var policy string + require.NoError(t, db.QueryRow(`SELECT lifecycle_policy FROM sessions WHERE id = 'eligible'`).Scan(&policy)) + require.Equal(t, "legacy", policy, "the transaction must roll back when the policy changes before commit") +} + +func newMigrationTestDB(t *testing.T) *sql.DB { + t.Helper() + db, err := sql.Open(sqlutil.DriverName, filepath.Join(t.TempDir(), "lifecycle-migration.db")) + require.NoError(t, err) + db.SetMaxOpenConns(1) + t.Cleanup(func() { _ = db.Close() }) + _, err = db.Exec(` + CREATE TABLE sessions ( + id TEXT PRIMARY KEY, + lifecycle_policy TEXT NOT NULL DEFAULT 'legacy', + lifecycle_policy_revision TEXT NOT NULL DEFAULT '', + last_input_at DATETIME, + archive_at DATETIME, + conversation_expires_at DATETIME, + last_content_expires_at DATETIME, + history_expires_at DATETIME, + deleted_at DATETIME, + state TEXT NOT NULL, + title TEXT, + work_dir TEXT, + context_json TEXT, + platform_key_json TEXT + ); + CREATE TABLE events ( + id INTEGER PRIMARY KEY, + session_id TEXT NOT NULL, + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL DEFAULT 0, + data TEXT NOT NULL + ); + CREATE TABLE turns ( + id INTEGER PRIMARY KEY, + session_id TEXT NOT NULL, + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL DEFAULT 0, + content TEXT NOT NULL DEFAULT '', + tools_json TEXT + ); + CREATE TABLE execution_inputs ( + id TEXT PRIMARY KEY, + session_id TEXT NOT NULL, + status TEXT NOT NULL, + runtime_status TEXT NOT NULL DEFAULT 'completed', + fence_reason TEXT NOT NULL DEFAULT '' + );`) + require.NoError(t, err) + return db +} + +func TestMigrationPreviewUsesBatchBoundAndExpires(t *testing.T) { + t.Parallel() + + db := newMigrationTestDB(t) + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + for i := 0; i < 3; i++ { + lastInput := now.Add(-time.Duration(60+i) * 24 * time.Hour) + _, err := db.Exec(`INSERT INTO sessions (id, lifecycle_policy, last_input_at, state) VALUES (?, 'legacy', ?, 'terminated')`, strings.Repeat("s", i+1), lastInput) + require.NoError(t, err) + } + service := NewMigrationService(db, dbutil.DialectSQLite, func() RetentionPolicy { + return RetentionPolicy{ + ArchiveAfter: 7 * 24 * time.Hour, + ConversationRetention: 180 * 24 * time.Hour, + ContentRetention: 180 * 24 * time.Hour, + LegacyContentRetention: 30 * 24 * time.Hour, + BatchSize: 1, + } + }, func() time.Time { return now }) + + preview, err := service.Preview(context.Background()) + require.NoError(t, err) + require.EqualValues(t, 3, preview.Sessions.Count) + require.EqualValues(t, 1, preview.BatchSize) + require.True(t, preview.ExpiresAt.Equal(now.Add(migrationPreviewTTL))) +} + +func TestMigrationPreviewCapsConfiguredBatchSize(t *testing.T) { + t.Parallel() + + db := newMigrationTestDB(t) + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + service := NewMigrationService(db, dbutil.DialectSQLite, func() RetentionPolicy { + return RetentionPolicy{ + ArchiveAfter: 7 * 24 * time.Hour, + ConversationRetention: 180 * 24 * time.Hour, + ContentRetention: 180 * 24 * time.Hour, + LegacyContentRetention: 30 * 24 * time.Hour, + BatchSize: maxMigrationBatchSize + 1, + } + }, func() time.Time { return now }) + + preview, err := service.Preview(context.Background()) + require.NoError(t, err) + require.Equal(t, maxMigrationBatchSize, preview.BatchSize) +} + +func TestMigrationWaitsForAllContentBeforeSwitchingSessionPolicy(t *testing.T) { + t.Parallel() + + db := newMigrationTestDB(t) + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + lastInput := now.Add(-60 * 24 * time.Hour) + createdAt := lastInput.UnixMilli() + _, err := db.Exec(`INSERT INTO sessions (id, lifecycle_policy, last_input_at, state) VALUES ('large', 'legacy', ?, 'terminated')`, lastInput) + require.NoError(t, err) + _, err = db.Exec(` + INSERT INTO events (id, session_id, created_at, expires_at, data) + VALUES (1, 'large', ?, 0, 'first'), (2, 'large', ?, 0, 'second')`, + createdAt, createdAt+1) + require.NoError(t, err) + _, err = db.Exec(` + INSERT INTO turns (id, session_id, created_at, expires_at, content) + VALUES (1, 'large', ?, 0, 'first'), (2, 'large', ?, 0, 'second')`, + createdAt, createdAt+1) + require.NoError(t, err) + + service := NewMigrationService(db, dbutil.DialectSQLite, func() RetentionPolicy { + return RetentionPolicy{ + ArchiveAfter: 7 * 24 * time.Hour, + ConversationRetention: 180 * 24 * time.Hour, + ContentRetention: 180 * 24 * time.Hour, + LegacyContentRetention: 30 * 24 * time.Hour, + BatchSize: 1, + } + }, func() time.Time { return now }) + + firstPreview, err := service.Preview(context.Background()) + require.NoError(t, err) + firstBatch, err := service.Apply(context.Background(), firstPreview.PlanID, firstPreview.PlanID) + require.NoError(t, err) + require.Zero(t, firstBatch.Sessions, "session policy must wait for the remaining content batch") + require.EqualValues(t, 1, firstBatch.Events) + require.EqualValues(t, 1, firstBatch.Turns) + + var policy string + require.NoError(t, db.QueryRow(`SELECT lifecycle_policy FROM sessions WHERE id = 'large'`).Scan(&policy)) + require.Equal(t, "legacy", policy) + + secondPreview, err := service.Preview(context.Background()) + require.NoError(t, err) + secondBatch, err := service.Apply(context.Background(), secondPreview.PlanID, secondPreview.PlanID) + require.NoError(t, err) + require.EqualValues(t, 1, secondBatch.Sessions) + require.EqualValues(t, 1, secondBatch.Events) + require.EqualValues(t, 1, secondBatch.Turns) + + require.NoError(t, db.QueryRow(`SELECT lifecycle_policy FROM sessions WHERE id = 'large'`).Scan(&policy)) + require.Equal(t, "v2", policy) + var missingEventDeadlines, missingTurnDeadlines int64 + require.NoError(t, db.QueryRow(`SELECT COUNT(*) FROM events WHERE session_id = 'large' AND expires_at = 0`).Scan(&missingEventDeadlines)) + require.NoError(t, db.QueryRow(`SELECT COUNT(*) FROM turns WHERE session_id = 'large' AND expires_at = 0`).Scan(&missingTurnDeadlines)) + require.Zero(t, missingEventDeadlines) + require.Zero(t, missingTurnDeadlines) +} diff --git a/internal/messaging/feishu/adapter.go b/internal/messaging/feishu/adapter.go index 9d5223ba4..319b7b4db 100644 --- a/internal/messaging/feishu/adapter.go +++ b/internal/messaging/feishu/adapter.go @@ -21,6 +21,7 @@ import ( "github.com/hrygo/hotplex/internal/messaging" "github.com/hrygo/hotplex/internal/messaging/phrases" "github.com/hrygo/hotplex/internal/messaging/stt" + "github.com/hrygo/hotplex/internal/observability" "github.com/hrygo/hotplex/pkg/events" ) @@ -46,6 +47,7 @@ type Adapter struct { injectExclude []string transcriber Transcriber turnSummaryEnabled bool + mediaRetention time.Duration ttsPipeline *TTSPipeline phrases *phrases.Phrases displayName string @@ -77,6 +79,7 @@ func (a *Adapter) ConfigureWith(config messaging.AdapterConfig) error { // Shared adapter state (gate, backoff delays). a.ConfigureShared(config) + a.mediaRetention = config.ExtrasDuration("media_retention") // Feishu-specific: credentials. a.appID = config.ExtrasString("app_id") @@ -403,7 +406,7 @@ func extractTextFromContent(content string) string { const ( feishuMediaCleanupInterval = 6 * time.Hour - feishuMediaTTL = 24 * time.Hour + defaultFeishuMediaTTL = 24 * time.Hour ) // feishuMediaSubdirs lists the media subdirectories owned by the Feishu adapter. @@ -443,12 +446,16 @@ func (a *Adapter) doCleanupMedia() { } func (a *Adapter) cleanupMediaInDir(dir string) { + ttl := a.mediaRetention + if ttl <= 0 { + ttl = defaultFeishuMediaTTL + } var deleted, freedBytes int64 _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return nil } - if !info.IsDir() && time.Since(info.ModTime()) > feishuMediaTTL { + if !info.IsDir() && time.Since(info.ModTime()) > ttl { if err := os.Remove(path); err != nil { a.Log.Warn("feishu: failed to remove old media file", "path", path, "err", err) return nil @@ -462,5 +469,6 @@ func (a *Adapter) cleanupMediaInDir(dir string) { if deleted > 0 { a.Log.Debug("feishu: cleaned up old media files", "dir", dir, "deleted", deleted, "freed_bytes", freedBytes) + observability.RecordLifecycleGCProcessed(context.Background(), observability.LifecycleGCProcessedMediaFile, deleted) } } diff --git a/internal/messaging/feishu/adapter_test.go b/internal/messaging/feishu/adapter_test.go index 211d2131e..dd293ff2b 100644 --- a/internal/messaging/feishu/adapter_test.go +++ b/internal/messaging/feishu/adapter_test.go @@ -4,6 +4,8 @@ import ( "context" "io" "log/slog" + "os" + "path/filepath" "testing" "time" @@ -122,6 +124,28 @@ func TestAdapter_ConfigureWith(t *testing.T) { require.Equal(t, messaging.PlatformFeishu, a.Platform()) } +func TestCleanupMedia_UsesConfiguredRetention(t *testing.T) { + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + a := &Adapter{ + BaseAdapter: messaging.BaseAdapter[*FeishuConn]{ + PlatformAdapter: messaging.PlatformAdapter{Log: logger}, + }, + } + require.NoError(t, a.ConfigureWith(messaging.AdapterConfig{ + Extras: map[string]any{"media_retention": 2 * time.Hour}, + })) + + tmpDir := t.TempDir() + expiredFile := filepath.Join(tmpDir, "expired.txt") + require.NoError(t, os.WriteFile(expiredFile, []byte("expired"), 0o600)) + oldTime := time.Now().Add(-3 * time.Hour) + require.NoError(t, os.Chtimes(expiredFile, oldTime, oldTime)) + + a.cleanupMediaInDir(tmpDir) + + require.NoFileExists(t, expiredFile) +} + func TestAdapter_Start_MissingCredentials(t *testing.T) { t.Parallel() a := &Adapter{} diff --git a/internal/messaging/slack/adapter.go b/internal/messaging/slack/adapter.go index 7a95360d1..07a3ddf8f 100644 --- a/internal/messaging/slack/adapter.go +++ b/internal/messaging/slack/adapter.go @@ -18,6 +18,7 @@ import ( "github.com/hrygo/hotplex/internal/messaging/phrases" "github.com/hrygo/hotplex/internal/messaging/stt" "github.com/hrygo/hotplex/internal/messaging/textutil" + "github.com/hrygo/hotplex/internal/observability" "github.com/hrygo/hotplex/internal/session" "github.com/hrygo/hotplex/pkg/events" @@ -33,7 +34,7 @@ const ( dedupMaxEntries = 5000 dedupTTL = 30 * time.Minute mediaCleanupInt = 6 * time.Hour - mediaTTL = 24 * time.Hour + defaultMediaTTL = 24 * time.Hour maxMessageLength = 3800 // Slack limit is ~4000 errPrefix = "\u26a0\ufe0f " // ⚠️ // handlerMsgTimeout controls the context timeout for HandleTextMessage. @@ -102,6 +103,7 @@ type Adapter struct { assistantEnabled *bool transcriber stt.Transcriber turnSummaryEnabled bool + mediaRetention time.Duration ttsPipeline *TTSPipeline phrases *phrases.Phrases Extras map[string]any @@ -146,6 +148,7 @@ func (a *Adapter) ConfigureWith(config messaging.AdapterConfig) error { // Shared: gate, backoff delays. a.ConfigureShared(config) + a.mediaRetention = config.ExtrasDuration("media_retention") // Platform-specific extras. if v := config.ExtrasBoolPtr("assistant_enabled"); v != nil { @@ -1256,12 +1259,16 @@ func (a *Adapter) cleanupMedia(ctx context.Context) { } func (a *Adapter) cleanupMediaInDir(dir string) { + ttl := a.mediaRetention + if ttl <= 0 { + ttl = defaultMediaTTL + } var deleted, freedBytes int64 _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return nil } - if !info.IsDir() && time.Since(info.ModTime()) > mediaTTL { + if !info.IsDir() && time.Since(info.ModTime()) > ttl { if err := os.Remove(path); err != nil { a.Log.Warn("slack: failed to remove old media file", "path", path, "err", err) return nil @@ -1275,6 +1282,7 @@ func (a *Adapter) cleanupMediaInDir(dir string) { if deleted > 0 { a.Log.Debug("slack: cleaned up old media files", "dir", dir, "deleted", deleted, "freed_bytes", freedBytes) + observability.RecordLifecycleGCProcessed(context.Background(), observability.LifecycleGCProcessedMediaFile, deleted) } } diff --git a/internal/messaging/slack/adapter_test.go b/internal/messaging/slack/adapter_test.go index 264122918..7bf66053b 100644 --- a/internal/messaging/slack/adapter_test.go +++ b/internal/messaging/slack/adapter_test.go @@ -1122,6 +1122,24 @@ func TestCleanupMedia(t *testing.T) { require.NoError(t, err, "Directory should NOT be removed") } +func TestCleanupMedia_UsesConfiguredRetention(t *testing.T) { + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + a := &Adapter{BaseAdapter: messaging.BaseAdapter[*SlackConn]{PlatformAdapter: messaging.PlatformAdapter{Log: logger}}} + require.NoError(t, a.ConfigureWith(messaging.AdapterConfig{ + Extras: map[string]any{"media_retention": 2 * time.Hour}, + })) + + tmpDir := t.TempDir() + expiredFile := filepath.Join(tmpDir, "expired.txt") + require.NoError(t, os.WriteFile(expiredFile, []byte("expired"), 0o600)) + oldTime := time.Now().Add(-3 * time.Hour) + require.NoError(t, os.Chtimes(expiredFile, oldTime, oldTime)) + + a.cleanupMediaInDir(tmpDir) + + require.NoFileExists(t, expiredFile) +} + func TestChunkContent_Integration(t *testing.T) { t.Parallel() diff --git a/internal/observability/AGENTS.md b/internal/observability/AGENTS.md index 2ef35748e..9230809a4 100644 --- a/internal/observability/AGENTS.md +++ b/internal/observability/AGENTS.md @@ -1,14 +1,14 @@ # Observability Package ## OVERVIEW -Unified OpenTelemetry bootstrap for the gateway: Prometheus metrics endpoint plus OTLP trace/metric export, all behind one `Init()` call with a noop fallback. Owns the full instrument registry (39 metrics across 9 domains) and the `RegisterGaugeCallbacks` indirection that lets other packages register gauges without racing package `init()`. +Unified OpenTelemetry bootstrap for the gateway: Prometheus metrics endpoint plus OTLP trace/metric export, all behind one `Init()` call with a noop fallback. Owns the instrument registry and the `RegisterGaugeCallbacks` indirection that lets other packages register gauges without racing package `init()`. ## STRUCTURE ``` observability/ config.go # Config struct, DefaultConfig(), IsOTELSDKDisabled() observability.go # Init() (sync.Once), Meter()/Tracer(), resource + exporters, gauge registrar - instruments.go # 39 lazy instrument accessors, each guarded by its own sync.Once + instruments.go # lazy instrument accessors, each guarded by its own sync.Once ``` ## WHERE TO LOOK @@ -37,7 +37,7 @@ observability/ - Problem: packages like `session` and `pool` own gauge state but their `init()` runs before `observability.Init()`. Calling `Meter().RegisterCallback(...)` from their init hit the noop meter and silently dropped the callback. - Fix: those packages call `RegisterGaugeCallbacks(fn)` at init time. `Init()` invokes every registered `fn` with the live meter at the end of setup via `runGaugeCallbacks`. Callbacks are skipped under noop (the `globalMeter != noopMeter` guard). -**Per-metric sync.Once (39 occurrences)** +**Per-metric sync.Once** - Every instrument accessor (`SessionCreated()`, `GatewayMessages()`, ...) holds its own `sync.Once`. First call lazily creates the instrument through `Meter()` and caches it. Repeat calls are free. - Creation failures are logged via `warnInstrument` (metric name + err) rather than panicked, so a single misconfigured meter cannot take the gateway down. @@ -45,8 +45,9 @@ observability/ - Root spans sampled at `cfg.SampleRate` (default 10%). Child spans respect parent decisions via `ParentBased`. - Metric reader enforces `CardinalityLimit` (default 5000) to bound series explosion from high-cardinality labels. -**Instrument inventory (39 metrics, 9 domains)** +**Core instrument inventory** - Session: `hotplex.session.created`, `.terminated`, `.deleted`, `.start.attempts`, `.start.errors`, `.start.duration` +- Lifecycle GC: `hotplex.lifecycle.gc.failures`, `.retirement.lag`, `.backlog`, `.oldest_lag` - Worker: `hotplex.worker.starts`, `.execution.duration`, `.crashes`, `.memory.bytes`, `.creation.duration` - Gateway: `hotplex.gateway.connections`, `.messages`, `.events`, `.deltas.dropped`, `.platform.dropped`, `.no_subscribers.dropped`, `.delta.coalesced`, `.delta.flush`, `.errors`, `.init.handshake.duration` - Pool: `hotplex.pool.acquire`, `.release.errors` diff --git a/internal/observability/instruments.go b/internal/observability/instruments.go index c254f704f..b9aba8eda 100644 --- a/internal/observability/instruments.go +++ b/internal/observability/instruments.go @@ -1,9 +1,11 @@ package observability import ( + "context" "log/slog" "sync" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) @@ -31,6 +33,24 @@ var ( sessionDeleted metric.Int64Counter sessionDeletedInit sync.Once + lifecycleGCFailures metric.Int64Counter + lifecycleGCFailuresInit sync.Once + + lifecycleGCProcessed metric.Int64Counter + lifecycleGCProcessedInit sync.Once + + lifecycleGCRetirementLag metric.Float64Histogram + lifecycleGCRetirementLagInit sync.Once + + lifecycleGCBacklog metric.Int64ObservableGauge + lifecycleGCBacklogInit sync.Once + + lifecycleGCUnknownBlocked metric.Int64ObservableGauge + lifecycleGCUnknownBlockedInit sync.Once + + lifecycleGCOldestLag metric.Float64ObservableGauge + lifecycleGCOldestLagInit sync.Once + sessionStartAttempts metric.Int64Counter sessionStartAttemptsInit sync.Once @@ -74,7 +94,7 @@ func SessionDeleted() metric.Int64Counter { var err error sessionDeleted, err = Meter().Int64Counter( "hotplex.session.deleted", - metric.WithDescription("Total sessions deleted by retention GC"), + metric.WithDescription("Total sessions moved to deleted state"), ) if err != nil { warnInstrument("hotplex.session.deleted", err) @@ -83,6 +103,147 @@ func SessionDeleted() metric.Int64Counter { return sessionDeleted } +// LifecycleGCFailures counts failures in lifecycle-driven session expiry cleanup. +// The phase attribute is bounded to the lifecycle GC's fixed operation names. +func LifecycleGCFailures() metric.Int64Counter { + lifecycleGCFailuresInit.Do(func() { + var err error + lifecycleGCFailures, err = Meter().Int64Counter( + "hotplex.lifecycle.gc.failures", + metric.WithDescription("Lifecycle session GC failures by phase"), + ) + if err != nil { + warnInstrument("hotplex.lifecycle.gc.failures", err) + } + }) + return lifecycleGCFailures +} + +// LifecycleGCProcessedKind is a bounded category for lifecycle GC work. +type LifecycleGCProcessedKind string + +const ( + LifecycleGCProcessedSession LifecycleGCProcessedKind = "session" + LifecycleGCProcessedEvent LifecycleGCProcessedKind = "event" + LifecycleGCProcessedTurn LifecycleGCProcessedKind = "turn" + LifecycleGCProcessedDeliveryPayload LifecycleGCProcessedKind = "delivery_payload" + LifecycleGCProcessedDeliveryAttempt LifecycleGCProcessedKind = "delivery_attempt" + LifecycleGCProcessedExecutionDetail LifecycleGCProcessedKind = "execution_detail" + LifecycleGCProcessedAuditFactLegacy LifecycleGCProcessedKind = "audit_fact_legacy" + LifecycleGCProcessedAuditFactV2 LifecycleGCProcessedKind = "audit_fact_v2" + LifecycleGCProcessedACPTraceFile LifecycleGCProcessedKind = "acp_trace_file" + LifecycleGCProcessedMediaFile LifecycleGCProcessedKind = "media_file" + LifecycleGCProcessedConversationPurgeItem LifecycleGCProcessedKind = "conversation_purge_item" + LifecycleGCProcessedOther LifecycleGCProcessedKind = "other" +) + +// LifecycleGCProcessed counts records or files removed, redacted, or compacted +// by lifecycle cleanup. Its kind label is a fixed, bounded category. +func LifecycleGCProcessed() metric.Int64Counter { + lifecycleGCProcessedInit.Do(func() { + var err error + lifecycleGCProcessed, err = Meter().Int64Counter( + "hotplex.lifecycle.gc.processed", + metric.WithDescription("Lifecycle-managed records or files removed, redacted, or compacted by category"), + ) + if err != nil { + warnInstrument("hotplex.lifecycle.gc.processed", err) + } + }) + return lifecycleGCProcessed +} + +// RecordLifecycleGCProcessed records a successful lifecycle cleanup count. +func RecordLifecycleGCProcessed(ctx context.Context, kind LifecycleGCProcessedKind, count int64) { + if count <= 0 { + return + } + switch kind { + case LifecycleGCProcessedSession, + LifecycleGCProcessedEvent, + LifecycleGCProcessedTurn, + LifecycleGCProcessedDeliveryPayload, + LifecycleGCProcessedDeliveryAttempt, + LifecycleGCProcessedExecutionDetail, + LifecycleGCProcessedAuditFactLegacy, + LifecycleGCProcessedAuditFactV2, + LifecycleGCProcessedACPTraceFile, + LifecycleGCProcessedMediaFile, + LifecycleGCProcessedConversationPurgeItem, + LifecycleGCProcessedOther: + default: + kind = LifecycleGCProcessedOther + } + LifecycleGCProcessed().Add(ctx, count, metric.WithAttributes(attribute.String("kind", string(kind)))) +} + +// LifecycleGCRetirementLag records how long successful v2 session retirement +// waited past its latest required history deadline. +func LifecycleGCRetirementLag() metric.Float64Histogram { + lifecycleGCRetirementLagInit.Do(func() { + var err error + lifecycleGCRetirementLag, err = Meter().Float64Histogram( + "hotplex.lifecycle.gc.retirement.lag", + metric.WithDescription("Delay between the latest required session expiry deadline and retirement"), + metric.WithUnit("s"), + metric.WithExplicitBucketBoundaries(60, 300, 900, 3600, 21600, 86400, 604800), + ) + if err != nil { + warnInstrument("hotplex.lifecycle.gc.retirement.lag", err) + } + }) + return lifecycleGCRetirementLag +} + +// LifecycleGCBacklog reports overdue v2 sessions awaiting retirement, split +// into eligible and obligation-blocked counts. +func LifecycleGCBacklog() metric.Int64ObservableGauge { + lifecycleGCBacklogInit.Do(func() { + var err error + lifecycleGCBacklog, err = Meter().Int64ObservableGauge( + "hotplex.lifecycle.gc.backlog", + metric.WithDescription("Overdue v2 sessions awaiting retirement by eligibility"), + ) + if err != nil { + warnInstrument("hotplex.lifecycle.gc.backlog", err) + } + }) + return lifecycleGCBacklog +} + +// LifecycleGCUnknownBlocked reports overdue sessions held by unresolved or +// fenced execution evidence. +func LifecycleGCUnknownBlocked() metric.Int64ObservableGauge { + lifecycleGCUnknownBlockedInit.Do(func() { + var err error + lifecycleGCUnknownBlocked, err = Meter().Int64ObservableGauge( + "hotplex.lifecycle.gc.blocked_unknown_execution", + metric.WithDescription("Overdue v2 sessions held by unknown or fenced execution evidence"), + ) + if err != nil { + warnInstrument("hotplex.lifecycle.gc.blocked_unknown_execution", err) + } + }) + return lifecycleGCUnknownBlocked +} + +// LifecycleGCOldestLag reports the age of the oldest overdue v2 session +// deadline, split into eligible and obligation-blocked counts. +func LifecycleGCOldestLag() metric.Float64ObservableGauge { + lifecycleGCOldestLagInit.Do(func() { + var err error + lifecycleGCOldestLag, err = Meter().Float64ObservableGauge( + "hotplex.lifecycle.gc.oldest_lag", + metric.WithDescription("Age of the oldest overdue v2 session deadline"), + metric.WithUnit("s"), + ) + if err != nil { + warnInstrument("hotplex.lifecycle.gc.oldest_lag", err) + } + }) + return lifecycleGCOldestLag +} + func SessionStartAttempts() metric.Int64Counter { sessionStartAttemptsInit.Do(func() { var err error diff --git a/internal/session/cleanup_outbox.go b/internal/session/cleanup_outbox.go index 11bc799ce..4996dd97a 100644 --- a/internal/session/cleanup_outbox.go +++ b/internal/session/cleanup_outbox.go @@ -12,7 +12,9 @@ import ( "github.com/google/uuid" + "github.com/hrygo/hotplex/internal/config" "github.com/hrygo/hotplex/internal/worker" + "github.com/hrygo/hotplex/pkg/events" ) // CleanupTask is a durable request to delete a worker-owned remote session. @@ -52,6 +54,7 @@ const ( cleanupAttemptTimeout = 10 * time.Second cleanupPollInterval = time.Second cleanupRetryMax = 5 * time.Minute + cleanupFailureCode = "cleanup_failed" ) // CleanupRunner leases and executes persistent cleanup tasks until ctx ends. @@ -110,11 +113,11 @@ func (r *CleanupRunner) RunOnce(ctx context.Context) { continue } next := r.now().Add(cleanupBackoff(task.Attempts)) - if retryErr := r.store.RetryCleanupTask(ctx, task.ID, task.LeaseToken, next, err.Error()); retryErr != nil { + if retryErr := r.store.RetryCleanupTask(ctx, task.ID, task.LeaseToken, next, cleanupFailureCode); retryErr != nil { r.log.Warn("session cleanup: retry scheduling failed", "task_id", task.ID, "session_id", task.SessionID, "err", retryErr) continue } - r.log.Warn("session cleanup: remote delete failed; retry scheduled", "task_id", task.ID, "session_id", task.SessionID, "worker_type", task.WorkerType, "attempt", task.Attempts, "next_attempt_at", next, "err", err) + r.log.Warn("session cleanup: remote delete failed; retry scheduled", "task_id", task.ID, "session_id", task.SessionID, "worker_type", task.WorkerType, "attempt", task.Attempts, "next_attempt_at", next, "error_code", cleanupFailureCode) } } @@ -147,9 +150,15 @@ func newCleanupTask(info *SessionInfo, now time.Time) *CleanupTask { // time.Time.String()) does not hold on modernc v1.51.0 — the format is already // canonical, so no write-side reformatting is applied here. func upsertSessionArgs(info *SessionInfo, ctxJSON, pkJSON []byte) []any { + policy := info.LifecyclePolicy + if policy == "" { + policy = "legacy" + } return []any{info.ID, info.UserID, info.OwnerID, info.BotID, info.BotName, info.WorkerSessionID, info.WorkerType, string(info.State), info.Platform, string(pkJSON), info.WorkDir, info.Title, info.CreatedAt, info.UpdatedAt, info.ExpiresAt, info.IdleExpiresAt, - string(ctxJSON), info.Source, info.ClientKey, nullableString(info.WorkspaceID), info.ID} + string(ctxJSON), info.Source, info.ClientKey, nullableString(info.WorkspaceID), + policy, info.LifecyclePolicyRevision, info.LastInputAt, info.RuntimeFinishedAt, info.ArchiveAt, + info.ConversationExpiresAt, info.LastContentExpiresAt, info.HistoryExpiresAt, info.DeletedAt, info.ID} } func cleanupTaskColumns() string { @@ -191,6 +200,319 @@ func containsCleanupPending(message string) bool { return strings.Contains(message, "session cleanup pending") || strings.Contains(message, "session_cleanup_tasks") } +type cleanupExecer interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) +} + +type purgeQueryer interface { + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +func identityRebind(query string) string { return query } + +func insertPurgeJobAndItems(ctx context.Context, tx *sql.Tx, rebind func(string) string, info *SessionInfo, now time.Time) error { + policyRevision := info.LifecyclePolicyRevision + if policyRevision == "" { + policyRevision = "legacy" + } + jobID := uuid.NewString() + _, err := tx.ExecContext(ctx, rebind(`INSERT INTO session_purge_jobs + (id, session_id, user_id, owner_id, workspace_id, policy_revision, status, requested_at, completed_at, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?) + ON CONFLICT(session_id) DO NOTHING`), + jobID, info.ID, info.UserID, info.OwnerID, info.WorkspaceID, policyRevision, + string(PurgeJobPending), now, now, now) + if err != nil { + return fmt.Errorf("session purge: create job: %w", err) + } + if err := tx.QueryRowContext(ctx, rebind(`SELECT id FROM session_purge_jobs WHERE session_id = ?`), info.ID).Scan(&jobID); err != nil { + return fmt.Errorf("session purge: load job: %w", err) + } + + workerState := PurgeItemComplete + var workerCompletedAt *time.Time + if info.WorkerSessionID != "" { + workerState = PurgeItemPending + } else { + workerCompletedAt = &now + } + items := []struct { + kind string + state PurgeItemState + completedAt *time.Time + }{ + {kind: PurgeItemSessionMetadata, state: PurgeItemComplete, completedAt: &now}, + {kind: PurgeItemConversationContent, state: PurgeItemPending}, + {kind: PurgeItemWorkerSession, state: workerState, completedAt: workerCompletedAt}, + } + for _, item := range items { + _, err := tx.ExecContext(ctx, rebind(`INSERT INTO session_purge_items + (id, job_id, session_id, kind, status, attempts, next_attempt_at, lease_until, lease_token, completed_at, error_code, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, 0, ?, NULL, NULL, ?, '', ?, ?) + ON CONFLICT(job_id, kind) DO NOTHING`), + uuid.NewString(), jobID, info.ID, item.kind, string(item.state), now, item.completedAt, now, now) + if err != nil { + return fmt.Errorf("session purge: create %s item: %w", item.kind, err) + } + } + return refreshPurgeJobStatus(ctx, tx, rebind, info.ID, now) +} + +func refreshPurgeJobStatus(ctx context.Context, execer cleanupExecer, rebind func(string) string, sessionID string, now time.Time) error { + _, err := execer.ExecContext(ctx, rebind(`UPDATE session_purge_jobs + SET status = CASE + WHEN EXISTS ( + SELECT 1 FROM session_purge_items + WHERE session_id = ? AND status IN ('blocked', 'unsupported') + ) THEN 'blocked' + WHEN EXISTS ( + SELECT 1 FROM session_purge_items + WHERE session_id = ? AND status IN ('pending', 'running', 'retrying') + ) THEN 'in_progress' + ELSE 'complete' + END, + completed_at = CASE + WHEN NOT EXISTS ( + SELECT 1 FROM session_purge_items + WHERE session_id = ? AND status <> 'complete' + ) THEN COALESCE(completed_at, ?) + ELSE NULL + END, + updated_at = ? + WHERE session_id = ?`), sessionID, sessionID, sessionID, now, now, sessionID) + if err != nil { + return fmt.Errorf("session purge: refresh job status: %w", err) + } + return nil +} + +func getPurgeStatus(ctx context.Context, queryer purgeQueryer, rebind func(string) string, sessionID, userID string) (*PurgeStatus, error) { + status := &PurgeStatus{SessionID: sessionID, Items: make([]PurgeItemStatus, 0)} + var completedAt sql.NullTime + err := queryer.QueryRowContext(ctx, rebind(`SELECT id, status, requested_at, updated_at, workspace_id, completed_at + FROM session_purge_jobs WHERE session_id = ? AND user_id = ?`), sessionID, userID). + Scan(&status.JobID, &status.Status, &status.RequestedAt, &status.UpdatedAt, &status.WorkspaceID, &completedAt) + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrPurgeNotFound + } + if err != nil { + return nil, fmt.Errorf("session purge: load job status: %w", err) + } + if completedAt.Valid { + status.CompletedAt = &completedAt.Time + } + + rows, err := queryer.QueryContext(ctx, rebind(`SELECT kind, status, attempts, next_attempt_at, completed_at, error_code + FROM session_purge_items WHERE session_id = ? ORDER BY kind`), sessionID) + if err != nil { + return nil, fmt.Errorf("session purge: load item status: %w", err) + } + defer func() { _ = rows.Close() }() + for rows.Next() { + var item PurgeItemStatus + var nextAttemptAt, itemCompletedAt sql.NullTime + if err := rows.Scan(&item.Kind, &item.Status, &item.Attempts, &nextAttemptAt, &itemCompletedAt, &item.ErrorCode); err != nil { + return nil, fmt.Errorf("session purge: scan item status: %w", err) + } + if nextAttemptAt.Valid { + item.NextAttemptAt = &nextAttemptAt.Time + } + if itemCompletedAt.Valid { + item.CompletedAt = &itemCompletedAt.Time + } + if item.Status == PurgeItemComplete { + item.NextAttemptAt = nil + } + status.Items = append(status.Items, item) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("session purge: iterate item status: %w", err) + } + return status, nil +} + +func markCleanupItemRunning(ctx context.Context, tx *sql.Tx, rebind func(string) string, sessionID string, attempts int, nextAttemptAt, leaseUntil time.Time, leaseToken string, now time.Time) error { + _, err := tx.ExecContext(ctx, rebind(`UPDATE session_purge_items + SET status = ?, attempts = ?, next_attempt_at = ?, lease_until = ?, lease_token = ?, completed_at = NULL, error_code = '', updated_at = ? + WHERE session_id = ? AND kind = ? AND status <> ?`), + string(PurgeItemRunning), attempts, nextAttemptAt, leaseUntil, leaseToken, now, + sessionID, PurgeItemWorkerSession, string(PurgeItemComplete)) + if err != nil { + return fmt.Errorf("session purge: mark worker item running: %w", err) + } + return refreshPurgeJobStatus(ctx, tx, rebind, sessionID, now) +} + +func markCleanupItemComplete(ctx context.Context, tx *sql.Tx, rebind func(string) string, sessionID string, now time.Time) error { + _, err := tx.ExecContext(ctx, rebind(`UPDATE session_purge_items + SET status = ?, lease_until = NULL, lease_token = NULL, completed_at = ?, error_code = '', updated_at = ? + WHERE session_id = ? AND kind = ? AND status <> ?`), + string(PurgeItemComplete), now, now, sessionID, PurgeItemWorkerSession, string(PurgeItemComplete)) + if err != nil { + return fmt.Errorf("session purge: mark worker item complete: %w", err) + } + return refreshPurgeJobStatus(ctx, tx, rebind, sessionID, now) +} + +func markCleanupItemRetrying(ctx context.Context, tx *sql.Tx, rebind func(string) string, sessionID string, nextAttemptAt, now time.Time) error { + _, err := tx.ExecContext(ctx, rebind(`UPDATE session_purge_items + SET status = ?, next_attempt_at = ?, lease_until = NULL, lease_token = NULL, completed_at = NULL, error_code = 'cleanup_failed', updated_at = ? + WHERE session_id = ? AND kind = ? AND status <> ?`), + string(PurgeItemRetrying), nextAttemptAt, now, sessionID, PurgeItemWorkerSession, string(PurgeItemComplete)) + if err != nil { + return fmt.Errorf("session purge: mark worker item retrying: %w", err) + } + return refreshPurgeJobStatus(ctx, tx, rebind, sessionID, now) +} + +func cleanupTaskSessionID(ctx context.Context, tx *sql.Tx, rebind func(string) string, taskID, leaseToken string) (string, error) { + var sessionID string + err := tx.QueryRowContext(ctx, rebind(`SELECT session_id FROM session_cleanup_tasks WHERE id = ? AND lease_token = ?`), taskID, leaseToken).Scan(&sessionID) + if errors.Is(err, sql.ErrNoRows) { + return "", ErrCleanupLeaseLost + } + if err != nil { + return "", fmt.Errorf("session cleanup: load task session: %w", err) + } + return sessionID, nil +} + +func markDeletedAndEnqueueCleanup(ctx context.Context, tx *sql.Tx, execer cleanupExecer, getSessionQuery, sessionID string, now time.Time, rebind func(string) string) error { + locked, err := lockSessionRowForLifecycleChange(ctx, tx, rebind, sessionID) + if err != nil { + return err + } + if !locked { + return nil + } + return markDeletedAndEnqueueCleanupLocked(ctx, tx, execer, getSessionQuery, sessionID, now, rebind) +} + +func markDeletedAndEnqueueCleanupLocked(ctx context.Context, tx *sql.Tx, execer cleanupExecer, getSessionQuery, sessionID string, now time.Time, rebind func(string) string) error { + info, err := scanSession(tx.QueryRowContext(ctx, getSessionQuery, sessionID)) + if errors.Is(err, sql.ErrNoRows) { + return nil + } + if err != nil { + return fmt.Errorf("session cleanup: load session for deletion: %w", err) + } + if err := insertCleanupTask(ctx, execer, info, now); err != nil { + return err + } + if err := insertPurgeJobAndItems(ctx, tx, rebind, info, now); err != nil { + return err + } + if _, err := execer.ExecContext(ctx, `UPDATE sessions + SET state = ?, deleted_at = COALESCE(deleted_at, ?), + updated_at = CASE WHEN updated_at < ? THEN ? ELSE updated_at END, + title = '', work_dir = '', context_json = NULL, platform_key_json = '', + client_key = '', worker_session_id = '' + WHERE id = ?`, + string(events.StateDeleted), now, now, now, sessionID); err != nil { + return fmt.Errorf("session cleanup: mark deleted: %w", err) + } + info.State = events.StateDeleted + if info.DeletedAt == nil { + info.DeletedAt = &now + } + if info.UpdatedAt.Before(now) { + info.UpdatedAt = now + } + return nil +} + +func lockSessionRowForLifecycleChange(ctx context.Context, tx *sql.Tx, rebind func(string) string, sessionID string) (bool, error) { + // The no-op update is the common row lock for lifecycle writes and input + // acceptance. SQLite serializes it through its database writer lock; on + // PostgreSQL it takes a row lock until the transaction ends. + result, err := tx.ExecContext(ctx, rebind(`UPDATE sessions SET state = state WHERE id = ?`), sessionID) + if err != nil { + return false, fmt.Errorf("session lifecycle: lock session row: %w", err) + } + rows, err := result.RowsAffected() + if err != nil { + return false, fmt.Errorf("session lifecycle: locked rows affected: %w", err) + } + return rows > 0, nil +} + +func lifecycleRetirementBlocked(ctx context.Context, queryer purgeQueryer, rebind func(string) string, sessionID string) (bool, error) { + var blocked bool + err := queryer.QueryRowContext(ctx, rebind(`SELECT + EXISTS ( + SELECT 1 FROM execution_inputs + WHERE session_id = ? + AND ( + status IN ('accepted', 'unknown') + OR runtime_status IN ('queued', 'pending', 'running', 'unknown') + OR fence_reason <> '' + ) + ) + OR EXISTS (SELECT 1 FROM execution_queue WHERE session_id = ?) + OR EXISTS (SELECT 1 FROM session_cleanup_tasks WHERE session_id = ?)`), + sessionID, sessionID, sessionID).Scan(&blocked) + if err != nil { + return false, fmt.Errorf("session lifecycle: check retirement blockers: %w", err) + } + return blocked, nil +} + +func lifecycleSessionExpired(info *SessionInfo, now time.Time) bool { + if info == nil || + info.LifecyclePolicy != config.LifecyclePolicyV2 || + info.DeletedAt != nil || + info.State == events.StateRunning || + info.State == events.StateDeleted || + info.ConversationExpiresAt == nil || + info.HistoryExpiresAt == nil { + return false + } + return !info.ConversationExpiresAt.After(now) && + (info.LastContentExpiresAt == nil || !info.LastContentExpiresAt.After(now)) && + !info.HistoryExpiresAt.After(now) +} + +func retireExpiredLifecycleSession( + ctx context.Context, + tx *sql.Tx, + getSessionQuery string, + rebind func(string) string, + id string, + now time.Time, +) (*SessionInfo, error) { + locked, err := lockSessionRowForLifecycleChange(ctx, tx, rebind, id) + if err != nil || !locked { + return nil, err + } + info, err := scanSession(tx.QueryRowContext(ctx, getSessionQuery, id)) + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("session lifecycle: load retirement candidate: %w", err) + } + if !lifecycleSessionExpired(info, now) { + return nil, nil + } + blocked, err := lifecycleRetirementBlocked(ctx, tx, rebind, id) + if err != nil { + return nil, err + } + if blocked { + return nil, nil + } + if err := markDeletedAndEnqueueCleanupLocked( + ctx, tx, pgExec{tx: tx, rebind: rebind}, getSessionQuery, id, now, rebind, + ); err != nil { + return nil, err + } + info.State = events.StateDeleted + info.UpdatedAt = now + info.DeletedAt = &now + return info, nil +} + func ensureSQLiteLifecycleLock(ctx context.Context, tx *sql.Tx, sessionID string) error { // SQLiteStore already holds its process-wide writeMu for every caller of // this helper. The transaction therefore has the required lifecycle lock @@ -212,11 +534,11 @@ func (s *SQLiteStore) HasPendingCleanup(ctx context.Context, sessionID string) ( return exists, err } +func (s *SQLiteStore) GetPurgeStatus(ctx context.Context, sessionID, userID string) (*PurgeStatus, error) { + return getPurgeStatus(ctx, s.db, identityRebind, sessionID, userID) +} + func (s *SQLiteStore) MarkDeletedWithCleanup(ctx context.Context, info *SessionInfo) error { - ctxJSON, pkJSON, err := marshalSessionJSON(info) - if err != nil { - return err - } return s.writeMu.WithLock(func() error { tx, err := s.db.BeginTx(ctx, nil) if err != nil { @@ -226,17 +548,7 @@ func (s *SQLiteStore) MarkDeletedWithCleanup(ctx context.Context, info *SessionI if err := ensureSQLiteLifecycleLock(ctx, tx, info.ID); err != nil { return err } - ctxJSON, err = preservePersistedSpecSnapshot(ctx, tx, queries["store.get_context_json"], info.ID, ctxJSON) - if err != nil { - return err - } - if _, err := tx.ExecContext(ctx, queries["sessions.upsert_session"], upsertSessionArgs(info, ctxJSON, pkJSON)...); err != nil { - if isCleanupPendingError(err) { - return ErrSessionCleanupPending - } - return fmt.Errorf("session cleanup: mark deleted: %w", err) - } - if err := insertCleanupTask(ctx, tx, info, time.Now()); err != nil { + if err := markDeletedAndEnqueueCleanup(ctx, tx, tx, queries["store.get_session"], info.ID, time.Now(), identityRebind); err != nil { return err } return tx.Commit() @@ -254,6 +566,13 @@ func (s *SQLiteStore) DeletePhysicalWithCleanup(ctx context.Context, id string) if err := ensureSQLiteLifecycleLock(ctx, tx, id); err != nil { return err } + locked, err := lockSessionRowForLifecycleChange(ctx, tx, identityRebind, id) + if err != nil { + return err + } + if !locked { + return tx.Commit() + } info, err := scanSession(tx.QueryRowContext(ctx, queries["store.get_session"], id)) if errors.Is(err, sql.ErrNoRows) { return tx.Commit() @@ -273,6 +592,26 @@ func (s *SQLiteStore) DeletePhysicalWithCleanup(ctx context.Context, id string) return deleted, err } +func (s *SQLiteStore) RetireExpiredLifecycleSession(ctx context.Context, id string, now time.Time) (*SessionInfo, error) { + var retired *SessionInfo + err := s.writeMu.WithLock(func() error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + if err := ensureSQLiteLifecycleLock(ctx, tx, id); err != nil { + return err + } + retired, err = retireExpiredLifecycleSession(ctx, tx, queries["store.get_session"], identityRebind, id, now) + if err != nil { + return err + } + return tx.Commit() + }) + return retired, err +} + func (s *SQLiteStore) ClaimCleanupTasks(ctx context.Context, now, leaseUntil time.Time, limit int) ([]CleanupTask, error) { if limit <= 0 { return nil, nil @@ -310,6 +649,10 @@ func (s *SQLiteStore) ClaimCleanupTasks(ctx context.Context, now, leaseUntil tim task.LeaseUntil = &leaseUntil task.LeaseToken = leaseToken task.UpdatedAt = now + if err := markCleanupItemRunning(ctx, tx, identityRebind, task.SessionID, task.Attempts, task.NextAttemptAt, leaseUntil, leaseToken, now); err != nil { + _ = rows.Close() + return err + } tasks = append(tasks, task) } } @@ -327,21 +670,53 @@ func (s *SQLiteStore) ClaimCleanupTasks(ctx context.Context, now, leaseUntil tim func (s *SQLiteStore) CompleteCleanupTask(ctx context.Context, taskID, leaseToken string) error { return s.writeMu.WithLock(func() error { - result, err := s.db.ExecContext(ctx, `DELETE FROM session_cleanup_tasks WHERE id = ? AND lease_token = ?`, taskID, leaseToken) + tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } - return cleanupLeaseResult(result) + defer func() { _ = tx.Rollback() }() + sessionID, err := cleanupTaskSessionID(ctx, tx, identityRebind, taskID, leaseToken) + if err != nil { + return err + } + now := time.Now() + if err := markCleanupItemComplete(ctx, tx, identityRebind, sessionID, now); err != nil { + return err + } + result, err := tx.ExecContext(ctx, `DELETE FROM session_cleanup_tasks WHERE id = ? AND lease_token = ?`, taskID, leaseToken) + if err != nil { + return err + } + if err := cleanupLeaseResult(result); err != nil { + return err + } + return tx.Commit() }) } func (s *SQLiteStore) RetryCleanupTask(ctx context.Context, taskID, leaseToken string, nextAttemptAt time.Time, lastError string) error { return s.writeMu.WithLock(func() error { - result, err := s.db.ExecContext(ctx, `UPDATE session_cleanup_tasks SET next_attempt_at = ?, lease_until = NULL, lease_token = NULL, last_error = ?, updated_at = ? WHERE id = ? AND lease_token = ?`, nextAttemptAt, truncateCleanupError(lastError), time.Now(), taskID, leaseToken) + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + sessionID, err := cleanupTaskSessionID(ctx, tx, identityRebind, taskID, leaseToken) + if err != nil { + return err + } + now := time.Now() + result, err := tx.ExecContext(ctx, `UPDATE session_cleanup_tasks SET next_attempt_at = ?, lease_until = NULL, lease_token = NULL, last_error = ?, updated_at = ? WHERE id = ? AND lease_token = ?`, nextAttemptAt, normalizeCleanupErrorCode(lastError), now, taskID, leaseToken) if err != nil { return err } - return cleanupLeaseResult(result) + if err := cleanupLeaseResult(result); err != nil { + return err + } + if err := markCleanupItemRetrying(ctx, tx, identityRebind, sessionID, nextAttemptAt, now); err != nil { + return err + } + return tx.Commit() }) } @@ -351,11 +726,11 @@ func (s *pgStore) HasPendingCleanup(ctx context.Context, sessionID string) (bool return exists, err } +func (s *pgStore) GetPurgeStatus(ctx context.Context, sessionID, userID string) (*PurgeStatus, error) { + return getPurgeStatus(ctx, s.db, s.dialect.Rebind, sessionID, userID) +} + func (s *pgStore) MarkDeletedWithCleanup(ctx context.Context, info *SessionInfo) error { - ctxJSON, pkJSON, err := marshalSessionJSON(info) - if err != nil { - return err - } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err @@ -364,17 +739,7 @@ func (s *pgStore) MarkDeletedWithCleanup(ctx context.Context, info *SessionInfo) if err := ensurePGLifecycleLock(ctx, tx, s.dialect.Rebind, info.ID); err != nil { return err } - ctxJSON, err = preservePersistedSpecSnapshot(ctx, tx, s.queries["store.get_context_json"], info.ID, ctxJSON) - if err != nil { - return err - } - if _, err := tx.ExecContext(ctx, s.queries["sessions.upsert_session"], upsertSessionArgs(info, ctxJSON, pkJSON)...); err != nil { - if isCleanupPendingError(err) { - return ErrSessionCleanupPending - } - return fmt.Errorf("session cleanup: mark deleted: %w", err) - } - if err := insertCleanupTask(ctx, pgExec{tx: tx, rebind: s.dialect.Rebind}, info, time.Now()); err != nil { + if err := markDeletedAndEnqueueCleanup(ctx, tx, pgExec{tx: tx, rebind: s.dialect.Rebind}, s.queries["store.get_session"], info.ID, time.Now(), s.dialect.Rebind); err != nil { return err } return tx.Commit() @@ -389,6 +754,13 @@ func (s *pgStore) DeletePhysicalWithCleanup(ctx context.Context, id string) (*Se if err := ensurePGLifecycleLock(ctx, tx, s.dialect.Rebind, id); err != nil { return nil, err } + locked, err := lockSessionRowForLifecycleChange(ctx, tx, s.dialect.Rebind, id) + if err != nil { + return nil, err + } + if !locked { + return nil, tx.Commit() + } info, err := scanSession(tx.QueryRowContext(ctx, s.queries["store.get_session"], id)) if errors.Is(err, sql.ErrNoRows) { return nil, tx.Commit() @@ -405,6 +777,25 @@ func (s *pgStore) DeletePhysicalWithCleanup(ctx context.Context, id string) (*Se return info, tx.Commit() } +func (s *pgStore) RetireExpiredLifecycleSession(ctx context.Context, id string, now time.Time) (*SessionInfo, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + if err := ensurePGLifecycleLock(ctx, tx, s.dialect.Rebind, id); err != nil { + return nil, err + } + retired, err := retireExpiredLifecycleSession(ctx, tx, s.queries["store.get_session"], s.dialect.Rebind, id, now) + if err != nil { + return nil, err + } + if err := tx.Commit(); err != nil { + return nil, err + } + return retired, nil +} + func (s *pgStore) ClaimCleanupTasks(ctx context.Context, now, leaseUntil time.Time, limit int) ([]CleanupTask, error) { if limit <= 0 { return nil, nil @@ -436,6 +827,10 @@ func (s *pgStore) ClaimCleanupTasks(ctx context.Context, now, leaseUntil time.Ti task.LeaseUntil = &leaseUntil task.LeaseToken = leaseToken task.UpdatedAt = now + if err := markCleanupItemRunning(ctx, tx, s.dialect.Rebind, task.SessionID, task.Attempts, task.NextAttemptAt, leaseUntil, leaseToken, now); err != nil { + _ = rows.Close() + return nil, err + } tasks = append(tasks, task) } if err := rows.Err(); err != nil { @@ -449,19 +844,51 @@ func (s *pgStore) ClaimCleanupTasks(ctx context.Context, now, leaseUntil time.Ti } func (s *pgStore) CompleteCleanupTask(ctx context.Context, taskID, leaseToken string) error { - result, err := s.db.ExecContext(ctx, s.dialect.Rebind(`DELETE FROM session_cleanup_tasks WHERE id = ? AND lease_token = ?`), taskID, leaseToken) + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + sessionID, err := cleanupTaskSessionID(ctx, tx, s.dialect.Rebind, taskID, leaseToken) + if err != nil { + return err + } + now := time.Now() + if err := markCleanupItemComplete(ctx, tx, s.dialect.Rebind, sessionID, now); err != nil { + return err + } + result, err := tx.ExecContext(ctx, s.dialect.Rebind(`DELETE FROM session_cleanup_tasks WHERE id = ? AND lease_token = ?`), taskID, leaseToken) if err != nil { return err } - return cleanupLeaseResult(result) + if err := cleanupLeaseResult(result); err != nil { + return err + } + return tx.Commit() } func (s *pgStore) RetryCleanupTask(ctx context.Context, taskID, leaseToken string, nextAttemptAt time.Time, lastError string) error { - result, err := s.db.ExecContext(ctx, s.dialect.Rebind(`UPDATE session_cleanup_tasks SET next_attempt_at = ?, lease_until = NULL, lease_token = NULL, last_error = ?, updated_at = ? WHERE id = ? AND lease_token = ?`), nextAttemptAt, truncateCleanupError(lastError), time.Now(), taskID, leaseToken) + tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } - return cleanupLeaseResult(result) + defer func() { _ = tx.Rollback() }() + sessionID, err := cleanupTaskSessionID(ctx, tx, s.dialect.Rebind, taskID, leaseToken) + if err != nil { + return err + } + now := time.Now() + result, err := tx.ExecContext(ctx, s.dialect.Rebind(`UPDATE session_cleanup_tasks SET next_attempt_at = ?, lease_until = NULL, lease_token = NULL, last_error = ?, updated_at = ? WHERE id = ? AND lease_token = ?`), nextAttemptAt, normalizeCleanupErrorCode(lastError), now, taskID, leaseToken) + if err != nil { + return err + } + if err := cleanupLeaseResult(result); err != nil { + return err + } + if err := markCleanupItemRetrying(ctx, tx, s.dialect.Rebind, sessionID, nextAttemptAt, now); err != nil { + return err + } + return tx.Commit() } type pgExec struct { @@ -473,12 +900,8 @@ func (e pgExec) ExecContext(ctx context.Context, query string, args ...any) (sql return e.tx.ExecContext(ctx, e.rebind(query), args...) } -func truncateCleanupError(message string) string { - const maxLen = 1024 - if len(message) <= maxLen { - return message - } - return message[:maxLen] +func normalizeCleanupErrorCode(_ string) string { + return cleanupFailureCode } func cleanupLeaseResult(result sql.Result) error { diff --git a/internal/session/cleanup_outbox_test.go b/internal/session/cleanup_outbox_test.go index 7bd55ddda..6ba51411a 100644 --- a/internal/session/cleanup_outbox_test.go +++ b/internal/session/cleanup_outbox_test.go @@ -3,6 +3,7 @@ package session import ( "context" "errors" + "fmt" "sync/atomic" "testing" "time" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/hrygo/hotplex/internal/agentspec" + "github.com/hrygo/hotplex/internal/config" "github.com/hrygo/hotplex/internal/worker" "github.com/hrygo/hotplex/pkg/events" ) @@ -82,9 +84,8 @@ func TestSQLiteStore_MarkDeletedEnqueuesCleanupTask(t *testing.T) { stored, err := store.Get(ctx, info.ID) require.NoError(t, err) require.Equal(t, events.StateDeleted, stored.State) - require.NotNil(t, stored.SpecSnapshot) - require.Equal(t, snapshot.Hash, stored.SpecSnapshot.Hash, - "a stale delete candidate must preserve the targeted snapshot update") + require.Nil(t, stored.SpecSnapshot, + "deleted-session context must be scrubbed after the cleanup task captures its required identifiers") claimNow := time.Now().Add(time.Second) tasks, err := store.ClaimCleanupTasks(ctx, claimNow, claimNow.Add(time.Minute), 1) @@ -93,6 +94,225 @@ func TestSQLiteStore_MarkDeletedEnqueuesCleanupTask(t *testing.T) { require.Equal(t, "ocs-logical-delete", tasks[0].WorkerSessionID) } +func TestSQLiteStore_MarkDeletedCreatesPurgeStatus(t *testing.T) { + t.Parallel() + store, _ := helperDB(t) + ctx := context.Background() + now := time.Now() + info := ocsTerminatedInfo("sess-purge-status", "ocs-purge-status", now) + info.OwnerID = "owner-1" + info.WorkspaceID = "workspace-1" + info.LifecyclePolicy = "v2" + info.Title = "private title" + info.WorkDir = "/private/project" + info.Context = map[string]any{"prompt": "private prompt"} + info.PlatformKey = map[string]string{"channel": "private-channel"} + require.NoError(t, store.Upsert(ctx, info)) + + deleted := *info + deleted.State = events.StateDeleted + deleted.UpdatedAt = now.Add(time.Second) + require.NoError(t, store.MarkDeletedWithCleanup(ctx, &deleted)) + + status, err := store.GetPurgeStatus(ctx, info.ID, info.UserID) + require.NoError(t, err) + require.Equal(t, info.ID, status.SessionID) + require.Equal(t, PurgeJobInProgress, status.Status) + require.NotEmpty(t, status.JobID) + require.Equal(t, "workspace-1", status.WorkspaceID) + require.Len(t, status.Items, 3) + require.NotContains(t, fmt.Sprintf("%+v", status), "private prompt") + + items := make(map[string]PurgeItemStatus, len(status.Items)) + for _, item := range status.Items { + items[item.Kind] = item + } + require.Equal(t, PurgeItemComplete, items["session_metadata"].Status) + require.Equal(t, PurgeItemPending, items["conversation_content"].Status) + require.Equal(t, PurgeItemPending, items["worker_session"].Status) + _, err = store.GetPurgeStatus(ctx, info.ID, "another-user") + require.ErrorIs(t, err, ErrPurgeNotFound) + + claimNow := now.Add(2 * time.Second) + tasks, err := store.ClaimCleanupTasks(ctx, claimNow, claimNow.Add(time.Minute), 1) + require.NoError(t, err) + require.Len(t, tasks, 1) + status, err = store.GetPurgeStatus(ctx, info.ID, info.UserID) + require.NoError(t, err) + requirePurgeItem(t, status, PurgeItemWorkerSession, PurgeItemRunning, 1, "") + + retryAt := claimNow.Add(time.Minute) + require.NoError(t, store.RetryCleanupTask(ctx, tasks[0].ID, tasks[0].LeaseToken, retryAt, "private provider error")) + var lastError string + require.NoError(t, store.db.QueryRowContext(ctx, + `SELECT last_error FROM session_cleanup_tasks WHERE session_id = ?`, info.ID, + ).Scan(&lastError)) + require.Equal(t, "cleanup_failed", lastError) + require.NotContains(t, lastError, "private provider error") + status, err = store.GetPurgeStatus(ctx, info.ID, info.UserID) + require.NoError(t, err) + requirePurgeItem(t, status, PurgeItemWorkerSession, PurgeItemRetrying, 1, "cleanup_failed") + require.NotContains(t, fmt.Sprintf("%+v", status), "private provider error") + + tasks, err = store.ClaimCleanupTasks(ctx, retryAt, retryAt.Add(time.Minute), 1) + require.NoError(t, err) + require.Len(t, tasks, 1) + require.NoError(t, store.CompleteCleanupTask(ctx, tasks[0].ID, tasks[0].LeaseToken)) + status, err = store.GetPurgeStatus(ctx, info.ID, info.UserID) + require.NoError(t, err) + requirePurgeItem(t, status, PurgeItemWorkerSession, PurgeItemComplete, 2, "") + require.Equal(t, PurgeJobInProgress, status.Status, + "the root purge stays in progress while conversation content still needs cleanup") + + stored, err := store.Get(ctx, info.ID) + require.NoError(t, err) + require.Empty(t, stored.Title) + require.Empty(t, stored.WorkDir) + require.Nil(t, stored.Context) + require.Nil(t, stored.PlatformKey) + require.Empty(t, stored.WorkerSessionID, + "the remote identifier must live only in the durable worker cleanup task") +} + +func requirePurgeItem(t *testing.T, status *PurgeStatus, kind string, state PurgeItemState, attempts int, errorCode string) { + t.Helper() + for _, item := range status.Items { + if item.Kind == kind { + require.Equal(t, state, item.Status) + require.Equal(t, attempts, item.Attempts) + require.Equal(t, errorCode, item.ErrorCode) + return + } + } + require.Failf(t, "purge item missing", "kind %q not found in %+v", kind, status.Items) +} + +func TestManager_DeleteCacheHitAndMissPersistTombstones(t *testing.T) { + t.Parallel() + store, _ := helperDB(t) + ctx := context.Background() + now := time.Now() + hot := &SessionInfo{ + ID: "sess-delete-hot", + UserID: "user-1", + WorkerType: worker.TypeClaudeCode, + State: events.StateTerminated, + CreatedAt: now.Add(-time.Hour), + UpdatedAt: now.Add(-time.Hour), + } + cold := &SessionInfo{ + ID: "sess-delete-cold", + UserID: "user-1", + WorkerType: worker.TypeClaudeCode, + State: events.StateTerminated, + CreatedAt: now.Add(-time.Hour), + UpdatedAt: now.Add(-time.Hour), + } + require.NoError(t, store.Upsert(ctx, hot)) + require.NoError(t, store.Upsert(ctx, cold)) + + manager, err := NewManager(ctx, nil, config.Default(), nil, store) + require.NoError(t, err) + defer manager.Close() + + _, err = manager.Get(ctx, hot.ID) + require.NoError(t, err, "the first session is loaded into the manager cache") + require.NoError(t, manager.Delete(ctx, hot.ID)) + require.NoError(t, manager.Delete(ctx, cold.ID)) + + for _, id := range []string{hot.ID, cold.ID} { + stored, err := store.Get(ctx, id) + require.NoError(t, err, "delete must preserve the lifecycle root as a tombstone") + require.Equal(t, events.StateDeleted, stored.State) + require.NotNil(t, stored.DeletedAt) + + stale := *stored + stale.State = events.StateRunning + stale.DeletedAt = nil + stale.UpdatedAt = now.Add(time.Hour) + require.ErrorIs(t, store.Upsert(ctx, &stale), ErrSessionCleanupPending, + "stale worker snapshots must not revive a deleted session") + + storedAfterStaleWrite, err := store.Get(ctx, id) + require.NoError(t, err) + require.Equal(t, events.StateDeleted, storedAfterStaleWrite.State) + require.NotNil(t, storedAfterStaleWrite.DeletedAt) + } +} + +type blockingSessionGetStore struct { + Store + CleanupTaskStore + getStarted chan struct{} + unblockGet chan struct{} +} + +func (s *blockingSessionGetStore) Get(ctx context.Context, id string) (*SessionInfo, error) { + close(s.getStarted) + <-s.unblockGet + return s.Store.Get(ctx, id) +} + +func TestManager_DeleteColdCacheFencesReadsAndSequenceWriters(t *testing.T) { + t.Parallel() + base, _ := helperDB(t) + ctx := context.Background() + info := &SessionInfo{ + ID: "sess-delete-cold-fence", UserID: "user-1", WorkerType: worker.TypeClaudeCode, + State: events.StateTerminated, CreatedAt: time.Now().Add(-time.Hour), UpdatedAt: time.Now().Add(-time.Hour), + } + require.NoError(t, base.Upsert(ctx, info)) + + store := &blockingSessionGetStore{ + Store: base, + CleanupTaskStore: base, + getStarted: make(chan struct{}), + unblockGet: make(chan struct{}), + } + manager, err := NewManager(ctx, nil, config.Default(), nil, store) + require.NoError(t, err) + defer manager.Close() + + deleteDone := make(chan error, 1) + go func() { deleteDone <- manager.Delete(ctx, info.ID) }() + <-store.getStarted + + require.False(t, manager.IsSeqActive(ctx, info.ID)) + _, err = manager.Get(ctx, info.ID) + require.ErrorIs(t, err, ErrSessionCleanupPending) + + close(store.unblockGet) + require.NoError(t, <-deleteDone) + stored, err := base.Get(ctx, info.ID) + require.NoError(t, err) + require.Equal(t, events.StateDeleted, stored.State) + require.NotNil(t, stored.DeletedAt) +} + +func TestSQLiteStore_StaleUpsertCannotReviveLegacyDeletedRow(t *testing.T) { + t.Parallel() + store, _ := helperDB(t) + ctx := context.Background() + now := time.Now() + info := &SessionInfo{ + ID: "sess-legacy-deleted", UserID: "user-1", WorkerType: worker.TypeClaudeCode, + State: events.StateTerminated, CreatedAt: now.Add(-time.Hour), UpdatedAt: now, + } + require.NoError(t, store.Upsert(ctx, info)) + _, err := store.db.ExecContext(ctx, `UPDATE sessions SET state = ?, deleted_at = NULL WHERE id = ?`, string(events.StateDeleted), info.ID) + require.NoError(t, err) + + stale := *info + stale.State = events.StateRunning + stale.UpdatedAt = now.Add(time.Hour) + require.ErrorIs(t, store.Upsert(ctx, &stale), ErrSessionCleanupPending) + + stored, err := store.Get(ctx, info.ID) + require.NoError(t, err) + require.Equal(t, events.StateDeleted, stored.State) + require.Nil(t, stored.DeletedAt, "the legacy row remains protected even without a tombstone timestamp") +} + func TestCleanupRunner_RunReturnsOnCancellation(t *testing.T) { t.Parallel() store, _ := helperDB(t) @@ -135,6 +355,13 @@ func TestCleanupRunner_RetriesAndCompletesDurably(t *testing.T) { runner.now = func() time.Time { return base } runner.RunOnce(ctx) + var lastError string + require.NoError(t, store.db.QueryRowContext(ctx, + `SELECT last_error FROM session_cleanup_tasks WHERE session_id = ?`, info.ID, + ).Scan(&lastError)) + require.Equal(t, "cleanup_failed", lastError) + require.NotContains(t, lastError, "ocs temporarily unavailable") + tasks, err := store.ClaimCleanupTasks(ctx, base, base.Add(time.Minute), 1) require.NoError(t, err) require.Empty(t, tasks, "failure must back off instead of losing or immediately re-leasing the task") @@ -176,6 +403,81 @@ func TestCleanupRunner_ReclaimsExpiredLeaseAfterRestart(t *testing.T) { require.EqualValues(t, 1, calls.Load()) } +func TestPurgeItemRunner_RetriesAndCompletesConversationDurably(t *testing.T) { + t.Parallel() + store, _ := helperDB(t) + ctx := context.Background() + now := time.Now() + info := ocsTerminatedInfo("sess-content-purge", "", now) + require.NoError(t, store.Upsert(ctx, info)) + info.State = events.StateDeleted + require.NoError(t, store.MarkDeletedWithCleanup(ctx, info)) + + var calls atomic.Int32 + runner := NewPurgeItemRunner(nil, store, func(_ context.Context, sessionID string) error { + require.Equal(t, info.ID, sessionID) + if calls.Add(1) == 1 { + return errors.New("private content-store failure") + } + return nil + }) + base := now.Add(time.Second) + runner.now = func() time.Time { return base } + runner.RunOnce(ctx) + + status, err := store.GetPurgeStatus(ctx, info.ID, info.UserID) + require.NoError(t, err) + require.Equal(t, PurgeJobInProgress, status.Status) + requirePurgeItem(t, status, PurgeItemConversationContent, PurgeItemRetrying, 1, "cleanup_failed") + require.NotContains(t, fmt.Sprintf("%+v", status), "private content-store failure") + + runner.now = func() time.Time { return base.Add(2 * time.Second) } + runner.RunOnce(ctx) + require.EqualValues(t, 2, calls.Load()) + + status, err = store.GetPurgeStatus(ctx, info.ID, info.UserID) + require.NoError(t, err) + require.Equal(t, PurgeJobComplete, status.Status) + requirePurgeItem(t, status, PurgeItemConversationContent, PurgeItemComplete, 2, "") +} + +func TestPurgeItemLease_ReclaimsAfterRestartAndFencesStaleCompletion(t *testing.T) { + t.Parallel() + store, _ := helperDB(t) + ctx := context.Background() + now := time.Now() + info := ocsTerminatedInfo("sess-content-purge-lease", "", now) + require.NoError(t, store.Upsert(ctx, info)) + info.State = events.StateDeleted + require.NoError(t, store.MarkDeletedWithCleanup(ctx, info)) + + firstNow := time.Now().Add(time.Second) + firstLeaseUntil := firstNow.Add(time.Minute) + first, err := store.ClaimPurgeItems(ctx, firstNow, firstLeaseUntil, 1) + require.NoError(t, err) + require.Len(t, first, 1) + + tooEarly, err := store.ClaimPurgeItems(ctx, firstLeaseUntil.Add(-time.Second), firstLeaseUntil.Add(time.Minute), 1) + require.NoError(t, err) + require.Empty(t, tooEarly) + + reclaimedAt := firstLeaseUntil.Add(time.Second) + second, err := store.ClaimPurgeItems(ctx, reclaimedAt, reclaimedAt.Add(time.Minute), 1) + require.NoError(t, err) + require.Len(t, second, 1) + require.NotEqual(t, first[0].LeaseToken, second[0].LeaseToken) + require.Equal(t, 2, second[0].Attempts) + + require.ErrorIs(t, store.CompletePurgeItem(ctx, first[0].ID, first[0].LeaseToken), ErrCleanupLeaseLost) + require.ErrorIs(t, store.RetryPurgeItem(ctx, first[0].ID, first[0].LeaseToken, reclaimedAt.Add(time.Minute), "private error"), ErrCleanupLeaseLost) + require.NoError(t, store.CompletePurgeItem(ctx, second[0].ID, second[0].LeaseToken)) + + status, err := store.GetPurgeStatus(ctx, info.ID, info.UserID) + require.NoError(t, err) + require.Equal(t, PurgeJobComplete, status.Status) + requirePurgeItem(t, status, PurgeItemConversationContent, PurgeItemComplete, 2, "") +} + func TestCleanupTask_ExpiredLeaseCannotCompleteOrRetryNewLease(t *testing.T) { t.Parallel() store, _ := helperDB(t) diff --git a/internal/session/coverage_test.go b/internal/session/coverage_test.go index af45666cf..3a17a6dd9 100644 --- a/internal/session/coverage_test.go +++ b/internal/session/coverage_test.go @@ -141,7 +141,8 @@ func TestSQLiteStore_UpdateWorkspace_WorkDirChangeBlockedByActiveSession(t *test // Active session inserted after the handler's hypothetical Count pre-check. _, err := store.db.ExecContext(ctx, queries["sessions.upsert_session"], "sess-1", "u-1", "u-1", "", "", "", "claude_code", "running", "webchat", "{}", "/tmp/old", "", - int64(1700000000), int64(1700000000), int64(1800000000), int64(1800000000), "{}", "", "ck-1", "ws-1", "sess-1") + int64(1700000000), int64(1700000000), int64(1800000000), int64(1800000000), "{}", "", "ck-1", "ws-1", + "legacy", "", nil, nil, nil, nil, nil, nil, nil, "sess-1") require.NoError(t, err) ws, _ := store.GetWorkspaceByID(ctx, "ws-1") diff --git a/internal/session/delete_audit_context.go b/internal/session/delete_audit_context.go new file mode 100644 index 000000000..888bc01d5 --- /dev/null +++ b/internal/session/delete_audit_context.go @@ -0,0 +1,24 @@ +package session + +import "context" + +type deleteAuditContextKey struct{} + +type deleteAuditRequest struct { + IP string + UserAgent string +} + +// WithDeleteAuditRequest adds the HTTP request metadata used by the session +// manager's durable delete audit event. +func WithDeleteAuditRequest(ctx context.Context, ip, userAgent string) context.Context { + return context.WithValue(ctx, deleteAuditContextKey{}, deleteAuditRequest{IP: ip, UserAgent: userAgent}) +} + +func deleteAuditRequestFromContext(ctx context.Context) deleteAuditRequest { + if ctx == nil { + return deleteAuditRequest{} + } + request, _ := ctx.Value(deleteAuditContextKey{}).(deleteAuditRequest) + return request +} diff --git a/internal/session/lifecycle_metrics_test.go b/internal/session/lifecycle_metrics_test.go new file mode 100644 index 000000000..c0cac4e95 --- /dev/null +++ b/internal/session/lifecycle_metrics_test.go @@ -0,0 +1,67 @@ +package session + +import ( + "context" + "io" + "log/slog" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestLifecycleExpirationLagUsesLatestRequiredDeadline(t *testing.T) { + t.Parallel() + + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + conversationDeadline := now.Add(-2 * time.Hour) + contentDeadline := now.Add(-time.Hour) + + lag := lifecycleExpirationLag(&SessionInfo{ + ConversationExpiresAt: &conversationDeadline, + LastContentExpiresAt: &contentDeadline, + }, now) + + require.Equal(t, time.Hour, lag) +} + +func TestLifecycleGCStatusUpdatesBacklogAndRaisesMaxLagState(t *testing.T) { + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + statusStore := &staticLifecycleGCStatusStore{ + status: lifecycleGCStatus{ + eligible: 4, + blocked: 2, + unknownExecutionHold: 1, + eligibleLag: 2 * time.Hour, + blockedLag: 30 * time.Minute, + }, + } + manager := &Manager{ + store: statusStore, + log: slog.New(slog.NewTextHandler(io.Discard, nil)), + } + + manager.refreshLifecycleGCStatus(context.Background(), now, time.Hour) + require.True(t, manager.lifecycleGCLagExceeded.Load()) + require.EqualValues(t, 4, lifecycleGCGauges.eligibleBacklog.Load()) + require.EqualValues(t, 2, lifecycleGCGauges.blockedBacklog.Load()) + require.EqualValues(t, 1, lifecycleGCGauges.unknownExecutionHold.Load()) + require.Equal(t, int64(2*time.Hour), lifecycleGCGauges.eligibleLagNanos.Load()) + require.Equal(t, int64(30*time.Minute), lifecycleGCGauges.blockedLagNanos.Load()) + + statusStore.status = lifecycleGCStatus{} + manager.refreshLifecycleGCStatus(context.Background(), now, time.Hour) + require.False(t, manager.lifecycleGCLagExceeded.Load()) + require.Zero(t, lifecycleGCGauges.eligibleBacklog.Load()) + require.Zero(t, lifecycleGCGauges.blockedBacklog.Load()) + require.Zero(t, lifecycleGCGauges.unknownExecutionHold.Load()) +} + +type staticLifecycleGCStatusStore struct { + Store + status lifecycleGCStatus +} + +func (s *staticLifecycleGCStatusStore) GetLifecycleGCStatus(context.Context, time.Time) (lifecycleGCStatus, error) { + return s.status, nil +} diff --git a/internal/session/lifecycle_policy.go b/internal/session/lifecycle_policy.go new file mode 100644 index 000000000..95da32078 --- /dev/null +++ b/internal/session/lifecycle_policy.go @@ -0,0 +1,99 @@ +package session + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "time" + + "github.com/hrygo/hotplex/internal/config" +) + +type initialLifecycleState struct { + policy string + revision string + + archiveAt *time.Time + conversationExpiresAt *time.Time + historyExpiresAt *time.Time +} + +// shouldNotifyTerminationCleanup reports whether a terminated session should +// trigger logical-session cleanup (queued inputs and attached Cron jobs). +// Lifecycle v2 treats runtime-release reasons as separate from session cleanup. +func shouldNotifyTerminationCleanup(policy, reason string) bool { + if policy != config.LifecyclePolicyV2 { + return true + } + + switch reason { + case "idle_timeout", "max_lifetime", "zombie", "gateway_restart", "max_turns", "gc", "cron_complete": + return false + default: + return true + } +} + +func updateArchiveProjection(info *SessionInfo, now time.Time) { + if info == nil { + return + } + info.Archived = info.LifecyclePolicy == config.LifecyclePolicyV2 && + info.ArchiveAt != nil && + !info.ArchiveAt.After(now) +} + +func newInitialLifecycleState(now time.Time, cfg config.LifecycleConfig) (initialLifecycleState, error) { + switch cfg.Policy { + case config.LifecyclePolicyLegacy: + return initialLifecycleState{policy: config.LifecyclePolicyLegacy}, nil + case config.LifecyclePolicyV2: + default: + return initialLifecycleState{}, fmt.Errorf("session lifecycle: unsupported policy %q", cfg.Policy) + } + + if cfg.Conversation.ArchiveAfter <= 0 { + return initialLifecycleState{}, fmt.Errorf("session lifecycle: archive_after must be positive") + } + if cfg.Conversation.RetentionAfterLastInput <= 0 { + return initialLifecycleState{}, fmt.Errorf("session lifecycle: retention_after_last_input must be positive") + } + if cfg.Conversation.ArchiveAfter > cfg.Conversation.RetentionAfterLastInput { + return initialLifecycleState{}, fmt.Errorf("session lifecycle: archive_after must not exceed retention_after_last_input") + } + + archiveAt := now.Add(cfg.Conversation.ArchiveAfter) + conversationExpiresAt := now.Add(cfg.Conversation.RetentionAfterLastInput) + return initialLifecycleState{ + policy: config.LifecyclePolicyV2, + revision: lifecyclePolicyRevision(cfg), + archiveAt: &archiveAt, + conversationExpiresAt: &conversationExpiresAt, + historyExpiresAt: &conversationExpiresAt, + }, nil +} + +func lifecyclePolicyRevision(cfg config.LifecycleConfig) string { + encoded, _ := json.Marshal(cfg) + hash := sha256.Sum256(encoded) + return hex.EncodeToString(hash[:]) +} + +func latestLifecycleTime(current *time.Time, candidate time.Time) time.Time { + if current != nil && current.After(candidate) { + return *current + } + return candidate +} + +func (s initialLifecycleState) apply(info *SessionInfo) { + if info == nil { + return + } + info.LifecyclePolicy = s.policy + info.LifecyclePolicyRevision = s.revision + info.ArchiveAt = s.archiveAt + info.ConversationExpiresAt = s.conversationExpiresAt + info.HistoryExpiresAt = s.historyExpiresAt +} diff --git a/internal/session/lifecycle_policy_test.go b/internal/session/lifecycle_policy_test.go new file mode 100644 index 000000000..0c405eadc --- /dev/null +++ b/internal/session/lifecycle_policy_test.go @@ -0,0 +1,49 @@ +package session + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/hrygo/hotplex/internal/config" +) + +func TestNewInitialLifecycleState_V2(t *testing.T) { + t.Parallel() + + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + cfg := config.Default().Lifecycle + state, err := newInitialLifecycleState(now, cfg) + require.NoError(t, err) + require.Equal(t, config.LifecyclePolicyV2, state.policy) + require.NotEmpty(t, state.revision) + require.Equal(t, now.Add(7*24*time.Hour), *state.archiveAt) + require.Equal(t, now.Add(180*24*time.Hour), *state.conversationExpiresAt) + require.Equal(t, state.conversationExpiresAt, state.historyExpiresAt) +} + +func TestNewInitialLifecycleState_LegacyHasNoV2Deadlines(t *testing.T) { + t.Parallel() + + cfg := config.Default().Lifecycle + cfg.Policy = config.LifecyclePolicyLegacy + state, err := newInitialLifecycleState(time.Now(), cfg) + require.NoError(t, err) + require.Equal(t, config.LifecyclePolicyLegacy, state.policy) + require.Empty(t, state.revision) + require.Nil(t, state.archiveAt) + require.Nil(t, state.conversationExpiresAt) + require.Nil(t, state.historyExpiresAt) +} + +func TestLifecyclePolicyRevisionChangesWithPolicyValues(t *testing.T) { + t.Parallel() + + cfg := config.Default().Lifecycle + first := lifecyclePolicyRevision(cfg) + require.Equal(t, first, lifecyclePolicyRevision(cfg)) + + cfg.Conversation.RetentionAfterLastInput += 24 * time.Hour + require.NotEqual(t, first, lifecyclePolicyRevision(cfg)) +} diff --git a/internal/session/lifecycle_projection_test.go b/internal/session/lifecycle_projection_test.go new file mode 100644 index 000000000..0f7904182 --- /dev/null +++ b/internal/session/lifecycle_projection_test.go @@ -0,0 +1,134 @@ +package session + +import ( + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/hrygo/hotplex/internal/config" + "github.com/hrygo/hotplex/internal/worker" + "github.com/hrygo/hotplex/pkg/events" +) + +func TestShouldNotifyTerminationCleanup(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + policy string + reason string + want bool + }{ + {name: "v2 idle release", policy: config.LifecyclePolicyV2, reason: "idle_timeout", want: false}, + {name: "v2 archive command", policy: config.LifecyclePolicyV2, reason: "gc", want: false}, + {name: "v2 cron completion", policy: config.LifecyclePolicyV2, reason: "cron_complete", want: false}, + {name: "v2 explicit terminate", policy: config.LifecyclePolicyV2, reason: "client_kill", want: true}, + {name: "legacy idle behavior", policy: config.LifecyclePolicyLegacy, reason: "idle_timeout", want: true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, test.want, shouldNotifyTerminationCleanup(test.policy, test.reason)) + }) + } +} + +func TestUpdateArchiveProjection(t *testing.T) { + t.Parallel() + + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + archiveAt := now + tests := []struct { + name string + info SessionInfo + want bool + }{ + { + name: "v2 deadline reached", + info: SessionInfo{LifecyclePolicy: config.LifecyclePolicyV2, ArchiveAt: &archiveAt}, + want: true, + }, + { + name: "v2 deadline remains in future", + info: SessionInfo{LifecyclePolicy: config.LifecyclePolicyV2, ArchiveAt: ptr(now.Add(time.Second))}, + want: false, + }, + { + name: "legacy deadline does not imply archive", + info: SessionInfo{LifecyclePolicy: config.LifecyclePolicyLegacy, ArchiveAt: &archiveAt}, + want: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + updateArchiveProjection(&test.info, now) + require.Equal(t, test.want, test.info.Archived) + }) + } +} + +func TestManager_V2RuntimeTerminationSkipsLogicalCleanupCallback(t *testing.T) { + t.Parallel() + + ctx := t.Context() + store := new(mockStore) + store.Test(t) + store.On("Close").Return(nil) + store.On("Upsert", mock.Anything, mock.AnythingOfType("*session.SessionInfo")).Return(nil) + + manager, err := NewManager(ctx, nil, config.Default(), nil, store) + require.NoError(t, err) + defer manager.Close() + + now := time.Now() + manager.mu.Lock() + manager.sessions["v2-idle"] = &managedSession{ + info: SessionInfo{ + ID: "v2-idle", + UserID: "u1", + WorkerType: worker.TypeClaudeCode, + State: events.StateIdle, + LifecyclePolicy: config.LifecyclePolicyV2, + CreatedAt: now, + UpdatedAt: now, + }, + } + manager.mu.Unlock() + + var cleanupCalls atomic.Int32 + manager.OnTerminate = func(string) { cleanupCalls.Add(1) } + + require.NoError(t, manager.TransitionWithReason(ctx, "v2-idle", events.StateTerminated, "idle_timeout")) + require.Never(t, func() bool { return cleanupCalls.Load() > 0 }, 50*time.Millisecond, time.Millisecond) +} + +func TestManager_ListSetsArchiveProjection(t *testing.T) { + t.Parallel() + + store := new(mockStore) + store.Test(t) + store.On("Close").Return(nil) + now := time.Now() + store.On("List", mock.Anything, "u1", "webchat", "", 20, 0).Return([]*SessionInfo{ + { + ID: "archived", + LifecyclePolicy: config.LifecyclePolicyV2, + ArchiveAt: ptr(now.Add(-time.Hour)), + }, + }, nil) + + manager, err := NewManager(t.Context(), nil, config.Default(), nil, store) + require.NoError(t, err) + defer manager.Close() + + sessions, err := manager.List(t.Context(), "u1", "webchat", "", 20, 0) + require.NoError(t, err) + require.Len(t, sessions, 1) + require.True(t, sessions[0].Archived) +} diff --git a/internal/session/lifecycle_retirement_pg_test.go b/internal/session/lifecycle_retirement_pg_test.go new file mode 100644 index 000000000..802fcbe6a --- /dev/null +++ b/internal/session/lifecycle_retirement_pg_test.go @@ -0,0 +1,186 @@ +//go:build pg + +package session + +import ( + "context" + "errors" + "fmt" + "os" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/hrygo/hotplex/internal/config" + "github.com/hrygo/hotplex/internal/dbutil" + "github.com/hrygo/hotplex/internal/execution" + "github.com/hrygo/hotplex/pkg/events" +) + +func openLifecyclePGStores(t *testing.T) (*pgStore, *pgStore, *execution.SQLStore, *execution.SQLStore) { + t.Helper() + + dsn := os.Getenv("HOTPLEX_TEST_PG_DSN") + if dsn == "" { + t.Skip("HOTPLEX_TEST_PG_DSN not set; skipping PG lifecycle concurrency test") + } + + cfg := &config.DBConfig{ + Driver: "postgres", + Postgres: config.PostgresConfig{ConnStr: dsn}, + } + dbA, err := dbutil.Open(dbutil.DialectPostgres, cfg) + require.NoError(t, err) + t.Cleanup(func() { _ = dbA.Close() }) + + dbB, err := dbutil.Open(dbutil.DialectPostgres, cfg) + require.NoError(t, err) + t.Cleanup(func() { _ = dbB.Close() }) + + ctx := context.Background() + _, err = dbA.DB.ExecContext(ctx, `DROP SCHEMA IF EXISTS public CASCADE`) + require.NoError(t, err) + _, err = dbA.DB.ExecContext(ctx, `CREATE SCHEMA public`) + require.NoError(t, err) + + storeAValue, err := NewPGStore(ctx, dbA) + require.NoError(t, err) + storeBValue, err := NewPGStore(ctx, dbB) + require.NoError(t, err) + storeA, ok := storeAValue.(*pgStore) + require.True(t, ok) + storeB, ok := storeBValue.(*pgStore) + require.True(t, ok) + + executionA, err := execution.NewSQLStore(ctx, dbA.DB, dbutil.DialectPostgres, nil, nil) + require.NoError(t, err) + executionB, err := execution.NewSQLStore(ctx, dbB.DB, dbutil.DialectPostgres, nil, nil) + require.NoError(t, err) + + return storeA, storeB, executionA, executionB +} + +func TestPGStore_RetirementSerializesWithInputAcceptanceAcrossInstances(t *testing.T) { + storeA, storeB, executionA, executionB := openLifecyclePGStores(t) + ctx := context.Background() + + for i := range 16 { + id := fmt.Sprintf("session-lifecycle-race-%02d", i) + now := time.Now().UTC() + require.NoError(t, storeA.Upsert(ctx, expiredLifecycleSession(id, now))) + + start := make(chan struct{}) + type acceptResult struct { + record *execution.Record + err error + } + accepted := make(chan acceptResult, 1) + retired := make(chan *SessionInfo, 1) + retireErrs := make(chan error, 1) + + acceptStore := executionA + retireStore := storeB + if i%2 == 1 { + acceptStore = executionB + retireStore = storeA + } + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + <-start + record, _, err := acceptStore.Accept(ctx, execution.AcceptRequest{ + SessionID: id, ClientMessageID: "message-" + id, PayloadHash: "payload-" + id, + OwnerInstanceID: "gateway-A", WorkerRunID: "run-" + id, + }) + accepted <- acceptResult{record: record, err: err} + }() + go func() { + defer wg.Done() + <-start + info, err := retireStore.RetireExpiredLifecycleSession(ctx, id, now) + retired <- info + retireErrs <- err + }() + + close(start) + wg.Wait() + accept := <-accepted + retiredInfo := <-retired + require.NoError(t, <-retireErrs) + + if accept.err == nil { + require.NotNil(t, accept.record) + require.Nil(t, retiredInfo, "accepted input must block retirement") + stored, err := storeA.Get(ctx, id) + require.NoError(t, err) + require.NotEqual(t, events.StateDeleted, stored.State) + continue + } + + require.ErrorIs(t, accept.err, execution.ErrSessionExpired, + "retirement winning the session row lock must reject new input") + require.NotNil(t, retiredInfo, "rejected input must have a committed retirement") + stored, err := storeA.Get(ctx, id) + require.NoError(t, err) + require.Equal(t, events.StateDeleted, stored.State) + } +} + +func TestPGStore_DeleteFencesConcurrentLateUpsertAcrossInstances(t *testing.T) { + storeA, storeB, _, _ := openLifecyclePGStores(t) + ctx := context.Background() + id := "session-lifecycle-delete-upsert-race" + now := time.Now().UTC() + info := expiredLifecycleSession(id, now) + require.NoError(t, storeA.Upsert(ctx, info)) + + lateWriter := *info + lateWriter.State = events.StateRunning + lateWriter.Title = "stale writer title" + lateWriter.WorkDir = "/stale/writer" + lateWriter.Context = map[string]any{"late": "write"} + + start := make(chan struct{}) + deleteErrs := make(chan error, 1) + upsertErrs := make(chan error, 1) + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + <-start + deleteErrs <- storeA.MarkDeletedWithCleanup(ctx, info) + }() + go func() { + defer wg.Done() + <-start + upsertErrs <- storeB.Upsert(ctx, &lateWriter) + }() + + close(start) + wg.Wait() + require.NoError(t, <-deleteErrs) + upsertErr := <-upsertErrs + if upsertErr != nil { + require.True(t, errors.Is(upsertErr, ErrSessionCleanupPending), + "late writer should be fenced by the cleanup tombstone") + } + + stored, err := storeA.Get(ctx, id) + require.NoError(t, err) + require.Equal(t, events.StateDeleted, stored.State) + require.NotNil(t, stored.DeletedAt) + require.Empty(t, stored.Title) + require.Empty(t, stored.WorkDir) + require.Nil(t, stored.Context) + require.Empty(t, stored.WorkerSessionID) + + require.ErrorIs(t, storeB.Upsert(ctx, &lateWriter), ErrSessionCleanupPending, + "a stale writer after deletion must not recreate session data") + pending, err := storeA.HasPendingCleanup(ctx, id) + require.NoError(t, err) + require.True(t, pending, "local deletion and remote cleanup must remain durably linked") +} diff --git a/internal/session/lifecycle_retirement_test.go b/internal/session/lifecycle_retirement_test.go new file mode 100644 index 000000000..3f74c8c3f --- /dev/null +++ b/internal/session/lifecycle_retirement_test.go @@ -0,0 +1,243 @@ +package session + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/hrygo/hotplex/internal/config" + "github.com/hrygo/hotplex/pkg/events" +) + +func expiredLifecycleSession(id string, now time.Time) *SessionInfo { + conversationExpiresAt := now.Add(-2 * time.Hour) + historyExpiresAt := now.Add(-time.Hour) + lastContentExpiresAt := now.Add(-time.Hour) + return &SessionInfo{ + ID: id, + UserID: "user-1", + WorkerType: "claude_code", + WorkerSessionID: "worker-session-" + id, + State: events.StateTerminated, + CreatedAt: now.Add(-200 * 24 * time.Hour), + UpdatedAt: now.Add(-24 * time.Hour), + LifecyclePolicy: config.LifecyclePolicyV2, + LifecyclePolicyRevision: "v2-test", + ConversationExpiresAt: &conversationExpiresAt, + LastContentExpiresAt: &lastContentExpiresAt, + HistoryExpiresAt: &historyExpiresAt, + Context: map[string]any{"private": "remove-on-retirement"}, + Title: "retained title", + WorkDir: "/private/workdir", + } +} + +func TestSQLiteStore_ListExpiredLifecycleSessionsRequiresExpiredHistoryAndNoRecoveryObligations(t *testing.T) { + t.Parallel() + store, _ := helperDB(t) + ctx := context.Background() + now := time.Now() + + expired := expiredLifecycleSession("sess-lifecycle-expired", now) + require.NoError(t, store.Upsert(ctx, expired)) + + liveHistory := expiredLifecycleSession("sess-lifecycle-live-history", now) + liveHistory.HistoryExpiresAt = ptr(now.Add(time.Hour)) + require.NoError(t, store.Upsert(ctx, liveHistory)) + + liveLastContent := expiredLifecycleSession("sess-lifecycle-live-content", now) + liveLastContent.LastContentExpiresAt = ptr(now.Add(time.Hour)) + require.NoError(t, store.Upsert(ctx, liveLastContent)) + + unknown := expiredLifecycleSession("sess-lifecycle-unknown", now) + require.NoError(t, store.Upsert(ctx, unknown)) + _, err := store.db.ExecContext(ctx, `INSERT INTO execution_inputs + (execution_id, session_id, client_message_id, payload_hash, status, created_at, updated_at, + runtime_status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + "exec-unknown", unknown.ID, "msg-unknown", "hash", "unknown", + now.UnixMilli(), now.UnixMilli(), "unknown") + require.NoError(t, err) + + ids, err := store.ListExpiredLifecycleSessions(ctx, now, 10) + require.NoError(t, err) + require.Equal(t, []string{expired.ID}, ids) +} + +func TestSQLiteStore_GetLifecycleGCStatusCountsBacklogAndBlockers(t *testing.T) { + t.Parallel() + store, _ := helperDB(t) + ctx := context.Background() + now := time.Now().UTC() + + eligible := expiredLifecycleSession("sess-lifecycle-status-eligible", now) + require.NoError(t, store.Upsert(ctx, eligible)) + + blocked := expiredLifecycleSession("sess-lifecycle-status-blocked", now) + blocked.ConversationExpiresAt = ptr(now.Add(-8 * time.Hour)) + blocked.LastContentExpiresAt = ptr(now.Add(-6 * time.Hour)) + blocked.HistoryExpiresAt = ptr(now.Add(-5 * time.Hour)) + require.NoError(t, store.Upsert(ctx, blocked)) + _, err := store.db.ExecContext(ctx, `INSERT INTO execution_inputs + (execution_id, session_id, client_message_id, payload_hash, status, created_at, updated_at, + runtime_status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + "exec-status-blocked", blocked.ID, "msg-status-blocked", "hash", "accepted", + now.UnixMilli(), now.UnixMilli(), "pending") + require.NoError(t, err) + + unknown := expiredLifecycleSession("sess-lifecycle-status-unknown", now) + unknown.ConversationExpiresAt = ptr(now.Add(-6 * time.Hour)) + unknown.LastContentExpiresAt = ptr(now.Add(-4 * time.Hour)) + unknown.HistoryExpiresAt = ptr(now.Add(-3 * time.Hour)) + require.NoError(t, store.Upsert(ctx, unknown)) + _, err = store.db.ExecContext(ctx, `INSERT INTO execution_inputs + (execution_id, session_id, client_message_id, payload_hash, status, created_at, updated_at, + runtime_status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + "exec-status-unknown", unknown.ID, "msg-status-unknown", "hash", "unknown", + now.UnixMilli(), now.UnixMilli(), "unknown") + require.NoError(t, err) + + notExpired := expiredLifecycleSession("sess-lifecycle-status-live", now) + notExpired.HistoryExpiresAt = ptr(now.Add(time.Hour)) + require.NoError(t, store.Upsert(ctx, notExpired)) + + status, err := store.GetLifecycleGCStatus(ctx, now) + require.NoError(t, err) + require.EqualValues(t, 1, status.eligible) + require.EqualValues(t, 2, status.blocked) + require.EqualValues(t, 1, status.unknownExecutionHold) + require.Equal(t, time.Hour, status.eligibleLag) + require.Equal(t, 5*time.Hour, status.blockedLag) +} + +func TestSQLiteStore_RetireExpiredLifecycleSessionRedactsAndQueuesCleanupAtomically(t *testing.T) { + t.Parallel() + store, _ := helperDB(t) + ctx := context.Background() + now := time.Now() + info := expiredLifecycleSession("sess-lifecycle-retire", now) + require.NoError(t, store.Upsert(ctx, info)) + + retired, err := store.RetireExpiredLifecycleSession(ctx, info.ID, now) + require.NoError(t, err) + require.NotNil(t, retired) + require.Equal(t, info.WorkerSessionID, retired.WorkerSessionID) + require.Equal(t, events.StateDeleted, retired.State) + require.NotNil(t, retired.DeletedAt) + + stored, err := store.Get(ctx, info.ID) + require.NoError(t, err) + require.Equal(t, events.StateDeleted, stored.State) + require.Equal(t, "", stored.Title) + require.Equal(t, "", stored.WorkDir) + require.Empty(t, stored.Context) + require.Equal(t, "", stored.WorkerSessionID) + + cleanup, err := store.ClaimCleanupTasks(ctx, now, now.Add(time.Minute), 10) + require.NoError(t, err) + require.Len(t, cleanup, 1) + require.Equal(t, info.WorkerSessionID, cleanup[0].WorkerSessionID) + + status, err := store.GetPurgeStatus(ctx, info.ID, info.UserID) + require.NoError(t, err) + require.Equal(t, PurgeJobInProgress, status.Status) + require.Len(t, status.Items, 3) +} + +func TestSQLiteStore_RetireExpiredLifecycleSessionRechecksDeadlineAndExecutionState(t *testing.T) { + t.Parallel() + store, _ := helperDB(t) + ctx := context.Background() + now := time.Now() + info := expiredLifecycleSession("sess-lifecycle-retire-recheck", now) + require.NoError(t, store.Upsert(ctx, info)) + + extended := now.Add(time.Hour) + _, err := store.db.ExecContext(ctx, + `UPDATE sessions SET conversation_expires_at = ?, history_expires_at = ? WHERE id = ?`, + extended, extended, info.ID) + require.NoError(t, err) + retired, err := store.RetireExpiredLifecycleSession(ctx, info.ID, now) + require.NoError(t, err) + require.Nil(t, retired, "a stale expiry candidate must not retire a renewed session") + + _, err = store.db.ExecContext(ctx, + `UPDATE sessions SET conversation_expires_at = ?, history_expires_at = ? WHERE id = ?`, + now.Add(-time.Hour), now.Add(-time.Hour), info.ID) + require.NoError(t, err) + _, err = store.db.ExecContext(ctx, `INSERT INTO execution_inputs + (execution_id, session_id, client_message_id, payload_hash, status, created_at, updated_at, + runtime_status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + "exec-active", info.ID, "msg-active", "hash", "accepted", + now.UnixMilli(), now.UnixMilli(), "pending") + require.NoError(t, err) + + retired, err = store.RetireExpiredLifecycleSession(ctx, info.ID, now) + require.NoError(t, err) + require.Nil(t, retired, "an active execution must protect its session from retirement") +} + +func TestSQLiteStore_DeleteTerminatedPreservesUnknownExecutionEvidence(t *testing.T) { + t.Parallel() + store, _ := helperDB(t) + ctx := context.Background() + now := time.Now() + info := &SessionInfo{ + ID: "sess-legacy-unknown", + UserID: "user-1", + WorkerType: "claude_code", + State: events.StateTerminated, + CreatedAt: now.Add(-30 * 24 * time.Hour), + UpdatedAt: now.Add(-8 * 24 * time.Hour), + } + require.NoError(t, store.Upsert(ctx, info)) + _, err := store.db.ExecContext(ctx, `INSERT INTO execution_inputs + (execution_id, session_id, client_message_id, payload_hash, status, created_at, updated_at, + runtime_status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + "exec-legacy-unknown", info.ID, "msg-legacy-unknown", "hash", "unknown", + now.UnixMilli(), now.UnixMilli(), "unknown") + require.NoError(t, err) + + deleted, err := store.DeleteTerminated(ctx, now.Add(-24*time.Hour), now.Add(-7*24*time.Hour)) + require.NoError(t, err) + require.Empty(t, deleted) + stored, err := store.Get(ctx, info.ID) + require.NoError(t, err) + require.Equal(t, events.StateTerminated, stored.State) + + var count int + require.NoError(t, store.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM execution_inputs WHERE session_id = ?`, info.ID).Scan(&count)) + require.Equal(t, 1, count, "unknown execution evidence must not cascade away") +} + +func TestManager_GCRetiresExpiredV2SessionThroughCleanupOutbox(t *testing.T) { + t.Parallel() + store, cfg := helperDB(t) + cfg.Session.GCScanInterval = time.Hour + ctx := context.Background() + manager, err := NewManager(ctx, nil, cfg, nil, store) + require.NoError(t, err) + defer func() { require.NoError(t, manager.Close()) }() + + now := time.Now() + info := expiredLifecycleSession("sess-manager-lifecycle-expired", now) + require.NoError(t, store.Upsert(ctx, info)) + loaded, err := manager.Get(ctx, info.ID) + require.NoError(t, err) + require.Equal(t, config.LifecyclePolicyV2, loaded.LifecyclePolicy) + + manager.gc(ctx) + + stored, err := store.Get(ctx, info.ID) + require.NoError(t, err) + require.Equal(t, events.StateDeleted, stored.State) + require.NotNil(t, stored.DeletedAt) + require.Empty(t, stored.Title) +} diff --git a/internal/session/manager.go b/internal/session/manager.go index efdf29c91..a9eb0da7e 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -50,8 +50,17 @@ const MaxClientKeyLen = 256 var ( sessionsActiveByState sync.Map // map[string]*atomic.Int64 — key is state name workersRunningByType sync.Map // map[string]*atomic.Int64 — key is worker_type + lifecycleGCGauges lifecycleGCMetricsSnapshot ) +type lifecycleGCMetricsSnapshot struct { + eligibleBacklog atomic.Int64 + blockedBacklog atomic.Int64 + unknownExecutionHold atomic.Int64 + eligibleLagNanos atomic.Int64 + blockedLagNanos atomic.Int64 +} + // sessionsActiveGauge tracks the count of sessions per state. func sessionsActiveGauge(key string) *atomic.Int64 { v, _ := sessionsActiveByState.LoadOrStore(key, &atomic.Int64{}) @@ -103,6 +112,37 @@ func init() { }) return nil }, workerGauge) + + backlogGauge := observability.LifecycleGCBacklog() + unknownBlockedGauge := observability.LifecycleGCUnknownBlocked() + oldestLagGauge := observability.LifecycleGCOldestLag() + _, err = m.RegisterCallback(func(ctx context.Context, o metric.Observer) error { + o.ObserveInt64( + backlogGauge, + lifecycleGCGauges.eligibleBacklog.Load(), + metric.WithAttributes(attribute.String("status", "eligible")), + ) + o.ObserveInt64( + backlogGauge, + lifecycleGCGauges.blockedBacklog.Load(), + metric.WithAttributes(attribute.String("status", "blocked")), + ) + o.ObserveInt64(unknownBlockedGauge, lifecycleGCGauges.unknownExecutionHold.Load()) + o.ObserveFloat64( + oldestLagGauge, + float64(lifecycleGCGauges.eligibleLagNanos.Load())/float64(time.Second), + metric.WithAttributes(attribute.String("status", "eligible")), + ) + o.ObserveFloat64( + oldestLagGauge, + float64(lifecycleGCGauges.blockedLagNanos.Load())/float64(time.Second), + metric.WithAttributes(attribute.String("status", "blocked")), + ) + return nil + }, backlogGauge, unknownBlockedGauge, oldestLagGauge) + if err != nil { + slog.Warn("session: failed to register lifecycle GC gauges", "err", err) + } }) } @@ -122,9 +162,10 @@ type Manager struct { riMu sync.RWMutex runningIndex map[string]struct{} - gcStop context.CancelFunc - gcDone chan struct{} - gcReset chan time.Duration // signals GC ticker reset + gcStop context.CancelFunc + gcDone chan struct{} + gcReset chan time.Duration // signals GC ticker reset + lifecycleGCLagExceeded atomic.Bool OnTerminate func(sessionID string) OnDelete func(ctx context.Context, info SessionInfo) @@ -216,7 +257,22 @@ type SessionInfo struct { UpdatedAt time.Time `json:"updated_at"` ExpiresAt *time.Time `json:"expires_at,omitempty"` IdleExpiresAt *time.Time `json:"idle_expires_at,omitempty"` - Context map[string]any `json:"context,omitempty"` + // Lifecycle fields are independent of runtime expiry. Only dedicated + // lifecycle operations may change them; ordinary session Upserts preserve + // the values already stored in the database. + LifecyclePolicy string `json:"lifecycle_policy,omitempty"` + LifecyclePolicyRevision string `json:"lifecycle_policy_revision,omitempty"` + LastInputAt *time.Time `json:"last_input_at,omitempty"` + RuntimeFinishedAt *time.Time `json:"runtime_finished_at,omitempty"` + ArchiveAt *time.Time `json:"archive_at,omitempty"` + ConversationExpiresAt *time.Time `json:"conversation_expires_at,omitempty"` + LastContentExpiresAt *time.Time `json:"last_content_expires_at,omitempty"` + HistoryExpiresAt *time.Time `json:"history_expires_at,omitempty"` + DeletedAt *time.Time `json:"deleted_at,omitempty"` + // Archived is a response projection derived from ArchiveAt. It is not + // persisted and does not change the AEP runtime state. + Archived bool `json:"archived,omitempty"` + Context map[string]any `json:"context,omitempty"` // WorkerSessionID is the session ID used by the worker runtime itself. // Only populated for workers that auto-generate their own session IDs (OpenCode Server). // For Claude Code this is always empty — the gateway's ID IS the worker's session ID @@ -383,6 +439,10 @@ func (m *Manager) CreateWithBot(ctx context.Context, id, userID, botID, botName if _, isCron := platformKey["cron_job_id"]; isCron { source = SourceCron } + lifecycle, err := newInitialLifecycleState(now, m.cfg.Lifecycle) + if err != nil { + return nil, err + } info := &SessionInfo{ ID: id, UserID: userID, @@ -402,6 +462,7 @@ func (m *Manager) CreateWithBot(ctx context.Context, id, userID, botID, botName ClientKey: clientKey, WorkspaceID: workspaceID, } + lifecycle.apply(info) // Bind the AgentIdentity snapshot (#848) from the final creation inputs. bindIdentity(info) // Capture the effective runtime policy snapshot (#866) so it survives @@ -446,10 +507,11 @@ func (m *Manager) emitSessionCreateAudit(userID, botID, platform, sessionID, wor // emitSessionDeleteAudit enqueues a session.delete audit event. // Uses context.Background() so audit never blocks the caller. -func (m *Manager) emitSessionDeleteAudit(userID, botID, platform, sessionID, workerType, outcome string, id agentspec.AgentIdentity, snap *agentspec.EffectiveAgentSpecSnapshot) { +func (m *Manager) emitSessionDeleteAudit(ctx context.Context, userID, botID, platform, sessionID, workerType, outcome string, id agentspec.AgentIdentity, snap *agentspec.EffectiveAgentSpecSnapshot) { if m.auditCollector == nil { return } + request := deleteAuditRequestFromContext(ctx) _ = m.auditCollector.Enqueue(context.Background(), &audit.UserActivity{ Ts: time.Now().UnixMilli(), UserID: userID, @@ -461,6 +523,8 @@ func (m *Manager) emitSessionDeleteAudit(userID, botID, platform, sessionID, wor ResourceID: sessionID, Outcome: outcome, DetailJSON: auditDetailJSON(botID, workerType, id, snap), + IP: request.IP, + UserAgent: request.UserAgent, }) } @@ -554,8 +618,13 @@ func (m *Manager) Get(ctx context.Context, id string) (*SessionInfo, error) { m.mu.RUnlock() if ok { ms.mu.RLock() + if ms.deleting { + ms.mu.RUnlock() + return nil, ErrSessionCleanupPending + } info := ms.info ms.mu.RUnlock() + updateArchiveProjection(&info, time.Now()) return &info, nil } @@ -564,14 +633,20 @@ func (m *Manager) Get(ctx context.Context, id string) (*SessionInfo, error) { if err != nil { return nil, err } + updateArchiveProjection(info, time.Now()) m.mu.Lock() // Double-check: another goroutine may have populated this while we loaded from store. if existing, ok := m.sessions[id]; ok { m.mu.Unlock() existing.mu.RLock() + if existing.deleting { + existing.mu.RUnlock() + return nil, ErrSessionCleanupPending + } cached := existing.info existing.mu.RUnlock() + updateArchiveProjection(&cached, time.Now()) return &cached, nil } m.sessions[id] = &managedSession{info: *info, log: m.log.With("worker_type", info.WorkerType, "channel", info.Platform)} @@ -580,6 +655,16 @@ func (m *Manager) Get(ctx context.Context, id string) (*SessionInfo, error) { return info, nil } +// GetPurgeStatus reads a deletion job through its persisted user ownership +// snapshot. The status remains available after the deleted session is hidden. +func (m *Manager) GetPurgeStatus(ctx context.Context, sessionID, userID string) (*PurgeStatus, error) { + reader, ok := m.store.(PurgeStatusReader) + if !ok { + return nil, ErrPurgeStatusNotReady + } + return reader.GetPurgeStatus(ctx, sessionID, userID) +} + // IsSeqActive reports whether a session may still allocate or persist AEP // sequence events. It rejects the in-memory deleting phase before the durable // DELETED write completes, closing the gap where late producers could enter @@ -759,7 +844,7 @@ func (m *Manager) transitionState(ctx context.Context, ms *managedSession, from, observability.SessionDeleted().Add(ctx, 1) } - m.notifyStateChange(ctx, ms.info.ID, to, "") + m.notifyStateChange(ctx, ms.info.ID, to, "", termReason, candidate.LifecyclePolicy) m.updateRunningIndexForTransition(ms.info.ID, from, to) @@ -818,7 +903,7 @@ func (m *Manager) forceTerminateInMemory(ctx context.Context, ms *managedSession sessionsActiveGauge(string(events.StateTerminated)).Add(1) observability.SessionTerminated().Add(ctx, 1, metric.WithAttributes(attribute.String("reason", termReason))) - m.notifyStateChange(ctx, ms.info.ID, events.StateTerminated, termReason) + m.notifyStateChange(ctx, ms.info.ID, events.StateTerminated, termReason, termReason, ms.info.LifecyclePolicy) return workerToKill } @@ -1106,49 +1191,64 @@ func (m *Manager) detachWorkerUnchecked(id string, expected worker.Worker) bool // Lock ordering: m.mu → ms.mu (same as AttachWorker/DetachWorker to avoid deadlock). // DB write is performed outside locks to avoid holding mutexes during I/O. func (m *Manager) Delete(ctx context.Context, id string) error { - // Acquire m.mu first to maintain consistent lock order with AttachWorker. - var workerToTerminate worker.Worker m.mu.Lock() ms, ok := m.sessions[id] - if !ok { - m.mu.Unlock() - if cleanup, ok := m.store.(CleanupTaskStore); ok { - _, err := cleanup.DeletePhysicalWithCleanup(ctx, id) - if err == nil { - m.notifyRuntimeRelease(ctx, id) - } - return err + if ok { + ms.mu.Lock() + if ms.deleting { + ms.mu.Unlock() + m.mu.Unlock() + return ErrSessionCleanupPending } - var deletedInfo *SessionInfo - if m.OnDelete != nil { - if info, err := m.store.Get(ctx, id); err == nil { - deletedInfo = info - } + candidate := ms.info + wasRunning := candidate.State == events.StateRunning + now := time.Now() + candidate.State = events.StateDeleted + candidate.UpdatedAt = now + if candidate.DeletedAt == nil { + candidate.DeletedAt = &now } - if err := m.store.DeletePhysical(ctx, id); err != nil { - return err + ms.deleting = true + ms.mu.Unlock() + m.mu.Unlock() + return m.persistDelete(ctx, id, ms, candidate, nil, wasRunning) + } + + // Install a cache tombstone before loading the row. This blocks Get, + // AttachWorker, and sequence producers while the durable delete barrier is + // being established, even when the session was not previously cached. + ms = &managedSession{info: SessionInfo{ID: id}, deleting: true, log: m.log.With("session_id", id)} + m.sessions[id] = ms + m.mu.Unlock() + + info, err := m.store.Get(ctx, id) + if err != nil { + m.mu.Lock() + if current, exists := m.sessions[id]; exists && current == ms { + delete(m.sessions, id) } - m.notifyRuntimeRelease(ctx, id) - if deletedInfo != nil { - m.notifyDelete(*deletedInfo) + m.mu.Unlock() + if errors.Is(err, ErrSessionNotFound) { + m.notifyRuntimeRelease(ctx, id) + return nil } - return nil + return err } - - ms.mu.Lock() - workerType := ms.info.WorkerType - uid := ms.info.UserID - platform := ms.info.Platform - botID := ms.info.BotID - wasRunning := ms.info.State == events.StateRunning - // Copy-on-write: build candidate, persist, commit on success. - candidate := ms.info + candidate := *info + wasRunning := candidate.State == events.StateRunning + now := time.Now() candidate.State = events.StateDeleted - candidate.UpdatedAt = time.Now() - ms.deleting = true - ms.mu.Unlock() - m.mu.Unlock() + candidate.UpdatedAt = now + if candidate.DeletedAt == nil { + candidate.DeletedAt = &now + } + return m.persistDelete(ctx, id, ms, candidate, info, wasRunning) +} +// persistDelete commits the durable tombstone and cleanup task before removing +// the in-memory entry. original is non-nil when Delete installed a cold-cache +// tombstone and loaded the session from Store. +func (m *Manager) persistDelete(ctx context.Context, id string, ms *managedSession, candidate SessionInfo, original *SessionInfo, wasRunning bool) error { var persistErr error if cleanup, ok := m.store.(CleanupTaskStore); ok { persistErr = cleanup.MarkDeletedWithCleanup(ctx, &candidate) @@ -1157,28 +1257,36 @@ func (m *Manager) Delete(ctx context.Context, id string) error { } if persistErr != nil { m.mu.Lock() - if current, exists := m.sessions[id]; exists { - current.mu.Lock() - current.deleting = false - current.mu.Unlock() + if current, exists := m.sessions[id]; exists && current == ms { + if original != nil { + m.sessions[id] = &managedSession{ + info: *original, + log: m.log.With("worker_type", original.WorkerType, "channel", original.Platform), + } + } else { + ms.mu.Lock() + ms.deleting = false + ms.mu.Unlock() + } } m.mu.Unlock() - m.emitSessionDeleteAudit(uid, botID, platform, id, string(workerType), audit.OutcomeFailure, candidate.EffectiveIdentity(), candidate.SpecSnapshot) + m.emitSessionDeleteAudit(ctx, candidate.UserID, candidate.BotID, candidate.Platform, id, string(candidate.WorkerType), audit.OutcomeFailure, candidate.EffectiveIdentity(), candidate.SpecSnapshot) return persistErr } // AttachWorker observes deleting under the same mutex, so no worker can be // attached during the durable state-and-outbox transaction above. + var workerToTerminate worker.Worker m.mu.Lock() - if _, exists := m.sessions[id]; exists { + if current, exists := m.sessions[id]; exists && current == ms { ms.mu.Lock() ms.info = candidate if ms.worker != nil { workerToTerminate = ms.worker - workersRunningGauge(string(workerType)).Add(-1) + workersRunningGauge(string(candidate.WorkerType)).Add(-1) // Release against the workspace the quota was acquired on at attach // time (review P2 quota-drift fix), not the live ms.info.WorkspaceID. - m.pool.ReleaseForWorkspace(ctx, uid, ms.attachedWorkspaceID) + m.pool.ReleaseForWorkspace(ctx, candidate.UserID, ms.attachedWorkspaceID) } ms.mu.Unlock() delete(m.sessions, id) @@ -1188,7 +1296,7 @@ func (m *Manager) Delete(ctx context.Context, id string) error { } m.mu.Unlock() - m.notifyStateChange(ctx, id, events.StateDeleted, "session deleted") + m.notifyStateChange(ctx, id, events.StateDeleted, "session deleted", "session_deleted", candidate.LifecyclePolicy) m.terminateWorkerGracefully(ctx, workerToTerminate, id) m.notifyRuntimeRelease(ctx, id) if _, ok := m.store.(CleanupTaskStore); !ok { @@ -1196,7 +1304,7 @@ func (m *Manager) Delete(ctx context.Context, id string) error { } m.log.Info("session: deleted", "session_id", id) - m.emitSessionDeleteAudit(uid, botID, platform, id, string(workerType), audit.OutcomeSuccess, candidate.EffectiveIdentity(), candidate.SpecSnapshot) + m.emitSessionDeleteAudit(ctx, candidate.UserID, candidate.BotID, candidate.Platform, id, string(candidate.WorkerType), audit.OutcomeSuccess, candidate.EffectiveIdentity(), candidate.SpecSnapshot) return nil } @@ -1462,7 +1570,15 @@ func (m *Manager) Lock(id string) (release func(), err error) { // List returns all sessions from Store. Use ListActive for in-memory active sessions only. func (m *Manager) List(ctx context.Context, userID, platform, workspaceID string, limit, offset int) ([]*SessionInfo, error) { - return m.store.List(ctx, userID, platform, workspaceID, limit, offset) + sessions, err := m.store.List(ctx, userID, platform, workspaceID, limit, offset) + if err != nil { + return nil, err + } + now := time.Now() + for _, info := range sessions { + updateArchiveProjection(info, now) + } + return sessions, nil } // ListActive returns in-memory active sessions (no DB round-trip). @@ -1470,11 +1586,13 @@ func (m *Manager) ListActive() []*SessionInfo { m.mu.RLock() defer m.mu.RUnlock() + now := time.Now() sessions := make([]*SessionInfo, 0, len(m.sessions)) for _, ms := range m.sessions { ms.mu.RLock() info := ms.info ms.mu.RUnlock() + updateArchiveProjection(&info, now) sessions = append(sessions, &info) } return sessions @@ -1526,6 +1644,80 @@ func (m *Manager) ResetExpiry(ctx context.Context, id string) error { }) } +// RecordInputAccepted advances the v2 session-level deadlines after a new +// durable input acceptance. The stored policy revision must match the active +// configuration; older sessions are left unchanged rather than being +// retroactively assigned a different retention policy. +func (m *Manager) RecordInputAccepted(ctx context.Context, id string, acceptedAt time.Time) error { + if m == nil { + return ErrSessionNotFound + } + if acceptedAt.IsZero() { + return fmt.Errorf("session: accepted input time must be set") + } + + cfg := m.cfg + if m.cfgStore != nil { + if current := m.cfgStore.Load(); current != nil { + cfg = current + } + } + if cfg == nil { + return fmt.Errorf("session: lifecycle configuration is unavailable") + } + + candidate, err := newInitialLifecycleState(acceptedAt, cfg.Lifecycle) + if err != nil { + return err + } + if candidate.policy != config.LifecyclePolicyV2 { + return nil + } + + ms := m.getManagedSession(ctx, id) + if ms == nil { + return ErrSessionNotFound + } + + ms.mu.Lock() + defer ms.mu.Unlock() + if ms.info.LifecyclePolicy != config.LifecyclePolicyV2 || + ms.info.LifecyclePolicyRevision == "" || + ms.info.LifecyclePolicyRevision != candidate.revision { + return nil + } + + lastInputAt := latestLifecycleTime(ms.info.LastInputAt, acceptedAt) + archiveAt := latestLifecycleTime(ms.info.ArchiveAt, *candidate.archiveAt) + conversationExpiresAt := latestLifecycleTime(ms.info.ConversationExpiresAt, *candidate.conversationExpiresAt) + historyExpiresAt := latestLifecycleTime(ms.info.HistoryExpiresAt, *candidate.historyExpiresAt) + updatedAt := latestLifecycleTime(&ms.info.UpdatedAt, time.Now()) + + store, ok := m.store.(lifecycleDeadlineStore) + if !ok { + return fmt.Errorf("session: store does not support lifecycle deadline updates") + } + if err := store.AdvanceLifecycleDeadlines( + ctx, + id, + candidate.revision, + lastInputAt, + archiveAt, + conversationExpiresAt, + historyExpiresAt, + updatedAt, + ); err != nil { + return fmt.Errorf("session: persist input lifecycle deadlines: %w", err) + } + + ms.info.LastInputAt = ptr(lastInputAt) + ms.info.ArchiveAt = ptr(archiveAt) + ms.info.ConversationExpiresAt = ptr(conversationExpiresAt) + ms.info.HistoryExpiresAt = ptr(historyExpiresAt) + ms.info.UpdatedAt = updatedAt + return nil +} + // WorkerHealthStatuses returns a snapshot of health for all active worker processes. func (m *Manager) WorkerHealthStatuses() []worker.WorkerHealth { m.mu.RLock() @@ -1765,6 +1957,155 @@ func (m *Manager) gc(ctx context.Context) { } } } + + // 5. Retire v2 conversations only after both the logical session and all + // retained history are past their persisted deadlines, and no execution + // recovery obligation remains. The store rechecks every condition while + // holding the session lifecycle lock before it creates cleanup outbox work. + lifecycleStore, ok := m.store.(lifecycleRetirementStore) + if !ok { + return + } + candidateCfg := m.cfg + if m.cfgStore != nil { + if current := m.cfgStore.Load(); current != nil { + candidateCfg = current + } + } + limit := candidateCfg.Lifecycle.GC.BatchSize + candidates, err := lifecycleStore.ListExpiredLifecycleSessions(ctx, now, limit) + if err != nil { + observability.LifecycleGCFailures().Add(ctx, 1, metric.WithAttributes(attribute.String("phase", "session_query"))) + m.log.Error("session: gc (lifecycle expiry query) failed", "err", err) + return + } + for _, id := range candidates { + m.retireExpiredLifecycleSession(ctx, lifecycleStore, id, now) + } + m.refreshLifecycleGCStatus(ctx, now, candidateCfg.Lifecycle.GC.MaxLag) +} + +func (m *Manager) refreshLifecycleGCStatus(ctx context.Context, now time.Time, maxLag time.Duration) { + statusStore, ok := m.store.(lifecycleGCStatusStore) + if !ok { + return + } + status, err := statusStore.GetLifecycleGCStatus(ctx, now) + if err != nil { + observability.LifecycleGCFailures().Add(ctx, 1, metric.WithAttributes(attribute.String("phase", "status_query"))) + m.log.Warn("session: gc lifecycle status query failed", "err", err) + return + } + + eligibleLag := status.eligibleLag + blockedLag := status.blockedLag + lifecycleGCGauges.eligibleBacklog.Store(status.eligible) + lifecycleGCGauges.blockedBacklog.Store(status.blocked) + lifecycleGCGauges.unknownExecutionHold.Store(status.unknownExecutionHold) + lifecycleGCGauges.eligibleLagNanos.Store(int64(eligibleLag)) + lifecycleGCGauges.blockedLagNanos.Store(int64(blockedLag)) + + if eligibleLag > maxLag || blockedLag > maxLag { + if m.lifecycleGCLagExceeded.CompareAndSwap(false, true) { + m.log.Warn( + "session: lifecycle GC overdue backlog exceeds max lag", + "eligible_backlog", status.eligible, + "blocked_backlog", status.blocked, + "oldest_eligible_lag", eligibleLag, + "oldest_blocked_lag", blockedLag, + "max_lag", maxLag, + ) + } + return + } + m.lifecycleGCLagExceeded.Store(false) +} + +func (m *Manager) retireExpiredLifecycleSession(ctx context.Context, store lifecycleRetirementStore, id string, now time.Time) { + m.mu.Lock() + ms, cached := m.sessions[id] + if cached { + ms.mu.Lock() + if ms.deleting || ms.worker != nil || ms.info.State == events.StateRunning || + ms.info.LifecyclePolicy != config.LifecyclePolicyV2 { + ms.mu.Unlock() + m.mu.Unlock() + return + } + ms.deleting = true + ms.mu.Unlock() + } else { + ms = &managedSession{ + info: SessionInfo{ID: id}, + deleting: true, + log: m.log.With("session_id", id), + } + m.sessions[id] = ms + } + m.mu.Unlock() + + retired, err := store.RetireExpiredLifecycleSession(ctx, id, now) + if err != nil || retired == nil { + m.mu.Lock() + if current, exists := m.sessions[id]; exists && current == ms { + if cached { + ms.mu.Lock() + ms.deleting = false + ms.mu.Unlock() + } else { + delete(m.sessions, id) + } + } + m.mu.Unlock() + if err != nil { + observability.LifecycleGCFailures().Add(ctx, 1, metric.WithAttributes(attribute.String("phase", "session_retire"))) + m.log.Warn("session: gc lifecycle retirement failed", "session_id", id, "err", err) + } + return + } + + m.mu.Lock() + if current, exists := m.sessions[id]; exists && current == ms { + ms.mu.Lock() + ms.info.State = events.StateDeleted + ms.info.DeletedAt = ptr(now) + ms.info.UpdatedAt = now + ms.mu.Unlock() + delete(m.sessions, id) + } + m.mu.Unlock() + + m.notifyStateChange(ctx, id, events.StateDeleted, "session retention expired", "retention_expired", retired.LifecyclePolicy) + m.notifyRuntimeRelease(ctx, id) + m.emitSessionDeleteAudit(ctx, retired.UserID, retired.BotID, retired.Platform, id, + string(retired.WorkerType), audit.OutcomeSuccess, retired.EffectiveIdentity(), retired.SpecSnapshot) + observability.SessionDeleted().Add(ctx, 1) + observability.RecordLifecycleGCProcessed(ctx, observability.LifecycleGCProcessedSession, 1) + observability.LifecycleGCRetirementLag().Record(ctx, lifecycleExpirationLag(retired, now).Seconds()) +} + +func lifecycleExpirationLag(info *SessionInfo, now time.Time) time.Duration { + if info == nil { + return 0 + } + var latestDeadline time.Time + if info.ConversationExpiresAt != nil { + latestDeadline = *info.ConversationExpiresAt + } + if info.LastContentExpiresAt != nil && info.LastContentExpiresAt.After(latestDeadline) { + latestDeadline = *info.LastContentExpiresAt + } + if latestDeadline.IsZero() || now.Before(latestDeadline) { + return 0 + } + return now.Sub(latestDeadline) +} + +func lifecycleDeadlineLag(deadline *time.Time, now time.Time) time.Duration { + if deadline == nil || now.Before(*deadline) { + return 0 + } + return now.Sub(*deadline) } // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -1787,7 +2128,7 @@ func (m *Manager) safeCall(fn func()) { } // notifyStateChange sends state change and termination callbacks. -func (m *Manager) notifyStateChange(ctx context.Context, sessionID string, state events.SessionState, message string) { +func (m *Manager) notifyStateChange(ctx context.Context, sessionID string, state events.SessionState, message, terminationReason, lifecyclePolicy string) { if m.StateNotifier != nil { notify := func() { m.StateNotifier(ctx, sessionID, state, message) } if state == events.StateDeleted { @@ -1799,7 +2140,9 @@ func (m *Manager) notifyStateChange(ctx context.Context, sessionID string, state m.safeGo(notify) } } - if (state == events.StateTerminated || state == events.StateDeleted) && m.OnTerminate != nil { + notifyTermination := state == events.StateDeleted || + (state == events.StateTerminated && shouldNotifyTerminationCleanup(lifecyclePolicy, terminationReason)) + if notifyTermination && m.OnTerminate != nil { m.safeGo(func() { m.OnTerminate(sessionID) }) } } diff --git a/internal/session/manager_test.go b/internal/session/manager_test.go index 36075dfeb..b535b9733 100644 --- a/internal/session/manager_test.go +++ b/internal/session/manager_test.go @@ -179,6 +179,101 @@ func TestManager_Create(t *testing.T) { require.Equal(t, worker.TypeClaudeCode, info.WorkerType) require.Equal(t, events.StateCreated, info.State) require.NotNil(t, info.ExpiresAt) + require.Equal(t, config.LifecyclePolicyV2, info.LifecyclePolicy) + require.NotEmpty(t, info.LifecyclePolicyRevision) + require.Nil(t, info.LastInputAt, "creating a session without input must not fabricate last_input_at") + require.Equal(t, info.CreatedAt.Add(7*24*time.Hour), *info.ArchiveAt) + require.Equal(t, info.CreatedAt.Add(180*24*time.Hour), *info.ConversationExpiresAt) + require.Equal(t, info.ConversationExpiresAt, info.HistoryExpiresAt) +} + +func TestManager_RecordInputAccepted_AdvancesAndPersistsLifecycleDeadlines(t *testing.T) { + t.Parallel() + + store, cfg := helperDB(t) + cfg.Session.GCScanInterval = time.Hour + m, err := NewManager(t.Context(), nil, cfg, nil, store) + require.NoError(t, err) + defer func() { _ = m.Close() }() + + info, err := m.Create(t.Context(), "sess_input_lifecycle", "user1", worker.TypeClaudeCode, nil, "", "test-session") + require.NoError(t, err) + + acceptedAt := info.CreatedAt.Add(24 * time.Hour) + require.NoError(t, m.RecordInputAccepted(t.Context(), info.ID, acceptedAt)) + + got, err := store.Get(t.Context(), info.ID) + require.NoError(t, err) + require.NotNil(t, got.LastInputAt) + require.NotNil(t, got.ArchiveAt) + require.NotNil(t, got.ConversationExpiresAt) + require.NotNil(t, got.HistoryExpiresAt) + require.True(t, got.LastInputAt.Equal(acceptedAt)) + require.True(t, got.ArchiveAt.Equal(acceptedAt.Add(cfg.Lifecycle.Conversation.ArchiveAfter))) + require.True(t, got.ConversationExpiresAt.Equal(acceptedAt.Add(cfg.Lifecycle.Conversation.RetentionAfterLastInput))) + require.True(t, got.HistoryExpiresAt.Equal(acceptedAt.Add(cfg.Lifecycle.Conversation.RetentionAfterLastInput))) + require.Nil(t, got.LastContentExpiresAt, "session activity must not invent per-record content expiry") + + // Out-of-order acceptance must not shorten any persisted lifecycle deadline. + require.NoError(t, m.RecordInputAccepted(t.Context(), info.ID, acceptedAt.Add(-48*time.Hour))) + got, err = store.Get(t.Context(), info.ID) + require.NoError(t, err) + require.NotNil(t, got.LastInputAt) + require.NotNil(t, got.ArchiveAt) + require.NotNil(t, got.ConversationExpiresAt) + require.NotNil(t, got.HistoryExpiresAt) + require.True(t, got.LastInputAt.Equal(acceptedAt)) + require.True(t, got.ArchiveAt.Equal(acceptedAt.Add(cfg.Lifecycle.Conversation.ArchiveAfter))) + require.True(t, got.ConversationExpiresAt.Equal(acceptedAt.Add(cfg.Lifecycle.Conversation.RetentionAfterLastInput))) + require.True(t, got.HistoryExpiresAt.Equal(acceptedAt.Add(cfg.Lifecycle.Conversation.RetentionAfterLastInput))) +} + +func TestManager_RecordInputAccepted_DoesNotRewriteLegacyOrDifferentPolicyRevision(t *testing.T) { + t.Parallel() + + store, cfg := helperDB(t) + cfg.Session.GCScanInterval = time.Hour + m, err := NewManager(t.Context(), nil, cfg, nil, store) + require.NoError(t, err) + defer func() { _ = m.Close() }() + + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + for _, tc := range []struct { + name string + id string + policy string + revision string + }{ + {name: "legacy", id: "sess_legacy", policy: config.LifecyclePolicyLegacy}, + {name: "different revision", id: "sess_revision", policy: config.LifecyclePolicyV2, revision: "older-policy"}, + } { + t.Run(tc.name, func(t *testing.T) { + before := &SessionInfo{ + ID: tc.id, + UserID: "user1", + WorkerType: worker.TypeClaudeCode, + State: events.StateCreated, + CreatedAt: now, + UpdatedAt: now, + LifecyclePolicy: tc.policy, + LifecyclePolicyRevision: tc.revision, + LastInputAt: ptr(now), + ArchiveAt: ptr(now.Add(24 * time.Hour)), + ConversationExpiresAt: ptr(now.Add(48 * time.Hour)), + HistoryExpiresAt: ptr(now.Add(48 * time.Hour)), + } + require.NoError(t, store.Upsert(t.Context(), before)) + + require.NoError(t, m.RecordInputAccepted(t.Context(), tc.id, now.Add(72*time.Hour))) + + after, err := store.Get(t.Context(), tc.id) + require.NoError(t, err) + require.Equal(t, before.LastInputAt, after.LastInputAt) + require.Equal(t, before.ArchiveAt, after.ArchiveAt) + require.Equal(t, before.ConversationExpiresAt, after.ConversationExpiresAt) + require.Equal(t, before.HistoryExpiresAt, after.HistoryExpiresAt) + }) + } } func TestManager_Get(t *testing.T) { @@ -692,8 +787,15 @@ func TestManager_DeleteNotInMemoryNotifiesWorkerSessionCleanup(t *testing.T) { ID: "sess_stored_cleanup", WorkerType: worker.TypeOpenCodeSrv, WorkerSessionID: "ocs-stored", - }, nil) - store.On("DeletePhysical", ctx, "sess_stored_cleanup").Return(nil) + State: events.StateTerminated, + CreatedAt: time.Now().Add(-time.Hour), + UpdatedAt: time.Now().Add(-time.Hour), + }, nil).Once() + store.On("Upsert", ctx, mock.MatchedBy(func(info *SessionInfo) bool { + return info.ID == "sess_stored_cleanup" && + info.State == events.StateDeleted && + info.DeletedAt != nil + })).Return(nil).Once() m, err := NewManager(ctx, nil, cfg, nil, store) require.NoError(t, err) diff --git a/internal/session/migration_041_test.go b/internal/session/migration_041_test.go new file mode 100644 index 000000000..9076e6776 --- /dev/null +++ b/internal/session/migration_041_test.go @@ -0,0 +1,56 @@ +package session + +import ( + "context" + "database/sql" + "io/fs" + "path/filepath" + "testing" + + "github.com/pressly/goose/v3" + "github.com/stretchr/testify/require" + + "github.com/hrygo/hotplex/internal/config" + "github.com/hrygo/hotplex/internal/sqlutil" +) + +func TestMigration041ContentExpiryPreservesExistingRows(t *testing.T) { + ctx := context.Background() + db, err := sql.Open(sqlutil.DriverName, filepath.Join(t.TempDir(), "migration-041.db")) + require.NoError(t, err) + t.Cleanup(func() { _ = db.Close() }) + + cfg := config.Default() + require.NoError(t, sqlutil.InitSQLiteDB(db, &cfg.DB, sqlutil.DialectSQLite, "migration_041_test")) + migrations, err := fs.Sub(migrationFS, "sql/migrations") + require.NoError(t, err) + provider, err := goose.NewProvider( + goose.DialectSQLite3, + db, + migrations, + goose.WithDisableGlobalRegistry(true), + ) + require.NoError(t, err) + _, err = provider.UpTo(ctx, 40) + require.NoError(t, err) + + _, err = db.ExecContext(ctx, `INSERT INTO events + (session_id, seq, type, data, direction, source, created_at) + VALUES ('s-legacy', 1, 'message', '{}', 'outbound', 'normal', 1000)`) + require.NoError(t, err) + _, err = db.ExecContext(ctx, `INSERT INTO turns + (session_id, generation, turn_num, role, content, created_at) + VALUES ('s-legacy', 1, 1, 'user', 'legacy content', 1000)`) + require.NoError(t, err) + + _, err = provider.UpTo(ctx, 41) + require.NoError(t, err) + + var eventExpiry, turnExpiry int64 + require.NoError(t, db.QueryRowContext(ctx, + `SELECT expires_at FROM events WHERE session_id = 's-legacy'`).Scan(&eventExpiry)) + require.NoError(t, db.QueryRowContext(ctx, + `SELECT expires_at FROM turns WHERE session_id = 's-legacy'`).Scan(&turnExpiry)) + require.Zero(t, eventExpiry, "existing event rows must keep legacy expiry handling") + require.Zero(t, turnExpiry, "existing turns must keep legacy expiry handling") +} diff --git a/internal/session/multitenancy_store_test.go b/internal/session/multitenancy_store_test.go index 10b05d4c1..b3811cf62 100644 --- a/internal/session/multitenancy_store_test.go +++ b/internal/session/multitenancy_store_test.go @@ -234,7 +234,8 @@ func TestWorkspacesStore_DeleteIfEmpty_BlockedByActiveSession(t *testing.T) { // 插入一条活跃会话(state=running, workspace_id=ws-1)模拟 Count↔Delete 间的并发新建。 _, err := store.db.ExecContext(ctx, queries["sessions.upsert_session"], "sess-1", "u-1", "u-1", "", "", "", "claude_code", "running", "webchat", "{}", "/tmp/x", "", - int64(1700000000), int64(1700000000), int64(1800000000), int64(1800000000), "{}", "", "ck-1", "ws-1", "sess-1") + int64(1700000000), int64(1700000000), int64(1800000000), int64(1800000000), "{}", "", "ck-1", "ws-1", + "legacy", "", nil, nil, nil, nil, nil, nil, nil, "sess-1") require.NoError(t, err) // 有活跃会话:原子删除拒绝(防 TOCTOU 后 workspace_id 悬空)。 diff --git a/internal/session/pg_store.go b/internal/session/pg_store.go index d3b8f0274..8c631eef7 100644 --- a/internal/session/pg_store.go +++ b/internal/session/pg_store.go @@ -9,6 +9,7 @@ import ( "time" "github.com/hrygo/hotplex/internal/agentspec" + "github.com/hrygo/hotplex/internal/config" "github.com/hrygo/hotplex/internal/dbutil" "github.com/hrygo/hotplex/pkg/events" ) @@ -108,6 +109,55 @@ func (s *pgStore) UpdateWorkerSessionIDSQL(ctx context.Context, id, workerSessio return nil } +// AdvanceLifecycleDeadlines atomically advances only the session-level +// lifecycle deadlines. Turn/event expiry remains independent and is not +// inferred from this session aggregate. +func (s *pgStore) AdvanceLifecycleDeadlines( + ctx context.Context, + id, policyRevision string, + lastInputAt, archiveAt, conversationExpiresAt, historyExpiresAt, updatedAt time.Time, +) error { + ctx, cancel := upsertTimeout(ctx) + defer cancel() + + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + if err := ensurePGLifecycleLock(ctx, tx, s.dialect.Rebind, id); err != nil { + return err + } + result, err := tx.ExecContext(ctx, s.queries["sessions.advance_lifecycle_deadlines"], + lastInputAt, lastInputAt, + archiveAt, archiveAt, + conversationExpiresAt, conversationExpiresAt, + historyExpiresAt, historyExpiresAt, + updatedAt, updatedAt, + id, config.LifecyclePolicyV2, policyRevision, + ) + if err != nil { + return fmt.Errorf("session store: advance lifecycle deadlines: %w", err) + } + updated, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("session store: lifecycle deadline rows affected: %w", err) + } + if updated == 0 { + var pending bool + if err := tx.QueryRowContext(ctx, + s.dialect.Rebind(`SELECT EXISTS(SELECT 1 FROM session_cleanup_tasks WHERE session_id = ?)`), id, + ).Scan(&pending); err != nil { + return fmt.Errorf("session store: check cleanup task: %w", err) + } + if pending { + return ErrSessionCleanupPending + } + return ErrSessionNotFound + } + return tx.Commit() +} + // SetPermissionCeilingIfEmpty atomically captures the first effective Worker // permission ceiling and returns the authoritative stored value. func (s *pgStore) SetPermissionCeilingIfEmpty(ctx context.Context, id, ceiling string) (string, error) { @@ -225,6 +275,35 @@ func (s *pgStore) GetExpiredIdle(ctx context.Context, now time.Time) ([]string, return collectIDs(rows) } +func (s *pgStore) ListExpiredLifecycleSessions(ctx context.Context, now time.Time, limit int) ([]string, error) { + if limit <= 0 { + return nil, nil + } + rows, err := s.db.QueryContext(ctx, s.queries["store.select_expired_lifecycle_ids"], now, now, now, limit) + if err != nil { + return nil, fmt.Errorf("session store: select expired lifecycle sessions: %w", err) + } + defer func() { _ = rows.Close() }() + return collectIDs(rows) +} + +func (s *pgStore) GetLifecycleGCStatus(ctx context.Context, now time.Time) (lifecycleGCStatus, error) { + var status lifecycleGCStatus + var oldestEligible, oldestBlocked sql.NullTime + err := s.db.QueryRowContext(ctx, s.queries["store.lifecycle_gc_status_pg"], now, now, now). + Scan(&status.eligible, &status.blocked, &status.unknownExecutionHold, &oldestEligible, &oldestBlocked) + if err != nil { + return lifecycleGCStatus{}, fmt.Errorf("session store: get lifecycle GC status: %w", err) + } + if oldestEligible.Valid { + status.eligibleLag = lifecycleDeadlineLag(&oldestEligible.Time, now) + } + if oldestBlocked.Valid { + status.blockedLag = lifecycleDeadlineLag(&oldestBlocked.Time, now) + } + return status, nil +} + // DeleteTerminated removes terminated sessions older than the respective cutoffs. func (s *pgStore) DeleteTerminated(ctx context.Context, cronCutoff, defaultCutoff time.Time) ([]*SessionInfo, error) { tx, err := s.db.BeginTx(ctx, nil) @@ -250,6 +329,13 @@ func (s *pgStore) DeleteTerminated(ctx context.Context, cronCutoff, defaultCutof if err := ensurePGLifecycleLock(ctx, tx, s.dialect.Rebind, id); err != nil { return nil, err } + locked, err := lockSessionRowForLifecycleChange(ctx, tx, s.dialect.Rebind, id) + if err != nil { + return nil, err + } + if !locked { + continue + } deletedRows, err := tx.QueryContext(ctx, s.queries["store.delete_terminated_by_id"], id, events.StateTerminated, cronCutoff, defaultCutoff) if err != nil { return nil, fmt.Errorf("session store: delete terminated: %w", err) diff --git a/internal/session/pg_store_test.go b/internal/session/pg_store_test.go index f595b9daa..b3acfbdea 100644 --- a/internal/session/pg_store_test.go +++ b/internal/session/pg_store_test.go @@ -26,12 +26,13 @@ func newPGMock(t *testing.T) (*pgStore, sqlmock.Sqlmock, func()) { // Rebind all queries to PG $N placeholders manually (same as NewPGStore does). q := make(map[string]string) - q["store.get_session"] = dbutil.DialectPostgres.Rebind( - "SELECT id, user_id, COALESCE(owner_id, user_id), worker_session_id, worker_type, state, bot_id, COALESCE(bot_name, ''), platform, platform_key_json, COALESCE(work_dir, ''), COALESCE(title, ''), created_at, updated_at, expires_at, idle_expires_at, context_json, source, COALESCE(client_key, ''), COALESCE(workspace_id, ''), COALESCE(permission_ceiling, '') FROM sessions WHERE id = ?") + q["store.get_session"] = dbutil.DialectPostgres.Rebind(queries["store.get_session"]) q["store.select_terminated_ids"] = dbutil.DialectPostgres.Rebind( - "SELECT id FROM sessions WHERE state = ? AND ((source = 'cron' AND updated_at <= ?) OR (source != 'cron' AND updated_at <= ?))") - q["store.delete_terminated_by_id"] = dbutil.DialectPostgres.Rebind( - "DELETE FROM sessions WHERE id = ? AND state = ? AND ((source = 'cron' AND updated_at <= ?) OR (source != 'cron' AND updated_at <= ?)) RETURNING id, user_id, COALESCE(owner_id, user_id), worker_session_id, worker_type, state, bot_id, COALESCE(bot_name, ''), platform, platform_key_json, COALESCE(work_dir, ''), COALESCE(title, ''), created_at, updated_at, expires_at, idle_expires_at, context_json, source, COALESCE(client_key, ''), COALESCE(workspace_id, ''), COALESCE(permission_ceiling, '')") + queries["store.select_terminated_ids"]) + q["store.delete_terminated_by_id"] = dbutil.DialectPostgres.Rebind(queries["store.delete_terminated_by_id"]) + q["store.select_expired_lifecycle_ids"] = dbutil.DialectPostgres.Rebind( + queries["store.select_expired_lifecycle_ids"]) + q["store.lifecycle_gc_status_pg"] = queries["store.lifecycle_gc_status_pg"] q["store.get_sessions_by_state"] = dbutil.DialectPostgres.Rebind( "SELECT id FROM sessions WHERE state = ?") q["store.delete_physical"] = dbutil.DialectPostgres.Rebind( @@ -44,6 +45,8 @@ func newPGMock(t *testing.T) (*pgStore, sqlmock.Sqlmock, func()) { queries["store.get_context_json"]) q["sessions.update_spec_snapshot_pg"] = dbutil.DialectPostgres.Rebind( queries["sessions.update_spec_snapshot_pg"]) + q["sessions.advance_lifecycle_deadlines"] = dbutil.DialectPostgres.Rebind( + queries["sessions.advance_lifecycle_deadlines"]) store := &pgStore{ db: db, @@ -63,6 +66,8 @@ func sessionColumns() []string { "id", "user_id", "owner_id", "worker_session_id", "worker_type", "state", "bot_id", "bot_name", "platform", "platform_key_json", "work_dir", "title", "created_at", "updated_at", "expires_at", "idle_expires_at", "context_json", "source", "client_key", "workspace_id", "permission_ceiling", + "lifecycle_policy", "lifecycle_policy_revision", "last_input_at", "runtime_finished_at", "archive_at", + "conversation_expires_at", "last_content_expires_at", "history_expires_at", "deleted_at", } } @@ -75,12 +80,10 @@ func TestPGStore_Get_Found(t *testing.T) { rows := sqlmock.NewRows(sessionColumns()). AddRow("sess-1", "user-1", "owner-1", "", "claude_code", string(events.StateRunning), "bot-1", "", "slack", `{"channel_id":"C123"}`, "/work", "My Session", - now, now, nil, nil, `{"key":"value"}`, "", "", "", "") + now, now, nil, nil, `{"key":"value"}`, "", "", "", "", + "legacy", "", nil, nil, nil, nil, nil, nil, nil) - q := dbutil.DialectPostgres.Rebind( - "SELECT id, user_id, COALESCE(owner_id, user_id), worker_session_id, worker_type, state, bot_id, COALESCE(bot_name, ''), platform, platform_key_json, COALESCE(work_dir, ''), COALESCE(title, ''), created_at, updated_at, expires_at, idle_expires_at, context_json, source, COALESCE(client_key, ''), COALESCE(workspace_id, ''), COALESCE(permission_ceiling, '') FROM sessions WHERE id = ?") - - mock.ExpectQuery(regexp.QuoteMeta(q)).WithArgs("sess-1").WillReturnRows(rows) + mock.ExpectQuery(regexp.QuoteMeta(store.queries["store.get_session"])).WithArgs("sess-1").WillReturnRows(rows) info, err := store.Get(context.Background(), "sess-1") require.NoError(t, err) @@ -96,10 +99,7 @@ func TestPGStore_Get_NotFound(t *testing.T) { store, mock, cleanup := newPGMock(t) defer cleanup() - q := dbutil.DialectPostgres.Rebind( - "SELECT id, user_id, COALESCE(owner_id, user_id), worker_session_id, worker_type, state, bot_id, COALESCE(bot_name, ''), platform, platform_key_json, COALESCE(work_dir, ''), COALESCE(title, ''), created_at, updated_at, expires_at, idle_expires_at, context_json, source, COALESCE(client_key, ''), COALESCE(workspace_id, ''), COALESCE(permission_ceiling, '') FROM sessions WHERE id = ?") - - mock.ExpectQuery(regexp.QuoteMeta(q)).WithArgs("nonexistent").WillReturnError(sql.ErrNoRows) + mock.ExpectQuery(regexp.QuoteMeta(store.queries["store.get_session"])).WithArgs("nonexistent").WillReturnError(sql.ErrNoRows) pendingQuery := dbutil.DialectPostgres.Rebind(`SELECT EXISTS(SELECT 1 FROM session_cleanup_tasks WHERE session_id = ?)`) mock.ExpectQuery(regexp.QuoteMeta(pendingQuery)).WithArgs("nonexistent").WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(false)) @@ -148,6 +148,38 @@ func TestPGStore_UpdateSpecSnapshot(t *testing.T) { require.NoError(t, store.UpdateSpecSnapshot(t.Context(), "sess-1", &snapshot)) } +func TestPGStore_AdvanceLifecycleDeadlines(t *testing.T) { + t.Parallel() + store, mock, cleanup := newPGMock(t) + defer cleanup() + + lastInputAt := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + archiveAt := lastInputAt.Add(7 * 24 * time.Hour) + conversationExpiresAt := lastInputAt.Add(180 * 24 * time.Hour) + historyExpiresAt := conversationExpiresAt + updatedAt := lastInputAt.Add(time.Minute) + mock.ExpectBegin() + mock.ExpectExec(regexp.QuoteMeta(dbutil.DialectPostgres.Rebind( + `SELECT pg_advisory_xact_lock(hashtextextended(?, 0))`, + ))).WithArgs("sess-1").WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectExec(regexp.QuoteMeta(store.queries["sessions.advance_lifecycle_deadlines"])). + WithArgs( + lastInputAt, lastInputAt, + archiveAt, archiveAt, + conversationExpiresAt, conversationExpiresAt, + historyExpiresAt, historyExpiresAt, + updatedAt, updatedAt, + "sess-1", "v2", "revision-1", + ). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectCommit() + + require.NoError(t, store.AdvanceLifecycleDeadlines( + t.Context(), "sess-1", "revision-1", + lastInputAt, archiveAt, conversationExpiresAt, historyExpiresAt, updatedAt, + )) +} + func TestPGStore_DeleteTerminated(t *testing.T) { t.Parallel() store, mock, cleanup := newPGMock(t) @@ -162,14 +194,19 @@ func TestPGStore_DeleteTerminated(t *testing.T) { "id", "user_id", "owner_id", "worker_session_id", "worker_type", "state", "bot_id", "bot_name", "platform", "platform_key_json", "work_dir", "title", "created_at", "updated_at", "expires_at", "idle_expires_at", "context_json", "source", "client_key", "workspace_id", "permission_ceiling", + "lifecycle_policy", "lifecycle_policy_revision", "last_input_at", "runtime_finished_at", "archive_at", + "conversation_expires_at", "last_content_expires_at", "history_expires_at", "deleted_at", }).AddRow("sess-old", "u1", "u1", "ocs-old", "opencode_server", string(events.StateTerminated), "", "", - "webchat", "", "", "", time.Now(), time.Now(), nil, nil, nil, "", "", "", "") + "webchat", "", "", "", time.Now(), time.Now(), nil, nil, nil, "", "", "", "", + "legacy", "", nil, nil, nil, nil, nil, nil, nil) mock.ExpectBegin() mock.ExpectQuery(regexp.QuoteMeta(selectQuery)). WithArgs(string(events.StateTerminated), cronCutoff, defaultCutoff). WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("sess-old")) mock.ExpectExec(regexp.QuoteMeta(`SELECT pg_advisory_xact_lock(hashtextextended($1, 0))`)). WithArgs("sess-old").WillReturnResult(sqlmock.NewResult(0, 0)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE sessions SET state = state WHERE id = $1`)). + WithArgs("sess-old").WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectQuery(regexp.QuoteMeta(deleteQuery)). WithArgs("sess-old", string(events.StateTerminated), cronCutoff, defaultCutoff).WillReturnRows(rows) mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO session_cleanup_tasks (id, session_id, worker_type, worker_session_id, attempts, next_attempt_at, lease_until, lease_token, last_error, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, NULL, NULL, $7, $8, $9) ON CONFLICT(session_id) DO NOTHING`)). @@ -183,6 +220,73 @@ func TestPGStore_DeleteTerminated(t *testing.T) { require.Equal(t, "ocs-old", deleted[0].WorkerSessionID) } +func TestPGStore_ListExpiredLifecycleSessions(t *testing.T) { + t.Parallel() + store, mock, cleanup := newPGMock(t) + defer cleanup() + + now := time.Now() + query := store.queries["store.select_expired_lifecycle_ids"] + mock.ExpectQuery(regexp.QuoteMeta(query)). + WithArgs(now, now, now, 10). + WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("sess-expired")) + + ids, err := store.ListExpiredLifecycleSessions(context.Background(), now, 10) + require.NoError(t, err) + require.Equal(t, []string{"sess-expired"}, ids) +} + +func TestPGStore_GetLifecycleGCStatus(t *testing.T) { + t.Parallel() + store, mock, cleanup := newPGMock(t) + defer cleanup() + + now := time.Now().UTC() + eligibleDeadline := now.Add(-time.Hour) + blockedDeadline := now.Add(-2 * time.Hour) + query := store.queries["store.lifecycle_gc_status_pg"] + mock.ExpectQuery(regexp.QuoteMeta(query)). + WithArgs(now, now, now). + WillReturnRows(sqlmock.NewRows([]string{ + "eligible_count", "blocked_count", "unknown_execution_hold_count", + "oldest_eligible_deadline", "oldest_blocked_deadline", + }).AddRow(int64(3), int64(2), int64(1), eligibleDeadline, blockedDeadline)) + + status, err := store.GetLifecycleGCStatus(context.Background(), now) + require.NoError(t, err) + require.EqualValues(t, 3, status.eligible) + require.EqualValues(t, 2, status.blocked) + require.EqualValues(t, 1, status.unknownExecutionHold) + require.Equal(t, time.Hour, status.eligibleLag) + require.Equal(t, 2*time.Hour, status.blockedLag) +} + +func TestPGStore_RetireExpiredLifecycleSessionRechecksCurrentDeadline(t *testing.T) { + t.Parallel() + store, mock, cleanup := newPGMock(t) + defer cleanup() + + now := time.Now() + future := now.Add(time.Hour) + rows := sqlmock.NewRows(sessionColumns()).AddRow( + "sess-renewed", "user-1", "user-1", "", "claude_code", string(events.StateTerminated), "", "", + "webchat", "", "", "title", now.Add(-30*24*time.Hour), now, nil, nil, nil, "", "", "", "", + "v2", "revision-1", nil, nil, nil, future, nil, future, nil, + ) + mock.ExpectBegin() + mock.ExpectExec(regexp.QuoteMeta(`SELECT pg_advisory_xact_lock(hashtextextended($1, 0))`)). + WithArgs("sess-renewed").WillReturnResult(sqlmock.NewResult(0, 0)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE sessions SET state = state WHERE id = $1`)). + WithArgs("sess-renewed").WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectQuery(regexp.QuoteMeta(store.queries["store.get_session"])). + WithArgs("sess-renewed").WillReturnRows(rows) + mock.ExpectCommit() + + retired, err := store.RetireExpiredLifecycleSession(context.Background(), "sess-renewed", now) + require.NoError(t, err) + require.Nil(t, retired, "the locked row's renewed deadline must override a stale candidate") +} + func TestPGStore_GetSessionsByState(t *testing.T) { t.Parallel() store, mock, cleanup := newPGMock(t) diff --git a/internal/session/purge.go b/internal/session/purge.go new file mode 100644 index 000000000..e7bd5a3dc --- /dev/null +++ b/internal/session/purge.go @@ -0,0 +1,86 @@ +package session + +import ( + "context" + "errors" + "time" +) + +var ( + ErrPurgeNotFound = errors.New("session purge job not found") + ErrPurgeStatusNotReady = errors.New("session purge status unavailable") +) + +type PurgeJobState string + +const ( + PurgeJobPending PurgeJobState = "pending" + PurgeJobInProgress PurgeJobState = "in_progress" + PurgeJobBlocked PurgeJobState = "blocked" + PurgeJobComplete PurgeJobState = "complete" +) + +type PurgeItemState string + +const ( + PurgeItemPending PurgeItemState = "pending" + PurgeItemRunning PurgeItemState = "running" + PurgeItemRetrying PurgeItemState = "retrying" + PurgeItemBlocked PurgeItemState = "blocked" + PurgeItemUnsupported PurgeItemState = "unsupported" + PurgeItemComplete PurgeItemState = "complete" +) + +const ( + PurgeItemSessionMetadata = "session_metadata" + PurgeItemConversationContent = "conversation_content" + PurgeItemWorkerSession = "worker_session" +) + +// PurgeItemStatus contains only bounded, non-content cleanup state. ErrorCode +// is an internal enum; raw provider or worker errors are never exposed. +type PurgeItemStatus struct { + Kind string `json:"kind"` + Status PurgeItemState `json:"status"` + Attempts int `json:"attempts"` + NextAttemptAt *time.Time `json:"next_attempt_at,omitempty"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + ErrorCode string `json:"error_code,omitempty"` +} + +// PurgeStatus is the user-visible progress snapshot for one deletion intent. +// WorkspaceID is retained only for the authorization check and never serialized. +type PurgeStatus struct { + JobID string `json:"job_id"` + SessionID string `json:"session_id"` + Status PurgeJobState `json:"status"` + RequestedAt time.Time `json:"requested_at"` + UpdatedAt time.Time `json:"updated_at"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + Items []PurgeItemStatus `json:"items"` + WorkspaceID string `json:"-"` +} + +// PurgeStatusReader reads a deletion job through its immutable ownership +// snapshot, so status remains queryable after the session itself is hidden. +type PurgeStatusReader interface { + GetPurgeStatus(ctx context.Context, sessionID, userID string) (*PurgeStatus, error) +} + +// PurgeItemTask is a lease-fenced unit of durable session-content cleanup. +type PurgeItemTask struct { + ID string + SessionID string + Kind string + Attempts int + NextAttemptAt time.Time + LeaseUntil *time.Time + LeaseToken string +} + +// PurgeItemStore leases and records completion of durable content purge work. +type PurgeItemStore interface { + ClaimPurgeItems(ctx context.Context, now, leaseUntil time.Time, limit int) ([]PurgeItemTask, error) + CompletePurgeItem(ctx context.Context, itemID, leaseToken string) error + RetryPurgeItem(ctx context.Context, itemID, leaseToken string, nextAttemptAt time.Time, errorCode string) error +} diff --git a/internal/session/purge_runner.go b/internal/session/purge_runner.go new file mode 100644 index 000000000..ba9ad642c --- /dev/null +++ b/internal/session/purge_runner.go @@ -0,0 +1,86 @@ +package session + +import ( + "context" + "log/slog" + "time" + + "github.com/hrygo/hotplex/internal/observability" +) + +// PurgeContentExecutor removes the content owned by one HotPlex session. +type PurgeContentExecutor func(context.Context, string) error + +// PurgeItemRunner leases and executes durable conversation-content cleanup. +type PurgeItemRunner struct { + log *slog.Logger + store PurgeItemStore + execute PurgeContentExecutor + now func() time.Time +} + +func NewPurgeItemRunner(log *slog.Logger, store PurgeItemStore, execute PurgeContentExecutor) *PurgeItemRunner { + if log == nil { + log = slog.Default() + } + return &PurgeItemRunner{ + log: log.With("component", "session_content_purge"), + store: store, + execute: execute, + now: time.Now, + } +} + +// Run drains due work at startup and then polls until ctx ends. +func (r *PurgeItemRunner) Run(ctx context.Context) { + if r == nil || r.store == nil || r.execute == nil { + return + } + r.RunOnce(ctx) + ticker := time.NewTicker(cleanupPollInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + r.RunOnce(ctx) + } + } +} + +// RunOnce executes one leased batch. It is exported for deterministic tests. +func (r *PurgeItemRunner) RunOnce(ctx context.Context) { + if r == nil || r.store == nil || r.execute == nil { + return + } + now := r.now() + tasks, err := r.store.ClaimPurgeItems(ctx, now, now.Add(cleanupLeaseDuration), cleanupBatchSize) + if err != nil { + r.log.Warn("session purge: claim content items failed", "err", err) + return + } + for _, task := range tasks { + attemptCtx, cancel := context.WithTimeout(ctx, cleanupAttemptTimeout) + err := r.execute(attemptCtx, task.SessionID) + cancel() + if err == nil { + if completeErr := r.store.CompletePurgeItem(ctx, task.ID, task.LeaseToken); completeErr != nil { + r.log.Warn("session purge: complete content item failed", "item_id", task.ID, "session_id", task.SessionID, "err", completeErr) + } else { + observability.RecordLifecycleGCProcessed(ctx, observability.LifecycleGCProcessedConversationPurgeItem, 1) + } + continue + } + next := r.now().Add(cleanupBackoff(task.Attempts)) + if retryErr := r.store.RetryPurgeItem(ctx, task.ID, task.LeaseToken, next, "cleanup_failed"); retryErr != nil { + r.log.Warn("session purge: schedule content retry failed", "item_id", task.ID, "session_id", task.SessionID, "err", retryErr) + continue + } + // Provider errors may contain identifiers or content; retain only a + // stable error code in storage and logs. + r.log.Warn("session purge: content deletion failed; retry scheduled", + "item_id", task.ID, "session_id", task.SessionID, + "attempt", task.Attempts, "next_attempt_at", next, "error_code", "cleanup_failed") + } +} diff --git a/internal/session/purge_store.go b/internal/session/purge_store.go new file mode 100644 index 000000000..22572f24d --- /dev/null +++ b/internal/session/purge_store.go @@ -0,0 +1,269 @@ +package session + +import ( + "context" + "database/sql" + "errors" + "fmt" + "time" + + "github.com/google/uuid" +) + +func purgeItemColumns() string { + return "id, session_id, kind, attempts, next_attempt_at, lease_until, COALESCE(lease_token, '')" +} + +func scanPurgeItemTask(sc interface{ Scan(...any) error }) (PurgeItemTask, error) { + var task PurgeItemTask + var leaseUntil sql.NullTime + if err := sc.Scan(&task.ID, &task.SessionID, &task.Kind, &task.Attempts, &task.NextAttemptAt, &leaseUntil, &task.LeaseToken); err != nil { + return PurgeItemTask{}, err + } + if leaseUntil.Valid { + task.LeaseUntil = &leaseUntil.Time + } + return task, nil +} + +func purgeItemSessionID(ctx context.Context, tx *sql.Tx, rebind func(string) string, itemID, leaseToken string) (string, error) { + var sessionID string + err := tx.QueryRowContext(ctx, rebind(`SELECT session_id FROM session_purge_items + WHERE id = ? AND kind = ? AND status = ? AND lease_token = ?`), + itemID, PurgeItemConversationContent, PurgeItemRunning, leaseToken).Scan(&sessionID) + if errors.Is(err, sql.ErrNoRows) { + return "", ErrCleanupLeaseLost + } + if err != nil { + return "", fmt.Errorf("session purge: load item session: %w", err) + } + return sessionID, nil +} + +func markPurgeItemRunning(ctx context.Context, tx *sql.Tx, rebind func(string) string, task *PurgeItemTask, leaseUntil time.Time, leaseToken string, now time.Time) error { + result, err := tx.ExecContext(ctx, rebind(`UPDATE session_purge_items + SET status = ?, attempts = attempts + 1, lease_until = ?, lease_token = ?, + completed_at = NULL, error_code = '', updated_at = ? + WHERE id = ? AND kind = ? AND status IN (?, ?, ?) + AND (lease_until IS NULL OR lease_until <= ?)`), + string(PurgeItemRunning), leaseUntil, leaseToken, now, task.ID, + PurgeItemConversationContent, string(PurgeItemPending), string(PurgeItemRetrying), string(PurgeItemRunning), now) + if err != nil { + return fmt.Errorf("session purge: lease content item: %w", err) + } + if err := cleanupLeaseResult(result); err != nil { + return err + } + task.Attempts++ + task.LeaseUntil = &leaseUntil + task.LeaseToken = leaseToken + return refreshPurgeJobStatus(ctx, tx, rebind, task.SessionID, now) +} + +func (s *SQLiteStore) ClaimPurgeItems(ctx context.Context, now, leaseUntil time.Time, limit int) ([]PurgeItemTask, error) { + if limit <= 0 { + return nil, nil + } + tasks := make([]PurgeItemTask, 0, limit) + err := s.writeMu.WithLock(func() error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + rows, err := tx.QueryContext(ctx, `SELECT `+purgeItemColumns()+` + FROM session_purge_items + WHERE kind = ? AND status IN (?, ?, ?) + AND next_attempt_at <= ? AND (lease_until IS NULL OR lease_until <= ?) + ORDER BY next_attempt_at, created_at LIMIT ?`, + PurgeItemConversationContent, string(PurgeItemPending), string(PurgeItemRetrying), string(PurgeItemRunning), + now, now, limit) + if err != nil { + return fmt.Errorf("session purge: select due content items: %w", err) + } + for rows.Next() { + task, err := scanPurgeItemTask(rows) + if err != nil { + _ = rows.Close() + return fmt.Errorf("session purge: scan due content item: %w", err) + } + token := uuid.NewString() + if err := markPurgeItemRunning(ctx, tx, identityRebind, &task, leaseUntil, token, now); err != nil { + _ = rows.Close() + return err + } + tasks = append(tasks, task) + } + if err := rows.Err(); err != nil { + _ = rows.Close() + return fmt.Errorf("session purge: iterate due content items: %w", err) + } + if err := rows.Close(); err != nil { + return fmt.Errorf("session purge: close due content items: %w", err) + } + return tx.Commit() + }) + return tasks, err +} + +func (s *SQLiteStore) CompletePurgeItem(ctx context.Context, itemID, leaseToken string) error { + return s.writeMu.WithLock(func() error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + sessionID, err := purgeItemSessionID(ctx, tx, identityRebind, itemID, leaseToken) + if err != nil { + return err + } + now := time.Now() + result, err := tx.ExecContext(ctx, `UPDATE session_purge_items + SET status = ?, lease_until = NULL, lease_token = NULL, + completed_at = ?, error_code = '', updated_at = ? + WHERE id = ? AND kind = ? AND status = ? AND lease_token = ?`, + string(PurgeItemComplete), now, now, itemID, PurgeItemConversationContent, string(PurgeItemRunning), leaseToken) + if err != nil { + return fmt.Errorf("session purge: complete content item: %w", err) + } + if err := cleanupLeaseResult(result); err != nil { + return err + } + if err := refreshPurgeJobStatus(ctx, tx, identityRebind, sessionID, now); err != nil { + return err + } + return tx.Commit() + }) +} + +func (s *SQLiteStore) RetryPurgeItem(ctx context.Context, itemID, leaseToken string, nextAttemptAt time.Time, errorCode string) error { + return s.writeMu.WithLock(func() error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + sessionID, err := purgeItemSessionID(ctx, tx, identityRebind, itemID, leaseToken) + if err != nil { + return err + } + now := time.Now() + result, err := tx.ExecContext(ctx, `UPDATE session_purge_items + SET status = ?, next_attempt_at = ?, lease_until = NULL, lease_token = NULL, + completed_at = NULL, error_code = ?, updated_at = ? + WHERE id = ? AND kind = ? AND status = ? AND lease_token = ?`, + string(PurgeItemRetrying), nextAttemptAt, normalizeCleanupErrorCode(errorCode), now, + itemID, PurgeItemConversationContent, string(PurgeItemRunning), leaseToken) + if err != nil { + return fmt.Errorf("session purge: retry content item: %w", err) + } + if err := cleanupLeaseResult(result); err != nil { + return err + } + if err := refreshPurgeJobStatus(ctx, tx, identityRebind, sessionID, now); err != nil { + return err + } + return tx.Commit() + }) +} + +func (s *pgStore) ClaimPurgeItems(ctx context.Context, now, leaseUntil time.Time, limit int) ([]PurgeItemTask, error) { + if limit <= 0 { + return nil, nil + } + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + query := s.dialect.Rebind(`SELECT ` + purgeItemColumns() + ` + FROM session_purge_items + WHERE kind = ? AND status IN (?, ?, ?) + AND next_attempt_at <= ? AND (lease_until IS NULL OR lease_until <= ?) + ORDER BY next_attempt_at, created_at LIMIT ? FOR UPDATE SKIP LOCKED`) + rows, err := tx.QueryContext(ctx, query, + PurgeItemConversationContent, string(PurgeItemPending), string(PurgeItemRetrying), string(PurgeItemRunning), + now, now, limit) + if err != nil { + return nil, fmt.Errorf("session purge: select due content items: %w", err) + } + tasks := make([]PurgeItemTask, 0, limit) + for rows.Next() { + task, err := scanPurgeItemTask(rows) + if err != nil { + _ = rows.Close() + return nil, fmt.Errorf("session purge: scan due content item: %w", err) + } + token := uuid.NewString() + if err := markPurgeItemRunning(ctx, tx, s.dialect.Rebind, &task, leaseUntil, token, now); err != nil { + _ = rows.Close() + return nil, err + } + tasks = append(tasks, task) + } + if err := rows.Err(); err != nil { + _ = rows.Close() + return nil, fmt.Errorf("session purge: iterate due content items: %w", err) + } + if err := rows.Close(); err != nil { + return nil, fmt.Errorf("session purge: close due content items: %w", err) + } + return tasks, tx.Commit() +} + +func (s *pgStore) CompletePurgeItem(ctx context.Context, itemID, leaseToken string) error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + sessionID, err := purgeItemSessionID(ctx, tx, s.dialect.Rebind, itemID, leaseToken) + if err != nil { + return err + } + now := time.Now() + result, err := tx.ExecContext(ctx, s.dialect.Rebind(`UPDATE session_purge_items + SET status = ?, lease_until = NULL, lease_token = NULL, + completed_at = ?, error_code = '', updated_at = ? + WHERE id = ? AND kind = ? AND status = ? AND lease_token = ?`), + string(PurgeItemComplete), now, now, itemID, PurgeItemConversationContent, string(PurgeItemRunning), leaseToken) + if err != nil { + return fmt.Errorf("session purge: complete content item: %w", err) + } + if err := cleanupLeaseResult(result); err != nil { + return err + } + if err := refreshPurgeJobStatus(ctx, tx, s.dialect.Rebind, sessionID, now); err != nil { + return err + } + return tx.Commit() +} + +func (s *pgStore) RetryPurgeItem(ctx context.Context, itemID, leaseToken string, nextAttemptAt time.Time, errorCode string) error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + sessionID, err := purgeItemSessionID(ctx, tx, s.dialect.Rebind, itemID, leaseToken) + if err != nil { + return err + } + now := time.Now() + result, err := tx.ExecContext(ctx, s.dialect.Rebind(`UPDATE session_purge_items + SET status = ?, next_attempt_at = ?, lease_until = NULL, lease_token = NULL, + completed_at = NULL, error_code = ?, updated_at = ? + WHERE id = ? AND kind = ? AND status = ? AND lease_token = ?`), + string(PurgeItemRetrying), nextAttemptAt, normalizeCleanupErrorCode(errorCode), now, + itemID, PurgeItemConversationContent, string(PurgeItemRunning), leaseToken) + if err != nil { + return fmt.Errorf("session purge: retry content item: %w", err) + } + if err := cleanupLeaseResult(result); err != nil { + return err + } + if err := refreshPurgeJobStatus(ctx, tx, s.dialect.Rebind, sessionID, now); err != nil { + return err + } + return tx.Commit() +} diff --git a/internal/session/sql/migrate_pg_test.go b/internal/session/sql/migrate_pg_test.go index c96301f21..d1bd7b82b 100644 --- a/internal/session/sql/migrate_pg_test.go +++ b/internal/session/sql/migrate_pg_test.go @@ -44,16 +44,14 @@ func openTestPGDB(t *testing.T) *sql.DB { return db } -// TestMigrations_PG_023UserActivity_TriggerBlocksUpdate is the PostgreSQL -// counterpart to TestMigrations_023UserActivity_AppliesAndIsImmutable. It -// guards the audit system's core tamper-evidence invariant on PG: migration -// 023 creates a BEFORE UPDATE trigger that must reject every mutation of -// user_activity (review I4 — the PR shipped with zero PG migration tests). +// TestMigrations_PG_AuditChainsStayImmutable is the PostgreSQL counterpart +// to TestMigrations_AuditChainsApplyAndStayImmutable. It guards both audit +// epochs' tamper-evidence and checkpoint-anchored cleanup contracts. // // A semantic bug here (e.g. the trigger firing AFTER instead of BEFORE, or // the function existing but never bound to the table) would let UPDATEs // succeed silently and defeat the entire audit chain's tamper-evidence. -func TestMigrations_PG_023UserActivity_TriggerBlocksUpdate(t *testing.T) { +func TestMigrations_PG_AuditChainsStayImmutable(t *testing.T) { ctx := context.Background() db := openTestPGDB(t) defer func() { _ = db.Close() }() @@ -81,6 +79,24 @@ func TestMigrations_PG_023UserActivity_TriggerBlocksUpdate(t *testing.T) { // BEFORE UPDATE with no WHEN clause, so it fires unconditionally. _, err = db.ExecContext(ctx, `UPDATE user_activity SET ts = ts`) require.Error(t, err, "self-assign UPDATE must also be blocked") + + var v2ID int64 + err = db.QueryRowContext(ctx, `INSERT INTO user_activity_v2 + (ts, user_id, user_id_type, platform, action, outcome, detail_json, prev_hash, self_hash, expires_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + RETURNING id`, + 1700000000000, "u_v2", "system", "api", "auth.login", "success", "{}", "", "hash-v2", 1700000000000+180*24*60*60*1000). + Scan(&v2ID) + require.NoError(t, err, "insert into lifecycle-v2 audit chain") + _, err = db.ExecContext(ctx, `UPDATE user_activity_v2 SET expires_at=0 WHERE id=$1`, v2ID) + require.Error(t, err, "lifecycle-v2 audit updates must be rejected") + _, err = db.ExecContext(ctx, `DELETE FROM user_activity_v2 WHERE id=$1`, v2ID) + require.Error(t, err, "unanchored lifecycle-v2 audit deletes must be rejected") + _, err = db.ExecContext(ctx, `INSERT INTO audit_chain_checkpoints_v2 (pruned_at, last_self_hash, next_id) VALUES ($1, $2, $3)`, + time.Now().UnixMilli(), "hash-v2", v2ID+1) + require.NoError(t, err, "checkpoint must anchor the lifecycle-v2 prefix") + _, err = db.ExecContext(ctx, `DELETE FROM user_activity_v2 WHERE id=$1`, v2ID) + require.NoError(t, err, "checkpoint-anchored lifecycle-v2 cleanup must be allowed") } // TestMigrations_PG_030AuditNoDelete_BlocksUnauthorizedRowDeletes is the diff --git a/internal/session/sql/migrate_test.go b/internal/session/sql/migrate_test.go index 66ac53240..d20d4f50b 100644 --- a/internal/session/sql/migrate_test.go +++ b/internal/session/sql/migrate_test.go @@ -31,14 +31,14 @@ func openMigrationTestDB(t *testing.T) *sql.DB { return db } -// TestMigrations_023UserActivity_AppliesAndIsImmutable covers the user-facing contract -// of issue #833 Phase 1 migration 023 (Spec §5.1 / §5.5 / §11.2): +// TestMigrations_AuditChainsApplyAndStayImmutable covers the user-facing contract +// of audit migrations 023 and 044: // - user_activity table exists with the expected columns // - audit_chain_checkpoints table exists // - the BEFORE UPDATE trigger blocks mutations with the spec-mandated message // - all 3 expected indexes are present // - the 3 dead v_turns* views (spec §11.2) are dropped -func TestMigrations_023UserActivity_AppliesAndIsImmutable(t *testing.T) { +func TestMigrations_AuditChainsApplyAndStayImmutable(t *testing.T) { t.Parallel() ctx := context.Background() @@ -137,6 +137,53 @@ func TestMigrations_023UserActivity_AppliesAndIsImmutable(t *testing.T) { for col, found := range wantCP { require.True(t, found, "audit_chain_checkpoints missing column %s", col) } + + // 7) Migration 044 gives lifecycle-v2 its own chain and frozen, + // hash-covered expiry deadline without changing the legacy table. + for _, table := range []string{"user_activity_v2", "audit_chain_checkpoints_v2"} { + var n int + err := db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?`, table, + ).Scan(&n) + require.NoError(t, err) + require.Equal(t, 1, n, "expected table %s to exist after migration 044", table) + } + v2Columns := map[string]bool{} + v2Rows, err := db.QueryContext(ctx, `PRAGMA table_info(user_activity_v2)`) + require.NoError(t, err) + for v2Rows.Next() { + var cid int + var name, ctype string + var notnull, pk int + var dflt sql.NullString + require.NoError(t, v2Rows.Scan(&cid, &name, &ctype, ¬null, &dflt, &pk)) + v2Columns[name] = true + } + require.NoError(t, v2Rows.Err()) + require.NoError(t, v2Rows.Close()) + require.True(t, v2Columns["expires_at"], "lifecycle-v2 audit rows must persist their expiry") + var expiryIndex int + require.NoError(t, db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name='idx_ua_v2_expires_at' AND tbl_name='user_activity_v2'`, + ).Scan(&expiryIndex)) + require.Equal(t, 1, expiryIndex) + + result, err := db.ExecContext(ctx, `INSERT INTO user_activity_v2 + (ts, user_id, user_id_type, platform, action, outcome, detail_json, prev_hash, self_hash, expires_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + 1700000000000, "u_v2", "system", "api", "auth.login", "success", "{}", "", "hash-v2", 1700000000000+180*24*60*60*1000) + require.NoError(t, err, "insert into lifecycle-v2 audit chain") + v2ID, err := result.LastInsertId() + require.NoError(t, err) + _, err = db.ExecContext(ctx, `UPDATE user_activity_v2 SET expires_at=0 WHERE id=?`, v2ID) + require.Error(t, err, "lifecycle-v2 audit updates must be rejected") + _, err = db.ExecContext(ctx, `DELETE FROM user_activity_v2 WHERE id=?`, v2ID) + require.Error(t, err, "unanchored lifecycle-v2 audit deletes must be rejected") + _, err = db.ExecContext(ctx, `INSERT INTO audit_chain_checkpoints_v2 (pruned_at, last_self_hash, next_id) VALUES (?, ?, ?)`, + time.Now().UnixMilli(), "hash-v2", v2ID+1) + require.NoError(t, err, "checkpoint must anchor the lifecycle-v2 prefix") + _, err = db.ExecContext(ctx, `DELETE FROM user_activity_v2 WHERE id=?`, v2ID) + require.NoError(t, err, "checkpoint-anchored lifecycle-v2 cleanup must be allowed") } // TestMigrations_027_ExecutionOwnerLease_SchemaAndConstraints covers the durable diff --git a/internal/session/sql/migrations-postgres/040_session_lifecycle.pg.sql b/internal/session/sql/migrations-postgres/040_session_lifecycle.pg.sql new file mode 100644 index 000000000..23266efd8 --- /dev/null +++ b/internal/session/sql/migrations-postgres/040_session_lifecycle.pg.sql @@ -0,0 +1,35 @@ +-- +goose Up +-- Separate logical-history clocks from Worker runtime expiry and reserve +-- immutable per-turn deadline fields. Existing rows keep legacy behavior. +ALTER TABLE "sessions" ADD COLUMN "lifecycle_policy" TEXT NOT NULL DEFAULT 'legacy'; +ALTER TABLE "sessions" ADD COLUMN "lifecycle_policy_revision" TEXT NOT NULL DEFAULT ''; +ALTER TABLE "sessions" ADD COLUMN "last_input_at" TIMESTAMPTZ; +ALTER TABLE "sessions" ADD COLUMN "runtime_finished_at" TIMESTAMPTZ; +ALTER TABLE "sessions" ADD COLUMN "archive_at" TIMESTAMPTZ; +ALTER TABLE "sessions" ADD COLUMN "conversation_expires_at" TIMESTAMPTZ; +ALTER TABLE "sessions" ADD COLUMN "last_content_expires_at" TIMESTAMPTZ; +ALTER TABLE "sessions" ADD COLUMN "history_expires_at" TIMESTAMPTZ; +ALTER TABLE "sessions" ADD COLUMN "deleted_at" TIMESTAMPTZ; + +ALTER TABLE "execution_inputs" ADD COLUMN "turn_started_at" BIGINT; +ALTER TABLE "execution_inputs" ADD COLUMN "turn_deadline_at" BIGINT; +ALTER TABLE "execution_inputs" ADD COLUMN "turn_policy_revision" TEXT NOT NULL DEFAULT ''; + +CREATE INDEX "idx_sessions_lifecycle_archive" ON "sessions"("lifecycle_policy", "archive_at"); +CREATE INDEX "idx_sessions_lifecycle_history" ON "sessions"("lifecycle_policy", "history_expires_at"); + +-- +goose Down +DROP INDEX IF EXISTS "idx_sessions_lifecycle_history"; +DROP INDEX IF EXISTS "idx_sessions_lifecycle_archive"; +ALTER TABLE "execution_inputs" DROP COLUMN "turn_policy_revision"; +ALTER TABLE "execution_inputs" DROP COLUMN "turn_deadline_at"; +ALTER TABLE "execution_inputs" DROP COLUMN "turn_started_at"; +ALTER TABLE "sessions" DROP COLUMN "deleted_at"; +ALTER TABLE "sessions" DROP COLUMN "history_expires_at"; +ALTER TABLE "sessions" DROP COLUMN "last_content_expires_at"; +ALTER TABLE "sessions" DROP COLUMN "conversation_expires_at"; +ALTER TABLE "sessions" DROP COLUMN "archive_at"; +ALTER TABLE "sessions" DROP COLUMN "runtime_finished_at"; +ALTER TABLE "sessions" DROP COLUMN "last_input_at"; +ALTER TABLE "sessions" DROP COLUMN "lifecycle_policy_revision"; +ALTER TABLE "sessions" DROP COLUMN "lifecycle_policy"; diff --git a/internal/session/sql/migrations-postgres/041_content_expiry.pg.sql b/internal/session/sql/migrations-postgres/041_content_expiry.pg.sql new file mode 100644 index 000000000..80daf51ce --- /dev/null +++ b/internal/session/sql/migrations-postgres/041_content_expiry.pg.sql @@ -0,0 +1,14 @@ +-- +goose Up +-- Assign immutable per-record deadlines to newly written conversation content. +-- Existing rows receive zero and continue to use the legacy events.retention window. +ALTER TABLE "events" ADD COLUMN "expires_at" BIGINT NOT NULL DEFAULT 0; +ALTER TABLE "turns" ADD COLUMN "expires_at" BIGINT NOT NULL DEFAULT 0; + +CREATE INDEX IF NOT EXISTS "idx_events_expires_at" ON "events"("expires_at"); +CREATE INDEX IF NOT EXISTS "idx_turns_expires_at" ON "turns"("expires_at"); + +-- +goose Down +DROP INDEX IF EXISTS "idx_turns_expires_at"; +DROP INDEX IF EXISTS "idx_events_expires_at"; +ALTER TABLE "turns" DROP COLUMN "expires_at"; +ALTER TABLE "events" DROP COLUMN "expires_at"; diff --git a/internal/session/sql/migrations-postgres/042_session_purge_jobs.pg.sql b/internal/session/sql/migrations-postgres/042_session_purge_jobs.pg.sql new file mode 100644 index 000000000..df46696fb --- /dev/null +++ b/internal/session/sql/migrations-postgres/042_session_purge_jobs.pg.sql @@ -0,0 +1,44 @@ +-- +goose Up +CREATE TABLE session_purge_jobs ( + id TEXT PRIMARY KEY, + session_id TEXT NOT NULL UNIQUE, + user_id TEXT NOT NULL, + owner_id TEXT NOT NULL DEFAULT '', + workspace_id TEXT NOT NULL DEFAULT '', + policy_revision TEXT NOT NULL DEFAULT 'legacy', + status TEXT NOT NULL CHECK(status IN ('pending', 'in_progress', 'blocked', 'complete')), + requested_at TIMESTAMPTZ NOT NULL, + completed_at TIMESTAMPTZ NULL, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL +); +CREATE INDEX idx_session_purge_jobs_user + ON session_purge_jobs(user_id, requested_at); + +CREATE TABLE session_purge_items ( + id TEXT PRIMARY KEY, + job_id TEXT NOT NULL, + session_id TEXT NOT NULL, + kind TEXT NOT NULL, + status TEXT NOT NULL CHECK(status IN ('pending', 'running', 'retrying', 'blocked', 'unsupported', 'complete')), + attempts INTEGER NOT NULL DEFAULT 0, + next_attempt_at TIMESTAMPTZ NOT NULL, + lease_until TIMESTAMPTZ NULL, + lease_token TEXT NULL, + completed_at TIMESTAMPTZ NULL, + error_code TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + UNIQUE(job_id, kind) +); +CREATE INDEX idx_session_purge_items_due + ON session_purge_items(kind, status, next_attempt_at, lease_until); +CREATE INDEX idx_session_purge_items_session + ON session_purge_items(session_id, kind); + +-- +goose Down +DROP INDEX IF EXISTS idx_session_purge_items_session; +DROP INDEX IF EXISTS idx_session_purge_items_due; +DROP TABLE IF EXISTS session_purge_items; +DROP INDEX IF EXISTS idx_session_purge_jobs_user; +DROP TABLE IF EXISTS session_purge_jobs; diff --git a/internal/session/sql/migrations-postgres/043_effect_payload_retention.pg.sql b/internal/session/sql/migrations-postgres/043_effect_payload_retention.pg.sql new file mode 100644 index 000000000..5f6186184 --- /dev/null +++ b/internal/session/sql/migrations-postgres/043_effect_payload_retention.pg.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- Track when an external-delivery obligation reaches a terminal state. The +-- nullable value intentionally leaves legacy and unresolved effects protected. +ALTER TABLE "effects" ADD COLUMN "settled_at" BIGINT; +CREATE INDEX "idx_effects_payload_settled" ON "effects"("payload_id", "settled_at"); + +-- +goose Down +DROP INDEX IF EXISTS "idx_effects_payload_settled"; +ALTER TABLE "effects" DROP COLUMN "settled_at"; diff --git a/internal/session/sql/migrations-postgres/044_audit_lifecycle_v2.pg.sql b/internal/session/sql/migrations-postgres/044_audit_lifecycle_v2.pg.sql new file mode 100644 index 000000000..1db91dde7 --- /dev/null +++ b/internal/session/sql/migrations-postgres/044_audit_lifecycle_v2.pg.sql @@ -0,0 +1,70 @@ +-- +goose Up +-- Lifecycle v2 starts a separate content-minimized audit chain so its +-- configurable retention cannot shorten the legacy user_activity chain. + +CREATE TABLE "user_activity_v2" ( + "id" BIGSERIAL PRIMARY KEY, + "ts" BIGINT NOT NULL, + "user_id" TEXT NOT NULL, + "user_id_type" TEXT NOT NULL, + "platform" TEXT NOT NULL, + "session_id" TEXT, + "action" TEXT NOT NULL, + "resource_type" TEXT, + "resource_id" TEXT, + "outcome" TEXT NOT NULL, + "detail_json" TEXT NOT NULL, + "event_ref" TEXT, + "ip" TEXT, + "user_agent" TEXT, + "prev_hash" TEXT NOT NULL, + "self_hash" TEXT NOT NULL, + "expires_at" BIGINT NOT NULL +); + +CREATE INDEX "idx_ua_v2_user_ts" ON "user_activity_v2"("user_id", "ts"); +CREATE INDEX "idx_ua_v2_ts" ON "user_activity_v2"("ts"); +CREATE INDEX "idx_ua_v2_action_ts" ON "user_activity_v2"("action", "ts"); +CREATE INDEX "idx_ua_v2_expires_at" ON "user_activity_v2"("expires_at"); + +CREATE TABLE "audit_chain_checkpoints_v2" ( + "id" BIGSERIAL PRIMARY KEY, + "pruned_at" BIGINT NOT NULL, + "last_self_hash" TEXT NOT NULL, + "next_id" BIGINT NOT NULL +); + +-- +goose StatementBegin +CREATE OR REPLACE FUNCTION "fn_ua_v2_no_update"() RETURNS TRIGGER AS $$ +BEGIN + RAISE EXCEPTION 'audit: rows are immutable'; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION "fn_ua_v2_no_delete"() RETURNS TRIGGER AS $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM "audit_chain_checkpoints_v2" WHERE "next_id" > OLD."id" + ) THEN + RAISE EXCEPTION 'audit: rows are immutable except via checkpoint-anchored GC'; + END IF; + RETURN OLD; +END; +$$ LANGUAGE plpgsql; +-- +goose StatementEnd + +CREATE TRIGGER "trg_ua_v2_no_update" +BEFORE UPDATE ON "user_activity_v2" +FOR EACH ROW EXECUTE FUNCTION "fn_ua_v2_no_update"(); + +CREATE TRIGGER "trg_ua_v2_no_delete" +BEFORE DELETE ON "user_activity_v2" +FOR EACH ROW EXECUTE FUNCTION "fn_ua_v2_no_delete"(); + +-- +goose Down +DROP TRIGGER IF EXISTS "trg_ua_v2_no_delete" ON "user_activity_v2"; +DROP TRIGGER IF EXISTS "trg_ua_v2_no_update" ON "user_activity_v2"; +DROP FUNCTION IF EXISTS "fn_ua_v2_no_delete"(); +DROP FUNCTION IF EXISTS "fn_ua_v2_no_update"(); +DROP TABLE IF EXISTS "user_activity_v2"; +DROP TABLE IF EXISTS "audit_chain_checkpoints_v2"; diff --git a/internal/session/sql/migrations/040_session_lifecycle.sql b/internal/session/sql/migrations/040_session_lifecycle.sql new file mode 100644 index 000000000..6b9344d36 --- /dev/null +++ b/internal/session/sql/migrations/040_session_lifecycle.sql @@ -0,0 +1,35 @@ +-- +goose Up +-- Separate logical-history clocks from Worker runtime expiry and reserve +-- immutable per-turn deadline fields. Existing rows keep legacy behavior. +ALTER TABLE sessions ADD COLUMN lifecycle_policy TEXT NOT NULL DEFAULT 'legacy'; +ALTER TABLE sessions ADD COLUMN lifecycle_policy_revision TEXT NOT NULL DEFAULT ''; +ALTER TABLE sessions ADD COLUMN last_input_at DATETIME; +ALTER TABLE sessions ADD COLUMN runtime_finished_at DATETIME; +ALTER TABLE sessions ADD COLUMN archive_at DATETIME; +ALTER TABLE sessions ADD COLUMN conversation_expires_at DATETIME; +ALTER TABLE sessions ADD COLUMN last_content_expires_at DATETIME; +ALTER TABLE sessions ADD COLUMN history_expires_at DATETIME; +ALTER TABLE sessions ADD COLUMN deleted_at DATETIME; + +ALTER TABLE execution_inputs ADD COLUMN turn_started_at INTEGER; +ALTER TABLE execution_inputs ADD COLUMN turn_deadline_at INTEGER; +ALTER TABLE execution_inputs ADD COLUMN turn_policy_revision TEXT NOT NULL DEFAULT ''; + +CREATE INDEX idx_sessions_lifecycle_archive ON sessions(lifecycle_policy, archive_at); +CREATE INDEX idx_sessions_lifecycle_history ON sessions(lifecycle_policy, history_expires_at); + +-- +goose Down +DROP INDEX IF EXISTS idx_sessions_lifecycle_history; +DROP INDEX IF EXISTS idx_sessions_lifecycle_archive; +ALTER TABLE execution_inputs DROP COLUMN turn_policy_revision; +ALTER TABLE execution_inputs DROP COLUMN turn_deadline_at; +ALTER TABLE execution_inputs DROP COLUMN turn_started_at; +ALTER TABLE sessions DROP COLUMN deleted_at; +ALTER TABLE sessions DROP COLUMN history_expires_at; +ALTER TABLE sessions DROP COLUMN last_content_expires_at; +ALTER TABLE sessions DROP COLUMN conversation_expires_at; +ALTER TABLE sessions DROP COLUMN archive_at; +ALTER TABLE sessions DROP COLUMN runtime_finished_at; +ALTER TABLE sessions DROP COLUMN last_input_at; +ALTER TABLE sessions DROP COLUMN lifecycle_policy_revision; +ALTER TABLE sessions DROP COLUMN lifecycle_policy; diff --git a/internal/session/sql/migrations/041_content_expiry.sql b/internal/session/sql/migrations/041_content_expiry.sql new file mode 100644 index 000000000..1d30dfb05 --- /dev/null +++ b/internal/session/sql/migrations/041_content_expiry.sql @@ -0,0 +1,14 @@ +-- +goose Up +-- Assign immutable per-record deadlines to newly written conversation content. +-- Existing rows receive zero and continue to use the legacy events.retention window. +ALTER TABLE events ADD COLUMN expires_at INTEGER NOT NULL DEFAULT 0; +ALTER TABLE turns ADD COLUMN expires_at INTEGER NOT NULL DEFAULT 0; + +CREATE INDEX idx_events_expires_at ON events(expires_at); +CREATE INDEX idx_turns_expires_at ON turns(expires_at); + +-- +goose Down +DROP INDEX IF EXISTS idx_turns_expires_at; +DROP INDEX IF EXISTS idx_events_expires_at; +ALTER TABLE turns DROP COLUMN expires_at; +ALTER TABLE events DROP COLUMN expires_at; diff --git a/internal/session/sql/migrations/042_session_purge_jobs.sql b/internal/session/sql/migrations/042_session_purge_jobs.sql new file mode 100644 index 000000000..c6faf8448 --- /dev/null +++ b/internal/session/sql/migrations/042_session_purge_jobs.sql @@ -0,0 +1,44 @@ +-- +goose Up +CREATE TABLE session_purge_jobs ( + id TEXT PRIMARY KEY, + session_id TEXT NOT NULL UNIQUE, + user_id TEXT NOT NULL, + owner_id TEXT NOT NULL DEFAULT '', + workspace_id TEXT NOT NULL DEFAULT '', + policy_revision TEXT NOT NULL DEFAULT 'legacy', + status TEXT NOT NULL CHECK(status IN ('pending', 'in_progress', 'blocked', 'complete')), + requested_at TIMESTAMP NOT NULL, + completed_at TIMESTAMP NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL +); +CREATE INDEX idx_session_purge_jobs_user + ON session_purge_jobs(user_id, requested_at); + +CREATE TABLE session_purge_items ( + id TEXT PRIMARY KEY, + job_id TEXT NOT NULL, + session_id TEXT NOT NULL, + kind TEXT NOT NULL, + status TEXT NOT NULL CHECK(status IN ('pending', 'running', 'retrying', 'blocked', 'unsupported', 'complete')), + attempts INTEGER NOT NULL DEFAULT 0, + next_attempt_at TIMESTAMP NOT NULL, + lease_until TIMESTAMP NULL, + lease_token TEXT NULL, + completed_at TIMESTAMP NULL, + error_code TEXT NOT NULL DEFAULT '', + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + UNIQUE(job_id, kind) +); +CREATE INDEX idx_session_purge_items_due + ON session_purge_items(kind, status, next_attempt_at, lease_until); +CREATE INDEX idx_session_purge_items_session + ON session_purge_items(session_id, kind); + +-- +goose Down +DROP INDEX IF EXISTS idx_session_purge_items_session; +DROP INDEX IF EXISTS idx_session_purge_items_due; +DROP TABLE IF EXISTS session_purge_items; +DROP INDEX IF EXISTS idx_session_purge_jobs_user; +DROP TABLE IF EXISTS session_purge_jobs; diff --git a/internal/session/sql/migrations/043_effect_payload_retention.sql b/internal/session/sql/migrations/043_effect_payload_retention.sql new file mode 100644 index 000000000..578da2c98 --- /dev/null +++ b/internal/session/sql/migrations/043_effect_payload_retention.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- Track when an external-delivery obligation reaches a terminal state. The +-- nullable value intentionally leaves legacy and unresolved effects protected. +ALTER TABLE effects ADD COLUMN settled_at INTEGER; +CREATE INDEX idx_effects_payload_settled ON effects(payload_id, settled_at); + +-- +goose Down +DROP INDEX IF EXISTS idx_effects_payload_settled; +ALTER TABLE effects DROP COLUMN settled_at; diff --git a/internal/session/sql/migrations/044_audit_lifecycle_v2.sql b/internal/session/sql/migrations/044_audit_lifecycle_v2.sql new file mode 100644 index 000000000..c4efe5a49 --- /dev/null +++ b/internal/session/sql/migrations/044_audit_lifecycle_v2.sql @@ -0,0 +1,59 @@ +-- +goose Up +-- Lifecycle v2 starts a separate content-minimized audit chain so its +-- configurable retention cannot shorten the legacy user_activity chain. + +CREATE TABLE user_activity_v2 ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ts INTEGER NOT NULL, + user_id TEXT NOT NULL, + user_id_type TEXT NOT NULL, + platform TEXT NOT NULL, + session_id TEXT, + action TEXT NOT NULL, + resource_type TEXT, + resource_id TEXT, + outcome TEXT NOT NULL, + detail_json TEXT NOT NULL, + event_ref TEXT, + ip TEXT, + user_agent TEXT, + prev_hash TEXT NOT NULL, + self_hash TEXT NOT NULL, + expires_at INTEGER NOT NULL +); + +CREATE INDEX idx_ua_v2_user_ts ON user_activity_v2(user_id, ts); +CREATE INDEX idx_ua_v2_ts ON user_activity_v2(ts); +CREATE INDEX idx_ua_v2_action_ts ON user_activity_v2(action, ts); +CREATE INDEX idx_ua_v2_expires_at ON user_activity_v2(expires_at); + +CREATE TABLE audit_chain_checkpoints_v2 ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pruned_at INTEGER NOT NULL, + last_self_hash TEXT NOT NULL, + next_id INTEGER NOT NULL +); + +-- +goose StatementBegin +CREATE TRIGGER trg_ua_v2_no_update +BEFORE UPDATE ON user_activity_v2 +BEGIN + SELECT RAISE(ABORT, 'audit: rows are immutable'); +END; + +CREATE TRIGGER trg_ua_v2_no_delete +BEFORE DELETE ON user_activity_v2 +FOR EACH ROW +BEGIN + SELECT RAISE(ABORT, 'audit: rows are immutable except via checkpoint-anchored GC') + WHERE NOT EXISTS ( + SELECT 1 FROM audit_chain_checkpoints_v2 WHERE next_id > OLD.id + ); +END; +-- +goose StatementEnd + +-- +goose Down +DROP TRIGGER IF EXISTS trg_ua_v2_no_delete; +DROP TRIGGER IF EXISTS trg_ua_v2_no_update; +DROP TABLE IF EXISTS user_activity_v2; +DROP TABLE IF EXISTS audit_chain_checkpoints_v2; diff --git a/internal/session/sql/queries/sessions.advance_lifecycle_deadlines.sql b/internal/session/sql/queries/sessions.advance_lifecycle_deadlines.sql new file mode 100644 index 000000000..c1731acb2 --- /dev/null +++ b/internal/session/sql/queries/sessions.advance_lifecycle_deadlines.sql @@ -0,0 +1,12 @@ +UPDATE sessions +SET + last_input_at = CASE WHEN last_input_at IS NULL OR last_input_at < ? THEN ? ELSE last_input_at END, + archive_at = CASE WHEN archive_at IS NULL OR archive_at < ? THEN ? ELSE archive_at END, + conversation_expires_at = CASE WHEN conversation_expires_at IS NULL OR conversation_expires_at < ? THEN ? ELSE conversation_expires_at END, + history_expires_at = CASE WHEN history_expires_at IS NULL OR history_expires_at < ? THEN ? ELSE history_expires_at END, + updated_at = CASE WHEN updated_at < ? THEN ? ELSE updated_at END +WHERE id = ? + AND lifecycle_policy = ? + AND lifecycle_policy_revision = ? + AND deleted_at IS NULL + AND state <> 'deleted'; diff --git a/internal/session/sql/queries/sessions.upsert_session.sql b/internal/session/sql/queries/sessions.upsert_session.sql index ae819c6bc..d40f470ad 100644 --- a/internal/session/sql/queries/sessions.upsert_session.sql +++ b/internal/session/sql/queries/sessions.upsert_session.sql @@ -7,8 +7,8 @@ -- (e.g. heartbeat), silently breaking the transitionState guard + safety-net -- two-layer persistence design. -- See also: manager.go transitionState guard (WorkerSessionID empty→nonempty). -INSERT INTO sessions (id, user_id, owner_id, bot_id, bot_name, worker_session_id, worker_type, state, platform, platform_key_json, work_dir, title, created_at, updated_at, expires_at, idle_expires_at, context_json, source, client_key, workspace_id) - SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? +INSERT INTO sessions (id, user_id, owner_id, bot_id, bot_name, worker_session_id, worker_type, state, platform, platform_key_json, work_dir, title, created_at, updated_at, expires_at, idle_expires_at, context_json, source, client_key, workspace_id, lifecycle_policy, lifecycle_policy_revision, last_input_at, runtime_finished_at, archive_at, conversation_expires_at, last_content_expires_at, history_expires_at, deleted_at) + SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM session_cleanup_tasks WHERE session_id = ?) ON CONFLICT(id) DO UPDATE SET state=excluded.state, @@ -22,5 +22,7 @@ INSERT INTO sessions (id, user_id, owner_id, bot_id, bot_name, worker_session_id source=CASE WHEN excluded.source != '' THEN excluded.source ELSE sessions.source END, client_key=CASE WHEN excluded.client_key != '' THEN excluded.client_key ELSE sessions.client_key END, -- workspace_id 创建后不可变:仅当原值为 NULL 或空串(未绑定)时接受,防后续 upsert 覆盖。 - workspace_id=CASE WHEN sessions.workspace_id IS NULL OR sessions.workspace_id = '' THEN excluded.workspace_id ELSE sessions.workspace_id END - WHERE NOT EXISTS (SELECT 1 FROM session_cleanup_tasks WHERE session_id = excluded.id); + workspace_id=CASE WHEN sessions.workspace_id IS NULL OR sessions.workspace_id = '' THEN excluded.workspace_id ELSE sessions.workspace_id END + WHERE sessions.deleted_at IS NULL + AND sessions.state != 'deleted' + AND NOT EXISTS (SELECT 1 FROM session_cleanup_tasks WHERE session_id = excluded.id); diff --git a/internal/session/sql/queries/store.delete_terminated_by_id.sql b/internal/session/sql/queries/store.delete_terminated_by_id.sql index fa436a9d6..d7e9c6b5d 100644 --- a/internal/session/sql/queries/store.delete_terminated_by_id.sql +++ b/internal/session/sql/queries/store.delete_terminated_by_id.sql @@ -1,9 +1,27 @@ -DELETE FROM sessions WHERE id = ? AND state = ? AND ( +DELETE FROM sessions WHERE id = ? AND state = ? AND COALESCE(lifecycle_policy, 'legacy') <> 'v2' AND ( (source = 'cron' AND updated_at <= ?) OR (source != 'cron' AND updated_at <= ?) ) +AND NOT EXISTS ( + SELECT 1 FROM execution_inputs e + WHERE e.session_id = sessions.id + AND ( + e.status IN ('accepted', 'unknown') + OR e.runtime_status IN ('queued', 'pending', 'running', 'unknown') + OR e.fence_reason <> '' + ) +) +AND NOT EXISTS ( + SELECT 1 FROM execution_queue q WHERE q.session_id = sessions.id +) +AND NOT EXISTS ( + SELECT 1 FROM session_cleanup_tasks c WHERE c.session_id = sessions.id +) RETURNING id, user_id, COALESCE(owner_id, user_id), COALESCE(worker_session_id, ''), worker_type, state, COALESCE(bot_id, ''), COALESCE(bot_name, ''), platform, platform_key_json, COALESCE(work_dir, ''), COALESCE(title, ''), created_at, updated_at, expires_at, idle_expires_at, context_json, source, COALESCE(client_key, ''), - COALESCE(workspace_id, ''), COALESCE(permission_ceiling, ''); + COALESCE(workspace_id, ''), COALESCE(permission_ceiling, ''), + lifecycle_policy, lifecycle_policy_revision, last_input_at, runtime_finished_at, + archive_at, conversation_expires_at, last_content_expires_at, history_expires_at, + deleted_at; diff --git a/internal/session/sql/queries/store.get_session.sql b/internal/session/sql/queries/store.get_session.sql index 64b903887..dd36921dd 100644 --- a/internal/session/sql/queries/store.get_session.sql +++ b/internal/session/sql/queries/store.get_session.sql @@ -1,3 +1,3 @@ -- get_session loads a session by ID. -SELECT id, user_id, COALESCE(owner_id, user_id), COALESCE(worker_session_id, ''), worker_type, state, COALESCE(bot_id, ''), COALESCE(bot_name, ''), platform, platform_key_json, COALESCE(work_dir, ''), COALESCE(title, ''), created_at, updated_at, expires_at, idle_expires_at, context_json, source, COALESCE(client_key, ''), COALESCE(workspace_id, ''), COALESCE(permission_ceiling, '') +SELECT id, user_id, COALESCE(owner_id, user_id), COALESCE(worker_session_id, ''), worker_type, state, COALESCE(bot_id, ''), COALESCE(bot_name, ''), platform, platform_key_json, COALESCE(work_dir, ''), COALESCE(title, ''), created_at, updated_at, expires_at, idle_expires_at, context_json, source, COALESCE(client_key, ''), COALESCE(workspace_id, ''), COALESCE(permission_ceiling, ''), lifecycle_policy, lifecycle_policy_revision, last_input_at, runtime_finished_at, archive_at, conversation_expires_at, last_content_expires_at, history_expires_at, deleted_at FROM sessions WHERE id = ?; diff --git a/internal/session/sql/queries/store.lifecycle_gc_status.sql b/internal/session/sql/queries/store.lifecycle_gc_status.sql new file mode 100644 index 000000000..6feedd223 --- /dev/null +++ b/internal/session/sql/queries/store.lifecycle_gc_status.sql @@ -0,0 +1,51 @@ +SELECT + COALESCE(SUM(CASE WHEN overdue.is_blocked = 0 THEN 1 ELSE 0 END), 0), + COALESCE(SUM(CASE WHEN overdue.is_blocked = 1 THEN 1 ELSE 0 END), 0), + COALESCE(SUM(CASE WHEN overdue.has_unknown_execution = 1 THEN 1 ELSE 0 END), 0), + MIN(CASE WHEN overdue.is_blocked = 0 THEN overdue.required_deadline END), + MIN(CASE WHEN overdue.is_blocked = 1 THEN overdue.required_deadline END) +FROM ( + SELECT + MAX( + s.conversation_expires_at, + COALESCE(s.last_content_expires_at, s.conversation_expires_at), + s.history_expires_at + ) AS required_deadline, + CASE WHEN s.state = 'running' + OR EXISTS ( + SELECT 1 FROM execution_inputs e + WHERE e.session_id = s.id + AND ( + e.status IN ('accepted', 'unknown') + OR e.runtime_status IN ('queued', 'pending', 'running', 'unknown') + OR e.fence_reason <> '' + ) + ) + OR EXISTS ( + SELECT 1 FROM execution_queue q + WHERE q.session_id = s.id + ) + OR EXISTS ( + SELECT 1 FROM session_cleanup_tasks c + WHERE c.session_id = s.id + ) + THEN 1 ELSE 0 END AS is_blocked, + CASE WHEN EXISTS ( + SELECT 1 FROM execution_inputs e + WHERE e.session_id = s.id + AND ( + e.status = 'unknown' + OR e.runtime_status = 'unknown' + OR e.fence_reason <> '' + ) + ) THEN 1 ELSE 0 END AS has_unknown_execution + FROM sessions s + WHERE s.lifecycle_policy = 'v2' + AND s.deleted_at IS NULL + AND s.state <> 'deleted' + AND s.conversation_expires_at IS NOT NULL + AND s.conversation_expires_at <= ? + AND (s.last_content_expires_at IS NULL OR s.last_content_expires_at <= ?) + AND s.history_expires_at IS NOT NULL + AND s.history_expires_at <= ? +) AS overdue; diff --git a/internal/session/sql/queries/store.lifecycle_gc_status_pg.sql b/internal/session/sql/queries/store.lifecycle_gc_status_pg.sql new file mode 100644 index 000000000..902d4ffdd --- /dev/null +++ b/internal/session/sql/queries/store.lifecycle_gc_status_pg.sql @@ -0,0 +1,51 @@ +SELECT + COALESCE(SUM(CASE WHEN overdue.is_blocked = 0 THEN 1 ELSE 0 END), 0), + COALESCE(SUM(CASE WHEN overdue.is_blocked = 1 THEN 1 ELSE 0 END), 0), + COALESCE(SUM(CASE WHEN overdue.has_unknown_execution = 1 THEN 1 ELSE 0 END), 0), + MIN(CASE WHEN overdue.is_blocked = 0 THEN overdue.required_deadline END), + MIN(CASE WHEN overdue.is_blocked = 1 THEN overdue.required_deadline END) +FROM ( + SELECT + GREATEST( + s.conversation_expires_at, + COALESCE(s.last_content_expires_at, s.conversation_expires_at), + s.history_expires_at + ) AS required_deadline, + CASE WHEN s.state = 'running' + OR EXISTS ( + SELECT 1 FROM execution_inputs e + WHERE e.session_id = s.id + AND ( + e.status IN ('accepted', 'unknown') + OR e.runtime_status IN ('queued', 'pending', 'running', 'unknown') + OR e.fence_reason <> '' + ) + ) + OR EXISTS ( + SELECT 1 FROM execution_queue q + WHERE q.session_id = s.id + ) + OR EXISTS ( + SELECT 1 FROM session_cleanup_tasks c + WHERE c.session_id = s.id + ) + THEN 1 ELSE 0 END AS is_blocked, + CASE WHEN EXISTS ( + SELECT 1 FROM execution_inputs e + WHERE e.session_id = s.id + AND ( + e.status = 'unknown' + OR e.runtime_status = 'unknown' + OR e.fence_reason <> '' + ) + ) THEN 1 ELSE 0 END AS has_unknown_execution + FROM sessions s + WHERE s.lifecycle_policy = 'v2' + AND s.deleted_at IS NULL + AND s.state <> 'deleted' + AND s.conversation_expires_at IS NOT NULL + AND s.conversation_expires_at <= $1 + AND (s.last_content_expires_at IS NULL OR s.last_content_expires_at <= $2) + AND s.history_expires_at IS NOT NULL + AND s.history_expires_at <= $3 +) AS overdue; diff --git a/internal/session/sql/queries/store.list_sessions.sql b/internal/session/sql/queries/store.list_sessions.sql index bdcc47157..64449c423 100644 --- a/internal/session/sql/queries/store.list_sessions.sql +++ b/internal/session/sql/queries/store.list_sessions.sql @@ -1,6 +1,6 @@ -- list_sessions lists sessions with pagination, excluding soft-deleted. -- Filters by user_id and platform if provided. -SELECT id, user_id, COALESCE(owner_id, user_id), COALESCE(worker_session_id, ''), worker_type, state, COALESCE(bot_id, ''), COALESCE(bot_name, ''), platform, platform_key_json, COALESCE(work_dir, ''), COALESCE(title, ''), created_at, updated_at, expires_at, idle_expires_at, context_json, source, COALESCE(client_key, ''), COALESCE(workspace_id, ''), COALESCE(permission_ceiling, '') +SELECT id, user_id, COALESCE(owner_id, user_id), COALESCE(worker_session_id, ''), worker_type, state, COALESCE(bot_id, ''), COALESCE(bot_name, ''), platform, platform_key_json, COALESCE(work_dir, ''), COALESCE(title, ''), created_at, updated_at, expires_at, idle_expires_at, context_json, source, COALESCE(client_key, ''), COALESCE(workspace_id, ''), COALESCE(permission_ceiling, ''), lifecycle_policy, lifecycle_policy_revision, last_input_at, runtime_finished_at, archive_at, conversation_expires_at, last_content_expires_at, history_expires_at, deleted_at FROM sessions WHERE state != 'deleted' AND (? = '' OR user_id = ?) diff --git a/internal/session/sql/queries/store.select_expired_lifecycle_ids.sql b/internal/session/sql/queries/store.select_expired_lifecycle_ids.sql new file mode 100644 index 000000000..d281edb46 --- /dev/null +++ b/internal/session/sql/queries/store.select_expired_lifecycle_ids.sql @@ -0,0 +1,27 @@ +SELECT s.id +FROM sessions s +WHERE s.lifecycle_policy = 'v2' + AND s.deleted_at IS NULL + AND s.state NOT IN ('running', 'deleted') + AND s.conversation_expires_at IS NOT NULL + AND s.conversation_expires_at <= ? + AND (s.last_content_expires_at IS NULL OR s.last_content_expires_at <= ?) + AND s.history_expires_at IS NOT NULL + AND s.history_expires_at <= ? + AND NOT EXISTS ( + SELECT 1 FROM execution_inputs e + WHERE e.session_id = s.id + AND ( + e.status IN ('accepted', 'unknown') + OR e.runtime_status IN ('queued', 'pending', 'running', 'unknown') + OR e.fence_reason <> '' + ) + ) + AND NOT EXISTS ( + SELECT 1 FROM execution_queue q WHERE q.session_id = s.id + ) + AND NOT EXISTS ( + SELECT 1 FROM session_cleanup_tasks c WHERE c.session_id = s.id + ) +ORDER BY s.conversation_expires_at ASC, s.id ASC +LIMIT ?; diff --git a/internal/session/sql/queries/store.select_terminated_ids.sql b/internal/session/sql/queries/store.select_terminated_ids.sql index 049dbdef6..11bb707b1 100644 --- a/internal/session/sql/queries/store.select_terminated_ids.sql +++ b/internal/session/sql/queries/store.select_terminated_ids.sql @@ -1,4 +1,19 @@ -SELECT id FROM sessions WHERE state = ? AND ( +SELECT id FROM sessions WHERE state = ? AND COALESCE(lifecycle_policy, 'legacy') <> 'v2' AND ( (source = 'cron' AND updated_at <= ?) OR (source != 'cron' AND updated_at <= ?) +) +AND NOT EXISTS ( + SELECT 1 FROM execution_inputs e + WHERE e.session_id = sessions.id + AND ( + e.status IN ('accepted', 'unknown') + OR e.runtime_status IN ('queued', 'pending', 'running', 'unknown') + OR e.fence_reason <> '' + ) +) +AND NOT EXISTS ( + SELECT 1 FROM execution_queue q WHERE q.session_id = sessions.id +) +AND NOT EXISTS ( + SELECT 1 FROM session_cleanup_tasks c WHERE c.session_id = sessions.id ); diff --git a/internal/session/store.go b/internal/session/store.go index 0bbcb7390..71c072d6e 100644 --- a/internal/session/store.go +++ b/internal/session/store.go @@ -32,6 +32,31 @@ type Store interface { Close() error } +type lifecycleDeadlineStore interface { + AdvanceLifecycleDeadlines( + ctx context.Context, + id, policyRevision string, + lastInputAt, archiveAt, conversationExpiresAt, historyExpiresAt, updatedAt time.Time, + ) error +} + +type lifecycleRetirementStore interface { + ListExpiredLifecycleSessions(ctx context.Context, now time.Time, limit int) ([]string, error) + RetireExpiredLifecycleSession(ctx context.Context, id string, now time.Time) (*SessionInfo, error) +} + +type lifecycleGCStatus struct { + eligible int64 + blocked int64 + unknownExecutionHold int64 + eligibleLag time.Duration + blockedLag time.Duration +} + +type lifecycleGCStatusStore interface { + GetLifecycleGCStatus(ctx context.Context, now time.Time) (lifecycleGCStatus, error) +} + var _ Store = (*SQLiteStore)(nil) // SQLiteStore implements Store using SQLite. @@ -169,6 +194,57 @@ func (s *SQLiteStore) UpdateWorkerSessionIDSQL(ctx context.Context, id, workerSe }) } +// AdvanceLifecycleDeadlines updates only the session-level lifecycle deadlines. +// It does not set content expiry because turns/events have no per-record policy +// or expiry timestamp yet. +func (s *SQLiteStore) AdvanceLifecycleDeadlines( + ctx context.Context, + id, policyRevision string, + lastInputAt, archiveAt, conversationExpiresAt, historyExpiresAt, updatedAt time.Time, +) error { + ctx, cancel := upsertTimeout(ctx) + defer cancel() + + return s.writeMu.WithLock(func() error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + if err := ensureSQLiteLifecycleLock(ctx, tx, id); err != nil { + return err + } + result, err := tx.ExecContext(ctx, queries["sessions.advance_lifecycle_deadlines"], + lastInputAt, lastInputAt, + archiveAt, archiveAt, + conversationExpiresAt, conversationExpiresAt, + historyExpiresAt, historyExpiresAt, + updatedAt, updatedAt, + id, config.LifecyclePolicyV2, policyRevision, + ) + if err != nil { + return fmt.Errorf("session store: advance lifecycle deadlines: %w", err) + } + updated, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("session store: lifecycle deadline rows affected: %w", err) + } + if updated == 0 { + var pending bool + if err := tx.QueryRowContext(ctx, + `SELECT EXISTS(SELECT 1 FROM session_cleanup_tasks WHERE session_id = ?)`, id, + ).Scan(&pending); err != nil { + return fmt.Errorf("session store: check cleanup task: %w", err) + } + if pending { + return ErrSessionCleanupPending + } + return ErrSessionNotFound + } + return tx.Commit() + }) +} + // SetPermissionCeilingIfEmpty atomically captures the first effective Worker // permission ceiling and returns the authoritative stored value. func (s *SQLiteStore) SetPermissionCeilingIfEmpty(ctx context.Context, id, ceiling string) (string, error) { @@ -285,13 +361,17 @@ func scanSession(sc rowScanner) (*SessionInfo, error) { var info SessionInfo var ctxJSON, platformKeyStr sql.NullString var expiresAt, idleExpiresAt sql.NullTime + var lastInputAt, runtimeFinishedAt, archiveAt, conversationExpiresAt sql.NullTime + var lastContentExpiresAt, historyExpiresAt, deletedAt sql.NullTime var createdAt, updatedAt time.Time err := sc.Scan( &info.ID, &info.UserID, &info.OwnerID, &info.WorkerSessionID, &info.WorkerType, &info.State, &info.BotID, &info.BotName, &info.Platform, &platformKeyStr, &info.WorkDir, &info.Title, &createdAt, &updatedAt, &expiresAt, &idleExpiresAt, &ctxJSON, &info.Source, &info.ClientKey, &info.WorkspaceID, - &info.PermissionCeiling, + &info.PermissionCeiling, &info.LifecyclePolicy, &info.LifecyclePolicyRevision, + &lastInputAt, &runtimeFinishedAt, &archiveAt, &conversationExpiresAt, + &lastContentExpiresAt, &historyExpiresAt, &deletedAt, ) if err != nil { return nil, err @@ -305,6 +385,27 @@ func scanSession(sc rowScanner) (*SessionInfo, error) { if idleExpiresAt.Valid { info.IdleExpiresAt = &idleExpiresAt.Time } + if lastInputAt.Valid { + info.LastInputAt = &lastInputAt.Time + } + if runtimeFinishedAt.Valid { + info.RuntimeFinishedAt = &runtimeFinishedAt.Time + } + if archiveAt.Valid { + info.ArchiveAt = &archiveAt.Time + } + if conversationExpiresAt.Valid { + info.ConversationExpiresAt = &conversationExpiresAt.Time + } + if lastContentExpiresAt.Valid { + info.LastContentExpiresAt = &lastContentExpiresAt.Time + } + if historyExpiresAt.Valid { + info.HistoryExpiresAt = &historyExpiresAt.Time + } + if deletedAt.Valid { + info.DeletedAt = &deletedAt.Time + } if ctxJSON.Valid && ctxJSON.String != "" { if err := json.Unmarshal([]byte(ctxJSON.String), &info.Context); err != nil { return nil, fmt.Errorf("session store: unmarshal context: %w", err) @@ -420,6 +521,55 @@ func (s *SQLiteStore) GetExpiredIdle(ctx context.Context, now time.Time) ([]stri return collectIDs(rows) } +func (s *SQLiteStore) ListExpiredLifecycleSessions(ctx context.Context, now time.Time, limit int) ([]string, error) { + if limit <= 0 { + return nil, nil + } + rows, err := s.db.QueryContext(ctx, queries["store.select_expired_lifecycle_ids"], now, now, now, limit) + if err != nil { + return nil, fmt.Errorf("session store: select expired lifecycle sessions: %w", err) + } + defer func() { _ = rows.Close() }() + return collectIDs(rows) +} + +func (s *SQLiteStore) GetLifecycleGCStatus(ctx context.Context, now time.Time) (lifecycleGCStatus, error) { + var status lifecycleGCStatus + var oldestEligible, oldestBlocked sql.NullString + err := s.db.QueryRowContext(ctx, queries["store.lifecycle_gc_status"], now, now, now). + Scan(&status.eligible, &status.blocked, &status.unknownExecutionHold, &oldestEligible, &oldestBlocked) + if err != nil { + return lifecycleGCStatus{}, fmt.Errorf("session store: get lifecycle GC status: %w", err) + } + if oldestEligible.Valid { + deadline, err := parseSQLiteDateTime(oldestEligible.String) + if err != nil { + return lifecycleGCStatus{}, fmt.Errorf("session store: parse oldest eligible lifecycle deadline: %w", err) + } + status.eligibleLag = lifecycleDeadlineLag(&deadline, now) + } + if oldestBlocked.Valid { + deadline, err := parseSQLiteDateTime(oldestBlocked.String) + if err != nil { + return lifecycleGCStatus{}, fmt.Errorf("session store: parse oldest blocked lifecycle deadline: %w", err) + } + status.blockedLag = lifecycleDeadlineLag(&deadline, now) + } + return status, nil +} + +func parseSQLiteDateTime(value string) (time.Time, error) { + const sqliteDateTimeLayout = "2006-01-02 15:04:05.999999999 -0700 MST" + deadline, err := time.Parse(sqliteDateTimeLayout, value) + if err == nil { + return deadline, nil + } + if rfc3339, rfcErr := time.Parse(time.RFC3339Nano, value); rfcErr == nil { + return rfc3339, nil + } + return time.Time{}, fmt.Errorf("unsupported SQLite datetime %q: %w", value, err) +} + // Events lifecycle is managed independently — session deletion does not cascade to events. func (s *SQLiteStore) DeleteTerminated(ctx context.Context, cronCutoff, defaultCutoff time.Time) ([]*SessionInfo, error) { deleted := make([]*SessionInfo, 0) @@ -446,6 +596,13 @@ func (s *SQLiteStore) DeleteTerminated(ctx context.Context, cronCutoff, defaultC if err := ensureSQLiteLifecycleLock(ctx, tx, id); err != nil { return err } + locked, err := lockSessionRowForLifecycleChange(ctx, tx, identityRebind, id) + if err != nil { + return err + } + if !locked { + continue + } deletedRows, err := tx.QueryContext(ctx, queries["store.delete_terminated_by_id"], id, events.StateTerminated, cronCutoff, defaultCutoff) if err != nil { return fmt.Errorf("session store: delete terminated: %w", err) diff --git a/internal/session/store_test.go b/internal/session/store_test.go index 77eb6252c..7cdf035a2 100644 --- a/internal/session/store_test.go +++ b/internal/session/store_test.go @@ -136,6 +136,56 @@ func TestSQLiteStore_Upsert_WithContext(t *testing.T) { require.Equal(t, "T123", got.PlatformKey["team_id"]) } +func TestSQLiteStore_Upsert_SessionLifecycleFields(t *testing.T) { + t.Parallel() + + store, _ := helperDB(t) + ctx := t.Context() + now := time.Date(2026, time.October, 8, 12, 0, 0, 0, time.UTC) + info := &SessionInfo{ + ID: "sess_lifecycle", + UserID: "user1", + WorkerType: "claude_code", + State: events.StateCreated, + CreatedAt: now, + UpdatedAt: now, + LifecyclePolicy: "v2", + LifecyclePolicyRevision: "rev-1", + RuntimeFinishedAt: ptr(now.Add(-time.Hour)), + ArchiveAt: ptr(now.Add(7 * 24 * time.Hour)), + ConversationExpiresAt: ptr(now.Add(180 * 24 * time.Hour)), + LastContentExpiresAt: ptr(now.Add(181 * 24 * time.Hour)), + HistoryExpiresAt: ptr(now.Add(181 * 24 * time.Hour)), + LastInputAt: ptr(now), + } + require.NoError(t, store.Upsert(ctx, info)) + + got, err := store.Get(ctx, info.ID) + require.NoError(t, err) + require.Equal(t, info.LifecyclePolicy, got.LifecyclePolicy) + require.Equal(t, info.LifecyclePolicyRevision, got.LifecyclePolicyRevision) + require.Equal(t, info.LastInputAt, got.LastInputAt) + require.Equal(t, info.RuntimeFinishedAt, got.RuntimeFinishedAt) + require.Equal(t, info.ArchiveAt, got.ArchiveAt) + require.Equal(t, info.ConversationExpiresAt, got.ConversationExpiresAt) + require.Equal(t, info.LastContentExpiresAt, got.LastContentExpiresAt) + require.Equal(t, info.HistoryExpiresAt, got.HistoryExpiresAt) + + // General stale Upsert snapshots must not overwrite lifecycle deadlines + // written by the dedicated lifecycle update path. + future := now.Add(365 * 24 * time.Hour) + _, err = store.db.ExecContext(ctx, + `UPDATE sessions SET conversation_expires_at = ?, history_expires_at = ? WHERE id = ?`, + future, future, info.ID) + require.NoError(t, err) + require.NoError(t, store.Upsert(ctx, info)) + + got, err = store.Get(ctx, info.ID) + require.NoError(t, err) + require.Equal(t, future, *got.ConversationExpiresAt) + require.Equal(t, future, *got.HistoryExpiresAt) +} + // ─── SQLiteStore: List with pagination ─────────────────────────────────────── func TestSQLiteStore_List_DefaultLimit(t *testing.T) { @@ -263,6 +313,26 @@ func TestSQLiteStore_DeleteTerminated(t *testing.T) { require.NoError(t, err, "recent normal session should survive") } +func TestSQLiteStore_DeleteTerminated_PreservesLifecycleV2(t *testing.T) { + store, _ := helperDB(t) + ctx := t.Context() + now := time.Now() + require.NoError(t, store.Upsert(ctx, &SessionInfo{ + ID: "v2_old_terminated", UserID: "u1", WorkerType: "claude_code", + State: events.StateTerminated, LifecyclePolicy: config.LifecyclePolicyV2, + CreatedAt: now.Add(-20 * 24 * time.Hour), UpdatedAt: now.Add(-8 * 24 * time.Hour), + ArchiveAt: ptr(now.Add(-13 * 24 * time.Hour)), + ConversationExpiresAt: ptr(now.Add(160 * 24 * time.Hour)), + HistoryExpiresAt: ptr(now.Add(160 * 24 * time.Hour)), + })) + + _, err := store.DeleteTerminated(ctx, now.Add(-24*time.Hour), now.Add(-7*24*time.Hour)) + require.NoError(t, err) + + _, err = store.Get(ctx, "v2_old_terminated") + require.NoError(t, err, "v2 sessions must not use the legacy terminated-row cutoff") +} + // ─── SQLiteStore: GetSessionsByState ───────────────────────────────────────── func TestSQLiteStore_GetSessionsByState(t *testing.T) { diff --git a/internal/session/upgrade_fixture_test.go b/internal/session/upgrade_fixture_test.go index 99f131fa5..51a46f953 100644 --- a/internal/session/upgrade_fixture_test.go +++ b/internal/session/upgrade_fixture_test.go @@ -185,6 +185,17 @@ func TestUpgradeFromPreQueueFixture(t *testing.T) { `SELECT COUNT(*) FROM sessions WHERE id = 'sess-legacy' AND title = 'legacy run'`).Scan(&sessions)) require.Equal(t, 1, sessions, "the session must survive the upgrade") + var policy, revision string + var conversationExpiry, historyExpiry sql.NullTime + require.NoError(t, db.QueryRowContext(ctx, + `SELECT lifecycle_policy, lifecycle_policy_revision, conversation_expires_at, history_expires_at + FROM sessions WHERE id = 'sess-legacy'`, + ).Scan(&policy, &revision, &conversationExpiry, &historyExpiry)) + require.Equal(t, "legacy", policy, "upgrade must preserve existing rows on their legacy policy") + require.Empty(t, revision, "upgrade must not fabricate a policy revision for old rows") + require.False(t, conversationExpiry.Valid, "upgrade must not invent a user-input-based expiry") + require.False(t, historyExpiry.Valid, "upgrade must not extend or shorten legacy history") + var effects, attempts int require.NoError(t, db.QueryRowContext(ctx, `SELECT COUNT(*) FROM effects WHERE effect_id = 'eff-legacy' AND status = 'delivered'`).Scan(&effects)) @@ -230,6 +241,18 @@ func TestUpgradeFromPreQueueFixture(t *testing.T) { }, got) }) + t.Run("legacy executions keep no fabricated turn deadline", func(t *testing.T) { + var startedAt, deadlineAt sql.NullInt64 + var revision string + require.NoError(t, db.QueryRowContext(ctx, + `SELECT turn_started_at, turn_deadline_at, turn_policy_revision + FROM execution_inputs WHERE execution_id = 'exec-done'`, + ).Scan(&startedAt, &deadlineAt, &revision)) + require.False(t, startedAt.Valid) + require.False(t, deadlineAt.Valid) + require.Empty(t, revision) + }) + t.Run("queue schema exists and admits queued", func(t *testing.T) { for _, table := range []string{ "execution_queue", "execution_queue_counters", diff --git a/internal/worker/acp/trace.go b/internal/worker/acp/trace.go index d234b29c2..740680ae1 100644 --- a/internal/worker/acp/trace.go +++ b/internal/worker/acp/trace.go @@ -6,10 +6,16 @@ import ( "log/slog" "os" "path/filepath" + "strings" "sync" "time" ) +var activeTraceFiles = struct { + mu sync.Mutex + paths map[string]int +}{paths: make(map[string]int)} + // TraceWriter logs all JSON-RPC messages to a JSONL trace file for debugging. // Enabled via acp.debug: true in config.yaml. // @@ -22,6 +28,7 @@ type TraceWriter struct { path string maxSize int64 writtenBytes int64 + registered bool } // NewTraceWriter creates a trace writer that appends to a JSONL file. @@ -29,9 +36,14 @@ func NewTraceWriter(dir, sessionID string) (*TraceWriter, error) { if err := os.MkdirAll(dir, 0o700); err != nil { return nil, fmt.Errorf("acp trace: create dir: %w", err) } - path := filepath.Join(dir, "acp-trace-"+filepath.Base(sessionID)+".jsonl") + path, err := filepath.Abs(filepath.Join(dir, "acp-trace-"+filepath.Base(sessionID)+".jsonl")) + if err != nil { + return nil, fmt.Errorf("acp trace: resolve file path: %w", err) + } + registerTraceFile(path) f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600) if err != nil { + unregisterTraceFile(path) return nil, fmt.Errorf("acp trace: open file: %w", err) } // Initialize byte counter from existing file size so rotation works @@ -45,9 +57,96 @@ func NewTraceWriter(dir, sessionID string) (*TraceWriter, error) { path: path, maxSize: 50 * 1024 * 1024, // 50 MB writtenBytes: written, + registered: true, }, nil } +func tracePathKey(path string) string { + abs, err := filepath.Abs(path) + if err != nil { + return filepath.Clean(path) + } + return abs +} + +func registerTraceFile(path string) { + key := tracePathKey(path) + activeTraceFiles.mu.Lock() + activeTraceFiles.paths[key]++ + activeTraceFiles.mu.Unlock() +} + +func unregisterTraceFile(path string) { + key := tracePathKey(path) + activeTraceFiles.mu.Lock() + if activeTraceFiles.paths[key] <= 1 { + delete(activeTraceFiles.paths, key) + } else { + activeTraceFiles.paths[key]-- + } + activeTraceFiles.mu.Unlock() +} + +// PruneExpiredTraceFiles removes old HotPlex ACP trace files and rotated +// backups. Active writers are protected from unlinking; other files in the +// directory are never considered. +func PruneExpiredTraceFiles(dir string, now time.Time, retention time.Duration, limit int) (int, error) { + if retention <= 0 { + return 0, fmt.Errorf("acp trace: retention must be positive") + } + if limit <= 0 { + return 0, nil + } + entries, err := os.ReadDir(dir) + if os.IsNotExist(err) { + return 0, nil + } + if err != nil { + return 0, fmt.Errorf("acp trace: read trace directory: %w", err) + } + + cutoff := now.Add(-retention) + deleted := 0 + for _, entry := range entries { + if deleted >= limit || entry.IsDir() || !isACPTraceName(entry.Name()) { + continue + } + path := filepath.Join(dir, entry.Name()) + key := tracePathKey(path) + + // Hold the registry lock through removal so a new writer cannot open + // the same path between the active check and os.Remove. + activeTraceFiles.mu.Lock() + if activeTraceFiles.paths[key] > 0 { + activeTraceFiles.mu.Unlock() + continue + } + info, err := entry.Info() + if err != nil { + activeTraceFiles.mu.Unlock() + return deleted, fmt.Errorf("acp trace: stat trace file: %w", err) + } + if !info.Mode().IsRegular() || info.ModTime().After(cutoff) { + activeTraceFiles.mu.Unlock() + continue + } + if err := os.Remove(path); err != nil { + activeTraceFiles.mu.Unlock() + return deleted, fmt.Errorf("acp trace: remove expired trace: %w", err) + } + activeTraceFiles.mu.Unlock() + deleted++ + } + return deleted, nil +} + +func isACPTraceName(name string) bool { + if !strings.HasPrefix(name, "acp-trace-") { + return false + } + return strings.HasSuffix(name, ".jsonl") || strings.HasSuffix(name, ".jsonl.1") +} + // Log writes a single trace entry. Safe to call on a nil TraceWriter (no-op). // Uses a byte counter under mutex to track size, avoiding Stat() syscall on every call. func (tw *TraceWriter) Log(direction string, msg any) { @@ -86,6 +185,7 @@ func (tw *TraceWriter) Log(direction string, msg any) { func (tw *TraceWriter) rotateLocked() { if err := tw.file.Close(); err != nil { tw.file = nil + tw.unregisterLocked() return } rotated := tw.path + ".1" @@ -93,17 +193,27 @@ func (tw *TraceWriter) rotateLocked() { if err := os.Rename(tw.path, rotated); err != nil { slog.Warn("acp trace: rename failed, trace data lost", "err", err, "path", tw.path) tw.file = nil + tw.unregisterLocked() return } f, err := os.OpenFile(tw.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600) if err != nil { slog.Warn("acp trace: reopen failed after rotation", "err", err, "path", tw.path) tw.file = nil + tw.unregisterLocked() return } tw.file = f } +func (tw *TraceWriter) unregisterLocked() { + if !tw.registered { + return + } + unregisterTraceFile(tw.path) + tw.registered = false +} + // Close flushes and closes the trace file. func (tw *TraceWriter) Close() error { if tw == nil { @@ -112,9 +222,13 @@ func (tw *TraceWriter) Close() error { tw.mu.Lock() defer tw.mu.Unlock() if tw.file == nil { + tw.unregisterLocked() return nil } - return tw.file.Close() + err := tw.file.Close() + tw.file = nil + tw.unregisterLocked() + return err } // Rotate forces a rotation. Safe to call on nil TraceWriter. diff --git a/internal/worker/acp/worker_phase3_test.go b/internal/worker/acp/worker_phase3_test.go index b6da1545f..02cbf8577 100644 --- a/internal/worker/acp/worker_phase3_test.go +++ b/internal/worker/acp/worker_phase3_test.go @@ -10,6 +10,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/stretchr/testify/require" @@ -409,3 +410,39 @@ func TestTraceWriter_PathFormat(t *testing.T) { expected := filepath.Join(dir, "acp-trace-my-session.jsonl") require.Equal(t, expected, tw.Path()) } + +func TestPruneExpiredTraceFiles_SkipsActiveAndUnrelatedFiles(t *testing.T) { + t.Parallel() + + now := time.Now() + dir := t.TempDir() + expired, err := NewTraceWriter(dir, "expired") + require.NoError(t, err) + expiredPath := expired.Path() + require.NoError(t, expired.Close()) + oldTime := now.Add(-72 * time.Hour) + require.NoError(t, os.Chtimes(expiredPath, oldTime, oldTime)) + + rotatedPath := expiredPath + ".1" + require.NoError(t, os.WriteFile(rotatedPath, []byte("old trace"), 0o600)) + require.NoError(t, os.Chtimes(rotatedPath, oldTime, oldTime)) + + active, err := NewTraceWriter(dir, "active") + require.NoError(t, err) + t.Cleanup(func() { _ = active.Close() }) + activePath := active.Path() + require.NoError(t, os.Chtimes(activePath, oldTime, oldTime)) + + unrelatedPath := filepath.Join(dir, "gateway.log") + require.NoError(t, os.WriteFile(unrelatedPath, []byte("log"), 0o600)) + require.NoError(t, os.Chtimes(unrelatedPath, oldTime, oldTime)) + + deleted, err := PruneExpiredTraceFiles(dir, now, 48*time.Hour, 100) + + require.NoError(t, err) + require.Equal(t, 2, deleted) + require.NoFileExists(t, expiredPath) + require.NoFileExists(t, rotatedPath) + require.FileExists(t, activePath) + require.FileExists(t, unrelatedPath) +} diff --git a/webchat/app/admin/activity/page.tsx b/webchat/app/admin/activity/page.tsx index 43c6a200b..95c418ca8 100644 --- a/webchat/app/admin/activity/page.tsx +++ b/webchat/app/admin/activity/page.tsx @@ -584,10 +584,11 @@ export default function AdminActivityPage() { ))} {rows.map((row) => { - const selected = drawerRow?.id === row.id; + const rowEpoch = row.chain_epoch ?? 'legacy'; + const selected = drawerRow?.id === row.id && (drawerRow.chain_epoch ?? 'legacy') === rowEpoch; return (