@hybridlabor-api/aos 4.13.2 → 4.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/AGENTS.md +8 -0
- package/.agents/nodes.json +5 -2
- package/.claude/hooks/conventional-commits.mjs +14 -15
- package/.claude/hooks/env-file-protection.mjs +14 -15
- package/.claude/hooks/go-gate.mjs +152 -10
- package/.claude/hooks/go-token.mjs +55 -0
- package/.claude/hooks/memb-inject.mjs +75 -62
- package/.claude/hooks/trail-autostart.mjs +27 -0
- package/.claude/hooks/trail-relay.mjs +1 -0
- package/.claude/settings.json +22 -4
- package/.claude/workflows/startcycle-dispatch.mjs +11 -4
- package/.opencode/plugins/bdb-aos.js +98 -121
- package/.opencode/plugins/lib/trail-autostart.js +38 -0
- package/CLAUDE.md +1 -1
- package/README.de.md +6 -6
- package/README.md +6 -6
- package/README.pt.md +6 -6
- package/THIRD_PARTY_NOTICES.md +19 -3
- package/assets/header-v5.png +0 -0
- package/bin/aos-acp.mjs +211 -0
- package/bin/aos-doctor.mjs +1 -1
- package/bin/aos-uninstall.mjs +2 -2
- package/docs/master-session-acp.md +51 -0
- package/installer.js +314 -42
- package/mcps/mcsc/packages/mcp/server.js +6 -7
- package/package.json +4 -3
- package/scripts/validate-skills.mjs +76 -0
- package/skills/basic/bdbmediastorm/SKILL.md +1 -1
- package/skills/basic/godmode-shipping/SKILL.md +3 -0
- package/skills/basic/master-session/SKILL.md +89 -0
- package/skills/basic/startcycle/SKILL.md +1 -1
- package/skills/basic/startcycle-graph/SKILL.md +2 -2
- package/skills/basic/startcycle-graph-user/SKILL.md +1 -1
- package/skills/basic/teamwork-preview/SKILL.md +1 -1
- package/skills/bdbrainstorm/SKILL.md +7 -1
- package/skills/global_config/agentic-harness-patterns/SKILL.md +257 -0
- package/skills/global_config/agentic-harness-patterns/metadata.json +10 -0
- package/skills/global_config/agentic-harness-patterns/references/agent-orchestration-pattern.md +97 -0
- package/skills/global_config/agentic-harness-patterns/references/bootstrap-sequence-pattern.md +106 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/compress-pattern.md +78 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/isolate-pattern.md +82 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/select-pattern.md +86 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering-pattern.md +29 -0
- package/skills/global_config/agentic-harness-patterns/references/hook-lifecycle-pattern.md +111 -0
- package/skills/global_config/agentic-harness-patterns/references/memory-persistence-pattern.md +109 -0
- package/skills/global_config/agentic-harness-patterns/references/permission-gate-pattern.md +111 -0
- package/skills/global_config/agentic-harness-patterns/references/skill-runtime-pattern.md +104 -0
- package/skills/global_config/agentic-harness-patterns/references/task-decomposition-pattern.md +92 -0
- package/skills/global_config/agentic-harness-patterns/references/tool-registry-pattern.md +101 -0
- package/skills/global_config/agenttrail/SKILL.md +8 -0
- package/skills/global_config/agenttrail/bin/agenttrail.mjs +14 -0
- package/skills/global_config/agenttrail/bin/ensure.mjs +118 -0
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
- package/skills/global_config/bdb-visual-edit/SKILL.md +51 -0
- package/skills/global_config/bdb-visual-edit/references/vite-react-source-attr.md +59 -0
- package/skills/global_config/bdb-visual-edit/scripts/pick-snippet.js +27 -0
- package/skills/global_config/bdb-visual-edit/scripts/sanitize-element.mjs +123 -0
- package/skills/global_config/factory-collect/SKILL.md +74 -0
- package/skills/global_config/factory-human-digest/SKILL.md +92 -0
- package/skills/global_config/factory-lookback/SKILL.md +95 -0
- package/skills/global_config/factory-review-prs/SKILL.md +63 -0
- package/skills/global_config/git-pr-review/SKILL.md +3 -0
- package/skills/global_config/grilling/SKILL.md +2 -0
- package/skills/global_config/mcsc/SKILL.md +1 -1
- package/skills/global_config/plan-arbiter/SKILL.md +125 -0
- package/skills/global_config/plan-canvas/SKILL.md +62 -5
- package/skills/global_config/plan-canvas/scripts/lib/loopback-guard.js +19 -3
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/README.md +285 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/agent-trail.js +129 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/board-client.js +124 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/canvas.mdx +19 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/plan.mdx +18 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/recap-demo/plan.mdx +72 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/README.md +29 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/architecture.json +30 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/00_architecture.html +14950 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/canvas.mdx +511 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/plan.mdx +208 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/recap/plan.mdx +102 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/standard/plan.md +136 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/canvas.mdx +124 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/plan.mdx +37 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/index.js +188 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/kit.js +123 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/mdx.js +411 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/render.js +1291 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/plan.mdx +195 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/standard.md +95 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/plan.mdx +105 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/standard.md +76 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/canvas.mdx +81 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/plan.mdx +145 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/standard.md +76 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/plan.mdx +172 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/standard.md +100 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/plan.mdx +67 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/standard.md +49 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/canvas.mdx +63 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/plan.mdx +49 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/standard.md +39 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/plan.mdx +118 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/standard.md +57 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/plan.mdx +173 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/standard.md +96 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/plan.mdx +91 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/standard.md +54 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/canvas.mdx +53 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/plan.mdx +225 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/standard.md +111 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/theme.css +472 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/trail.js +216 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/markdown.js +1 -1
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +37 -4
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +125 -29
- package/skills/global_config/plan-canvas/scripts/plan-canvas.js +196 -8
- package/skills/global_config/pr-recap/SKILL.md +47 -0
- package/skills/global_config/pr-recap/scripts/pr-recap.mjs +200 -0
- package/skills/global_config/quick-recap/SKILL.md +55 -0
- package/skills/global_config/stay-within-limits/SKILL.md +85 -0
- package/skills/global_config/triage/SKILL.md +3 -0
- package/skills/global_config/visual-edit/README.md +96 -0
- package/skills/global_config/visual-edit/SKILL.md +615 -0
- package/skills/global_config/visual-plan/README.md +93 -0
- package/skills/global_config/visual-plan/SKILL.md +544 -0
- package/skills/global_config/visual-plan/references/canvas.md +139 -0
- package/skills/global_config/visual-plan/references/connection.md +51 -0
- package/skills/global_config/visual-plan/references/document-quality.md +186 -0
- package/skills/global_config/visual-plan/references/exemplar.md +62 -0
- package/skills/global_config/visual-plan/references/local-files.md +99 -0
- package/skills/global_config/visual-plan/references/wireframe.md +319 -0
- package/skills/global_config/visual-recap/README.md +103 -0
- package/skills/global_config/visual-recap/SKILL.md +560 -0
- package/skills/global_config/visual-recap/references/connection.md +51 -0
- package/skills/global_config/visual-recap/references/local-files.md +99 -0
- package/skills/global_config/visual-recap/references/wireframe.md +319 -0
- package/skills/playbooks/pb-ci-fix/SKILL.md +49 -0
- package/skills/playbooks/pb-event-tracker/SKILL.md +45 -0
- package/skills/playbooks/pb-meeting-actions/SKILL.md +42 -0
- package/skills/playbooks/pb-project-new/SKILL.md +48 -0
- package/skills/playbooks/pb-week-plan/SKILL.md +45 -0
- package/assets/header-v4.jpg +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Trailmark - Saved Routes
|
|
3
|
+
status: draft
|
|
4
|
+
needs-api: foundations
|
|
5
|
+
needs-screens: api
|
|
6
|
+
needs-sync: api
|
|
7
|
+
needs-launch: screens, sync
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Trailmark: Saved Routes
|
|
11
|
+
|
|
12
|
+
<Callout tone="note" title="How to use this template">
|
|
13
|
+
|
|
14
|
+
Replace the Trailmark example with your feature: rewrite the Goal and success list, swap the Decision for your real fork in the road, edit the screens in canvas.mdx (keep ids in sync with the transitions), and replace the build map sections and file paths. Delete sections you do not need. All names here are invented.
|
|
15
|
+
|
|
16
|
+
</Callout>
|
|
17
|
+
|
|
18
|
+
## Goal
|
|
19
|
+
|
|
20
|
+
Trailmark is a fictional hiking app. Saved Routes lets a hiker bookmark a route from the map, find it again offline and sync it across devices. The feature ships to the beta group in four weeks.
|
|
21
|
+
|
|
22
|
+
- A hiker saves a route in two taps from the map.
|
|
23
|
+
- Saved routes open without a network connection.
|
|
24
|
+
- A route saved on the phone shows up on the tablet within a minute.
|
|
25
|
+
|
|
26
|
+
## Storyboard
|
|
27
|
+
|
|
28
|
+
Four screens in canvas.mdx cover the happy path: map, save sheet, saved list, route detail.
|
|
29
|
+
|
|
30
|
+
## Decision
|
|
31
|
+
|
|
32
|
+
<Decision title="Where do saved routes live?" question="Which store is the source of truth for a saved route?" options={[
|
|
33
|
+
{ id: "local-first", label: "Local database with background sync", detail: "Works offline by design, needs a conflict rule.", recommended: true },
|
|
34
|
+
{ id: "server", label: "Server only, cached reads", detail: "Simple model, but the list is empty offline until cached." },
|
|
35
|
+
{ id: "file", label: "Exported GPX files", detail: "Portable, but no sync and no list ordering." },
|
|
36
|
+
]}>
|
|
37
|
+
|
|
38
|
+
Hikers lose signal on exactly the trails they want to follow. A local database makes offline the normal case, and last-edit-wins per route is enough for one person using two devices.
|
|
39
|
+
|
|
40
|
+
</Decision>
|
|
41
|
+
|
|
42
|
+
## Flow
|
|
43
|
+
|
|
44
|
+
<Mermaid label="Save and sync" source={"sequenceDiagram\n participant H as Hiker\n participant A as App\n participant L as Local DB\n participant S as Sync API\n H->>A: Tap Save on a route\n A->>L: Insert route with revision 1\n A-->>H: Show Saved toast\n A->>S: Push pending changes\n S-->>A: Accepted, server revision\n A->>L: Mark route synced"} />
|
|
45
|
+
|
|
46
|
+
## Acceptance {#acceptance}
|
|
47
|
+
|
|
48
|
+
<Checklist title="Done when" items={[
|
|
49
|
+
{ label: "Save from the map takes two taps or fewer" },
|
|
50
|
+
{ label: "Saved list opens in airplane mode" },
|
|
51
|
+
{ label: "Removing a route on one device removes it on the other" },
|
|
52
|
+
{ label: "A list of 500 routes scrolls without dropped frames" },
|
|
53
|
+
{ label: "Screen reader announces Saved and Removed" },
|
|
54
|
+
]} />
|
|
55
|
+
|
|
56
|
+
## Build map
|
|
57
|
+
|
|
58
|
+
### Foundations {#foundations}
|
|
59
|
+
|
|
60
|
+
Schema and feature flag.
|
|
61
|
+
|
|
62
|
+
<Checklist title="Foundations tasks" items={[
|
|
63
|
+
{ label: "Feature flag saved_routes, off by default", checked: true },
|
|
64
|
+
{ label: "Local table saved_routes with revision and deleted_at" },
|
|
65
|
+
]} />
|
|
66
|
+
|
|
67
|
+
<ImplementationMap files={[
|
|
68
|
+
{ path: "app/db/migrations/012_saved_routes.sql", change: "added", note: "Table with revision and tombstone column" },
|
|
69
|
+
{ path: "app/flags.ts", change: "modified", note: "Add saved_routes flag" },
|
|
70
|
+
]} />
|
|
71
|
+
|
|
72
|
+
### Sync API {#api}
|
|
73
|
+
|
|
74
|
+
Push and pull endpoints with a revision check.
|
|
75
|
+
|
|
76
|
+
<Checklist title="API tasks" items={[
|
|
77
|
+
{ label: "POST /v1/saved-routes/push with revision check" },
|
|
78
|
+
{ label: "GET /v1/saved-routes/pull?since=cursor" },
|
|
79
|
+
{ label: "Tombstones kept for 30 days" },
|
|
80
|
+
]} />
|
|
81
|
+
|
|
82
|
+
<ImplementationMap files={[
|
|
83
|
+
{ path: "server/routes/saved-routes.ts", change: "added", note: "Push and pull handlers" },
|
|
84
|
+
{ path: "server/jobs/purge-tombstones.ts", change: "added", note: "Nightly cleanup after 30 days" },
|
|
85
|
+
]} />
|
|
86
|
+
|
|
87
|
+
### Screens {#screens}
|
|
88
|
+
|
|
89
|
+
Save sheet, saved list and route detail.
|
|
90
|
+
|
|
91
|
+
<Checklist title="Screen tasks" items={[
|
|
92
|
+
{ label: "Save button and bottom sheet on the map" },
|
|
93
|
+
{ label: "Saved list with sort and empty state" },
|
|
94
|
+
{ label: "Route detail with remove action" },
|
|
95
|
+
]} />
|
|
96
|
+
|
|
97
|
+
<ImplementationMap files={[
|
|
98
|
+
{ path: "app/screens/SaveSheet.tsx", change: "added", note: "Name, folder and Save button" },
|
|
99
|
+
{ path: "app/screens/SavedList.tsx", change: "added", note: "Virtualised list and empty state" },
|
|
100
|
+
{ path: "app/screens/RouteDetail.tsx", change: "added", note: "Map preview, stats, remove" },
|
|
101
|
+
]} />
|
|
102
|
+
|
|
103
|
+
### Sync client {#sync}
|
|
104
|
+
|
|
105
|
+
Background push and pull with retry.
|
|
106
|
+
|
|
107
|
+
<Checklist title="Sync tasks" items={[
|
|
108
|
+
{ label: "Push pending rows on connectivity change" },
|
|
109
|
+
{ label: "Pull on app start and every five minutes" },
|
|
110
|
+
{ label: "Last edit wins, ties broken by device id" },
|
|
111
|
+
]} />
|
|
112
|
+
|
|
113
|
+
<ImplementationMap files={[
|
|
114
|
+
{ path: "app/sync/saved-routes-sync.ts", change: "added", note: "Push, pull and conflict rule" },
|
|
115
|
+
]} />
|
|
116
|
+
|
|
117
|
+
### Launch {#launch}
|
|
118
|
+
|
|
119
|
+
Beta rollout.
|
|
120
|
+
|
|
121
|
+
<Checklist title="Launch tasks" items={[
|
|
122
|
+
{ label: "Enable flag for the beta group" },
|
|
123
|
+
{ label: "Watch sync error rate for three days" },
|
|
124
|
+
]} />
|
|
125
|
+
|
|
126
|
+
<AgentTrail />
|
|
127
|
+
|
|
128
|
+
## Risks
|
|
129
|
+
|
|
130
|
+
<Table title="Risks" columns={["Risk", "Likelihood", "Impact", "Mitigation"]} rows={[
|
|
131
|
+
["Two devices edit the same route offline", "Medium", "Low", "Last edit wins, keep the older copy in a recently-deleted list"],
|
|
132
|
+
["Large saved lists slow the map", "Low", "Medium", "Load the list lazily, never on map start"],
|
|
133
|
+
["Tombstones grow without bound", "Low", "Low", "Nightly purge after 30 days"],
|
|
134
|
+
]} />
|
|
135
|
+
|
|
136
|
+
## Open questions
|
|
137
|
+
|
|
138
|
+
<QuestionForm title="Open Questions" questions={[
|
|
139
|
+
{ title: "Can routes be grouped in folders?", mode: "single", options: [
|
|
140
|
+
{ label: "Not in the beta", recommended: true }, { label: "One level of folders" } ] },
|
|
141
|
+
{ title: "Default sort of the saved list?", mode: "single", options: [
|
|
142
|
+
{ label: "Recently saved", recommended: true }, { label: "Distance from here" }, { label: "Alphabetical" } ] },
|
|
143
|
+
{ title: "Which extras do we want in the beta?", mode: "multi", options: [
|
|
144
|
+
{ label: "Share a route link" }, { label: "Export as GPX" }, { label: "Notes on a saved route" } ] },
|
|
145
|
+
]} />
|
package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/standard.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Trailmark: Saved Routes
|
|
2
|
+
|
|
3
|
+
> **How to use this template:** replace the Trailmark example with your feature. Rewrite the goal, the decision, the screens, the build order and the open questions. All names here are invented.
|
|
4
|
+
|
|
5
|
+
**Status:** draft **Target:** beta group in four weeks
|
|
6
|
+
|
|
7
|
+
## 1. Goal
|
|
8
|
+
|
|
9
|
+
Trailmark is a fictional hiking app. Saved Routes lets a hiker bookmark a route from the map, find it again offline and sync it across devices.
|
|
10
|
+
|
|
11
|
+
- A hiker saves a route in two taps from the map.
|
|
12
|
+
- Saved routes open without a network connection.
|
|
13
|
+
- A route saved on the phone shows up on the tablet within a minute.
|
|
14
|
+
|
|
15
|
+
## 2. Screens
|
|
16
|
+
|
|
17
|
+
| # | Screen | Purpose | Next |
|
|
18
|
+
|---|--------|---------|------|
|
|
19
|
+
| 1 | Map | Route selected, Save button | Save sheet |
|
|
20
|
+
| 2 | Save sheet | Name, offline and sync toggles | Saved list |
|
|
21
|
+
| 3 | Saved list | Sort chips, sync state per row | Route detail |
|
|
22
|
+
| 4 | Route detail | Stats and Remove action | Saved list |
|
|
23
|
+
|
|
24
|
+
## 3. Decision
|
|
25
|
+
|
|
26
|
+
| Question | Options | Recommendation | Why |
|
|
27
|
+
|----------|---------|----------------|-----|
|
|
28
|
+
| Where do saved routes live? | Local database with sync / server only / GPX files | **Local database with background sync** | Hikers lose signal on the trails they want to follow; last edit wins is enough for one person on two devices |
|
|
29
|
+
|
|
30
|
+
## 4. Flow
|
|
31
|
+
|
|
32
|
+
```mermaid
|
|
33
|
+
sequenceDiagram
|
|
34
|
+
participant H as Hiker
|
|
35
|
+
participant A as App
|
|
36
|
+
participant L as Local DB
|
|
37
|
+
participant S as Sync API
|
|
38
|
+
H->>A: Tap Save on a route
|
|
39
|
+
A->>L: Insert route with revision 1
|
|
40
|
+
A-->>H: Show Saved toast
|
|
41
|
+
A->>S: Push pending changes
|
|
42
|
+
S-->>A: Accepted, server revision
|
|
43
|
+
A->>L: Mark route synced
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 5. Build order
|
|
47
|
+
|
|
48
|
+
- [x] Feature flag saved_routes, off by default
|
|
49
|
+
- [ ] Local table saved_routes with revision and deleted_at
|
|
50
|
+
- [ ] Push and pull endpoints with revision check
|
|
51
|
+
- [ ] Tombstones kept for 30 days, nightly purge
|
|
52
|
+
- [ ] Save sheet, saved list and route detail screens
|
|
53
|
+
- [ ] Background sync with last edit wins
|
|
54
|
+
- [ ] Enable for the beta group and watch the sync error rate
|
|
55
|
+
|
|
56
|
+
## 6. Acceptance
|
|
57
|
+
|
|
58
|
+
- [ ] Save from the map takes two taps or fewer
|
|
59
|
+
- [ ] Saved list opens in airplane mode
|
|
60
|
+
- [ ] Removing a route on one device removes it on the other
|
|
61
|
+
- [ ] A list of 500 routes scrolls without dropped frames
|
|
62
|
+
- [ ] Screen reader announces Saved and Removed
|
|
63
|
+
|
|
64
|
+
## 7. Risks
|
|
65
|
+
|
|
66
|
+
| Risk | Likelihood | Impact | Mitigation |
|
|
67
|
+
|------|------------|--------|------------|
|
|
68
|
+
| Two devices edit the same route offline | Medium | Low | Last edit wins, keep the older copy in a recently-deleted list |
|
|
69
|
+
| Large saved lists slow the map | Low | Medium | Load the list lazily, never on map start |
|
|
70
|
+
| Tombstones grow without bound | Low | Low | Nightly purge after 30 days |
|
|
71
|
+
|
|
72
|
+
## 8. Open questions
|
|
73
|
+
|
|
74
|
+
- Can routes be grouped in folders? Recommended: not in the beta.
|
|
75
|
+
- Default sort of the saved list? Recommended: recently saved.
|
|
76
|
+
- Extras for the beta: share a route link, export as GPX, notes on a saved route.
|
package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/meta.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"migration","label":"Migration","description":"Data or system migration plan with phases, cutover checklist, rollback and a risk table.","useWhen":"You move data or a service from an old system to a new one and need a safe cutover and a way back.","hasBoard":false}
|
package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/plan.mdx
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Lantern Ledger - Database Cutover
|
|
3
|
+
status: draft
|
|
4
|
+
needs-snapshot: prep
|
|
5
|
+
needs-replicate: prep, snapshot
|
|
6
|
+
needs-verify: replicate
|
|
7
|
+
needs-cutover: verify
|
|
8
|
+
needs-decommission: cutover
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Lantern Ledger: Move from legacy Postgres 12 to managed Postgres 16
|
|
12
|
+
|
|
13
|
+
<Callout tone="note" title="How to use this template">
|
|
14
|
+
|
|
15
|
+
Replace the system names, sizes, dates and owners with your own. Keep the phase structure, the rollback trigger and the cutover checklist: they are what make a migration reviewable. Delete the open questions you have already answered. All names and numbers below are invented.
|
|
16
|
+
|
|
17
|
+
</Callout>
|
|
18
|
+
|
|
19
|
+
## Goal
|
|
20
|
+
|
|
21
|
+
Move the Lantern Ledger billing database (410 GB, 38 tables) from a self-hosted Postgres 12 VM to a managed Postgres 16 cluster with at most 10 minutes of write downtime and no lost invoices.
|
|
22
|
+
|
|
23
|
+
- Cutover happens in one maintenance window on a Sunday, 02:00 to 04:00 UTC.
|
|
24
|
+
- The old database stays readable and in sync for 14 days as the rollback path.
|
|
25
|
+
- Every row count and a checksum of the money columns match before traffic moves.
|
|
26
|
+
|
|
27
|
+
## Decision
|
|
28
|
+
|
|
29
|
+
<Decision title="How do we move the data?" question="Which method gets 410 GB across with the shortest write freeze?" options={[
|
|
30
|
+
{ id: "logical", label: "Logical replication, then switch", detail: "Initial copy runs live, changes stream until cutover. Freeze is minutes.", recommended: true },
|
|
31
|
+
{ id: "dump", label: "Dump and restore in the window", detail: "Simple, but the freeze would last about 5 hours at our measured speed." },
|
|
32
|
+
{ id: "vendor", label: "Vendor migration service", detail: "Least work for us, but opaque failure modes and per-GB cost." },
|
|
33
|
+
]}>
|
|
34
|
+
|
|
35
|
+
Logical replication keeps the freeze short and lets us verify the target for days before the switch. The cost is that sequences and large objects need a manual step at cutover, which is on the checklist.
|
|
36
|
+
|
|
37
|
+
</Decision>
|
|
38
|
+
|
|
39
|
+
## Timeline
|
|
40
|
+
|
|
41
|
+
<Mermaid label="Phases and dates" source={"gantt\n dateFormat YYYY-MM-DD\n axisFormat %d %b\n section Prepare\n Inventory and schema diff :prep, 2026-11-02, 5d\n Target cluster and network :infra, 2026-11-04, 4d\n section Copy\n Snapshot and initial load :snap, 2026-11-09, 3d\n Streaming replication :repl, after snap, 7d\n section Prove\n Verification and dry run :ver, 2026-11-16, 4d\n section Cutover\n Maintenance window :crit, cut, 2026-11-22, 1d\n Rollback watch :watch, after cut, 14d\n section Close\n Decommission old VM :done, dec, 2026-12-07, 2d"} />
|
|
42
|
+
|
|
43
|
+
## Phases
|
|
44
|
+
|
|
45
|
+
<Table title="Phase plan" columns={["Phase", "Outcome", "Exit criterion", "Owner"]} rows={[
|
|
46
|
+
["1 Prepare", "Inventory, schema diff, target cluster", "Schema applies cleanly on PG 16", "Mara (platform)"],
|
|
47
|
+
["2 Copy", "Snapshot loaded, replication streaming", "Lag under 5 seconds for 24 hours", "Mara (platform)"],
|
|
48
|
+
["3 Prove", "Verification and one full dry run", "Counts and checksums match, dry run under 10 minutes", "Joss (backend)"],
|
|
49
|
+
["4 Cutover", "Writes frozen, final sync, traffic switched", "Smoke tests green, error rate at baseline", "Mara and Joss"],
|
|
50
|
+
["5 Watch", "Old database kept as warm fallback", "14 quiet days, no rollback trigger hit", "Joss (backend)"],
|
|
51
|
+
["6 Close", "Old VM snapshotted and removed", "Sign-off from finance on the final report", "Ines (finance ops)"],
|
|
52
|
+
]} />
|
|
53
|
+
|
|
54
|
+
## Build map
|
|
55
|
+
|
|
56
|
+
### Prepare {#prep}
|
|
57
|
+
|
|
58
|
+
<Checklist title="Prepare tasks" items={[
|
|
59
|
+
{ label: "Inventory tables, extensions and large objects", checked: true },
|
|
60
|
+
{ label: "Diff schema between PG 12 and PG 16, fix incompatibilities", checked: true },
|
|
61
|
+
{ label: "Provision the managed cluster and private network peering" },
|
|
62
|
+
{ label: "Lower DNS TTL for the database alias to 60 seconds" },
|
|
63
|
+
]} />
|
|
64
|
+
|
|
65
|
+
<ImplementationMap files={[
|
|
66
|
+
{ path: "migration/schema_diff.sql", change: "added", note: "Reviewed differences and fixes" },
|
|
67
|
+
{ path: "infra/db-cluster.tf", change: "added", note: "Managed cluster, replicas, backups" },
|
|
68
|
+
]} />
|
|
69
|
+
|
|
70
|
+
### Snapshot and initial load {#snapshot}
|
|
71
|
+
|
|
72
|
+
<Checklist title="Snapshot tasks" items={[
|
|
73
|
+
{ label: "Create the publication on the source for all 38 tables" },
|
|
74
|
+
{ label: "Run the initial copy with four parallel workers" },
|
|
75
|
+
{ label: "Record the start position and row counts" },
|
|
76
|
+
]} />
|
|
77
|
+
|
|
78
|
+
<ImplementationMap files={[
|
|
79
|
+
{ path: "migration/publication.sql", change: "added", note: "Publication and replication slot" },
|
|
80
|
+
{ path: "migration/initial_load.sh", change: "added", note: "Parallel table copy with logging" },
|
|
81
|
+
]} />
|
|
82
|
+
|
|
83
|
+
### Replication {#replicate}
|
|
84
|
+
|
|
85
|
+
<Checklist title="Replication tasks" items={[
|
|
86
|
+
{ label: "Subscribe and let changes stream" },
|
|
87
|
+
{ label: "Alert when lag exceeds 30 seconds" },
|
|
88
|
+
{ label: "Plan sequence resync for cutover" },
|
|
89
|
+
]} />
|
|
90
|
+
|
|
91
|
+
<ImplementationMap files={[
|
|
92
|
+
{ path: "migration/subscription.sql", change: "added", note: "Subscription on the target" },
|
|
93
|
+
{ path: "ops/alerts/replication-lag.yml", change: "added", note: "Lag alert and runbook link" },
|
|
94
|
+
]} />
|
|
95
|
+
|
|
96
|
+
### Verify {#verify}
|
|
97
|
+
|
|
98
|
+
<Checklist title="Verify tasks" items={[
|
|
99
|
+
{ label: "Row counts equal on all 38 tables" },
|
|
100
|
+
{ label: "Checksum of amount and tax columns equal" },
|
|
101
|
+
{ label: "Replay one week of read queries against the target" },
|
|
102
|
+
{ label: "Full dry run on a clone, stopwatch under 10 minutes" },
|
|
103
|
+
]} />
|
|
104
|
+
|
|
105
|
+
<ImplementationMap files={[
|
|
106
|
+
{ path: "migration/verify.py", change: "added", note: "Counts and checksums per table" },
|
|
107
|
+
{ path: "migration/replay_reads.sh", change: "added", note: "Read replay and latency compare" },
|
|
108
|
+
]} />
|
|
109
|
+
|
|
110
|
+
### Cutover {#cutover}
|
|
111
|
+
|
|
112
|
+
<Checklist title="Cutover checklist" items={[
|
|
113
|
+
{ label: "T-60 min: status page notice posted and on-call confirmed" },
|
|
114
|
+
{ label: "T-30 min: last backup of the source verified restorable" },
|
|
115
|
+
{ label: "T-10 min: pause the invoice scheduler and background workers" },
|
|
116
|
+
{ label: "T-0: set source to read-only, confirm zero active writers" },
|
|
117
|
+
{ label: "T+2 min: replication lag is zero, final counts match" },
|
|
118
|
+
{ label: "T+4 min: resync sequences, enable constraints, run ANALYZE" },
|
|
119
|
+
{ label: "T+6 min: switch the DNS alias and app config to the target" },
|
|
120
|
+
{ label: "T+8 min: start workers, run the smoke test suite" },
|
|
121
|
+
{ label: "T+10 min: go or rollback decision, announce on the status page" },
|
|
122
|
+
]} />
|
|
123
|
+
|
|
124
|
+
<ImplementationMap files={[
|
|
125
|
+
{ path: "migration/cutover_runbook.md", change: "added", note: "Minute by minute script with owners" },
|
|
126
|
+
{ path: "migration/sequences.sql", change: "added", note: "Sequence resync for every serial column" },
|
|
127
|
+
]} />
|
|
128
|
+
|
|
129
|
+
### Decommission {#decommission}
|
|
130
|
+
|
|
131
|
+
<Checklist title="Decommission tasks" items={[
|
|
132
|
+
{ label: "Stop reverse replication after 14 quiet days" },
|
|
133
|
+
{ label: "Take a final snapshot and archive it for one year" },
|
|
134
|
+
{ label: "Delete the old VM and the replication slot" },
|
|
135
|
+
]} />
|
|
136
|
+
|
|
137
|
+
<ImplementationMap files={[
|
|
138
|
+
{ path: "migration/final_report.md", change: "added", note: "Counts, timings, incidents, sign-off" },
|
|
139
|
+
]} />
|
|
140
|
+
|
|
141
|
+
## Agent trail
|
|
142
|
+
|
|
143
|
+
<AgentTrail />
|
|
144
|
+
|
|
145
|
+
## Rollback
|
|
146
|
+
|
|
147
|
+
<Callout tone="warning" title="Rollback trigger and path">
|
|
148
|
+
|
|
149
|
+
Roll back if any of these hold within the first 24 hours: smoke tests fail twice, p95 write latency is above 400 ms for 10 minutes, or any checksum differs. Path: freeze writes on the target, point the alias back to the old VM (kept in sync by reverse replication), unpause the workers, post a status update. Target time to roll back: 8 minutes. After 24 hours, a rollback needs a fresh GO from the owner.
|
|
150
|
+
|
|
151
|
+
</Callout>
|
|
152
|
+
|
|
153
|
+
## Risks
|
|
154
|
+
|
|
155
|
+
<Table title="Risk register" columns={["Risk", "Likelihood", "Impact", "Mitigation"]} rows={[
|
|
156
|
+
["Replication lag grows during the Friday batch", "Medium", "High", "Schedule the window after the batch, alert at 30 s"],
|
|
157
|
+
["Sequences not resynced, duplicate key errors", "Medium", "High", "Scripted resync on the checklist, tested in the dry run"],
|
|
158
|
+
["Extension missing on managed Postgres", "Low", "High", "Found in the inventory phase, replace before the copy"],
|
|
159
|
+
["DNS caches keep old address", "Medium", "Medium", "TTL 60 s a week ahead, app uses the alias only"],
|
|
160
|
+
["Cutover runs longer than the window", "Low", "Medium", "Dry run timing, hard stop at T+20 min triggers rollback"],
|
|
161
|
+
]} />
|
|
162
|
+
|
|
163
|
+
## Open questions
|
|
164
|
+
|
|
165
|
+
<QuestionForm title="Open Questions" questions={[
|
|
166
|
+
{ title: "How long do we keep the old VM?", mode: "single", options: [
|
|
167
|
+
{ label: "14 days", recommended: true }, { label: "30 days" }, { label: "Delete right after cutover" } ] },
|
|
168
|
+
{ title: "Who can call the rollback?", mode: "single", options: [
|
|
169
|
+
{ label: "The on-call engineer", recommended: true }, { label: "Only the migration owner" } ] },
|
|
170
|
+
{ title: "Which extras do we add to the window?", mode: "multi", options: [
|
|
171
|
+
{ label: "Upgrade the connection pooler" }, { label: "Enable query insights" }, { label: "Rotate database passwords" } ] },
|
|
172
|
+
]} />
|
package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/standard.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Lantern Ledger: Move from legacy Postgres 12 to managed Postgres 16
|
|
2
|
+
|
|
3
|
+
> Template guidance: replace the invented system names, sizes, dates and owners. Keep the phases, rollback trigger and cutover checklist. All names and numbers are invented.
|
|
4
|
+
|
|
5
|
+
**Status:** draft
|
|
6
|
+
|
|
7
|
+
## Goal
|
|
8
|
+
|
|
9
|
+
Move the Lantern Ledger billing database (410 GB, 38 tables) from a self-hosted Postgres 12 VM to a managed Postgres 16 cluster with at most 10 minutes of write downtime and no lost invoices.
|
|
10
|
+
|
|
11
|
+
- Cutover happens in one maintenance window on a Sunday, 02:00 to 04:00 UTC.
|
|
12
|
+
- The old database stays readable and in sync for 14 days as the rollback path.
|
|
13
|
+
- Every row count and a checksum of the money columns match before traffic moves.
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
| Question | Options | Recommendation | Why |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| How do we move the data? | Logical replication / dump and restore / vendor service | **Logical replication, then switch** | Freeze is minutes; the target can be verified for days. Sequences and large objects need a manual step at cutover. |
|
|
20
|
+
|
|
21
|
+
## Timeline
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
gantt
|
|
25
|
+
dateFormat YYYY-MM-DD
|
|
26
|
+
axisFormat %d %b
|
|
27
|
+
section Prepare
|
|
28
|
+
Inventory and schema diff :prep, 2026-11-02, 5d
|
|
29
|
+
Target cluster and network :infra, 2026-11-04, 4d
|
|
30
|
+
section Copy
|
|
31
|
+
Snapshot and initial load :snap, 2026-11-09, 3d
|
|
32
|
+
Streaming replication :repl, after snap, 7d
|
|
33
|
+
section Prove
|
|
34
|
+
Verification and dry run :ver, 2026-11-16, 4d
|
|
35
|
+
section Cutover
|
|
36
|
+
Maintenance window :crit, cut, 2026-11-22, 1d
|
|
37
|
+
Rollback watch :watch, after cut, 14d
|
|
38
|
+
section Close
|
|
39
|
+
Decommission old VM :done, dec, 2026-12-07, 2d
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Phases
|
|
43
|
+
|
|
44
|
+
| Phase | Outcome | Exit criterion | Owner |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| 1 Prepare | Inventory, schema diff, target cluster | Schema applies cleanly on PG 16 | Mara (platform) |
|
|
47
|
+
| 2 Copy | Snapshot loaded, replication streaming | Lag under 5 seconds for 24 hours | Mara (platform) |
|
|
48
|
+
| 3 Prove | Verification and one full dry run | Counts and checksums match, dry run under 10 minutes | Joss (backend) |
|
|
49
|
+
| 4 Cutover | Writes frozen, final sync, traffic switched | Smoke tests green, error rate at baseline | Mara and Joss |
|
|
50
|
+
| 5 Watch | Old database kept as warm fallback | 14 quiet days, no rollback trigger hit | Joss (backend) |
|
|
51
|
+
| 6 Close | Old VM snapshotted and removed | Sign-off from finance on the final report | Ines (finance ops) |
|
|
52
|
+
|
|
53
|
+
## Prepare tasks
|
|
54
|
+
|
|
55
|
+
- [x] Inventory tables, extensions and large objects
|
|
56
|
+
- [x] Diff schema between PG 12 and PG 16, fix incompatibilities
|
|
57
|
+
- [ ] Provision the managed cluster and private network peering
|
|
58
|
+
- [ ] Lower DNS TTL for the database alias to 60 seconds
|
|
59
|
+
|
|
60
|
+
## Copy and verify tasks
|
|
61
|
+
|
|
62
|
+
- [ ] Create the publication on the source for all 38 tables
|
|
63
|
+
- [ ] Run the initial copy with four parallel workers
|
|
64
|
+
- [ ] Subscribe, let changes stream, alert when lag exceeds 30 seconds
|
|
65
|
+
- [ ] Row counts and money-column checksums equal on all tables
|
|
66
|
+
- [ ] Replay one week of read queries against the target
|
|
67
|
+
- [ ] Full dry run on a clone, stopwatch under 10 minutes
|
|
68
|
+
|
|
69
|
+
## Cutover checklist
|
|
70
|
+
|
|
71
|
+
- [ ] T-60 min: status page notice posted and on-call confirmed
|
|
72
|
+
- [ ] T-30 min: last backup of the source verified restorable
|
|
73
|
+
- [ ] T-10 min: pause the invoice scheduler and background workers
|
|
74
|
+
- [ ] T-0: set source to read-only, confirm zero active writers
|
|
75
|
+
- [ ] T+2 min: replication lag is zero, final counts match
|
|
76
|
+
- [ ] T+4 min: resync sequences, enable constraints, run ANALYZE
|
|
77
|
+
- [ ] T+6 min: switch the DNS alias and app config to the target
|
|
78
|
+
- [ ] T+8 min: start workers, run the smoke test suite
|
|
79
|
+
- [ ] T+10 min: go or rollback decision, announce on the status page
|
|
80
|
+
|
|
81
|
+
## Rollback
|
|
82
|
+
|
|
83
|
+
**Trigger (first 24 hours):** smoke tests fail twice, p95 write latency above 400 ms for 10 minutes, or any checksum differs.
|
|
84
|
+
**Path:** freeze writes on the target, point the alias back to the old VM (kept in sync by reverse replication), unpause workers, post a status update. Target time: 8 minutes. After 24 hours a rollback needs a fresh GO from the owner.
|
|
85
|
+
|
|
86
|
+
## Risks
|
|
87
|
+
|
|
88
|
+
| Risk | Likelihood | Impact | Mitigation |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| Replication lag grows during the Friday batch | Medium | High | Schedule the window after the batch, alert at 30 s |
|
|
91
|
+
| Sequences not resynced, duplicate key errors | Medium | High | Scripted resync on the checklist, tested in the dry run |
|
|
92
|
+
| Extension missing on managed Postgres | Low | High | Found in the inventory phase, replace before the copy |
|
|
93
|
+
| DNS caches keep old address | Medium | Medium | TTL 60 s a week ahead, app uses the alias only |
|
|
94
|
+
| Cutover runs longer than the window | Low | Medium | Dry run timing, hard stop at T+20 min triggers rollback |
|
|
95
|
+
|
|
96
|
+
## Open questions
|
|
97
|
+
|
|
98
|
+
- How long do we keep the old VM? 14 days (recommended) / 30 days / delete right after cutover
|
|
99
|
+
- Who can call the rollback? The on-call engineer (recommended) / only the migration owner
|
|
100
|
+
- Which extras join the window? Connection pooler upgrade / query insights / password rotation
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"recap","label":"Visual recap","description":"A post-work recap with what changed, why, how it was verified and follow-ups.","useWhen":"Work is finished and reviewers or teammates need a quick, visual summary of the change.","hasBoard":false}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Retry failed exports automatically
|
|
3
|
+
subtitle: Failed Lanternfish exports now retry with backoff and show their state to the customer. Invented example.
|
|
4
|
+
kind: recap
|
|
5
|
+
pr: "#142"
|
|
6
|
+
branch: feat/export-retry
|
|
7
|
+
base: main
|
|
8
|
+
commit: 4c8a1f0
|
|
9
|
+
files: 5
|
|
10
|
+
additions: 318
|
|
11
|
+
deletions: 24
|
|
12
|
+
author: sample-author
|
|
13
|
+
date: 2026-03-09
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<Callout tone="note" title="How to use this template">
|
|
17
|
+
|
|
18
|
+
Replace the Lanternfish example with your own change: update the frontmatter chips (PR, branch, commit, counts), rewrite the summary and the reason, list the files you really touched, and record the checks you actually ran. Delete sections you do not need. All names and numbers here are invented.
|
|
19
|
+
|
|
20
|
+
</Callout>
|
|
21
|
+
|
|
22
|
+
## What changed
|
|
23
|
+
|
|
24
|
+
Failed exports used to disappear: the job died, the customer saw a spinner and support had to re-run it by hand. Exports now retry up to three times with growing delays, and the export list shows each job as Queued, Running, Retrying or Failed.
|
|
25
|
+
|
|
26
|
+
- Retry delays are 1, 5 and 25 minutes.
|
|
27
|
+
- A job that exhausts its retries stays visible with the last error message.
|
|
28
|
+
- Customers can re-run a failed export with one click.
|
|
29
|
+
|
|
30
|
+
<ImplementationMap title="Changed files" files={[
|
|
31
|
+
{ path: "worker/export-runner.ts", change: "modified", note: "Wrap the run in retry with backoff" },
|
|
32
|
+
{ path: "worker/retry-policy.ts", change: "added", note: "Delay table and attempt counter" },
|
|
33
|
+
{ path: "api/exports/list.ts", change: "modified", note: "Return state and last error" },
|
|
34
|
+
{ path: "app/exports/export-row.tsx", change: "modified", note: "State badge and re-run button" },
|
|
35
|
+
{ path: "tests/export-retry.test.ts", change: "added", note: "Backoff, exhaustion and re-run cases" },
|
|
36
|
+
]} />
|
|
37
|
+
|
|
38
|
+
## Why
|
|
39
|
+
|
|
40
|
+
<Table title="Reasons" columns={["Problem", "Evidence", "Fixed by"]} rows={[
|
|
41
|
+
["About 4 percent of exports failed once", "Support ticket sample", "Automatic retry"],
|
|
42
|
+
["Customers could not tell failed from slow", "Five tickets asking if an export was stuck", "Visible job state"],
|
|
43
|
+
["Support re-ran jobs by hand", "About 6 manual re-runs per week", "Re-run button"],
|
|
44
|
+
]} />
|
|
45
|
+
|
|
46
|
+
## How verified
|
|
47
|
+
|
|
48
|
+
<Checklist title="Checks">
|
|
49
|
+
- [x] Unit tests for backoff delays and attempt limit
|
|
50
|
+
- [x] Integration test: a job that fails twice then succeeds ends as Done
|
|
51
|
+
- [x] Manual run on staging with a forced failure, state badge changed as expected
|
|
52
|
+
- [ ] Load test with 300 queued jobs (not run yet)
|
|
53
|
+
</Checklist>
|
|
54
|
+
|
|
55
|
+
## Follow-ups
|
|
56
|
+
|
|
57
|
+
<Callout tone="warn" title="Not done in this change">
|
|
58
|
+
|
|
59
|
+
Customers are not emailed when an export finally fails, and the retry delays are fixed in code rather than configurable. Both are listed as follow-up tasks.
|
|
60
|
+
|
|
61
|
+
</Callout>
|
|
62
|
+
|
|
63
|
+
<Checklist title="Follow-ups">
|
|
64
|
+
- [ ] Email the customer after the final failure
|
|
65
|
+
- [ ] Make the delay table configurable per plan
|
|
66
|
+
- [ ] Run the 300 job load test on staging
|
|
67
|
+
</Checklist>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Recap: Retry failed exports automatically
|
|
2
|
+
|
|
3
|
+
> Invented example. Replace every fact with your own change.
|
|
4
|
+
|
|
5
|
+
**PR:** #142 **Branch:** feat/export-retry into main **Commit:** 4c8a1f0 **Date:** 2026-03-09 **Size:** 5 files, +318 / -24
|
|
6
|
+
|
|
7
|
+
## What changed
|
|
8
|
+
|
|
9
|
+
Failed exports used to disappear: the job died, the customer saw a spinner and support had to re-run it by hand. Exports now retry up to three times with growing delays (1, 5 and 25 minutes), and the export list shows each job as Queued, Running, Retrying or Failed. A job that exhausts its retries stays visible with the last error and can be re-run with one click.
|
|
10
|
+
|
|
11
|
+
| File | Change | Note |
|
|
12
|
+
|------|--------|------|
|
|
13
|
+
| worker/export-runner.ts | modified | Wrap the run in retry with backoff |
|
|
14
|
+
| worker/retry-policy.ts | added | Delay table and attempt counter |
|
|
15
|
+
| api/exports/list.ts | modified | Return state and last error |
|
|
16
|
+
| app/exports/export-row.tsx | modified | State badge and re-run button |
|
|
17
|
+
| tests/export-retry.test.ts | added | Backoff, exhaustion and re-run cases |
|
|
18
|
+
|
|
19
|
+
```mermaid
|
|
20
|
+
stateDiagram-v2
|
|
21
|
+
[*] --> Queued
|
|
22
|
+
Queued --> Running
|
|
23
|
+
Running --> Done
|
|
24
|
+
Running --> Retrying: error
|
|
25
|
+
Retrying --> Running: after delay
|
|
26
|
+
Retrying --> Failed: attempts exhausted
|
|
27
|
+
Failed --> Queued: re-run
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Why
|
|
31
|
+
|
|
32
|
+
| Problem | Evidence | Fixed by |
|
|
33
|
+
|---------|----------|----------|
|
|
34
|
+
| About 4 percent of exports failed once | Support ticket sample | Automatic retry |
|
|
35
|
+
| Customers could not tell failed from slow | Five tickets asking if an export was stuck | Visible job state |
|
|
36
|
+
| Support re-ran jobs by hand | About 6 manual re-runs per week | Re-run button |
|
|
37
|
+
|
|
38
|
+
## How verified
|
|
39
|
+
|
|
40
|
+
- [x] Unit tests for backoff delays and attempt limit
|
|
41
|
+
- [x] Integration test: a job that fails twice then succeeds ends as Done
|
|
42
|
+
- [x] Manual run on staging with a forced failure
|
|
43
|
+
- [ ] Load test with 300 queued jobs (not run yet)
|
|
44
|
+
|
|
45
|
+
## Follow-ups
|
|
46
|
+
|
|
47
|
+
- [ ] Email the customer after the final failure
|
|
48
|
+
- [ ] Make the delay table configurable per plan
|
|
49
|
+
- [ ] Run the 300 job load test on staging
|