-
Notifications
You must be signed in to change notification settings - Fork 11
Adding skills for general best practices #71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,104 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: key-package-lifecycle | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: Handles key package lifecycle in Marmot apps: creating and publishing packages, tracking kind-443 relay events, marking consumed packages, rotating stale/used packages, and deleting via remove or purge. Use when writing code that calls client.keyPackages, manages invite readiness, replenishes key package supply, or cleans up relay-published packages. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Key Package Lifecycle | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## API Overview | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| All operations live on `client.keyPackages` (`KeyPackageManager`): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Method | Effect | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |---|---| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | `create(options)` | Generate, store locally, sign and publish kind-443 to relays | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | `track(event)` | Record an observed kind-443 event; returns `false` if not applicable | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | `markUsed(ref)` | Flag a package as consumed without deleting it | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | `rotate(ref, options?)` | Publish kind-5 deletion for old events, create+publish replacement, remove old local material | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | `remove(ref)` | Local store deletion only — no relay cleanup | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | `purge(refs)` | Publish kind-5 deletion for all known event IDs, remove local material (bulk-friendly) | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Core Rules | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1. **Never transmit `privatePackage`** — private material stays local only. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2. **`create()` requires non-empty `relays`** — throws `MissingRelayError` otherwise. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3. **Track continuously** — feed all incoming kind-443 events to `track()` so publish records stay accurate across devices. This is required for `rotate()` and `purge()` to know which relay event IDs to delete. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4. **Prefer `rotate()` over `remove()` for published packages** — rotation cleans relays and replenishes supply atomically. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5. **`used` is rotation debt** — packages marked used should be rotated in the background, not immediately during the join flow. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6. **Relay deletion is best-effort** — kind-5 events are advisory; do not block UX on confirmation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Line 28 suggests delete publication should not affect UX, but both 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## App Workflow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### 1. Bootstrap supply | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Publish at least two key packages on onboarding (or when inventory is low) to reduce invite race failures: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```typescript | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await client.keyPackages.create({ relays, isLastResort: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await client.keyPackages.create({ relays, isLastResort: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### 2. Ingest relay state | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Subscribe to kind `443` for the user's pubkey and feed events to the manager: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```typescript | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const event of kind443Events) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await client.keyPackages.track(event); // false = not a valid key package, safe to ignore | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### 3. After a group join (consumption) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `MarmotClient` calls `markUsed()` automatically when processing a Welcome. Apps should schedule rotation in the background — do not block the join flow: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```typescript | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // After client.acceptInvite() resolves, rotate in background | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void maintainKeyPackages(client, relays); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### 4. Periodic maintenance | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```typescript | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function maintainKeyPackages( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client: MarmotClient, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| relays: string[], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const entries = await client.keyPackages.list(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const entry of entries) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (entry.used) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await client.keyPackages.rotate(entry.keyPackageRef); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ((await client.keyPackages.count()) < 2) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await client.keyPackages.create({ relays, isLastResort: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+63
to
+78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pass relay fallback into Line 71 can throw Suggested doc fix for (const entry of entries) {
if (entry.used) {
- await client.keyPackages.rotate(entry.keyPackageRef);
+ await client.keyPackages.rotate(entry.keyPackageRef, { relays });
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### 5. User-initiated deletion | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **Delete everywhere** (relay + local): `await client.keyPackages.purge(refs)` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **Local only** (package was never published, or relay cleanup already handled): `await client.keyPackages.remove(ref)` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| For deletion UX that operates on raw Nostr events (e.g. a relay browser), track the events first so `purge()` has the event IDs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```typescript | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const event of eventsToDelete) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await client.keyPackages.track(event); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const refs = eventsToDelete.map(getKeyPackageReference).filter(Boolean); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await client.keyPackages.purge(refs); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Error Handling | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Error | Thrown by | Resolution | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |---|---|---| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | `MissingRelayError` | `create()` | Prompt user for relay configuration | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | `KeyPackageNotFoundError` | `rotate()` | Refresh local state; fall back to `create()` | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | `KeyPackageRotatePreconditionError` | `rotate()` | Pass `options.relays` explicitly | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| If a package has no tracked `published` events, `rotate()`/`purge()` still removes local material but skips the kind-5 deletion step. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The If there are no tracked 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document
list()andcount()in the API overview.The workflow example on Line 67 and Line 75 uses
client.keyPackages.list()andclient.keyPackages.count(), but this table omits both methods. That makes the documented surface inconsistent within the same guide.🤖 Prompt for AI Agents