@friggframework/admin-scripts 2.0.0--canary.517.44c158e.0 → 2.0.0--canary.517.06fe141.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ Typical use cases:
8
8
  - **Recurring maintenance** — refresh webhooks/subscriptions before they expire.
9
9
  - **Built-in utilities** — OAuth token refresh, integration health checks.
10
10
 
11
- > Admin scripts are a **high-privilege** surface. Every endpoint is protected by an admin API key (`x-frigg-admin-api-key`), scripts run in your private VPC subnets, and every execution is tracked in the `AdminProcess` table. Never expose the admin API key to browsers or end users.
11
+ > Admin scripts are a **high-privilege** surface. Every endpoint is protected by an admin API key (`x-frigg-admin-api-key`), scripts run in your private VPC subnets, and every execution is tracked in the `AdminScriptExecution` table. Never expose the admin API key to browsers or end users.
12
12
 
13
13
  ---
14
14
 
@@ -129,7 +129,7 @@ module.exports = { AttioHealingScript };
129
129
  | `instantiate(integrationId)` | Returns a hydrated integration instance for calling external APIs. Requires `config.requireIntegrationInstance: true`. |
130
130
  | `queueScript(name, params?)` | Enqueue another script as a follow-up (tracked as a child of the current execution). |
131
131
  | `queueScriptBatch(entries)` | Enqueue many follow-up scripts at once. |
132
- | `getExecutionId()` | The current `AdminProcess` record id. |
132
+ | `getExecutionId()` | The current `AdminScriptExecution` record id. |
133
133
 
134
134
  Commands return **plain, decrypted data** on success (field-level encryption is handled transparently) or an `{ error, reason, code }` object on failure — scripts check `.error` themselves. Scripts have no direct repository access; all database interaction goes through the command layer.
135
135
 
@@ -247,7 +247,7 @@ async execute(params) {
247
247
  - **Sync** (`mode: 'sync'`) — runs in the API Lambda, result returned in the response. Capped by the API timeout.
248
248
  - **Async** (`mode: 'async'`, default) — queued to SQS and run by the worker Lambda (15-min budget). The SQS queue has a redrive policy (up to 3 receives) to a dead-letter queue, so a crashed invocation is retried at the infrastructure level. There is no application-level per-script retry — model idempotency accordingly.
249
249
 
250
- Every execution is persisted as an `AdminProcess` record with its `state` (`PENDING` → `RUNNING` → `COMPLETED`/`FAILED`), input, output, metrics, and logs.
250
+ Every execution is persisted as an `AdminScriptExecution` record with its `state` (`PENDING` → `RUNNING` → `COMPLETED`/`FAILED`), input, output, metrics, and logs.
251
251
 
252
252
  ---
253
253
 
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@friggframework/admin-scripts",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.517.44c158e.0",
4
+ "version": "2.0.0--canary.517.06fe141.0",
5
5
  "description": "Admin Script Runner for Frigg - Execute maintenance and operational scripts in hosted environments",
6
6
  "dependencies": {
7
7
  "@aws-sdk/client-scheduler": "^3.588.0",
8
- "@friggframework/core": "2.0.0--canary.517.44c158e.0",
8
+ "@friggframework/core": "2.0.0--canary.517.06fe141.0",
9
9
  "@hapi/boom": "^10.0.1",
10
10
  "express": "^4.18.2",
11
11
  "serverless-http": "^3.2.0"
12
12
  },
13
13
  "devDependencies": {
14
- "@friggframework/eslint-config": "2.0.0--canary.517.44c158e.0",
15
- "@friggframework/prettier-config": "2.0.0--canary.517.44c158e.0",
16
- "@friggframework/test": "2.0.0--canary.517.44c158e.0",
14
+ "@friggframework/eslint-config": "2.0.0--canary.517.06fe141.0",
15
+ "@friggframework/prettier-config": "2.0.0--canary.517.06fe141.0",
16
+ "@friggframework/test": "2.0.0--canary.517.06fe141.0",
17
17
  "eslint": "^8.22.0",
18
18
  "jest": "^29.7.0",
19
19
  "prettier": "^2.7.1",
@@ -44,5 +44,5 @@
44
44
  "maintenance",
45
45
  "operations"
46
46
  ],
47
- "gitHead": "44c158e7a3bbeaac4b410c1062b0ce209bffd7f0"
47
+ "gitHead": "06fe14103a72028ce7bf622631580fb24a4abaaf"
48
48
  }
@@ -38,9 +38,9 @@ describe('ScriptRunner', () => {
38
38
  scriptFactory = new ScriptFactory([TestScript]);
39
39
 
40
40
  mockCommands = {
41
- createAdminProcess: jest.fn(),
42
- updateAdminProcessState: jest.fn(),
43
- completeAdminProcess: jest.fn(),
41
+ createExecution: jest.fn(),
42
+ updateExecutionState: jest.fn(),
43
+ completeExecution: jest.fn(),
44
44
  };
45
45
 
46
46
  mockContext = {
@@ -52,11 +52,11 @@ describe('ScriptRunner', () => {
52
52
  createAdminScriptCommands.mockReturnValue(mockCommands);
53
53
  createAdminScriptContext.mockReturnValue(mockContext);
54
54
 
55
- mockCommands.createAdminProcess.mockResolvedValue({
55
+ mockCommands.createExecution.mockResolvedValue({
56
56
  id: 'exec-123',
57
57
  });
58
- mockCommands.updateAdminProcessState.mockResolvedValue({});
59
- mockCommands.completeAdminProcess.mockResolvedValue({ success: true });
58
+ mockCommands.updateExecutionState.mockResolvedValue({});
59
+ mockCommands.completeExecution.mockResolvedValue({ success: true });
60
60
  });
61
61
 
62
62
  afterEach(() => {
@@ -89,7 +89,7 @@ describe('ScriptRunner', () => {
89
89
  expect(result.executionId).toBe('exec-123');
90
90
  expect(result.metrics.durationMs).toBeGreaterThanOrEqual(0);
91
91
 
92
- expect(mockCommands.createAdminProcess).toHaveBeenCalledWith({
92
+ expect(mockCommands.createExecution).toHaveBeenCalledWith({
93
93
  scriptName: 'test-script',
94
94
  scriptVersion: '1.0.0',
95
95
  trigger: 'MANUAL',
@@ -98,12 +98,12 @@ describe('ScriptRunner', () => {
98
98
  audit: { apiKeyName: 'test-key' },
99
99
  });
100
100
 
101
- expect(mockCommands.updateAdminProcessState).toHaveBeenCalledWith(
101
+ expect(mockCommands.updateExecutionState).toHaveBeenCalledWith(
102
102
  'exec-123',
103
103
  'RUNNING'
104
104
  );
105
105
 
106
- expect(mockCommands.completeAdminProcess).toHaveBeenCalledWith(
106
+ expect(mockCommands.completeExecution).toHaveBeenCalledWith(
107
107
  'exec-123',
108
108
  expect.objectContaining({
109
109
  state: 'COMPLETED',
@@ -170,7 +170,7 @@ describe('ScriptRunner', () => {
170
170
  expect(result.scriptName).toBe('failing-script');
171
171
  expect(result.error.message).toBe('Script failed');
172
172
 
173
- expect(mockCommands.completeAdminProcess).toHaveBeenCalledWith(
173
+ expect(mockCommands.completeExecution).toHaveBeenCalledWith(
174
174
  'exec-123',
175
175
  expect.objectContaining({
176
176
  state: 'FAILED',
@@ -227,8 +227,8 @@ describe('ScriptRunner', () => {
227
227
  );
228
228
 
229
229
  expect(result.executionId).toBe('existing-exec-456');
230
- expect(mockCommands.createAdminProcess).not.toHaveBeenCalled();
231
- expect(mockCommands.updateAdminProcessState).toHaveBeenCalledWith(
230
+ expect(mockCommands.createExecution).not.toHaveBeenCalled();
231
+ expect(mockCommands.updateExecutionState).toHaveBeenCalledWith(
232
232
  'existing-exec-456',
233
233
  'RUNNING'
234
234
  );
@@ -237,7 +237,7 @@ describe('ScriptRunner', () => {
237
237
  it('reports COMPLETED even when persisting completion fails', async () => {
238
238
  // Commands return an error object (never throw). A successful script
239
239
  // must not be misreported as FAILED if the completion write fails.
240
- mockCommands.completeAdminProcess.mockResolvedValue({
240
+ mockCommands.completeExecution.mockResolvedValue({
241
241
  error: 500,
242
242
  reason: 'DB write failed',
243
243
  });
@@ -257,7 +257,7 @@ describe('ScriptRunner', () => {
257
257
  });
258
258
 
259
259
  it('throws when the execution record cannot be created', async () => {
260
- mockCommands.createAdminProcess.mockResolvedValue({
260
+ mockCommands.createExecution.mockResolvedValue({
261
261
  error: 500,
262
262
  reason: 'DB down',
263
263
  });
@@ -282,7 +282,7 @@ describe('ScriptRunner', () => {
282
282
 
283
283
  await runner.execute('test-script', {}, { trigger: 'MANUAL' });
284
284
 
285
- expect(mockCommands.completeAdminProcess).toHaveBeenCalledWith(
285
+ expect(mockCommands.completeExecution).toHaveBeenCalledWith(
286
286
  'exec-123',
287
287
  expect.objectContaining({
288
288
  logs: [{ level: 'info', message: 'hi' }],
@@ -21,7 +21,7 @@ const { QueuerUtil } = require('@friggframework/core/queues');
21
21
  class AdminScriptContext {
22
22
  /**
23
23
  * @param {Object} [params={}] - Context configuration
24
- * @param {string|number|null} [params.executionId] - ID of the AdminProcess record this context is scoped to (used for log persistence and script chaining)
24
+ * @param {string|number|null} [params.executionId] - ID of the AdminScriptExecution record this context is scoped to (used for log persistence and script chaining)
25
25
  * @param {Object|null} [params.integrationFactory] - Factory used to hydrate integration instances; required for scripts that call instantiate()
26
26
  * @param {Object|null} [params.commands] - Frigg command bundle ({ users, credentials, entities, integrations }) injected by the composition root and exposed to scripts as context.commands
27
27
  */
@@ -17,7 +17,7 @@ class ScriptRunner {
17
17
  * @param {Object} params
18
18
  * @param {ScriptFactory} params.scriptFactory - Required. The registry used
19
19
  * to resolve and instantiate scripts by name (built by bootstrap.js).
20
- * @param {Object} [params.commands] - Admin process command layer; defaults
20
+ * @param {Object} [params.commands] - Admin script execution command layer; defaults
21
21
  * to a fresh createAdminScriptCommands().
22
22
  * @param {Object} [params.integrationFactory] - Hydrates integration
23
23
  * instances for scripts that need them.
@@ -42,8 +42,8 @@ class ScriptRunner {
42
42
  * @param {string} options.trigger - 'MANUAL' | 'SCHEDULED' | 'QUEUE'
43
43
  * @param {string} options.mode - 'sync' | 'async'
44
44
  * @param {Object} options.audit - Audit info { apiKeyName, apiKeyLast4, ipAddress }
45
- * @param {string} options.executionId - Reuse existing AdminProcess record ID (NOT the Lambda execution ID).
46
- * This is the database ID from the AdminProcess collection/table that tracks script executions.
45
+ * @param {string} options.executionId - Reuse existing AdminScriptExecution record ID (NOT the Lambda execution ID).
46
+ * This is the database ID from the AdminScriptExecution collection/table that tracks script executions.
47
47
  * Pass this when resuming a queued execution to continue using the same execution record.
48
48
  */
49
49
  async execute(scriptName, params = {}, options = {}) {
@@ -78,7 +78,7 @@ class ScriptRunner {
78
78
 
79
79
  // Create execution record if not provided
80
80
  if (!executionId) {
81
- const execution = await this.commands.createAdminProcess({
81
+ const execution = await this.commands.createExecution({
82
82
  scriptName,
83
83
  scriptVersion: definition.version,
84
84
  trigger,
@@ -91,7 +91,8 @@ class ScriptRunner {
91
91
  // than tracking an `undefined` execution id.
92
92
  if (execution.error) {
93
93
  throw new Error(
94
- execution.reason || 'Failed to create admin process record'
94
+ execution.reason ||
95
+ 'Failed to create admin script execution record'
95
96
  );
96
97
  }
97
98
  executionId = execution.id;
@@ -108,7 +109,7 @@ class ScriptRunner {
108
109
 
109
110
  let output;
110
111
  try {
111
- await this.commands.updateAdminProcessState(executionId, 'RUNNING');
112
+ await this.commands.updateExecutionState(executionId, 'RUNNING');
112
113
 
113
114
  // Create script instance with context injected via constructor
114
115
  const script = this.scriptFactory.createInstance(scriptName, {
@@ -122,7 +123,7 @@ class ScriptRunner {
122
123
  } catch (error) {
123
124
  const durationMs = new Date() - startTime;
124
125
 
125
- const completion = await this.commands.completeAdminProcess(
126
+ const completion = await this.commands.completeExecution(
126
127
  executionId,
127
128
  {
128
129
  state: 'FAILED',
@@ -169,7 +170,7 @@ class ScriptRunner {
169
170
  metrics: { durationMs },
170
171
  };
171
172
 
172
- const completion = await this.commands.completeAdminProcess(
173
+ const completion = await this.commands.completeExecution(
173
174
  executionId,
174
175
  {
175
176
  state: 'COMPLETED',
@@ -58,9 +58,9 @@ describe('Admin Script Router', () => {
58
58
  };
59
59
 
60
60
  mockCommands = {
61
- createAdminProcess: jest.fn(),
62
- findAdminProcessById: jest.fn(),
63
- findAdminProcessesByName: jest.fn(),
61
+ createExecution: jest.fn(),
62
+ findExecutionById: jest.fn(),
63
+ findExecutionsByName: jest.fn(),
64
64
  };
65
65
 
66
66
  mockSchedulerAdapter = {
@@ -185,7 +185,7 @@ describe('Admin Script Router', () => {
185
185
  it('should queue script for async execution', async () => {
186
186
  process.env.ADMIN_SCRIPT_QUEUE_URL =
187
187
  'https://sqs.us-east-1.amazonaws.com/123/test-queue';
188
- mockCommands.createAdminProcess.mockResolvedValue({
188
+ mockCommands.createExecution.mockResolvedValue({
189
189
  id: 'exec-456',
190
190
  });
191
191
 
@@ -212,7 +212,7 @@ describe('Admin Script Router', () => {
212
212
  it('should default to async mode', async () => {
213
213
  process.env.ADMIN_SCRIPT_QUEUE_URL =
214
214
  'https://sqs.us-east-1.amazonaws.com/123/test-queue';
215
- mockCommands.createAdminProcess.mockResolvedValue({
215
+ mockCommands.createExecution.mockResolvedValue({
216
216
  id: 'exec-789',
217
217
  });
218
218
 
@@ -257,7 +257,7 @@ describe('Admin Script Router', () => {
257
257
 
258
258
  describe('GET /admin/scripts/:scriptName/executions/:executionId', () => {
259
259
  it('should return execution details', async () => {
260
- mockCommands.findAdminProcessById.mockResolvedValue({
260
+ mockCommands.findExecutionById.mockResolvedValue({
261
261
  id: 'exec-123',
262
262
  scriptName: 'test-script',
263
263
  status: 'COMPLETED',
@@ -273,7 +273,7 @@ describe('Admin Script Router', () => {
273
273
  });
274
274
 
275
275
  it('should return 404 for non-existent execution', async () => {
276
- mockCommands.findAdminProcessById.mockResolvedValue({
276
+ mockCommands.findExecutionById.mockResolvedValue({
277
277
  error: 404,
278
278
  reason: 'Execution not found',
279
279
  code: 'EXECUTION_NOT_FOUND',
@@ -290,7 +290,7 @@ describe('Admin Script Router', () => {
290
290
 
291
291
  describe('GET /admin/scripts/:scriptName/executions', () => {
292
292
  it('should list executions for specific script', async () => {
293
- mockCommands.findAdminProcessesByName.mockResolvedValue([
293
+ mockCommands.findExecutionsByName.mockResolvedValue([
294
294
  { id: 'exec-1', name: 'test-script', state: 'COMPLETED' },
295
295
  { id: 'exec-2', name: 'test-script', state: 'RUNNING' },
296
296
  ]);
@@ -301,7 +301,7 @@ describe('Admin Script Router', () => {
301
301
 
302
302
  expect(response.status).toBe(200);
303
303
  expect(response.body.executions).toHaveLength(2);
304
- expect(mockCommands.findAdminProcessesByName).toHaveBeenCalledWith(
304
+ expect(mockCommands.findExecutionsByName).toHaveBeenCalledWith(
305
305
  'test-script',
306
306
  {
307
307
  limit: 50,
@@ -310,13 +310,13 @@ describe('Admin Script Router', () => {
310
310
  });
311
311
 
312
312
  it('should accept query parameters (status maps to state, limit is bounded)', async () => {
313
- mockCommands.findAdminProcessesByName.mockResolvedValue([]);
313
+ mockCommands.findExecutionsByName.mockResolvedValue([]);
314
314
 
315
315
  await request(app).get(
316
316
  '/admin/scripts/test-script/executions?status=COMPLETED&limit=10'
317
317
  );
318
318
 
319
- expect(mockCommands.findAdminProcessesByName).toHaveBeenCalledWith(
319
+ expect(mockCommands.findExecutionsByName).toHaveBeenCalledWith(
320
320
  'test-script',
321
321
  {
322
322
  limit: 10,
@@ -22,7 +22,7 @@ describe('Admin Script Executor Handler', () => {
22
22
  mockScriptCommands = { id: 'script-commands' };
23
23
  mockRunner = { execute: jest.fn() };
24
24
  mockCommands = {
25
- completeAdminProcess: jest.fn().mockResolvedValue({}),
25
+ completeExecution: jest.fn().mockResolvedValue({}),
26
26
  };
27
27
 
28
28
  bootstrapAdminScripts.mockReturnValue({
@@ -89,7 +89,7 @@ describe('Admin Script Executor Handler', () => {
89
89
  expect(response.statusCode).toBe(200);
90
90
  const body = JSON.parse(response.body);
91
91
  expect(body.results[0].status).toBe('FAILED');
92
- expect(mockCommands.completeAdminProcess).toHaveBeenCalledWith(
92
+ expect(mockCommands.completeExecution).toHaveBeenCalledWith(
93
93
  'exec-2',
94
94
  expect.objectContaining({ state: 'FAILED' })
95
95
  );
@@ -159,7 +159,7 @@ describe('Admin Script Executor Handler', () => {
159
159
  expect(body.processed).toBe(2);
160
160
  expect(body.results[0].status).toBe('FAILED');
161
161
  expect(body.results[1].status).toBe('COMPLETED');
162
- expect(mockCommands.completeAdminProcess).toHaveBeenCalledWith(
162
+ expect(mockCommands.completeExecution).toHaveBeenCalledWith(
163
163
  'exec-bad',
164
164
  expect.objectContaining({ state: 'FAILED' })
165
165
  );
@@ -261,7 +261,7 @@ router.post('/scripts/:scriptName', async (req, res) => {
261
261
  }
262
262
 
263
263
  const commands = createAdminScriptCommands();
264
- const execution = await commands.createAdminProcess({
264
+ const execution = await commands.createExecution({
265
265
  scriptName,
266
266
  scriptVersion: definition.version,
267
267
  trigger: 'MANUAL',
@@ -309,7 +309,7 @@ router.get('/scripts/:scriptName/executions/:executionId', async (req, res) => {
309
309
  try {
310
310
  const { executionId } = req.params;
311
311
  const commands = createAdminScriptCommands();
312
- const execution = await commands.findAdminProcessById(executionId);
312
+ const execution = await commands.findExecutionById(executionId);
313
313
 
314
314
  if (execution.error) {
315
315
  return res.status(execution.error).json({
@@ -340,7 +340,7 @@ router.get('/scripts/:scriptName/executions', async (req, res) => {
340
340
  ? 50
341
341
  : Math.min(Math.max(parsedLimit, 1), 200);
342
342
 
343
- const executions = await commands.findAdminProcessesByName(scriptName, {
343
+ const executions = await commands.findExecutionsByName(scriptName, {
344
344
  limit: safeLimit,
345
345
  ...(status && { state: status }),
346
346
  });
@@ -8,7 +8,7 @@ const { bootstrapAdminScripts } = require('./bootstrap');
8
8
  * Run a single execution message through the ScriptRunner.
9
9
  * @param {Object} message - Parsed execution message.
10
10
  * @param {string} message.scriptName - Name of the registered script to run (required).
11
- * @param {string} [message.executionId] - Existing AdminProcess id to resume; when
11
+ * @param {string} [message.executionId] - Existing AdminScriptExecution id to resume; when
12
12
  * absent, ScriptRunner creates a new record.
13
13
  * @param {string} [message.trigger] - Execution trigger; defaults to 'QUEUE'.
14
14
  * @param {Object} [message.params] - Parameters passed to the script.
@@ -59,7 +59,7 @@ async function runMessage(
59
59
  }
60
60
 
61
61
  /**
62
- * Mark an admin process FAILED when the worker itself blows up (parse error,
62
+ * Mark an admin script execution FAILED when the worker itself blows up (parse error,
63
63
  * runner construction), so the record doesn't stay stuck in a non-terminal
64
64
  * state. No-op when there is no execution id.
65
65
  * @private
@@ -68,7 +68,7 @@ async function markFailed(executionId, error) {
68
68
  if (!executionId) return;
69
69
  try {
70
70
  const commands = createAdminScriptCommands();
71
- await commands.completeAdminProcess(executionId, {
71
+ await commands.completeExecution(executionId, {
72
72
  state: 'FAILED',
73
73
  error: {
74
74
  name: error.name,