@happyvertical/smrt-jobs 0.51.37 → 0.51.38

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
@@ -229,3 +229,118 @@ the same queue without duplicate-claiming a pending row.
229
229
  - `@happyvertical/logger` -- structured logging
230
230
  - `@happyvertical/utils` -- ID generation utilities
231
231
  - Peer (optional): `@happyvertical/smrt-svelte`, `svelte`
232
+
233
+ ### Restartable MCP input
234
+
235
+ For a workflow that must survive process restart, create the task with
236
+ `continuation: { recordId, revision, inputKey }`. These are immutable references
237
+ to an existing application review/action record, not approval or authority.
238
+ Create the store with both `ownerId` and `tenantId`; task creation must match
239
+ that active tenant. The application adapter must derive these values from its
240
+ verified principal, never tool arguments.
241
+
242
+ Configure `TaskRunner.authorizeMcpTask` to resolve live grants and validate the
243
+ bound actor/tenant against the owning record. It is mandatory for continuation tasks and for every remote app-created task,
244
+ and fails closed if absent or unavailable. Provider exceptions become the fixed
245
+ `MCP task authorization denied` error before task persistence; internal provider
246
+ details are never exposed through task status. It runs before each invocation;
247
+ `context.task.assertAuthorized()` repeats it and checks worker ownership before
248
+ apply. A permission snapshot must not be persisted in the job.
249
+
250
+ `context.task.requestContinuation(binding, inputDescriptor)` stores one input
251
+ round in the existing job row and suspends execution, releasing the worker.
252
+ Waiting rows are not claimable until `updateTask()` atomically stores the first
253
+ complete answer. A new worker starts the method from its beginning and retrieves
254
+ the saved answer. Calls before this seam must be read-only or use the owning
255
+ `runOnce`/action idempotency contract. The binding cannot change between rounds;
256
+ use a new owning action/task for another review revision. JSON input is bounded
257
+ to 64 KiB. Unknown response keys are ignored, incomplete responses do not wake
258
+ the job, and repeated answers cannot overwrite the first accepted response.
259
+ Once an answer is accepted, status is `working` while queued or resumed; the
260
+ immutable response remains available for replay without asking for input again.
261
+
262
+ `getContinuation(taskId)` exposes the waiting descriptor only through the same
263
+ actor/tenant-scoped store. Use it in an explicitly declared authorized workflow
264
+ tool or application form. It adds no fields to MCP `tasks/get`. The stateless
265
+ HTTP adapter retains no callback; absent elicitation, offer the application's
266
+ review URL/form. OpenAI MRTR is an optional protocol adapter responsibility and
267
+ is not implemented by this job API. The legacy `requestInput()` remains a
268
+ running-handler wait bounded by the job timeout and is not restartable.
269
+
270
+ Continuation tasks and remote app-created tasks do not automatically retry errors or unknown external outcomes.
271
+ Reconcile those through the owning action state's durable reservation/evidence
272
+ API; never blindly resubmit. Cancellation guards subsequent cooperative work
273
+ and terminal writes, but cannot retract an external side effect already sent.
274
+ The owning apply transaction must revalidate immutable review/revision and
275
+ idempotency. A host answer never creates final human approval; Iolaus continues
276
+ to use its dedicated human review page.
277
+
278
+ Persistence checks: `pnpm --filter @happyvertical/smrt-jobs test` and
279
+ `pnpm --filter @happyvertical/smrt-jobs test:postgres`. The continuation suite
280
+ also runs against a managed PostgreSQL database with:
281
+
282
+ ```sh
283
+ node scripts/run-with-ci-postgres.mjs -- pnpm --filter @happyvertical/smrt-jobs test src/__tests__/mcp-continuation.optional.test.ts
284
+ ```
285
+
286
+ Supply `CI_POSTGRES_BASE_URL` for a disposable test server; the wrapper creates
287
+ and drops a synthetic database. Do not point destructive fixtures at user data.
288
+
289
+ Generated stdio servers retain their existing trusted-local-process assumption:
290
+ `SMRT_MCP_TENANT_ID` is a deployment scope and existing task owner namespace,
291
+ not an authenticated human identity. The generated store now also passes the
292
+ same active tenant used by task creation. Multi-user remote workflows require
293
+ the verified application principal path and a live runner authorizer.
294
+
295
+ The app integration exports `continueMcpWorkflow()` and
296
+ `createMcpContinuationTool()` from `@happyvertical/smrt-app-mcp`.
297
+ The first composes this job seam with an application's `applyReviewed` callback;
298
+ that callback must invoke its owning immutable approval and idempotency API,
299
+ not a raw mutation. The second declares a read-only workflow tool for a host
300
+ without elicitation. Register that tool through the app server's existing
301
+ `workflowTools` option. Supply a `storeFor(verifiedPrincipal)` bound to the same
302
+ owner namespace and tenant used for task creation, and a live `authorize`
303
+ callback. It returns text and structured form/review URL data; no iframe,
304
+ subscription, or host-specific form implementation is required. Review URLs
305
+ must use HTTPS, or HTTP on loopback for a local deployment, without credentials.
306
+
307
+
308
+ Remote `createMcpAppServer` task stores always set `requireAuthorization: true`.
309
+ This server-owned requirement is persisted separately from user invocation
310
+ arguments; tool arguments cannot disable it. An ordinary remote task without
311
+ continuation metadata must therefore configure the same live worker callback.
312
+ The legacy input seam also checks current authority before waiting and after
313
+ input returns. Applications must call `context.task.assertAuthorized()` again
314
+ immediately before later side effects. Direct `McpTaskStore` callers default to
315
+ trusted-local compatibility; set `requireAuthorization: true` for any remote
316
+ or multi-user application. This default does not confer human identity.
317
+
318
+ Shutdown includes any in-flight claim in the existing bounded drain and keeps
319
+ the worker lease until that drain finishes. A claim returning after shutdown
320
+ started is released without dispatch or consuming an attempt, even if the
321
+ shutdown deadline has elapsed; the next incarnation may claim it normally.
322
+ The same runner cannot restart while an earlier poll or handler is still draining.
323
+
324
+ ### Durable continuation ready-index rollout
325
+
326
+ `_smrt_jobs_ready_idx` is an additive declared nonunique index on `(status,
327
+ run_at)`, restricted to `status = 'pending' AND (task_input_requests IS NULL
328
+ OR task_input_responses IS NOT NULL)`. Both `listReady()` and `claimReady()`
329
+ use this exact eligibility predicate. Suspended already-due jobs remain pending,
330
+ but SQLite/PostgreSQL exclude them from this index until an answer wakes them.
331
+ The original `_smrt_jobs_status_run_at_idx` remains for other status/time reads.
332
+
333
+ Deploy through the owning schema migration path: run `smrt db:migrate` before
334
+ running the updated workers; for continuously written PostgreSQL queues use
335
+ `smrt db:migrate --postgres-safe` so the index builds concurrently. No runtime
336
+ task/runner API creates the index. The old schema remains functionally correct
337
+ until migration, but polling can scan the suspended backlog. This changes no
338
+ column or task status. DuckDB/JSON use the existing DDL strategy's full-index
339
+ fallback and do **not** gain suspended-backlog exclusion; the readiness filter
340
+ still preserves correctness on those engines.
341
+
342
+ Task creation snapshots only validated `recordId`, `revision`, and `inputKey`
343
+ values before any awaited enqueue work. Extra caller fields are not persisted,
344
+ and later mutation of the caller's binding cannot change the durable review
345
+ reference. Live authorization still checks the saved reference at execution
346
+ and resume; the snapshot never grants approval.