Skip to content

SDK: add a bulk multi-item atomic-mutation helper on top of commitWorkspaceTransaction (create/update/close batch) — sync packages reinvent step+compensation wiring #613

Closed

Description

@unbraind

Summary

2026.7.19 shipped two great atomic-write primitives in the public SDK:

  • commitImportedItem(params) — atomic commit of a single imported item
  • commitWorkspaceTransaction(options) — a fully general, durable, crash-recoverable coordinator for arbitrary ordered steps (inspect/prepareCompensation/apply/compensate)

There is a gap between them: no first-class helper for the most common real-world case — committing N item create/update/close mutations atomically (all-or-nothing) in one transaction.

Why this matters

commitWorkspaceTransaction is intentionally low-level: each WorkspaceTransactionStep requires the caller to hand-write inspect(), prepareCompensation(), apply(), and an idempotent compensate(). For the ubiquitous "bulk import / bulk sync a set of items" workflow, every consumer package must independently reinvent the same wiring:

  • apply() → create(...) / update(...) and return the id as the journal result
  • inspect() → detect whether the item already exists (resumability) via some provenance field
  • compensate() → idempotent close(id) / restore prior fields, guarded on the item still existing
  • deriving a stable per-item transactionId / step id

This is exactly the "every foreign-domain package would otherwise have to reinvent [the crash-consistency protocol]" argument that motivated the primitive itself (see feature pm-4e12), applied one level up. Today the first-party sync/import extensions — pm-csv, pm-github, pm-jira, pm-linear — all perform per-row/per-issue writes with no atomic envelope (they spawnSync("pm create") in a loop; a mid-batch failure leaves partial state). Each would now have to hand-roll identical step/compensation boilerplate to adopt the primitive.

Proposal

Add a public convenience wrapper (name illustrative), e.g.:

commitItemMutations({
  pmRoot,
  author,
  transactionId,           // stable → resumable
  mutations: Array<
    | { op: "create"; item: CreateOptions; key: string }   // key = provenance/idempotency
    | { op: "update"; id: string; changes: UpdateOptions }
    | { op: "close";  id: string; reason?: string }
  >,
  lockTtlSeconds?, lockWaitMs?, onTransition?,
}): Promise<{ status: "committed"; recovered: boolean; results: Record<string, ItemMetadata> }>

Internally it builds WorkspaceTransactionSteps over the existing public create/update/close runtime functions, with a sensible default inspect() (exists-by-id or by provenance key) and default idempotent compensate() (create→close/delete, update→restore-captured-fields), so consumers get atomic, resumable, multi-agent-safe bulk item writes without re-implementing the compensation contract.

This keeps commitWorkspaceTransaction as the escape hatch for arbitrary domains while giving the 90% item-mutation case a one-call, correct-by-construction API.

Notes / acceptance ideas

  • Idempotent create compensation must respect the known delete/history caveats (prefer close over deleteItem, or make the compensation strategy configurable).
  • Failure-injection coverage for partial batches (mirroring pm-4e12's own test approach).
  • SDK.md example replacing a hand-rolled step array with the wrapper.

Environment: @unbrained/pm-cli 2026.7.19; observed while adopting commitWorkspaceTransaction in pm-csv bulk import.

Activity

  1. coderabbitai commented on Jul 19, 2026

    @coderabbitai
    🔗 Related PRs

    #425 - Strengthen SDK linked-test imports and mandatory quality gates [merged]
    #434 - Fix self-parent guards and pnpm 11 bootstrap gates [merged]
    #477 - feat(sdk): expand pm primitives and lifecycle aliases [merged]
    #549 - feat(sdk): ship history time travel, rich reads, and runtime integrity [merged]
    #605 - SDK crash-recoverable workspace transactions and atomic VCS merges [open]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. unbraind commented on Jul 19, 2026

    @unbraind
    OwnerAuthor

    pm tracker item: pm-y9hq

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions