Skip to content
This repository was archived by the owner on Feb 8, 2026. It is now read-only.

payroll scheduler refactoring#134

Merged
webpiratt merged 18 commits into
mainfrom
132-fix-time-triggers
Jul 29, 2025
Merged

payroll scheduler refactoring#134
webpiratt merged 18 commits into
mainfrom
132-fix-time-triggers

Conversation

@webpiratt

@webpiratt webpiratt commented Jul 21, 2025

Copy link
Copy Markdown
Contributor
  • Removed schedule from policy and implemented configuration handling for payroll;
  • time_triggers table removed and reworked as scheduler table;
  • Created payroll.scheduler running as separate instance, struct is generic enough and reusable for DCA scheduler handling / other plugins in future;
  • Dockerfile & docker-compose & GoLand config changes to add new payroll.scheduler;

Ref: vultisig/recipes#44 & #132

Summary by CodeRabbit

  • New Features

    • Introduced a dedicated Payroll Scheduler service and its configuration, including Docker and docker-compose support for easy deployment.
    • Added a new scheduling system for payroll policies, enabling flexible scheduling based on daily, weekly, biweekly, or monthly intervals.
  • Improvements

    • Simplified and unified policy scheduling management, providing more robust and maintainable scheduling operations.
    • Updated database schema to use a new scheduler table for managing scheduled executions.
  • Bug Fixes

    • Removed obsolete configuration keys and environment variables for improved clarity.
  • Refactor

    • Replaced the previous time trigger system with a streamlined scheduler interface and storage layer.
    • Updated internal interfaces to support transactional scheduling operations and improved modularity.
  • Chores

    • Updated dependencies for improved stability.
    • Cleaned up unused code, tests, and database migrations related to the old scheduling system.
  • Documentation

    • Added example configuration files for the new Payroll Scheduler service.

@webpiratt webpiratt linked an issue Jul 21, 2025 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Jul 21, 2025

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@webpiratt has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 21 minutes and 55 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between cf25938 and 803cb11.

📒 Files selected for processing (1)
  • storage/postgres/schema/schema.sql (3 hunks)

Walkthrough

This change set introduces a new unified scheduling system, replacing the previous time trigger mechanism with a streamlined scheduler table, interfaces, and worker logic. It removes legacy time trigger code, updates database schemas and migrations, adds new configuration and Docker support for a dedicated payroll scheduler service, and refactors server and plugin code to use the new scheduler interfaces and storage abstractions.

Changes

Cohort / File(s) Change Summary
Scheduler Infrastructure (Core, Storage, Interfaces)
internal/scheduler/interval.go, internal/scheduler/policy.go, internal/scheduler/service.go, internal/scheduler/service_nil.go, internal/scheduler/storage.go, internal/scheduler/storage_pg.go, internal/scheduler/worker.go, plugin/payroll/interval.go, plugin/payroll/scheduler.go
Introduces new scheduler interfaces (Interval, Service, PolicyFetcher, Storage), PostgreSQL-backed storage, and a worker for task scheduling. Implements payroll-specific interval logic and scheduler service.
Removal of Legacy Time Trigger System
internal/scheduler/scheduler.go, internal/scheduler/scheduler_test.go, internal/types/time_trigger.go, storage/postgres/time_trigger.go, storage/postgres/time_trigger_test.go, storage/db.go, storage/postgres/migrations/plugin/20241211182120_add_time_triggers.sql
Deletes all code, tests, and migrations related to the legacy time trigger scheduling system, including types, storage, and database schema. Removes related methods from storage interfaces.
Database Schema and Migration Updates
storage/postgres/schema/schema.sql, storage/postgres/migrations/plugin/20250721165241_scheduler.sql
Drops time trigger tables and enums, adds new scheduler table and indexes, and removes related triggers and constraints. Adds migration for the new scheduler table.
Server, Worker, and Service Refactoring
api/server.go, service/policy.go, cmd/dca/server/main.go, cmd/fees/server/main.go, cmd/payroll/server/main.go, scripts/dev/create_fee_policy/dummy_server.go
Refactors server and policy service construction to use new scheduler interfaces. Updates worker and server initializations to inject new scheduler services or nil implementations as needed.
Worker Queue Simplification
cmd/dca/worker/main.go, cmd/fees/worker/main.go, cmd/payroll/worker/main.go
Removes the "scheduled_plugin_queue" from Asynq worker configurations, leaving only the main task queue. Removes scheduler service logic from payroll worker.
Payroll Scheduler Service Introduction
cmd/payroll/scheduler/config.go, cmd/payroll/scheduler/main.go, payroll.scheduler.example.json, Dockerfile.Payroll.scheduler, docker-compose.yaml, .run/[git] payroll scheduler.run.xml, .run/[git] plugin stack.run.xml
Adds a new standalone payroll scheduler service with configuration, Dockerfile, Docker Compose integration, and IDE run configurations.
Plugin Recipe Specification Changes
plugin/dca/dca.go, plugin/payroll/transaction.go
Removes the Scheduling capability from recipe specifications in DCA and Payroll plugins. Updates transaction handler to use new scheduler types.
Configuration Cleanup
payroll.server.example.json, .run/[git] payroll worker.run.xml
Removes obsolete configuration keys and environment variables related to legacy base config paths.
Dependency Updates
go.mod
Updates and downgrades two Go module dependencies.
Task Type Cleanup
internal/tasks/tasks.go
Removes an unused DKLS key generation task type constant.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PayrollScheduler (New Service)
    participant Postgres
    participant Redis (Asynq)
    participant Plugin

    User->>PayrollScheduler: Start service (main.go)
    PayrollScheduler->>PayrollScheduler: Load config, connect to Postgres & Redis
    PayrollScheduler->>PayrollScheduler: Run Worker
    loop Every poll interval
        PayrollScheduler->>Postgres: Fetch pending scheduler entries
        PayrollScheduler->>Postgres: For each, fetch policy
        PayrollScheduler->>PayrollScheduler: Calculate next execution time
        PayrollScheduler->>Redis: Enqueue task to Asynq
        PayrollScheduler->>Postgres: Update next_execution in scheduler table
    end
    Redis->>Plugin: Deliver scheduled task for execution
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

A scheduler hops, the old triggers retire,
With new tables and workers, we leap ever higher!
The queues are now cleaner, the codebase less dense,
Our plugins and services make much more sense.
With Docker and configs, the future’s much brighter—
A bunny’s delight: the system feels lighter!
🐇✨

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 132-fix-time-triggers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@webpiratt
webpiratt marked this pull request as ready for review July 24, 2025 15:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (2)
storage/postgres/schema/schema.sql (2)

1-179: Resolve pipeline failure by updating schema.sql.

The pipeline failure indicates that the schema file is out of sync with the actual database schema after migrations.

Run the following command to fix the pipeline failure:

make dump-schema CONFIG=config-plugin.yaml

82-93: Fix Remaining References Before Dropping time_triggers Table

The time_triggers table is still referenced in the codebase and cannot be dropped until those references are removed or updated:

• storage/postgres/policy.go

// line showing DELETE FROM time_triggers
DELETE FROM time_triggers

Please:

  • Remove or replace the logic in storage/postgres/policy.go that mentions time_triggers.
  • Once all code references are removed, drop the time_triggers table and the trigger_status enum from storage/postgres/schema/schema.sql.
🧹 Nitpick comments (4)
plugin/payroll/interval.go (1)

29-29: Consider making time dependency injectable for better testability.

The direct use of time.Now() makes this code non-deterministic and difficult to test. Consider accepting a time provider interface or making the current time injectable.

-func (i *Interval) FromNowWhenNext(policy types.PluginPolicy) (time.Time, error) {
+func (i *Interval) FromNowWhenNext(policy types.PluginPolicy) (time.Time, error) {
+	return i.fromTimeWhenNext(policy, time.Now())
+}
+
+func (i *Interval) fromTimeWhenNext(policy types.PluginPolicy, now time.Time) (time.Time, error) {
 	recipe, err := policy.GetRecipe()
 	if err != nil {
 		return time.Time{}, fmt.Errorf("failed to unpack recipe: %w", err)
@@ -26,13 +30,13 @@
 	freq := cfg[frequency].GetStringValue()
 	switch freq {
 	case daily:
-		next = time.Now().AddDate(0, 0, 1)
+		next = now.AddDate(0, 0, 1)
 	case weekly:
-		next = time.Now().AddDate(0, 0, 7)
+		next = now.AddDate(0, 0, 7)
 	case biWeekly:
-		next = time.Now().AddDate(0, 0, 14)
+		next = now.AddDate(0, 0, 14)
 	case monthly:
-		next = time.Now().AddDate(0, 1, 0)
+		next = now.AddDate(0, 1, 0)
 	default:
 		return time.Time{}, fmt.Errorf("unknown frequency: %s", freq)
 	}

Also applies to: 31-31, 33-33, 35-35

Dockerfile.Payroll.scheduler (1)

38-38: Consider making configuration more flexible.

The current approach copies the example configuration file and renames it to config.json. While this works for development, consider making the configuration source more flexible for production deployments, perhaps through environment variables or volume mounts.

You could modify the approach to support external configuration:

-COPY payroll.scheduler.example.json config.json
+COPY payroll.scheduler.example.json ./

Then update the application to check for mounted config files or use the example as a fallback.

internal/scheduler/storage_pg.go (2)

23-32: Consider handling duplicate key errors

The current implementation doesn't distinguish between duplicate key errors and other database errors. Consider checking for duplicate key violations to provide more specific error messages.

func (s *PostgresStorage) CreateWithTx(ctx context.Context, tx pgx.Tx, policyID uuid.UUID, next time.Time) error {
	_, err := tx.Exec(ctx, `
		INSERT INTO scheduler (policy_id, next_execution)
		VALUES ($1, $2)
	`, policyID, next)
	if err != nil {
+		// Check if it's a unique constraint violation
+		if pgErr, ok := err.(*pgconn.PgError); ok && pgErr.Code == "23505" {
+			return fmt.Errorf("scheduler entry already exists for policy %s: %w", policyID, err)
+		}
		return fmt.Errorf("failed to create scheduler entry: %w", err)
	}
	return nil
}

77-99: Consider reducing code duplication

Both SetNext and SetNextWithTx execute the same SQL query. Consider extracting the common update logic.

+func (s *PostgresStorage) setNextExecution(ctx context.Context, db interface{ Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) }, policyID uuid.UUID, next time.Time) error {
+	_, err := db.Exec(ctx, `
+		UPDATE scheduler
+		SET next_execution = $2
+		WHERE policy_id = $1
+	`, policyID, next)
+	if err != nil {
+		return fmt.Errorf("failed to update next execution time: %w", err)
+	}
+	return nil
+}
+
func (s *PostgresStorage) SetNext(ctx context.Context, policyID uuid.UUID, next time.Time) error {
-	_, err := s.pool.Exec(ctx, `
-		UPDATE scheduler
-		SET next_execution = $2
-		WHERE policy_id = $1
-	`, policyID, next)
-	if err != nil {
-		return fmt.Errorf("failed to update next execution time: %w", err)
-	}
-	return nil
+	return s.setNextExecution(ctx, s.pool, policyID, next)
}

func (s *PostgresStorage) SetNextWithTx(ctx context.Context, tx pgx.Tx, policyID uuid.UUID, next time.Time) error {
-	_, err := tx.Exec(ctx, `
-		UPDATE scheduler
-		SET next_execution = $2
-		WHERE policy_id = $1
-	`, policyID, next)
-	if err != nil {
-		return fmt.Errorf("failed to update next execution time: %w", err)
-	}
-	return nil
+	return s.setNextExecution(ctx, tx, policyID, next)
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c39122 and ac16e56.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (40)
  • .run/[git] payroll worker.run.xml (1 hunks)
  • .run/[git] plugin stack.run.xml (1 hunks)
  • Dockerfile.Payroll.scheduler (1 hunks)
  • api/server.go (1 hunks)
  • cmd/dca/server/main.go (2 hunks)
  • cmd/dca/worker/main.go (1 hunks)
  • cmd/fees/server/main.go (2 hunks)
  • cmd/fees/worker/main.go (1 hunks)
  • cmd/payroll/scheduler/config.go (1 hunks)
  • cmd/payroll/scheduler/main.go (1 hunks)
  • cmd/payroll/server/main.go (2 hunks)
  • cmd/payroll/worker/main.go (1 hunks)
  • docker-compose.yaml (6 hunks)
  • go.mod (1 hunks)
  • internal/scheduler/interval.go (1 hunks)
  • internal/scheduler/policy.go (1 hunks)
  • internal/scheduler/scheduler.go (0 hunks)
  • internal/scheduler/scheduler_test.go (0 hunks)
  • internal/scheduler/service.go (1 hunks)
  • internal/scheduler/service_nil.go (1 hunks)
  • internal/scheduler/storage.go (1 hunks)
  • internal/scheduler/storage_pg.go (1 hunks)
  • internal/scheduler/worker.go (1 hunks)
  • internal/tasks/tasks.go (0 hunks)
  • internal/types/time_trigger.go (0 hunks)
  • payroll.scheduler.example.json (1 hunks)
  • payroll.server.example.json (0 hunks)
  • plugin/dca/dca.go (0 hunks)
  • plugin/fees/transaction.go (0 hunks)
  • plugin/payroll/interval.go (1 hunks)
  • plugin/payroll/scheduler.go (1 hunks)
  • plugin/payroll/transaction.go (1 hunks)
  • scripts/dev/create_fee_policy/dummy_server.go (2 hunks)
  • service/policy.go (4 hunks)
  • storage/db.go (0 hunks)
  • storage/postgres/migrations/plugin/20241211182120_add_time_triggers.sql (0 hunks)
  • storage/postgres/migrations/plugin/20250721165241_scheduler.sql (1 hunks)
  • storage/postgres/schema/schema.sql (3 hunks)
  • storage/postgres/time_trigger.go (0 hunks)
  • storage/postgres/time_trigger_test.go (0 hunks)
💤 Files with no reviewable changes (11)
  • internal/tasks/tasks.go
  • payroll.server.example.json
  • plugin/dca/dca.go
  • internal/scheduler/scheduler_test.go
  • plugin/fees/transaction.go
  • storage/postgres/time_trigger_test.go
  • storage/db.go
  • storage/postgres/migrations/plugin/20241211182120_add_time_triggers.sql
  • internal/types/time_trigger.go
  • storage/postgres/time_trigger.go
  • internal/scheduler/scheduler.go
🧰 Additional context used
🧠 Learnings (22)
📓 Common learnings
Learnt from: webpiratt
PR: vultisig/plugin#125
File: plugin/payroll/policy.go:160-160
Timestamp: 2025-07-09T22:23:17.348Z
Learning: In plugin/payroll/policy.go, the "token" case in the checkRule method intentionally does not call validateToken() as this is a temporary implementation that will be replaced with generic schema validation. The missing validation call is expected behavior during this transition period.
.run/[git] payroll worker.run.xml (1)

Learnt from: webpiratt
PR: #114
File: .run/[git] payroll worker.run.xml:5-8
Timestamp: 2025-07-03T16:08:11.917Z
Learning: GoLand run configuration files (.run/*.run.xml) are auto-generated by the IDE and should not be manually edited. Environment variables and other settings should be modified through GoLand's Run/Debug Configurations UI instead.

cmd/fees/server/main.go (3)

Learnt from: RaghavSood
PR: #36
File: api/server.go:21-33
Timestamp: 2025-05-07T08:23:45.882Z
Learning: The import path github.com/vultisig/verifier/plugin refers to an external dependency that provides the plugin interface, and should not be changed to github.com/vultisig/plugin/plugin as these are distinct packages with different purposes.

Learnt from: johnnyluo
PR: #108
File: Dockerfile.Payroll.server:14-19
Timestamp: 2025-07-02T04:55:36.331Z
Learning: In the vultisig/plugin repository, the team maintains both the main repository and the go-wrappers dependency repository, so they are comfortable downloading from the master branch rather than pinning to specific commits.

Learnt from: johnnyluo
PR: #108
File: cmd/payroll/worker/main.go:84-84
Timestamp: 2025-07-02T04:58:30.139Z
Learning: VaultServiceConfig is defined as a field of type vault_config.Config from the external package "github.com/vultisig/verifier/vault_config" in worker configuration structs across the vultisig/plugin codebase (cmd/payroll/worker/config.go, cmd/fees/worker/config.go, cmd/dca/worker/config.go). The vault_config.Config struct contains an EncryptionSecret field that can be accessed via cfg.VaultServiceConfig.EncryptionSecret.

go.mod (2)

Learnt from: RaghavSood
PR: #36
File: api/server.go:21-33
Timestamp: 2025-05-07T08:23:45.882Z
Learning: The import path github.com/vultisig/verifier/plugin refers to an external dependency that provides the plugin interface, and should not be changed to github.com/vultisig/plugin/plugin as these are distinct packages with different purposes.

Learnt from: johnnyluo
PR: #108
File: Dockerfile.Payroll.server:14-19
Timestamp: 2025-07-02T04:55:36.331Z
Learning: In the vultisig/plugin repository, the team maintains both the main repository and the go-wrappers dependency repository, so they are comfortable downloading from the master branch rather than pinning to specific commits.

cmd/payroll/server/main.go (3)

Learnt from: RaghavSood
PR: #36
File: api/server.go:21-33
Timestamp: 2025-05-07T08:23:45.882Z
Learning: The import path github.com/vultisig/verifier/plugin refers to an external dependency that provides the plugin interface, and should not be changed to github.com/vultisig/plugin/plugin as these are distinct packages with different purposes.

Learnt from: johnnyluo
PR: #108
File: Dockerfile.Payroll.server:14-19
Timestamp: 2025-07-02T04:55:36.331Z
Learning: In the vultisig/plugin repository, the team maintains both the main repository and the go-wrappers dependency repository, so they are comfortable downloading from the master branch rather than pinning to specific commits.

Learnt from: johnnyluo
PR: #108
File: cmd/payroll/worker/main.go:84-84
Timestamp: 2025-07-02T04:58:30.139Z
Learning: VaultServiceConfig is defined as a field of type vault_config.Config from the external package "github.com/vultisig/verifier/vault_config" in worker configuration structs across the vultisig/plugin codebase (cmd/payroll/worker/config.go, cmd/fees/worker/config.go, cmd/dca/worker/config.go). The vault_config.Config struct contains an EncryptionSecret field that can be accessed via cfg.VaultServiceConfig.EncryptionSecret.

scripts/dev/create_fee_policy/dummy_server.go (3)

Learnt from: RaghavSood
PR: #36
File: api/server.go:21-33
Timestamp: 2025-05-07T08:23:45.882Z
Learning: The import path github.com/vultisig/verifier/plugin refers to an external dependency that provides the plugin interface, and should not be changed to github.com/vultisig/plugin/plugin as these are distinct packages with different purposes.

Learnt from: RaghavSood
PR: #75
File: storage/postgres/db_plugin.go:25-40
Timestamp: 2025-05-30T02:44:31.711Z
Learning: The embed.FS directive //go:embed migrations/plugin/*.sql in storage/postgres/db_plugin.go correctly embeds plugin migration files that exist in the storage/postgres/migrations/plugin/ directory. The path "migrations/plugin" passed to goose.Up() references the embedded filesystem structure, not the physical file system path.

Learnt from: johnnyluo
PR: #108
File: Dockerfile.Payroll.server:14-19
Timestamp: 2025-07-02T04:55:36.331Z
Learning: In the vultisig/plugin repository, the team maintains both the main repository and the go-wrappers dependency repository, so they are comfortable downloading from the master branch rather than pinning to specific commits.

.run/[git] plugin stack.run.xml (1)

Learnt from: webpiratt
PR: #114
File: .run/[git] payroll worker.run.xml:5-8
Timestamp: 2025-07-03T16:08:11.917Z
Learning: GoLand run configuration files (.run/*.run.xml) are auto-generated by the IDE and should not be manually edited. Environment variables and other settings should be modified through GoLand's Run/Debug Configurations UI instead.

cmd/dca/server/main.go (3)

Learnt from: RaghavSood
PR: #36
File: api/server.go:21-33
Timestamp: 2025-05-07T08:23:45.882Z
Learning: The import path github.com/vultisig/verifier/plugin refers to an external dependency that provides the plugin interface, and should not be changed to github.com/vultisig/plugin/plugin as these are distinct packages with different purposes.

Learnt from: johnnyluo
PR: #108
File: Dockerfile.Payroll.server:14-19
Timestamp: 2025-07-02T04:55:36.331Z
Learning: In the vultisig/plugin repository, the team maintains both the main repository and the go-wrappers dependency repository, so they are comfortable downloading from the master branch rather than pinning to specific commits.

Learnt from: johnnyluo
PR: #108
File: cmd/payroll/worker/main.go:84-84
Timestamp: 2025-07-02T04:58:30.139Z
Learning: VaultServiceConfig is defined as a field of type vault_config.Config from the external package "github.com/vultisig/verifier/vault_config" in worker configuration structs across the vultisig/plugin codebase (cmd/payroll/worker/config.go, cmd/fees/worker/config.go, cmd/dca/worker/config.go). The vault_config.Config struct contains an EncryptionSecret field that can be accessed via cfg.VaultServiceConfig.EncryptionSecret.

internal/scheduler/policy.go (1)

Learnt from: RaghavSood
PR: #36
File: api/server.go:21-33
Timestamp: 2025-05-07T08:23:45.882Z
Learning: The import path github.com/vultisig/verifier/plugin refers to an external dependency that provides the plugin interface, and should not be changed to github.com/vultisig/plugin/plugin as these are distinct packages with different purposes.

api/server.go (2)

Learnt from: johnnyluo
PR: #108
File: cmd/payroll/worker/main.go:84-84
Timestamp: 2025-07-02T04:58:30.139Z
Learning: VaultServiceConfig is defined as a field of type vault_config.Config from the external package "github.com/vultisig/verifier/vault_config" in worker configuration structs across the vultisig/plugin codebase (cmd/payroll/worker/config.go, cmd/fees/worker/config.go, cmd/dca/worker/config.go). The vault_config.Config struct contains an EncryptionSecret field that can be accessed via cfg.VaultServiceConfig.EncryptionSecret.

Learnt from: johnnyluo
PR: #108
File: cmd/payroll/worker/main.go:84-84
Timestamp: 2025-07-02T04:58:30.139Z
Learning: VaultServiceConfig is defined as a field of type vault_config.Config in worker configuration structs across the vultisig/plugin codebase (cmd/payroll/worker/config.go, cmd/fees/worker/config.go, cmd/dca/worker/config.go). It contains an EncryptionSecret field that can be accessed via cfg.VaultServiceConfig.EncryptionSecret.

plugin/payroll/interval.go (2)

Learnt from: webpiratt
PR: #125
File: plugin/payroll/policy.go:160-160
Timestamp: 2025-07-09T22:23:17.348Z
Learning: In plugin/payroll/policy.go, the "token" case in the checkRule method intentionally does not call validateToken() as this is a temporary implementation that will be replaced with generic schema validation. The missing validation call is expected behavior during this transition period.

Learnt from: webpiratt
PR: #96
File: plugin/payroll/transaction.go:510-514
Timestamp: 2025-06-18T18:20:59.510Z
Learning: The erc20ABI constant is defined in plugin/payroll/constants.go within the payroll package, making it accessible to other files in the same package like transaction.go.

internal/scheduler/service.go (2)

Learnt from: johnnyluo
PR: #108
File: cmd/payroll/worker/main.go:84-84
Timestamp: 2025-07-02T04:58:30.139Z
Learning: VaultServiceConfig is defined as a field of type vault_config.Config from the external package "github.com/vultisig/verifier/vault_config" in worker configuration structs across the vultisig/plugin codebase (cmd/payroll/worker/config.go, cmd/fees/worker/config.go, cmd/dca/worker/config.go). The vault_config.Config struct contains an EncryptionSecret field that can be accessed via cfg.VaultServiceConfig.EncryptionSecret.

Learnt from: RaghavSood
PR: #36
File: api/server.go:21-33
Timestamp: 2025-05-07T08:23:45.882Z
Learning: The import path github.com/vultisig/verifier/plugin refers to an external dependency that provides the plugin interface, and should not be changed to github.com/vultisig/plugin/plugin as these are distinct packages with different purposes.

storage/postgres/schema/schema.sql (1)

Learnt from: webpiratt
PR: #89
File: storage/postgres/migrations/plugin/20250603181247_tx_indexer.sql:13-13
Timestamp: 2025-06-11T18:42:54.241Z
Learning: In the tx_indexer table (storage/postgres/migrations/plugin/*_tx_indexer.sql), the policy_id column is intentionally not defined with a foreign-key constraint to plugin_policies(id).

payroll.scheduler.example.json (2)

Learnt from: garry-sharp
PR: #113
File: docker-compose.yaml:80-81
Timestamp: 2025-07-03T15:55:04.605Z
Learning: For docker-compose.yaml files used for local development builds, hardcoded database credentials like "myuser:mypassword" in DATABASE_DSN environment variables are acceptable since they're only used locally and not exposed to external networks.

Learnt from: RaghavSood
PR: #70
File: .github/workflows/migration-test.yml:14-17
Timestamp: 2025-05-27T04:28:20.414Z
Learning: In GitHub Actions workflows for testing, hardcoded database credentials are acceptable when using ephemeral test services (like PostgreSQL containers) that only exist during the CI run, as there's no security risk with temporary test environments.

cmd/payroll/worker/main.go (6)

Learnt from: johnnyluo
PR: #108
File: cmd/payroll/worker/main.go:84-84
Timestamp: 2025-07-02T04:58:30.139Z
Learning: VaultServiceConfig is defined as a field of type vault_config.Config from the external package "github.com/vultisig/verifier/vault_config" in worker configuration structs across the vultisig/plugin codebase (cmd/payroll/worker/config.go, cmd/fees/worker/config.go, cmd/dca/worker/config.go). The vault_config.Config struct contains an EncryptionSecret field that can be accessed via cfg.VaultServiceConfig.EncryptionSecret.

Learnt from: RaghavSood
PR: #36
File: api/server.go:21-33
Timestamp: 2025-05-07T08:23:45.882Z
Learning: The import path github.com/vultisig/verifier/plugin refers to an external dependency that provides the plugin interface, and should not be changed to github.com/vultisig/plugin/plugin as these are distinct packages with different purposes.

Learnt from: johnnyluo
PR: #108
File: cmd/payroll/worker/main.go:84-84
Timestamp: 2025-07-02T04:58:30.139Z
Learning: VaultServiceConfig is defined as a field of type vault_config.Config in worker configuration structs across the vultisig/plugin codebase (cmd/payroll/worker/config.go, cmd/fees/worker/config.go, cmd/dca/worker/config.go). It contains an EncryptionSecret field that can be accessed via cfg.VaultServiceConfig.EncryptionSecret.

Learnt from: webpiratt
PR: #96
File: plugin/payroll/transaction.go:510-514
Timestamp: 2025-06-18T18:20:59.510Z
Learning: The erc20ABI constant is defined in plugin/payroll/constants.go within the payroll package, making it accessible to other files in the same package like transaction.go.

Learnt from: webpiratt
PR: #125
File: plugin/payroll/policy.go:160-160
Timestamp: 2025-07-09T22:23:17.348Z
Learning: In plugin/payroll/policy.go, the "token" case in the checkRule method intentionally does not call validateToken() as this is a temporary implementation that will be replaced with generic schema validation. The missing validation call is expected behavior during this transition period.

Learnt from: webpiratt
PR: #96
File: plugin/payroll/transaction.go:0-0
Timestamp: 2025-06-18T18:23:20.077Z
Learning: In the payroll plugin (plugin/payroll/transaction.go), signRequest.Transaction is stored with the "0x" prefix, making it compatible with gcommon.FromHex which requires 0x-prefixed hex strings.

internal/scheduler/interval.go (1)

Learnt from: RaghavSood
PR: #36
File: api/server.go:21-33
Timestamp: 2025-05-07T08:23:45.882Z
Learning: The import path github.com/vultisig/verifier/plugin refers to an external dependency that provides the plugin interface, and should not be changed to github.com/vultisig/plugin/plugin as these are distinct packages with different purposes.

internal/scheduler/storage.go (1)

Learnt from: RaghavSood
PR: #75
File: storage/postgres/db_plugin.go:25-40
Timestamp: 2025-05-30T02:44:31.711Z
Learning: The embed.FS directive //go:embed migrations/plugin/*.sql in storage/postgres/db_plugin.go correctly embeds plugin migration files that exist in the storage/postgres/migrations/plugin/ directory. The path "migrations/plugin" passed to goose.Up() references the embedded filesystem structure, not the physical file system path.

storage/postgres/migrations/plugin/20250721165241_scheduler.sql (2)

Learnt from: webpiratt
PR: #89
File: storage/postgres/migrations/plugin/20250603181247_tx_indexer.sql:13-13
Timestamp: 2025-06-11T18:42:54.241Z
Learning: In the tx_indexer table (storage/postgres/migrations/plugin/*_tx_indexer.sql), the policy_id column is intentionally not defined with a foreign-key constraint to plugin_policies(id).

Learnt from: RaghavSood
PR: #75
File: storage/postgres/db_plugin.go:25-40
Timestamp: 2025-05-30T02:44:31.711Z
Learning: The embed.FS directive //go:embed migrations/plugin/*.sql in storage/postgres/db_plugin.go correctly embeds plugin migration files that exist in the storage/postgres/migrations/plugin/ directory. The path "migrations/plugin" passed to goose.Up() references the embedded filesystem structure, not the physical file system path.

cmd/payroll/scheduler/config.go (2)

Learnt from: johnnyluo
PR: #108
File: cmd/payroll/worker/main.go:84-84
Timestamp: 2025-07-02T04:58:30.139Z
Learning: VaultServiceConfig is defined as a field of type vault_config.Config from the external package "github.com/vultisig/verifier/vault_config" in worker configuration structs across the vultisig/plugin codebase (cmd/payroll/worker/config.go, cmd/fees/worker/config.go, cmd/dca/worker/config.go). The vault_config.Config struct contains an EncryptionSecret field that can be accessed via cfg.VaultServiceConfig.EncryptionSecret.

Learnt from: johnnyluo
PR: #108
File: cmd/payroll/worker/main.go:84-84
Timestamp: 2025-07-02T04:58:30.139Z
Learning: VaultServiceConfig is defined as a field of type vault_config.Config in worker configuration structs across the vultisig/plugin codebase (cmd/payroll/worker/config.go, cmd/fees/worker/config.go, cmd/dca/worker/config.go). It contains an EncryptionSecret field that can be accessed via cfg.VaultServiceConfig.EncryptionSecret.

plugin/payroll/transaction.go (5)

Learnt from: webpiratt
PR: #96
File: plugin/payroll/transaction.go:0-0
Timestamp: 2025-06-18T18:23:20.077Z
Learning: In the payroll plugin (plugin/payroll/transaction.go), signRequest.Transaction is stored with the "0x" prefix, making it compatible with gcommon.FromHex which requires 0x-prefixed hex strings.

Learnt from: webpiratt
PR: #125
File: plugin/payroll/policy.go:160-160
Timestamp: 2025-07-09T22:23:17.348Z
Learning: In plugin/payroll/policy.go, the "token" case in the checkRule method intentionally does not call validateToken() as this is a temporary implementation that will be replaced with generic schema validation. The missing validation call is expected behavior during this transition period.

Learnt from: RaghavSood
PR: #36
File: api/server.go:21-33
Timestamp: 2025-05-07T08:23:45.882Z
Learning: The import path github.com/vultisig/verifier/plugin refers to an external dependency that provides the plugin interface, and should not be changed to github.com/vultisig/plugin/plugin as these are distinct packages with different purposes.

Learnt from: webpiratt
PR: #96
File: plugin/payroll/transaction.go:43-44
Timestamp: 2025-06-18T18:22:06.358Z
Learning: In the vultisig/plugin codebase, the hardcoded ethereumEvmChainID = big.NewInt(1) in plugin/payroll/transaction.go is intentional for the current implementation phase. The team is implementing ETH first, with plans to add other EVM chains later. The functions/methods are already designed to work with all EVM chains.

Learnt from: webpiratt
PR: #125
File: plugin/payroll/transaction.go:186-197
Timestamp: 2025-07-09T22:22:36.651Z
Learning: In plugin/payroll/transaction.go, the getTokenID function is designed to return evm.ZeroAddress.Hex() when no "token" parameter constraint is found, rather than returning an error. This is expected behavior according to the team's design intent, even though the token parameter is marked as required in the recipe specification.

plugin/payroll/scheduler.go (2)

Learnt from: webpiratt
PR: #125
File: plugin/payroll/policy.go:160-160
Timestamp: 2025-07-09T22:23:17.348Z
Learning: In plugin/payroll/policy.go, the "token" case in the checkRule method intentionally does not call validateToken() as this is a temporary implementation that will be replaced with generic schema validation. The missing validation call is expected behavior during this transition period.

Learnt from: johnnyluo
PR: #108
File: cmd/payroll/worker/main.go:84-84
Timestamp: 2025-07-02T04:58:30.139Z
Learning: VaultServiceConfig is defined as a field of type vault_config.Config from the external package "github.com/vultisig/verifier/vault_config" in worker configuration structs across the vultisig/plugin codebase (cmd/payroll/worker/config.go, cmd/fees/worker/config.go, cmd/dca/worker/config.go). The vault_config.Config struct contains an EncryptionSecret field that can be accessed via cfg.VaultServiceConfig.EncryptionSecret.

internal/scheduler/storage_pg.go (1)

Learnt from: RaghavSood
PR: #75
File: storage/postgres/db_plugin.go:25-40
Timestamp: 2025-05-30T02:44:31.711Z
Learning: The embed.FS directive //go:embed migrations/plugin/*.sql in storage/postgres/db_plugin.go correctly embeds plugin migration files that exist in the storage/postgres/migrations/plugin/ directory. The path "migrations/plugin" passed to goose.Up() references the embedded filesystem structure, not the physical file system path.

🧬 Code Graph Analysis (12)
cmd/fees/worker/main.go (1)
internal/tasks/tasks.go (1)
  • QUEUE_NAME (9-9)
cmd/dca/worker/main.go (1)
internal/tasks/tasks.go (1)
  • QUEUE_NAME (9-9)
cmd/fees/server/main.go (1)
internal/scheduler/service_nil.go (1)
  • NewNilService (15-17)
scripts/dev/create_fee_policy/dummy_server.go (1)
internal/scheduler/service_nil.go (1)
  • NewNilService (15-17)
cmd/dca/server/main.go (1)
internal/scheduler/service_nil.go (1)
  • NewNilService (15-17)
api/server.go (2)
internal/scheduler/service.go (1)
  • Service (11-15)
service/policy.go (1)
  • NewPolicyService (33-42)
plugin/payroll/interval.go (1)
internal/scheduler/interval.go (1)
  • Interval (9-11)
cmd/payroll/worker/main.go (1)
internal/tasks/tasks.go (1)
  • QUEUE_NAME (9-9)
cmd/payroll/scheduler/config.go (1)
storage/redis_storage.go (1)
  • RedisConfig (18-24)
plugin/payroll/transaction.go (1)
internal/scheduler/storage.go (1)
  • Scheduler (11-14)
plugin/payroll/scheduler.go (1)
internal/scheduler/storage.go (1)
  • Storage (16-23)
internal/scheduler/storage_pg.go (1)
internal/scheduler/storage.go (1)
  • Scheduler (11-14)
🪛 GitHub Actions: Migration Test
storage/postgres/schema/schema.sql

[error] 1-1: Schema has changed but schema.sql was not updated! Please run 'make dump-schema CONFIG=config-plugin.yaml' to update the schema file.

🪛 Checkov (3.2.334)
payroll.scheduler.example.json

[MEDIUM] 8-9: Basic Auth Credentials

(CKV_SECRET_4)

🔇 Additional comments (54)
internal/scheduler/interval.go (1)

9-11: Well-designed interface for scheduler modularity.

The Interval interface provides a clean abstraction for calculating next execution times based on plugin policies. The method signature is clear and the interface follows the single responsibility principle, making it easily testable and mockable.

go.mod (1)

23-23: Dependency update supports scheduler refactoring.

The version bump of github.com/vultisig/recipes aligns with the scheduler refactoring changes in this PR. The update likely includes supporting changes for the new scheduler abstractions.

cmd/fees/server/main.go (2)

11-11: Added scheduler import for interface injection.


108-108: Proper dependency injection of scheduler service.

Replacing the direct Redis options with scheduler.NewNilService() follows good architectural practices by injecting a scheduler service interface. The use of NilService is appropriate for the fees server, which doesn't appear to require active scheduling capabilities.

.run/[git] payroll worker.run.xml (1)

5-8: Run configuration updated to reflect architectural changes.

The removal of BASE_FILE_PATH aligns with the separation of scheduling responsibilities from the payroll worker. However, remember that GoLand run configuration files are auto-generated and should be modified through the IDE's Run/Debug Configurations UI rather than manual editing.

cmd/fees/worker/main.go (1)

59-61: Simplified queue configuration aligns with architectural separation.

Removing the "scheduled_plugin_queue" from the fees worker is appropriate given the refactoring that moves scheduling responsibilities to dedicated scheduler workers. This simplifies the worker to focus on its core fees-related tasks while maintaining the necessary tasks.QUEUE_NAME queue for plugin operations.

cmd/dca/worker/main.go (1)

47-49: LGTM! Queue configuration updated correctly.

The removal of the "scheduled_plugin_queue" aligns with the architectural refactoring that moves scheduling responsibilities to a dedicated scheduler service. The remaining configuration is correct and maintains the original plugin_queue functionality.

scripts/dev/create_fee_policy/dummy_server.go (2)

13-13: LGTM! Scheduler import added correctly.

The import of the internal scheduler package is properly added to support the new scheduler service dependency injection pattern.


124-124: LGTM! Scheduler service dependency injection implemented correctly.

The use of scheduler.NewNilService() is appropriate for a dummy server, providing a no-op scheduler implementation. This change aligns with the broader architectural refactoring that replaces direct Redis options with scheduler service interface injection.

cmd/dca/server/main.go (2)

11-11: LGTM! Scheduler import added correctly.

The import addition supports the new scheduler service dependency injection pattern implemented across the codebase.


88-88: LGTM! Scheduler service dependency injection implemented correctly.

The use of scheduler.NewNilService() is appropriate for the DCA server, providing a no-op scheduler implementation. This change is consistent with the architectural refactoring that moves from direct Redis configuration to scheduler service interface injection.

payroll.scheduler.example.json (1)

1-10: LGTM! Configuration structure is correct.

The JSON configuration file follows standard patterns for Redis and database connections. The hardcoded credentials are acceptable since this is an example configuration file for local development, as indicated by the .example.json suffix. The structure aligns with the new payroll scheduler service requirements.

.run/[git] plugin stack.run.xml (1)

3-3: LGTM! Payroll scheduler added to compound run configuration.

The addition of the "[git] payroll scheduler" configuration entry aligns with the new scheduler service introduced in this PR. The change appears to have been made through GoLand's Run/Debug Configurations UI, which is the correct approach for IDE configuration files.

internal/scheduler/policy.go (1)

10-12: Well-designed interface following Go best practices.

The PolicyFetcher interface is cleanly designed with a focused responsibility, proper context usage, and clear method signature. This abstraction will enable good testability and modularity in the scheduler system.

cmd/payroll/worker/main.go (1)

53-56: Good separation of concerns by removing scheduler from worker.

The removal of the scheduler service and its queue from the payroll worker aligns well with the architectural refactoring. This separation allows the worker to focus solely on task execution while scheduling is handled by the dedicated scheduler service.

storage/postgres/migrations/plugin/20250721165241_scheduler.sql (1)

4-10: Well-designed scheduler table with proper indexing.

The schema design is sound with policy_id as UUID primary key ensuring one entry per policy and next_execution timestamp for scheduling queries. The index on next_execution will optimize the scheduler's queries for finding due tasks.

storage/postgres/schema/schema.sql (2)

77-80: Schema addition looks correct but needs cleanup.

The new scheduler table definition matches the migration and follows the established patterns.


8-11: Consider removing trigger_status enum if no longer used.

The trigger_status enum appears to be related to the legacy time_triggers system. If the time_triggers table is being removed, this enum may also be obsolete.

cmd/payroll/server/main.go (2)

11-11: Clean integration of new scheduler package.

The import of the internal scheduler package is appropriate for accessing the new PostgreSQL storage implementation.


97-97: Well-structured dependency injection for scheduler service.

The integration of payroll.NewSchedulerService with PostgreSQL storage backend aligns perfectly with the architectural refactoring goals. This creates a clean separation between the API server and the scheduler worker while maintaining proper dependency injection.

internal/scheduler/service.go (1)

11-15: Well-designed interface for scheduler abstraction.

The interface provides a clean abstraction for scheduler operations with proper transactional support. The method signatures are consistent and follow Go conventions.

cmd/payroll/scheduler/main.go (3)

21-29: Redis client setup looks correct.

The Redis client initialization follows proper patterns with deferred cleanup to prevent resource leaks.


31-34: Database initialization is appropriate.

The PostgreSQL backend initialization follows the established pattern used elsewhere in the codebase.


36-50: Worker initialization and execution follow good practices.

The worker is properly initialized with all required dependencies and the error handling with panic is appropriate for a main function.

internal/scheduler/service_nil.go (1)

23-33: Null object pattern implementation is correct.

The no-op implementations for Create, Update, and Delete methods properly implement the null object pattern for plugins that don't require scheduling.

docker-compose.yaml (2)

118-138: New payroll-scheduler service is properly configured.

The service follows the established patterns in the compose file with appropriate dependencies, environment variables, and network configuration.


86-86: Good cleanup of trailing whitespace.

Removing trailing spaces from port mappings improves code consistency.

Also applies to: 151-151

plugin/payroll/interval.go (2)

17-40: Interval calculation logic is correct.

The method properly implements the Interval interface and handles different frequency types with appropriate date arithmetic. The error handling is well-structured.


26-26: No missing frequency constants – definitions exist in transaction.go

The frequency, daily, weekly, biWeekly, and monthly constants are defined at lines 271–274 in plugin/payroll/transaction.go. Since both files belong to the same payroll package, these package-level constants are visible in interval.go and no changes are needed.

cmd/payroll/scheduler/config.go (3)

12-17: Configuration structure looks good.

The PayrollSchedulerConfig struct properly embeds the storage.RedisConfig from the existing storage package and includes a simple database DSN configuration. The struct tags are consistent with the codebase patterns for both mapstructure and JSON serialization.


19-25: Environment variable handling is correct.

The function properly reads the VS_CONFIG_NAME environment variable with a sensible default fallback to "config". This pattern is consistent with other configuration handling in the codebase.


27-42: Configuration loading implementation is solid.

The Viper configuration setup follows best practices with proper config path, environment variable replacement, and automatic environment variable binding. Error handling appropriately wraps errors with context.

api/server.go (2)

54-54: Good dependency injection pattern.

The change from accepting redisOpts and creating a concrete scheduler internally to accepting a scheduler.Service interface improves testability and follows dependency injection principles. This aligns well with the new scheduler architecture.


58-58: Correct usage of injected scheduler service.

The policyService is now properly instantiated using the injected scheduler service interface, which maintains the decoupling from concrete implementations while ensuring the policy service has access to scheduling functionality.

Dockerfile.Payroll.scheduler (2)

14-21: Library setup and environment configuration look correct.

The download and extraction of the go-wrappers library for DKLS functionality is properly handled. The LD_LIBRARY_PATH environment variable is correctly set to include the native libraries directory, which will be needed for the scheduler's cryptographic operations.


26-31: Runtime stage properly configured.

The Ubuntu 24.04 base image is appropriate, and the installation of wget followed by cleanup of apt caches follows Docker best practices for minimizing image size. The package installation is efficient and clean.

internal/scheduler/storage.go (2)

11-14: Well-designed scheduler data model.

The Scheduler struct is appropriately simple and focused, containing only the essential fields needed for scheduling: policy identification and next execution time. The JSON tags are properly configured for serialization.


16-23: Comprehensive and well-structured storage interface.

The Storage interface provides a complete set of operations for scheduler management with proper separation between transactional and non-transactional methods. The consistent use of context for cancellation and pgx.Tx for transactional operations follows Go best practices. The interface supports both individual policy operations and bulk operations like GetPending().

plugin/payroll/scheduler.go (3)

14-22: Clean service structure and constructor.

The SchedulerService properly encapsulates the scheduler.Storage dependency and follows the dependency injection pattern. The constructor is straightforward and correctly initializes the service.


52-67: Efficient update logic with proper comparison.

The Update method efficiently compares old and new start dates before making database changes, avoiding unnecessary updates when the start date hasn't changed. The transactional approach ensures data consistency.


69-71: Simple and correct Delete implementation.

The Delete method properly delegates to the repository's transactional delete method, maintaining consistency with the other operations.

service/policy.go (5)

29-29: Good use of interface segregation principle

The change from concrete *scheduler.SchedulerService to interface scheduler.Service improves testability and follows SOLID principles.


33-33: Constructor properly updated for interface type

The constructor signature correctly accepts the interface type, maintaining consistency with the struct field change.


64-68: Proper transactional scheduler integration

The scheduler creation is correctly placed within the transaction context, ensuring atomicity between policy and scheduler operations.


84-98: Well-structured update flow with proper error handling

The update logic correctly retrieves the old policy for comparison and maintains transactional integrity by updating the scheduler before the policy.


120-124: Transactional delete maintains consistency

The scheduler deletion is properly integrated within the transaction, ensuring no orphaned scheduler entries if either operation fails.

plugin/payroll/transaction.go (1)

40-44: Correct migration to new scheduler type

The change from types.TimeTrigger to scheduler.Scheduler properly aligns with the new scheduler architecture.

internal/scheduler/storage_pg.go (4)

13-21: Well-structured storage implementation

The PostgresStorage struct and constructor follow standard patterns for database storage implementations.


34-47: Robust single record retrieval

Good use of QueryRow and defensive LIMIT 1 clause. Error handling is appropriate.


49-75: Well-implemented batch retrieval with proper resource management

The method correctly handles multiple rows with proper resource cleanup and error checking for row iteration.


101-110: Clean deletion implementation

The delete method is properly implemented with transaction support and error handling.

internal/scheduler/worker.go (3)

15-50: Well-structured worker with appropriate dependencies

The Worker struct properly encapsulates all necessary dependencies, and the constructor follows good initialization patterns.


52-85: Robust worker lifecycle management

The implementation properly handles graceful shutdown and continues operation despite individual enqueue errors, which is appropriate for a scheduler worker.


117-127: Appropriate task configuration for scheduler

Good choice to set MaxRetry(0) - scheduled tasks should not be retried automatically. The 5-minute timeout and 10-minute retention are reasonable defaults.

Comment thread internal/scheduler/service_nil.go Outdated
Comment thread internal/scheduler/worker.go
Comment thread plugin/payroll/scheduler.go
Comment thread plugin/payroll/scheduler.go
Comment thread plugin/payroll/transaction.go Outdated
johnnyluo
johnnyluo previously approved these changes Jul 25, 2025

@johnnyluo johnnyluo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

RaghavSood
RaghavSood previously approved these changes Jul 25, 2025

@RaghavSood RaghavSood left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

# Conflicts:
#	go.mod
#	go.sum
#	plugin/payroll/transaction.go
@webpiratt
webpiratt dismissed stale reviews from RaghavSood and johnnyluo via 65f43d1 July 29, 2025 12:15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
storage/postgres/schema/schema.sql (1)

75-76: Timestamp storage lacks timezone information.
next_execution is declared as timestamp without time zone. Schedulers usually run in UTC or need to respect user time-zones. Storing values WITH TIME ZONE prevents silent, locale-dependent conversions.

Confirm that every insert/query normalises to UTC; otherwise switch to timestamptz.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65f43d1 and cf25938.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (3)
  • go.mod (1 hunks)
  • plugin/payroll/transaction.go (2 hunks)
  • storage/postgres/schema/schema.sql (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • plugin/payroll/transaction.go
  • go.mod
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: webpiratt
PR: vultisig/plugin#141
File: plugin/payroll/transaction.go:208-267
Timestamp: 2025-07-24T19:34:47.441Z
Learning: In the vultisig/plugin codebase, duplicate transaction prevention for the payroll plugin has been moved to the verifier side, so the local `IsAlreadyProposed` check in plugin/payroll/transaction.go is no longer needed. The validation flow has changed and is now handled by the verifier rather than within the plugin itself.
Learnt from: webpiratt
PR: vultisig/plugin#125
File: plugin/payroll/policy.go:160-160
Timestamp: 2025-07-09T22:23:17.348Z
Learning: In plugin/payroll/policy.go, the "token" case in the checkRule method intentionally does not call validateToken() as this is a temporary implementation that will be replaced with generic schema validation. The missing validation call is expected behavior during this transition period.
storage/postgres/schema/schema.sql (4)

Learnt from: webpiratt
PR: #89
File: storage/postgres/migrations/plugin/20250603181247_tx_indexer.sql:13-13
Timestamp: 2025-06-11T18:42:54.241Z
Learning: In the tx_indexer table (storage/postgres/migrations/plugin/*_tx_indexer.sql), the policy_id column is intentionally not defined with a foreign-key constraint to plugin_policies(id).

Learnt from: johnnyluo
PR: #82
File: internal/types/time_trigger.go:22-22
Timestamp: 2025-06-03T23:38:43.733Z
Learning: In the vultisig/plugin codebase, enum types like rtypes.ScheduleFrequency are stored as integers (INT) in the PostgreSQL database rather than as text strings, providing better type safety and performance.

Learnt from: webpiratt
PR: #126
File: plugin/fees/policy.go:26-26
Timestamp: 2025-07-10T20:41:44.025Z
Learning: The constant vtypes.PluginVultisigFees_feee in the vultisig/plugin project is correctly spelled with "feee" (including the extra 'e'). This is the actual constant name defined in the external dependency github.com/vultisig/verifier/types and should not be changed to "fee" singular, as this is how it's defined in the external package.

Learnt from: garry-sharp
PR: #117
File: plugin/fees/constraints.go:3-3
Timestamp: 2025-07-04T10:50:42.832Z
Learning: For the vultisig/plugin project, use "fee" (singular) throughout the codebase wherever possible, not "fees" (plural). This applies to all naming conventions including plugin types, variable names, function names, comments, and documentation.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: migration-test
🔇 Additional comments (2)
storage/postgres/schema/schema.sql (2)

123-123: Index choice looks good.
idx_scheduler_next_execution enables efficient polling for the next due job and is aligned with the expected access pattern.


73-76: Enforce referential integrity or document intentional omission for scheduler.policy_id

The scheduler table defines policy_id as its primary key but omits a foreign-key to plugin_policies(id), just like tx_indexer. If this is a deliberate pattern (e.g. for performance or decoupling), please add a brief comment next to the DDL in both the schema and migration so future maintainers aren’t left guessing. Otherwise, enforce the FK with cascade:

ALTER TABLE ONLY "scheduler"
  ADD CONSTRAINT "scheduler_policy_id_fkey"
  FOREIGN KEY ("policy_id")
  REFERENCES "plugin_policies"("id")
  ON DELETE CASCADE;

Key locations to update:

  • storage/postgres/schema/schema.sql (CREATE TABLE at lines 73–76; ALTER TABLE near line 106)
  • storage/postgres/migrations/plugin/20250721165241_scheduler.sql (CREATE TABLE IF NOT EXISTS scheduler …)

@webpiratt
webpiratt merged commit 2fb19f1 into main Jul 29, 2025
3 checks passed
@webpiratt
webpiratt deleted the 132-fix-time-triggers branch July 29, 2025 12:56
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix time_triggers and scheduling logic in Payroll

3 participants