Skip to content

feat: add Mint Swap operation ownership primitives - #414

Closed
Egge21M wants to merge 2 commits into
masterfrom
agent/operation-ownership-primitives
Closed

feat: add Mint Swap operation ownership primitives#414
Egge21M wants to merge 2 commits into
masterfrom
agent/operation-ownership-primitives

Conversation

@Egge21M

@Egge21M Egge21M commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Related to #411. The scope is intentionally
narrower than that issue: Batch Mint Operations remain standalone aggregates under ADR 0009.

Problem

Future Mint Swap orchestration needs to durably associate its Mint and Melt Operations without
allowing stale ordinary updates to erase ownership. Although the persistence shape can support a
discriminator, the domain behavior should not imply that unknown future parent operations share
Mint Swap claim semantics.

Summary

  • Add an optional MintSwapOperationParent reference to Mint and Melt Operations.
  • Expose the Mint Swap-specific claimForMintSwap(operationId, expectedState, mintSwapOperationId) repository operation; callers cannot supply arbitrary parent kinds.
  • Persist ownership metadata as parentKind and parentId across SQL and IndexedDB adapters, with
    legacy-row compatibility and validation of the currently supported mint-swap kind.
  • Preserve stored ownership through regular Mint and Melt state updates.
  • Add shared adapter contracts for round trips, stale updates, guarded claims, and transaction
    rollback.
  • Keep Batch Mint Operations, generic ownership behavior, and batching policy out of these
    primitives.

Verification

  • bun run --filter='@cashu/coco-core' test:unit
  • bun run --filter='@cashu/coco-sql-storage' test
  • SQLite Bun adapter contract suite
  • Core, adapter-tests, SQL storage, and IndexedDB typechecks
  • Core, adapter-tests, and SQL storage builds
  • Prettier and Changesets checks

Changeset

  • Added .changeset/operation-ownership-primitives.md for the public Mint Swap ownership type,
    repository interface, and adapter persistence changes.

@github-project-automation github-project-automation Bot moved this to Backlog in coco Aug 5, 2026
@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 471631e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
@cashu/coco-core Major
@cashu/coco-adapter-tests Major
@cashu/coco-sqlite Major
@cashu/coco-sqlite-bun Major
@cashu/coco-expo-sqlite Major
@cashu/coco-indexeddb Major
@cashu/coco-react Major
@cashu/coco-sql-storage Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.62500% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.41%. Comparing base (ffecf8f) to head (471631e).

Files with missing lines Patch % Lines
...positories/memory/MemoryMintOperationRepository.ts 88.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #414      +/-   ##
==========================================
- Coverage   93.42%   93.41%   -0.01%     
==========================================
  Files         113      113              
  Lines       17981    18009      +28     
==========================================
+ Hits        16798    16823      +25     
- Misses       1183     1186       +3     
Flag Coverage Δ
core-integration 50.16% <43.75%> (-0.20%) ⬇️
core-unit 93.89% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Egge21M
Egge21M marked this pull request as ready for review August 5, 2026 11:50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6452187749

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

operation.terminalFailure ? JSON.stringify(operation.terminalFailure) : null,
JSON.stringify(operation.outputData),
operation.id,
operation.parent?.kind ?? null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve existing parent on unguarded updates

In multi-process/tab runs where a standalone executor has read an unparented mint operation before another transaction claims it with updateIfStateAndParentMatch, this regular update() path writes NULL for parentKind/parentId from the stale in-memory object and erases the durable parent claim. Existing mint flows still use mintOperationRepository.update() for pending/executing/finalized transitions, and the IndexedDB put path has the same replacement behavior when operation.parent is absent, so stale standalone writers can still overwrite ownership despite the new guarded method; preserve the stored parent or require the same parent precondition before clearing it.

AGENTS.md reference: AGENTS.md:L161-L164

Useful? React with 👍 / 👎.

Comment thread packages/core/repositories/index.ts Outdated
*/
updateIfStateAndParentMatch(
operation: MintOperation,
expected: { state: MintOperationState; parent?: OperationParent },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include batching eligibility in guarded claims

When a batch worker read an unparented operation before another transaction marks it batchingDisabled, this guard still matches because it only checks state and parent, so the later batch claim can overwrite the opt-out marker while assigning a mint-batch parent. Since the new field is the durable batch-eligibility invariant, include the expected batchingDisabled value in the conditional update contract or provide a claim-specific method that requires it to be unset before mutating.

AGENTS.md reference: AGENTS.md:L161-L164

Useful? React with 👍 / 👎.

@igbopharaoh

Copy link
Copy Markdown
Contributor

Hello @Egge21M good day, looking at the changes here and how the established primitive is going to be OperationParent, should I wait for this to get merged and remodel my work on the slice 2 implementation of mintswaps to fit into this shape ?

@Egge21M
Egge21M force-pushed the agent/operation-ownership-primitives branch from 3de5cc5 to 4454802 Compare August 19, 2026 08:28
@Egge21M Egge21M changed the title feat: add durable operation ownership primitives feat: add Mint Swap operation ownership primitives Aug 19, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 445480202a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,12 @@
---
'@cashu/coco-core': minor

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Release the required repository method as a major

For consumers that implement the public MintOperationRepository contract, upgrading to this minor release makes their adapters fail to typecheck because assignMintSwapParentIfUnparented is a new required method. This repository treats required adapter-contract changes as major releases (for example, the existing atomic key-allocation changeset), so mark @cashu/coco-core as major to avoid shipping a breaking API change under a compatible version range.

Useful? React with 👍 / 👎.

return false;
}

this.operations.set(operationId, { ...current, parent, updatedAt: Date.now() });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clone parent references before storing claims

When a caller mutates the parent object after a successful in-memory claim, this stores the same reference, so the operation's owner changes without another guarded call; getById and the query methods also return that same nested reference, allowing a read result to mutate repository state directly. Clone parent when storing and when returning operations so ownership can only change through repository mutation paths.

AGENTS.md reference: AGENTS.md:L167-L168

Useful? React with 👍 / 👎.

`UPDATE coco_cashu_mint_operations
SET parentKind = ?, parentId = ?, updatedAt = ?
WHERE id = ? AND state = ? AND parentKind IS NULL AND parentId IS NULL`,
[parent.kind, parent.id, getUnixTimeSeconds(), operationId, expectedState],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject empty parent IDs before claiming

When a caller supplies { kind: 'mint-swap', id: '' }, which satisfies the exported TypeScript interface, this SQL update succeeds and returns true because neither the method nor the schema validates parentId; the next repository read then throws from parseParent because an empty ID is treated as invalid. Validate the parent before mutating so a successful claim cannot make the operation unreadable; the IndexedDB implementation has the same write/read mismatch.

AGENTS.md reference: AGENTS.md:L153-L153

Useful? React with 👍 / 👎.

Comment on lines +30 to +31
/** Durable Mint Swap Operation ownership assigned at creation or by a conditional claim. */
parent?: MintSwapOperationParent;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exclude owned init operations from standalone cleanup

When a Mint Swap-owned child remains in init across a restart, initializeCoco() invokes MintOperationService.recoverPendingOperations(), which enumerates every init operation and deletes it in recoverInitOperation() after checking only its state; a concurrent init-state claim after that read can be erased the same way. The analogous melt recovery also deletes parented init melts, even though this change explicitly persists parents across init states. Fresh evidence is the unconditional cleanup in MintOperationService.ts:524-527,970-979 and MeltOperationService.ts:624-628,770-783; skip owned rows or make deletion conditional on the operation remaining unparented.

AGENTS.md reference: AGENTS.md:L167-L168

Useful? React with 👍 / 👎.

@Egge21M

Egge21M commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator Author

@igbopharaoh I think this is now ready to be merged. However Batch Minting no longer requires it. Do you want to keep it for the Mint Swaps or would you rather go with your approach?

@igbopharaoh

Copy link
Copy Markdown
Contributor

Hello @Egge21M kindly give me sometime to take a look at your approach here again and get back to you on this

@igbopharaoh

Copy link
Copy Markdown
Contributor

@igbopharaoh I think this is now ready to be merged. However Batch Minting no longer requires it. Do you want to keep it for the Mint Swaps or would you rather go with your approach?

Hello Egge, I've been able to look at this pull request and compare it with mine particularly in the definition of the mint-swap primitives. I think this PR does a better job at defining the primitives and provides a clear way of defining the MintSwapOperationParent.

I think I can adopt the modelling here into #402 particularly the MintSwapOperationParent with the kind and id fields. Mine was using only the id but your interface definition is better.
I think I can just add that as an extra commit to my PR and refactor my approach and concurrent slices to follow that pattern and we can close this.

What are your thoughts ?

@Egge21M

Egge21M commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

I think I can adopt the modelling here into #402 particularly the MintSwapOperationParent with the kind and id fields. Mine was using only the id but your interface definition is better. I think I can just add that as an extra commit to my PR and refactor my approach and concurrent slices to follow that pattern and we can close this.

What are your thoughts ?

Yes great, whatever you think is best. I have closed this, but kept the branch alive on the remote. Feel free to pick whatever you need.

@Egge21M Egge21M closed this Aug 21, 2026
@github-project-automation github-project-automation Bot moved this from Backlog to Done in coco Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants