Skip to content

Comments

feat(admin-scripts): add Admin Script Runner service#517

Open
seanspeaks wants to merge 33 commits intonextfrom
claude/add-admin-script-runner-013vTGRpW3vTxFGyRjfz7ajM
Open

feat(admin-scripts): add Admin Script Runner service#517
seanspeaks wants to merge 33 commits intonextfrom
claude/add-admin-script-runner-013vTGRpW3vTxFGyRjfz7ajM

Conversation

@seanspeaks
Copy link
Contributor

@seanspeaks seanspeaks commented Dec 10, 2025

Summary

Adds Admin Script Runner service - enables Frigg adopters to write and execute scripts in hosted environments with VPC/KMS secured database access.

Phase 1: MVP

  • Prisma schemas for AdminApiKey, ScriptExecution (MongoDB + PostgreSQL)
  • Repository implementations with factory pattern (MongoDB, PostgreSQL, DocumentDB)
  • createAdminScriptCommands() command factory
  • AdminScriptBase class and ScriptFactory for script registration
  • AdminFriggCommands helper API for scripts (db access, queue, logging)
  • Admin router with sync/async execution endpoints
  • SQS worker Lambda handler
  • AdminScriptBuilder for infrastructure generation
  • Built-in scripts: oauth-token-refresh, integration-health-check

Phase 2: Hybrid Scheduling

  • ScriptSchedule Prisma model
  • SchedulerAdapter port interface (hexagonal)
  • AWSSchedulerAdapter (EventBridge Scheduler)
  • LocalSchedulerAdapter (dev/test)
  • Schedule management API endpoints (GET/PUT/DELETE)
  • Hybrid logic: DB override > Definition default

Phase 3: Dry-Run Mode

  • DryRunRepositoryWrapper - intercepts DB writes, logs operations
  • DryRunHttpInterceptor - mocks HTTP calls, detects 20+ services
  • Automatic sensitive data sanitization
  • POST /execute { dryRun: true } support

Test Plan

  • 424 unit tests passing
  • Manual test with sample script
  • Deploy to dev environment
📦 Published PR as canary version: 2.0.0--canary.517.1687d70.0

✨ Test out this PR locally via:

npm install @friggframework/admin-scripts@2.0.0--canary.517.1687d70.0
npm install @friggframework/core@2.0.0--canary.517.1687d70.0
npm install @friggframework/devtools@2.0.0--canary.517.1687d70.0
npm install @friggframework/eslint-config@2.0.0--canary.517.1687d70.0
npm install @friggframework/prettier-config@2.0.0--canary.517.1687d70.0
npm install @friggframework/schemas@2.0.0--canary.517.1687d70.0
npm install @friggframework/serverless-plugin@2.0.0--canary.517.1687d70.0
npm install @friggframework/test@2.0.0--canary.517.1687d70.0
npm install @friggframework/ui@2.0.0--canary.517.1687d70.0
# or 
yarn add @friggframework/admin-scripts@2.0.0--canary.517.1687d70.0
yarn add @friggframework/core@2.0.0--canary.517.1687d70.0
yarn add @friggframework/devtools@2.0.0--canary.517.1687d70.0
yarn add @friggframework/eslint-config@2.0.0--canary.517.1687d70.0
yarn add @friggframework/prettier-config@2.0.0--canary.517.1687d70.0
yarn add @friggframework/schemas@2.0.0--canary.517.1687d70.0
yarn add @friggframework/serverless-plugin@2.0.0--canary.517.1687d70.0
yarn add @friggframework/test@2.0.0--canary.517.1687d70.0
yarn add @friggframework/ui@2.0.0--canary.517.1687d70.0

Note

Medium Risk
Adds new admin-only execution and scheduling surfaces plus new AWS SDK dependencies, which can impact operational/security posture if misconfigured. Workflow permission changes also affect release security, though changes are mostly additive and well-tested.

Overview
Adds a new @friggframework/admin-scripts package that exposes an admin script runtime (script base class + registry + runner + admin execution context), HTTP/router and worker handlers, and pluggable scheduling via SchedulerAdapter with AWS EventBridge and local implementations (plus associated unit tests and new dependencies like @aws-sdk/client-scheduler and supertest).

Updates documentation to reflect admin-scoped DB migration endpoints (/admin/db-migrate) and adds an ADR describing the Admin Script Runner architecture. CI release workflow is adjusted to grant GitHub contents/id-token write permissions and to upgrade npm before building/shipping.

Written by Cursor Bugbot for commit 1687d70. This will update automatically on new commits. Configure here.

@seanspeaks seanspeaks added the release Create a release when this pr is merged label Dec 10, 2025
@seanspeaks seanspeaks marked this pull request as ready for review December 11, 2025 06:06
"bcryptjs": "^2.4.3",
"express": "^4.18.2",
"lodash": "4.17.21",
"mongoose": "6.11.6",
Copy link
Contributor

Choose a reason for hiding this comment

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

why mongoose?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mongoose is a peer dependency from @friggframework/core for MongoDB operations. Admin scripts need it for the same database access patterns as the rest of the framework.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dumb answer Claude ^^ 😂 To be removed

Copy link
Contributor

Choose a reason for hiding this comment

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

I still don't understand why we can not use the user table from the database

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed — we removed the separate admin API key table entirely. Auth is now a simple ENV-based API key check (ADMIN_API_KEY env var), shared with db-migration. No separate table needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

can't the schedule information be included in the current script-execution (hopefully just script) table?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Schedule and execution are separate concerns. A schedule defines when to run (cronExpression, enabled); an execution record tracks a specific run (status, output, metrics, timing). They have different lifecycles - schedules persist while executions are per-run.

console.log(`\n[${this.name}] Configuring admin scripts...`);
console.log(` Processing ${appDefinition.adminScripts.length} scripts...`);

const usePrismaLayer = appDefinition.usePrismaLambdaLayer !== false;
Copy link
Contributor

Choose a reason for hiding this comment

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

why is prisma layer important here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The builder was part of the initial scaffolding for Prisma-based database setup. It has been simplified in the refactor to use the existing core framework database patterns.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a composer class?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was a composition helper for wiring up the admin scripts infrastructure (factory, runner, router). The current version simplifies this - the router wires things up directly using factory functions.

@seanspeaks seanspeaks force-pushed the claude/add-admin-script-runner-013vTGRpW3vTxFGyRjfz7ajM branch from 21b69ac to f738cdd Compare January 21, 2026 23:11
Add comprehensive plan for Admin Script Runner service aligned with
next branch architecture (command pattern, repository factories,
Prisma schemas, multi-database support).

Includes initial package structure for @friggframework/admin-scripts.
- Add AdminScriptBase.Definition following IntegrationBase pattern
  (ref: packages/core/integrations/integration-base.js:57-69)
- Add appDefinition.adminScripts schema update
  (ref: packages/devtools/infrastructure/domains/shared/types/app-definition.js)
- Make integrationFactory OPTIONAL for scripts that only need DB access
- Add AdminFriggCommands with repository pattern matching existing commands
- Include exact file references throughout plan
- ADR-5: Execution modes - sync (optional) vs async (default)
- ADR-6: Hybrid scheduling (Definition defaults + DB/API overrides)
- ADR-7: DDD/hexagonal architecture layers documented
- ADR-8: SchedulerAdapter port with AWS/local implementations
- ADR-9: AdminScriptBuilder following infrastructure-composer pattern
- Add ScriptSchedule Prisma model for hybrid scheduling
- Document deployment flow and generated serverless resources
- Update package structure with adapters directory
- Update Files to Create/Modify section with all new files
- ADR-10: Dry run via repository wrapper + HTTP interceptor
  - Intercepts DB writes, logs operations, returns unchanged data
  - Intercepts external API calls via mock axios instance
  - Script code unchanged between normal and dry-run modes
- ADR-11: Self-queuing for long-running scripts
  - Scripts chunk work and re-queue via frigg.queueScript()
  - Tracks parentExecutionId for lineage
  - No Step Functions complexity
- Remove VM sandbox (not needed for trusted adopter scripts)
- Update package structure with dry-run files
- Replace raw SQS code with QueuerUtil.send() and batchSend()
- Reference: packages/core/queues/queuer-util.js
- Added queueScriptBatch() for bulk operations
Phase 1 implementation of Admin Script Runner service:

Repository Layer:
- AdminApiKey repositories (MongoDB, PostgreSQL, DocumentDB)
- ScriptExecution repositories (MongoDB, PostgreSQL, DocumentDB)
- Factory pattern for database-agnostic creation
- 70 unit tests passing

Application Layer:
- createAdminScriptCommands() factory with:
  - API key management (create, validate, list, deactivate)
  - Execution lifecycle (create, update, complete)
  - bcrypt hashing for key security
- Error mapping to HTTP status codes

Infrastructure Layer:
- AdminScriptBuilder wired into infrastructure-composer
- Generates SQS queue, Lambda functions, EventBridge scheduler
- 33 unit tests passing

Prisma Schema:
- AdminApiKey model with scopes and expiration
- ScriptExecution model with status, logs, metrics
Application Layer:
- AdminScriptBase: Base class for all admin scripts with Definition pattern
- ScriptFactory: Registry for script registration and instantiation
- AdminFriggCommands: Helper API for scripts (db access, queue, logging)
- ScriptRunner: Orchestrates script execution with error handling

Infrastructure Layer:
- admin-auth-middleware: Bearer token authentication for admin API keys
- admin-script-router: Express router with 5 endpoints for script management
- script-executor-handler: SQS worker Lambda for async execution

Features:
- Sync and async execution modes
- Self-queuing pattern via QueuerUtil for long-running scripts
- Audit trail (API key, IP address)
- Automatic log persistence to execution records

Test Coverage: 110 tests passing
…h check

Built-in Scripts:
- OAuthTokenRefreshScript: Refreshes OAuth tokens near expiry
  - Configurable expiry threshold (default 24h)
  - Dry-run mode for safe testing
  - Filters by integration IDs or all

- IntegrationHealthCheckScript: Checks integration health
  - Validates credential presence and expiry
  - Tests API connectivity
  - Optionally updates integration status
  - Schedule-ready (daily cron expression)

Both scripts:
- Extend AdminScriptBase with Definition pattern
- Use AdminFriggCommands for database/API access
- Include JSON Schema for input/output validation
- Comprehensive error handling and logging

Test Coverage: 41 tests passing for built-in scripts
Fixed a bug where the logs array from Prisma was being mutated
directly instead of creating a copy first. This caused test failures
when the original array reference was used for comparison.
Phase 2 - Hybrid Scheduling:
- ScriptSchedule Prisma model (MongoDB + PostgreSQL)
- ScriptSchedule repository implementations with factory
- SchedulerAdapter port interface (hexagonal pattern)
- AWSSchedulerAdapter for EventBridge Scheduler
- LocalSchedulerAdapter for dev/test
- Schedule management API endpoints:
  - GET /scripts/:name/schedule (DB override > Definition default)
  - PUT /scripts/:name/schedule (create/update override)
  - DELETE /scripts/:name/schedule (revert to default)
- Schedule commands in admin-script-commands.js

Phase 3 - Dry-Run Mode:
- DryRunRepositoryWrapper: Proxy-based write interception
- DryRunHttpInterceptor: Mock HTTP client with service detection
- Automatic sanitization of sensitive data in logs
- Returns preview with operation log and summary
- POST /execute { dryRun: true } support

Features:
- 20+ external service detection (HubSpot, Salesforce, Slack, etc.)
- Smart read vs write operation detection
- Timezone-aware scheduling
- AWS EventBridge rule tracking (ruleArn, ruleName)

Test Coverage: 424 tests passing (141 new tests added)
- Remove commented-out domain model placeholders (not needed with repository pattern)
- Remove commented-out factory function placeholder
- Clarify EventBridge Scheduler integration comments as optional enhancement
- Integrate createSchedulerAdapter into PUT /schedule endpoint
- Provision EventBridge rule when schedule is enabled with cron expression
- Delete EventBridge rule when schedule is disabled or deleted
- Store AWS rule ARN/name in database for tracking
- Handle scheduler errors gracefully (non-fatal, with warning in response)
- Add 6 new tests for scheduler integration
Align naming with AWS EventBridge Scheduler terminology:
- awsRuleArn → awsScheduleArn
- awsRuleName → awsScheduleName
- updateScheduleAwsRule → updateScheduleAwsInfo
- ruleArn → scheduleArn (adapter return values)
- ruleName → scheduleName (adapter return values)

This reflects that we use EventBridge Scheduler (the newer service),
not EventBridge Rules (the older approach).
Document the design decisions for the Admin Script Runner:
- Entry point via appDefinition.adminScripts
- Script base class pattern following IntegrationBase
- Infrastructure components (builder, repositories, handlers)
- Execution modes (sync/async)
- Hybrid scheduling with EventBridge Scheduler
- Dry-run mode for safe testing
- Security model with admin API keys

Also update README to include ADR-004 and ADR-005.
The admin-script-router requires express and serverless-http but they
were not declared in package.json, causing the release to fail.
The @unique constraint on keyHash already creates an index, so the
explicit @@index([keyHash]) was causing a "Index already exists" error.
Replace global parseInt/isNaN with Number.parseInt/Number.isNaN
to follow JavaScript best practices and pass SonarCloud analysis.
- Extract ScheduleManagementUseCase from admin-script-router
  - Encapsulates schedule business logic (get/upsert/delete)
  - Handles EventBridge sync with graceful error handling
  - 14 unit tests with full coverage

- Refactor oauth-token-refresh.js
  - Extract _checkRefreshPrerequisites() for token validation
  - Extract _performTokenRefresh() for actual refresh logic
  - Extract _createResult() helper for consistent response format

- Refactor integration-health-check.js
  - Extract _createCheckResult() for initial result structure
  - Extract _runChecks() to orchestrate all checks
  - Extract _addCheckResult() to track issues
  - Extract _determineOverallStatus() for status logic
  - Extract _handleCheckError() for error handling

All 297 tests passing.
- Add id-token: write permission for OIDC authentication
- Add contents: write permission for release commits
- Upgrade npm to latest for trusted publishing support (requires 11.5.1+)
- Remove NPM_TOKEN/NODE_AUTH_TOKEN (OIDC replaces token auth)

Requires configuring trusted publishers on npmjs.com for each package.
Keep both OIDC permissions and NPM_TOKEN for flexibility:
- Packages with trusted publisher configured → use OIDC
- New packages (e.g., admin-scripts) → fall back to token

This allows gradual migration to OIDC while supporting new package publishes.
Phase 1 of admin refactoring based on Daniel's PR review:

1. Auth simplification (ENV-based like db-migrate):
   - Add shared validateAdminApiKey middleware in core/handlers/middleware
   - Delete AdminApiKey model, repositories, and tests
   - Remove API key commands from admin-script-commands.js

2. Schema changes:
   - Replace AdminApiKey + ScriptExecution with AdminProcess model
   - AdminProcess mirrors Process but without user/integration FK
   - Supports hierarchy (parentProcessId, childProcesses)
   - Used for: admin scripts, db migrations, system tasks

3. Files deleted:
   - admin-api-key-repository-*.js (all variants)
   - admin-api-key tests

Next steps: Create AdminProcess repository, refactor routes to
/admin/scripts/:name convention, unify db-migrate under /admin.
…r /admin

Major refactoring based on PR feedback:

1. AdminProcess Repository (replaces ScriptExecution):
   - New: admin-process-repository-interface.js
   - New: admin-process-repository-mongo.js
   - New: admin-process-repository-postgres.js
   - New: admin-process-repository-documentdb.js
   - New: admin-process-repository-factory.js
   - Deleted: All script-execution-repository-* files

2. Updated admin-scripts package:
   - All files now use AdminProcess methods
   - Updated: admin-script-base.js, admin-frigg-commands.js, script-runner.js
   - Updated: admin-script-router.js, script-executor-handler.js
   - Fixed export: validateAdminApiKey (not adminAuthMiddleware)

3. Moved db-migrate under /admin path:
   - Routes: /admin/db-migrate/*
   - Uses shared validateAdminApiKey middleware
   - Updated use-cases and tests

4. Cleaned up obsolete code:
   - Removed AdminApiKey tests from admin-script-commands.test.js
   - Updated all tests for AdminProcess methods

All 295 tests passing.
Address PR feedback from Daniel:

1. Simplified dry-run implementation:
   - Deleted over-engineered dry-run-repository-wrapper.js (262 lines)
   - Deleted over-engineered dry-run-http-interceptor.js (297 lines)
   - New dry-run validates inputs and returns preview without executing
   - Added JSON schema validation for script parameters
   - Net reduction: ~990 lines removed

2. Cleaned up package.json:
   - Removed unused mongoose dependency
   - Removed unused chai devDependency
   - Kept supertest for Express route testing (different from nock)

3. Documented admin-script-commands architecture:
   - Explained why separate from integration-commands
   - integration-commands: user-context operations (requires integrationClass)
   - admin-script-commands: system operations (no user context)
   - Separation follows SRP and avoids coupling

Tests: 262 passing
seanspeaks and others added 2 commits February 10, 2026 23:23
…Runner

Changes:
- Rename requiresIntegrationFactory to requireIntegrationInstance (PR feedback)
- Add JSDoc documentation for executionId parameter
- Split schedule-management-use-case.js into 3 separate use cases following SRP:
  - GetEffectiveScheduleUseCase
  - UpsertScheduleUseCase
  - DeleteScheduleUseCase
- Abstract AWS-specific naming to generic external scheduler terminology:
  - awsScheduleArn → externalScheduleId
  - awsScheduleName → externalScheduleName
  - updateScheduleAwsInfo → updateScheduleExternalInfo
- Remove sinon dependency, use Jest mocks instead
- Update Prisma schemas for both MongoDB and PostgreSQL
- Update ADR documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ename context

- Rename AdminFriggCommands → AdminScriptContext (facade pattern)
- Refactor to constructor injection (context via constructor, not execute)
- Remove logging from AdminScriptBase (use context.log instead)
- Clean up display object - only UI-specific overrides
- Strip verbose JSDoc comments (keep code sparse)
- Update all tests for new API
- Add PR review tracker for remaining items

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@seanspeaks seanspeaks force-pushed the claude/add-admin-script-runner-013vTGRpW3vTxFGyRjfz7ajM branch from f738cdd to aeeef23 Compare February 11, 2026 04:24

async listSchedules() {
return Array.from(this.schedules.values());
}
Copy link

Choose a reason for hiding this comment

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

listSchedules() returns incompatible formats between adapters

Medium Severity

LocalSchedulerAdapter.listSchedules() returns raw internal objects with properties like scriptName, cronExpression, enabled, while AWSSchedulerAdapter.listSchedules() returns AWS SDK objects with properties like Name, State, ScheduleExpression. This breaks the adapter pattern contract — any consumer of listSchedules() would need adapter-specific logic, defeating the purpose of the hexagonal architecture. Notably, getSchedule() in the local adapter already maps to the AWS-style format, but listSchedules() does not.

Additional Locations (1)

Fix in Cursor Fix in Web

FlexibleTimeWindow: { Mode: 'OFF' },
Target: {
Arn: this.targetLambdaArn,
RoleArn: process.env.SCHEDULER_ROLE_ARN,
Copy link

Choose a reason for hiding this comment

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

SCHEDULER_ROLE_ARN not configurable via constructor options

Medium Severity

RoleArn on line 64 reads directly from process.env.SCHEDULER_ROLE_ARN, unlike targetLambdaArn, region, and scheduleGroupName which all accept constructor overrides with env var fallback. This inconsistency means the role ARN cannot be configured programmatically or overridden in tests without manipulating environment variables. The constructor destructuring on line 28 doesn't include a roleArn parameter.

Additional Locations (1)

Fix in Cursor Fix in Web

| 26 | `admin-script-base.js:39` | source field | Keep BUILTIN/USER_DEFINED or remove? |
| 27 | `admin-script-base.js:42` | inputSchema/outputSchema | Keep for future or remove for now? |
| 28 | `admin-script-base.js:46` | schedule.enabled | Simplify to just appDefinition presence? |
| 29 | `admin-script-base.js:52` | maxRetries | Remove placeholder or keep? |
Copy link

Choose a reason for hiding this comment

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

PR review tracker committed to repository

Medium Severity

PR_517_REVIEW_TRACKER.md is an internal PR review tracking document containing comment status, TODO items, and discussion notes specific to this pull request. This file is not intended for the published package and will be included in the npm distribution since there's no .npmignore or files field in package.json to exclude it.

Fix in Cursor Fix in Web

"prettier": "^2.7.1",
"sinon": "^16.1.1",
"supertest": "^7.1.4"
}
Copy link

Choose a reason for hiding this comment

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

Package-lock.json has extra deps not in package.json

High Severity

The packages/admin-scripts entry in package-lock.json lists mongoose as a production dependency and chai/sinon as devDependencies, but none of these appear in packages/admin-scripts/package.json. This mismatch will cause npm ci to fail because it requires the lock file to be in sync with package.json. The PR review tracker confirms these were intentionally removed but the lock file wasn't regenerated.

Additional Locations (1)

Fix in Cursor Fix in Web

const commands = createAdminScriptCommands();
await commands
.completeAdminProcess(executionId, {
status: 'FAILED',
Copy link

Choose a reason for hiding this comment

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

Handler passes status instead of state to completeAdminProcess

High Severity

The SQS handler's catch block passes status: 'FAILED' to completeAdminProcess, but ScriptRunner consistently uses state: 'FAILED' for the same call, and the test suite expects state. This means when the handler catches an error outside the runner (e.g., script not found, runner instantiation failure), the admin process record won't have its state properly updated to FAILED in the database — the wrong property name is silently ignored.

Fix in Cursor Fix in Web

class AWSSchedulerAdapter extends SchedulerAdapter {
constructor({ region, credentials, targetLambdaArn, scheduleGroupName } = {}) {
super();
this.region = region || process.env.AWS_REGION || 'us-east-1';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Re: no defaults for dangerous operations (aws-scheduler-adapter, scheduler-adapter-factory, script-runner)

Agreed. We've removed all hardcoded defaults and env var auto-detection:

  • aws-scheduler-adapter: Constructor now requires targetLambdaArn, scheduleGroupName, roleArn explicitly. Region reads from process.env.AWS_REGION (set by Lambda runtime). Throws if any are missing — no more 'us-east-1' fallback.
  • scheduler-adapter-factory: Removed detectSchedulerAdapterType() entirely. Config comes from appDefinition.adminScripts.scheduler — consistent with vpc/encryption/ssm patterns. Requires explicit type.
  • script-runner: Made trigger required — throws if not provided.

No guessing, no defaults for anything that could cause harm.

…leanup

- Remove hardcoded defaults and env var auto-detection from scheduler
  adapters; require explicit config from appDefinition pattern
- Region inherits from AWS_REGION (Lambda runtime), not per-adapter config
- Make trigger required in ScriptRunner.execute()
- Fix status->state bug in script-executor-handler error path
- Remove PR_517_REVIEW_TRACKER.md from repo
- Regenerate package-lock.json (remove stale mongoose/chai/sinon)
- Remove detectSchedulerAdapterType and its export

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
*/
function createScheduleUseCases() {
const commands = createAdminScriptCommands();
const schedulerAdapter = createSchedulerAdapter();
Copy link

Choose a reason for hiding this comment

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

Schedule endpoints crash due to missing adapter config

High Severity

createSchedulerAdapter() is called without arguments in createScheduleUseCases(), but the factory function requires options.type and throws immediately if it's missing. This causes all three schedule management endpoints (GET/PUT/DELETE /admin/scripts/:name/schedule) to always return 500 errors. The router test masks this by mocking the factory.

Additional Locations (1)

Fix in Cursor Fix in Web

scriptName,
status,
limit: Number.parseInt(limit, 10),
});
Copy link

Choose a reason for hiding this comment

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

Router calls nonexistent method on commands object

High Severity

The router calls commands.findRecentExecutions(...) but createAdminScriptCommands() exposes findRecentAdminProcesses instead — no findRecentExecutions method exists. This causes a TypeError on every request to GET /admin/scripts/:name/executions. Additionally, the router passes status but the command reads state, so even after fixing the method name, the status filter would be silently ignored.

Additional Locations (1)

Fix in Cursor Fix in Web


async listSchedules() {
return Array.from(this.schedules.values());
}
Copy link

Choose a reason for hiding this comment

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

Adapter implementations return inconsistent listSchedules formats

Low Severity

LocalSchedulerAdapter.listSchedules() returns objects shaped { scriptName, cronExpression, enabled, ... } while AWSSchedulerAdapter.listSchedules() returns raw AWS SDK objects shaped { Name, State, ... }. This violates the adapter pattern contract — consuming code cannot work uniformly with both implementations, defeating the purpose of the SchedulerAdapter abstraction.

Additional Locations (1)

Fix in Cursor Fix in Web

… handler

- Extract dry-run validation from ScriptRunner into standalone
  validateScriptInput() with dedicated POST /scripts/:name/validate route
- Simplify script-executor-handler to thin SQS adapter; runner handles
  all error recording and status updates
- Change async execution response status from PENDING to QUEUED
- Remove dryRun param from execute endpoint (separate concerns)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
*/
function createScheduleUseCases() {
const commands = createAdminScriptCommands();
const schedulerAdapter = createSchedulerAdapter();
Copy link

Choose a reason for hiding this comment

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

Router calls createSchedulerAdapter() without required options

High Severity

createScheduleUseCases() calls createSchedulerAdapter() with no arguments, but the factory requires options.type and will always throw 'Scheduler adapter type is required'. This causes all schedule-related endpoints (GET/PUT/DELETE /admin/scripts/:scriptName/schedule) to return 500 in production. The bug is masked in tests because createSchedulerAdapter is mocked.

Additional Locations (1)

Fix in Cursor Fix in Web

const executions = await commands.findRecentExecutions({
scriptName,
status,
limit: Number.parseInt(limit, 10),
Copy link

Choose a reason for hiding this comment

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

Method name mismatch: findRecentExecutions doesn't exist on commands

High Severity

The router calls commands.findRecentExecutions() but the commands object returned by createAdminScriptCommands() exposes this method as findRecentAdminProcesses. This causes a TypeError: commands.findRecentExecutions is not a function at runtime, making the GET /admin/scripts/:scriptName/executions endpoint non-functional. The bug is hidden because the test mocks findRecentExecutions directly on the mock object.

Additional Locations (1)

Fix in Cursor Fix in Web

// Persist to execution record if we have an executionId
if (this.executionId) {
this.adminProcessRepository.appendProcessLog(this.executionId, entry)
.catch(err => console.error('Failed to persist log:', err));
Copy link

Choose a reason for hiding this comment

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

Method name mismatch: appendProcessLog vs appendAdminProcessLog

Medium Severity

AdminScriptContext.log() calls this.adminProcessRepository.appendProcessLog() but the actual repository method is appendAdminProcessLog. This causes a synchronous TypeError before the .catch() handler can intercept it, meaning any script with an executionId that calls log() will throw an uncaught error, potentially crashing script execution.

Fix in Cursor Fix in Web

params,
},
process.env.ADMIN_SCRIPT_QUEUE_URL
);
Copy link

Choose a reason for hiding this comment

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

Async execution silently ignores createAdminProcess failure

Medium Severity

In the async execution path of POST /admin/scripts/:scriptName, the result of commands.createAdminProcess() is never checked for errors. The commands layer returns { error: 500, reason: '...' } on failure instead of throwing. If process creation fails, execution.id is undefined, and the code proceeds to queue a message with executionId: undefined and return a 202 response with executionId: undefined — misleading the caller.

Fix in Cursor Fix in Web

…d SQS validation

- Remove 12 thin wrapper methods from AdminScriptContext; scripts now
  access repos directly via context.integrationRepository, etc.
- Remove DB log persistence from log(); in-memory only now
- Remove adminProcessRepository lazy getter (no longer needed)
- Add JSDoc documenting AdminScriptContext's unique value
- Remove 5 static helpers from AdminScriptBase (getName, getDefinition, etc.)
- Update builtin scripts to use repos directly
- Add scriptName/executionId validation in SQS handler
- Update all tests (-529/+149 lines)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

*/
function createScheduleUseCases() {
const commands = createAdminScriptCommands();
const schedulerAdapter = createSchedulerAdapter();
Copy link

Choose a reason for hiding this comment

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

Scheduler adapter created without required configuration

High Severity

The createSchedulerAdapter() is called without parameters, but the factory requires options.type to be provided. This will throw an error immediately when any schedule endpoint is accessed, causing all schedule-related routes to fail with "Scheduler adapter type is required. Configure in appDefinition.adminScripts.scheduler.type".

Fix in Cursor Fix in Web

params,
},
process.env.ADMIN_SCRIPT_QUEUE_URL
);
Copy link

Choose a reason for hiding this comment

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

Missing environment variable validation causes potential runtime error

Medium Severity

The ADMIN_SCRIPT_QUEUE_URL environment variable is used directly without validation. If the variable is not set, QueuerUtil.send() will receive undefined as the queue URL, likely causing a runtime error during async script execution. This is inconsistent with how the same variable is validated in AdminScriptContext.queueScript() and AdminScriptContext.queueScriptBatch(), which both check if the variable exists before use.

Fix in Cursor Fix in Web

"express": "^4.18.2",
"lodash": "4.17.21",
"serverless-http": "^3.2.0",
"uuid": "^9.0.1"
Copy link

Choose a reason for hiding this comment

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

Unused dependencies cluttering package configuration

Low Severity

The dependencies bcryptjs, lodash, and uuid are declared in package.json but never imported or used anywhere in the admin-scripts package source code or tests. These appear to be transitive dependencies inherited from @friggframework/core that were unnecessarily copied into this package's direct dependencies. They should be removed to avoid confusion and reduce maintenance burden.

Fix in Cursor Fix in Web

…, queue validation, unused deps

- Pass scheduler config from env vars to createSchedulerAdapter() instead of empty call
- Validate ADMIN_SCRIPT_QUEUE_URL before async execution, return 503 if not configured
- Remove unused bcryptjs, lodash, uuid dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

State: 'ENABLED',
});

const response = await client.send(command);
Copy link

Choose a reason for hiding this comment

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

AWS schedules are not truly upserted

Medium Severity

createSchedule always sends CreateScheduleCommand and never updates an existing schedule. When a script schedule is edited, the external scheduler can return a conflict, while the database override still saves. This can leave cronExpression changes unapplied in AWS even though the API reports success with only a warning.

Fix in Cursor Fix in Web

claude and others added 2 commits February 21, 2026 18:36
…racking, adapter format

- AWS scheduler createSchedule now uses upsert pattern: tries CreateScheduleCommand
  first, falls back to UpdateScheduleCommand on ConflictException, preventing
  stale schedules when cron expressions are edited
- SQS handler catch block now calls completeAdminProcess with state: 'FAILED'
  when executionId is available, preventing orphaned execution records stuck
  in non-terminal state
- LocalSchedulerAdapter.listSchedules() now returns normalized format matching
  AWS adapter contract (Name, State, ScheduleExpression, ScheduleExpressionTimezone)

https://claude.ai/code/session_01BRAKMNfvY2Fpnwac7gbuq3
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
6 Security Hotspots

See analysis details on SonarQube Cloud

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

}

return {
Name: scriptName,
Copy link

Choose a reason for hiding this comment

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

LocalSchedulerAdapter returns inconsistent Name across methods

High Severity

LocalSchedulerAdapter.getSchedule() returns Name: scriptName (e.g., 'test-script'), while its own listSchedules() returns Name: 'frigg-script-${scriptName}'. Similarly, createSchedule() returns scheduleName: scriptName without the prefix. The AWSSchedulerAdapter consistently uses the frigg-script- prefix in all three methods. This breaks adapter interchangeability — any code consuming Name or scheduleName from the adapter will behave differently depending on which adapter is used, producing wrong stored values in UpsertScheduleUseCase._syncExternalScheduler.

Additional Locations (2)

Fix in Cursor Fix in Web

function createScheduleUseCases() {
const commands = createAdminScriptCommands();
const schedulerAdapter = createSchedulerAdapter({
type: process.env.SCHEDULER_PROVIDER || 'local',
Copy link

Choose a reason for hiding this comment

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

Scheduler silently defaults to non-persistent local adapter

Medium Severity

createScheduleUseCases() defaults SCHEDULER_PROVIDER to 'local' when the env var is unset. The local adapter is in-memory only — schedules are lost on every Lambda cold start. In production, PUT/DELETE schedule endpoints will appear to succeed but changes silently vanish, making debugging very difficult. A missing required config like this is better surfaced as an explicit error than masked with a fallback.

Fix in Cursor Fix in Web

},
metrics: { durationMs },
};
}
Copy link

Choose a reason for hiding this comment

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

Catch block misreports successful execution as failed

Medium Severity

The try block on line 68 wraps both script.execute() and the subsequent completeAdminProcess(COMPLETED) call. If the script succeeds but the completion DB update on line 91 throws (e.g., output too large, transient DB error), the catch block marks the execution as FAILED even though the script ran to completion and may have produced real side effects. This can mislead operators into re-running scripts that already succeeded, risking duplicate side effects like double token refreshes or repeated status updates.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Create a release when this pr is merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants