From fc6cf9add1da2c74f076680c89c96dd53898c4a9 Mon Sep 17 00:00:00 2001 From: Lucas Carlson Date: Thu, 8 Oct 2026 01:16:32 -0700 Subject: [PATCH 1/4] docs: fix agent guide gaps from the evaluations The Track B and Track C agent evaluations found code and answers that the guide did not prevent: - The guide said this.reject(code, message). The method takes a code and an options object: this.reject(code, { message }). - A worker process runs only the actor classes that it knows. Run against the published 0.17.1 package, a worker without runtime.register left an enqueued message in ready_messages with 0 attempts and no error; with runtime.register it completed. Step 7 now says to register each class before runtime.run(signal). - Agent code called schedule() without an operation and named the effect handler arguments in the wrong order. The guide lists these mistakes with the correct form. - The install step now says to take the current release and points to step 5, because every authorization callback denies by default. --- CHANGELOG.md | 8 ++++++++ docs/agents.md | 31 ++++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06f5586..fa9e964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +- Correct `docs/agents.md`: `reject` takes a code and an options object, + `this.reject(code, { message })`, not `this.reject(code, message)`. +- `docs/agents.md` now says that a worker process runs only the actor classes + that it knows. A message for an unregistered class stays in SQL with no + attempt and no error, so register each class before `runtime.run(signal)`. + It also tells agents to install the current release, puts the + deny-by-default reminder beside the install step, and lists the API + mistakes found in agent-written code. - Claim the Context7 library: `context7.json` now carries the library `url` and the maintainer `public_key`. - Add the Context7 refresh workflow. A push to `main` that changes the README, diff --git a/docs/agents.md b/docs/agents.md index c05a38e..a761812 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -68,6 +68,10 @@ The quickstart runs 25 concurrent calls against one identity on a temporary SQLite database. It proves that no update is lost. Add `pg` or `mysql2` when you use PostgreSQL or MySQL. +Install the current release. `npm install solid-objects` selects it. Do not +pin a version that you remember from earlier work; the API changed between +releases. The current version is on . + Create the runtime and its tables at startup: ```typescript @@ -81,7 +85,9 @@ const runtime = configure({ await runtime.install() ``` -[Configuration](configuration.md) lists each option and database adapter. +Every authorization callback denies by default. Do step 5 before you call an +actor. [Configuration](configuration.md) lists each option and database +adapter. ## 5. Authorize @@ -184,14 +190,24 @@ Obey these rules in actor code: object argument. - Use `this.schedule({ at, key })` for delayed work. A new `schedule` with the same key moves the alarm. -- Use `this.reject(code, message)` for a business rule failure that must not - retry. +- Use `this.reject(code, { message })` for a business rule failure that must + not retry. The second argument is an object, for example + `this.reject("room_full", { message: "The room is full" })`. - Do not write application tables directly from an operation. Use `this.commitAction()` for a short write in the same database. - Do not call an external API in an operation. Use `this.emit()` and an effect handler. - Write each operation so that it can run again. Delivery is at least once. +Avoid these mistakes: + +| Mistake | Correct form | +| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `this.schedule({ at, key })` with no operation after it | `this.schedule({ at, key }).expire({ buyer })`. `schedule()` stages a reminder only when you call an operation on its result | +| `this.reject("room full")` or `this.reject(code, message)` | `this.reject("room_full", { message: "The room is full" })` | +| `registerEffect(name, (context, argumentsValue) => ...)` | `registerEffect(name, (argumentsValue, context) => ...)`. The staged arguments come first | +| A worker process that never names the actor class | `runtime.register(TicketSale)` before `runtime.run(signal)`. See step 7 | + [State and lifecycle](state-and-lifecycle.md) and the [public API](api.md) give the full rules. @@ -206,13 +222,22 @@ runs in the caller. These features need a process that calls - Messages to other actors. - Realtime broadcasts. +The worker process runs only the actor classes that it knows. Register each +class before `run()`. A message for an unregistered class stays in SQL with no +attempt and no error: + ```typescript +runtime.register(TicketSale) + const controller = new AbortController() process.once("SIGTERM", () => controller.abort()) await runtime.run(controller.signal) await runtime.close() ``` +`TicketSale.ref(...)` also registers the class, so a script that calls `ref()` +before `run()`, like the example in step 6, already works. + The packaged `solid-objects start` command does the same for a runtime that `solid-objects.config.js` exports. When no process runs, committed work waits in SQL and runs later. [Operations](operations.md) covers roles and shutdown. From f518f1c72fceaedb05ba67cf63fc594eb934835c Mon Sep 17 00:00:00 2001 From: Lucas Carlson Date: Thu, 8 Oct 2026 01:28:25 -0700 Subject: [PATCH 2/4] docs: describe the unregistered worker failure exactly Review feedback on #66. The guide said that a message for an unregistered actor class waits with no error. That was wrong. Rerun against solid-objects 0.17.1 with an instrumentation callback, the worker reported solid_objects.activation.failed six times in four seconds with errorName UnknownActorType, and returned the message to the queue each time without a counted attempt. Nothing printed without the callback, which is why the first run looked silent. The guide and the changelog now describe that failure. The step 7 note also points to the direct ref() call above instead of the step 6 class definition, which registers nothing. --- CHANGELOG.md | 7 +++++-- docs/agents.md | 11 +++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa9e964..d57c291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,11 @@ - Correct `docs/agents.md`: `reject` takes a code and an options object, `this.reject(code, { message })`, not `this.reject(code, message)`. - `docs/agents.md` now says that a worker process runs only the actor classes - that it knows. A message for an unregistered class stays in SQL with no - attempt and no error, so register each class before `runtime.run(signal)`. + that it knows. For an unregistered class, actor setup fails with + `UnknownActorType`, the worker reports `solid_objects.activation.failed`, + and the message returns to the queue without a counted attempt. Nothing + prints without an `instrumentation` callback. Register each class before + `runtime.run(signal)`. It also tells agents to install the current release, puts the deny-by-default reminder beside the install step, and lists the API mistakes found in agent-written code. diff --git a/docs/agents.md b/docs/agents.md index a761812..46c4c52 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -222,9 +222,12 @@ runs in the caller. These features need a process that calls - Messages to other actors. - Realtime broadcasts. -The worker process runs only the actor classes that it knows. Register each -class before `run()`. A message for an unregistered class stays in SQL with no -attempt and no error: +The worker process runs only the actor classes that it knows. For a message +whose class is not registered, actor setup fails with `UnknownActorType`. The +worker reports `solid_objects.activation.failed` through instrumentation, +returns the message to the queue without counting an attempt, and tries again. +Nothing prints unless the application sets an `instrumentation` callback, so +the message seems to wait with no error. Register each class before `run()`: ```typescript runtime.register(TicketSale) @@ -236,7 +239,7 @@ await runtime.close() ``` `TicketSale.ref(...)` also registers the class, so a script that calls `ref()` -before `run()`, like the example in step 6, already works. +before `run()`, like the direct call above, already works. The packaged `solid-objects start` command does the same for a runtime that `solid-objects.config.js` exports. When no process runs, committed work waits From 9a54ff623dc236eed5ae50783b77fdd48fad73e4 Mon Sep 17 00:00:00 2001 From: Lucas Carlson Date: Thu, 8 Oct 2026 01:47:16 -0700 Subject: [PATCH 3/4] docs: add the Track C findings to the agent guide Track C finished: 13 of 16 implementation attempts passed. Its report proposed three more changes, applied here: - Codex opened the agent guide in 1 of 8 attempts. The README now names the agent guide at the start of Installation. - One attempt computed expiry from the clock in a query, so a hold read as released before the reminder ran. The agent guide now says that a reminder changes state only when it runs and that a query must read the committed state. - The current-release note now also appears where Track C pointed. --- CHANGELOG.md | 3 +++ README.md | 3 ++- docs/agents.md | 3 +++ docs/virtual-actors.md | 4 ++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d57c291..b503d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- The README names the agent guide at the start of Installation, and the agent + guide says that a reminder changes state only when it runs under + `runtime.run(signal)`, so a query must not compute expiry from the clock. - Correct `docs/agents.md`: `reject` takes a code and an options object, `this.reject(code, { message })`, not `this.reject(code, message)`. - `docs/agents.md` now says that a worker process runs only the actor classes diff --git a/README.md b/README.md index a35ef91..203640b 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,8 @@ subscriptions. Start with the [Cloudflare example](examples/cloudflare/README.md ## Installation Solid Objects is ESM-only and requires Node.js 24.4 or newer. TypeScript users -need TypeScript 5.9 or newer. +need TypeScript 5.9 or newer. Coding agents should follow the +[agent guide](docs/agents.md), which gives each step in order. ```bash npm install solid-objects diff --git a/docs/agents.md b/docs/agents.md index 46c4c52..898dea6 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -198,6 +198,9 @@ Obey these rules in actor code: - Do not call an external API in an operation. Use `this.emit()` and an effect handler. - Write each operation so that it can run again. Delivery is at least once. +- A reminder changes state only when it runs, and it runs only while a process + calls `runtime.run(signal)`. Do not compute expiry from the clock in a query; + read the state that the reminder committed. Avoid these mistakes: diff --git a/docs/virtual-actors.md b/docs/virtual-actors.md index 895d9b4..091e138 100644 --- a/docs/virtual-actors.md +++ b/docs/virtual-actors.md @@ -113,6 +113,10 @@ buyer gets the ticket. The hold and its reminder commit in one transaction. If the worker stops, the reminder stays in `tickets.sqlite3`. It runs when the worker starts again. +The `work` branch can run the reminder because `TicketSale.ref(...)` registers +the class first. A separate worker process must call +`runtime.register(TicketSale)` before `runtime.run(signal)`. + The authorization callbacks above allow every caller. Use them only for a local example. The package release check runs this file against the packed npm tarball. The source is [`examples/ticket-sale.ts`](../examples/ticket-sale.ts). From 0815c5e3e2a378a5d34a454a66dde2c969838e2f Mon Sep 17 00:00:00 2001 From: Lucas Carlson Date: Thu, 8 Oct 2026 01:54:47 -0700 Subject: [PATCH 4/4] chore: prepare version 0.17.2 --- CHANGELOG.md | 2 +- docs/parity.md | 2 +- package.json | 2 +- src/version.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b503d7a..425e91e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.17.2 - 2026-10-08 - The README names the agent guide at the start of Installation, and the agent guide says that a reminder changes state only when it runs under diff --git a/docs/parity.md b/docs/parity.md index 5324f70..05a6749 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -4,7 +4,7 @@ This ledger tracks capability parity with the Ruby `solid_objects` gem. Parity preserves a capability and its correctness or security boundary. It does not copy a Rails API into Node. -Reference: Ruby `solid_objects` 0.17.1. The JavaScript package began at the Ruby +Reference: Ruby `solid_objects` 0.17.2. The JavaScript package began at the Ruby design's `0.12` capability generation; that version number did not imply earlier JavaScript releases. diff --git a/package.json b/package.json index 201836e..04c1d58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solid-objects", - "version": "0.17.1", + "version": "0.17.2", "description": "SQL-backed virtual actor library for TypeScript and Node.js, with durable state, ordered operations, and automatic activation on SQLite, PostgreSQL, or MySQL", "type": "module", "license": "MIT", diff --git a/src/version.ts b/src/version.ts index f92d2a2..91b8da3 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = "0.17.1" +export const VERSION = "0.17.2"