feat: add internal-message enqueue and snapshot incarnation APIs - #14
Conversation
Give a host package (such as a future commercial scaling layer) two things it cannot build from public API alone: a way to enqueue an internal-delivery-mode actor message that skips user authorization, with a transaction-scoped variant for atomic multi-write commits, and a way to read instance identity/recency alongside a snapshot so a derived write can fence against a stale or superseded actor incarnation. instanceId is a random UUID, not monotonic, so fencing uses createdAtMs instead.
Greptile SummaryThe PR adds privileged internal-message enqueue APIs and an authorized snapshot API exposing actor-incarnation metadata, then updates the package version and documentation.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains. No blocking failure remains. Important Files Changed
Sequence DiagramsequenceDiagram
participant Host
participant Runtime
participant DB
participant Worker
Host->>DB: Begin transaction
Host->>Runtime: enqueueInternalMessageInTransaction(connection, options)
Runtime->>DB: Insert internal message
DB-->>Host: Message row
Host->>DB: Commit transaction
Host->>Runtime: announceInternalMessage(message)
Runtime->>Worker: Wake actor role
Worker->>DB: Claim and process message
Reviews (2): Last reviewed commit: "docs: document snapshotWithIncarnation's..." | Re-trigger Greptile |
| snapshot: readonlyCopy(snapshot) as ActorSnapshot<ActorType>, | ||
| instanceId: instance?.id ?? "0", | ||
| revision: String(instance?.state_revision ?? 0), | ||
| createdAtMs: Number(instance?.created_at_ms ?? 0), |
There was a problem hiding this comment.
Timestamp fence aliases incarnations
When an actor is destroyed and recreated within the same database-clock millisecond, both incarnations receive the same createdAtMs, so a stale derived write can pass the advertised incarnation fence and overwrite or publish data as though it came from the current incarnation.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/runtime.ts
Line: 754
Comment:
**Timestamp fence aliases incarnations**
When an actor is destroyed and recreated within the same database-clock millisecond, both incarnations receive the same `createdAtMs`, so a stale derived write can pass the advertised incarnation fence and overwrite or publish data as though it came from the current incarnation.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| @@ -1443,6 +1477,66 @@ export class SolidObjectsRuntime { | |||
| } | |||
| } | |||
There was a problem hiding this comment.
Generic default violates type rule
The new Result = unknown annotation violates the repository requirement to use concrete TypeScript types, forcing consumers that omit the generic to narrow or assert the message result. Replace it with the concrete result type supported by message result storage.
Rule Used: What: Disallow the use of unknown in TypeScript ... (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/runtime.ts
Line: 1478
Comment:
**Generic default violates type rule**
The new `Result = unknown` annotation violates the repository requirement to use concrete TypeScript types, forcing consumers that omit the generic to narrow or assert the message result. Replace it with the concrete result type supported by message result storage.
**Rule Used:** What: Disallow the use of `unknown` in TypeScript ... ([source](https://app.greptile.com/craftsmanfounder/-/custom-context?memory=af673ae0-6488-4c8b-8b4a-1bfea4eb4de7))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
createdAtMs orders actor incarnations at the same millisecond granularity every adapter stores created_at_ms at. Destroying and recreating the same actor identity within one database-clock millisecond produces two incarnations a caller cannot order by createdAtMs alone, since neither timestamp precision nor instanceId (a random UUID) can break the tie without a schema-level monotonic sequence, which is out of scope here. Record the boundary in docs/correctness.md and docs/api.md, and make the existing recreation test's title state why it asserts >= rather than >.
|
Addressed both Greptile findings: Issue 1 (timestamp fence aliases incarnations) — valid, fixed via documentation. A same-millisecond destroy+recreate can produce two incarnations with an equal Issue 2 ( |
Summary
runtime.enqueueInternalMessage()andruntime.enqueueInternalMessageInTransaction(connection, options)— public entry points to enqueue aninternal-delivery-mode actor message that skips user authorization, with a transaction-scoped variant for atomic multi-write commits, plusruntime.announceInternalMessage(message)to trigger the post-commit wake-up.runtime.snapshotWithIncarnation(reference)— returns the same authorized fields assnapshot()plusinstanceId,revision, andcreatedAtMsfrom one shared read, so a caller can fence a derived write against a stale or superseded actor incarnation.instanceIdis a random UUID (not monotonic), so fencing usescreatedAtMs.0.14.0and updatedocs/api.md/docs/parity.mdaccordingly.These close two real gaps found while designing a Node port of the commercial
solid_objects_proRuby gem: no public way to enqueue a system-internal message inside a caller-managed transaction, and no way to detect actor recreation without a monotonic instance identity.Test plan
pnpm run format:checkpnpm run check(parameter style, documentation completeness, type checks)pnpm run test:coverage(262 passed, 11 skipped, 0 failed)pnpm run buildpnpm run pack:checkpnpm run test:package(packaged tarball smoke test)pnpm run test:recoverypnpm audit --audit-level=high(no known vulnerabilities)postgresql/mysql/redis/browsermatrix (needs live services, not run locally)