@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.
Files changed (151) hide show
  1. package/.agents/AGENTS.md +8 -0
  2. package/.agents/nodes.json +5 -2
  3. package/.claude/hooks/conventional-commits.mjs +14 -15
  4. package/.claude/hooks/env-file-protection.mjs +14 -15
  5. package/.claude/hooks/go-gate.mjs +152 -10
  6. package/.claude/hooks/go-token.mjs +55 -0
  7. package/.claude/hooks/memb-inject.mjs +75 -62
  8. package/.claude/hooks/trail-autostart.mjs +27 -0
  9. package/.claude/hooks/trail-relay.mjs +1 -0
  10. package/.claude/settings.json +22 -4
  11. package/.claude/workflows/startcycle-dispatch.mjs +11 -4
  12. package/.opencode/plugins/bdb-aos.js +98 -121
  13. package/.opencode/plugins/lib/trail-autostart.js +38 -0
  14. package/CLAUDE.md +1 -1
  15. package/README.de.md +6 -6
  16. package/README.md +6 -6
  17. package/README.pt.md +6 -6
  18. package/THIRD_PARTY_NOTICES.md +19 -3
  19. package/assets/header-v5.png +0 -0
  20. package/bin/aos-acp.mjs +211 -0
  21. package/bin/aos-doctor.mjs +1 -1
  22. package/bin/aos-uninstall.mjs +2 -2
  23. package/docs/master-session-acp.md +51 -0
  24. package/installer.js +314 -42
  25. package/mcps/mcsc/packages/mcp/server.js +6 -7
  26. package/package.json +4 -3
  27. package/scripts/validate-skills.mjs +76 -0
  28. package/skills/basic/bdbmediastorm/SKILL.md +1 -1
  29. package/skills/basic/godmode-shipping/SKILL.md +3 -0
  30. package/skills/basic/master-session/SKILL.md +89 -0
  31. package/skills/basic/startcycle/SKILL.md +1 -1
  32. package/skills/basic/startcycle-graph/SKILL.md +2 -2
  33. package/skills/basic/startcycle-graph-user/SKILL.md +1 -1
  34. package/skills/basic/teamwork-preview/SKILL.md +1 -1
  35. package/skills/bdbrainstorm/SKILL.md +7 -1
  36. package/skills/global_config/agentic-harness-patterns/SKILL.md +257 -0
  37. package/skills/global_config/agentic-harness-patterns/metadata.json +10 -0
  38. package/skills/global_config/agentic-harness-patterns/references/agent-orchestration-pattern.md +97 -0
  39. package/skills/global_config/agentic-harness-patterns/references/bootstrap-sequence-pattern.md +106 -0
  40. package/skills/global_config/agentic-harness-patterns/references/context-engineering/compress-pattern.md +78 -0
  41. package/skills/global_config/agentic-harness-patterns/references/context-engineering/isolate-pattern.md +82 -0
  42. package/skills/global_config/agentic-harness-patterns/references/context-engineering/select-pattern.md +86 -0
  43. package/skills/global_config/agentic-harness-patterns/references/context-engineering-pattern.md +29 -0
  44. package/skills/global_config/agentic-harness-patterns/references/hook-lifecycle-pattern.md +111 -0
  45. package/skills/global_config/agentic-harness-patterns/references/memory-persistence-pattern.md +109 -0
  46. package/skills/global_config/agentic-harness-patterns/references/permission-gate-pattern.md +111 -0
  47. package/skills/global_config/agentic-harness-patterns/references/skill-runtime-pattern.md +104 -0
  48. package/skills/global_config/agentic-harness-patterns/references/task-decomposition-pattern.md +92 -0
  49. package/skills/global_config/agentic-harness-patterns/references/tool-registry-pattern.md +101 -0
  50. package/skills/global_config/agenttrail/SKILL.md +8 -0
  51. package/skills/global_config/agenttrail/bin/agenttrail.mjs +14 -0
  52. package/skills/global_config/agenttrail/bin/ensure.mjs +118 -0
  53. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
  54. package/skills/global_config/bdb-visual-edit/SKILL.md +51 -0
  55. package/skills/global_config/bdb-visual-edit/references/vite-react-source-attr.md +59 -0
  56. package/skills/global_config/bdb-visual-edit/scripts/pick-snippet.js +27 -0
  57. package/skills/global_config/bdb-visual-edit/scripts/sanitize-element.mjs +123 -0
  58. package/skills/global_config/factory-collect/SKILL.md +74 -0
  59. package/skills/global_config/factory-human-digest/SKILL.md +92 -0
  60. package/skills/global_config/factory-lookback/SKILL.md +95 -0
  61. package/skills/global_config/factory-review-prs/SKILL.md +63 -0
  62. package/skills/global_config/git-pr-review/SKILL.md +3 -0
  63. package/skills/global_config/grilling/SKILL.md +2 -0
  64. package/skills/global_config/mcsc/SKILL.md +1 -1
  65. package/skills/global_config/plan-arbiter/SKILL.md +125 -0
  66. package/skills/global_config/plan-canvas/SKILL.md +62 -5
  67. package/skills/global_config/plan-canvas/scripts/lib/loopback-guard.js +19 -3
  68. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/README.md +285 -0
  69. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/agent-trail.js +129 -0
  70. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/board-client.js +124 -0
  71. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/canvas.mdx +19 -0
  72. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/plan.mdx +18 -0
  73. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/recap-demo/plan.mdx +72 -0
  74. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/README.md +29 -0
  75. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/architecture.json +30 -0
  76. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/00_architecture.html +14950 -0
  77. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/canvas.mdx +511 -0
  78. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/plan.mdx +208 -0
  79. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/recap/plan.mdx +102 -0
  80. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/standard/plan.md +136 -0
  81. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/canvas.mdx +124 -0
  82. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/plan.mdx +37 -0
  83. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/index.js +188 -0
  84. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/kit.js +123 -0
  85. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/mdx.js +411 -0
  86. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/render.js +1291 -0
  87. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/meta.json +1 -0
  88. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/plan.mdx +195 -0
  89. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/standard.md +95 -0
  90. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/meta.json +1 -0
  91. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/plan.mdx +105 -0
  92. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/standard.md +76 -0
  93. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/canvas.mdx +81 -0
  94. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/meta.json +1 -0
  95. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/plan.mdx +145 -0
  96. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/standard.md +76 -0
  97. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/meta.json +1 -0
  98. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/plan.mdx +172 -0
  99. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/standard.md +100 -0
  100. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/meta.json +1 -0
  101. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/plan.mdx +67 -0
  102. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/standard.md +49 -0
  103. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/canvas.mdx +63 -0
  104. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/meta.json +1 -0
  105. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/plan.mdx +49 -0
  106. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/standard.md +39 -0
  107. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/meta.json +1 -0
  108. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/plan.mdx +118 -0
  109. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/standard.md +57 -0
  110. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/meta.json +1 -0
  111. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/plan.mdx +173 -0
  112. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/standard.md +96 -0
  113. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/meta.json +1 -0
  114. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/plan.mdx +91 -0
  115. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/standard.md +54 -0
  116. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/canvas.mdx +53 -0
  117. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/meta.json +1 -0
  118. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/plan.mdx +225 -0
  119. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/standard.md +111 -0
  120. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/theme.css +472 -0
  121. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/trail.js +216 -0
  122. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/markdown.js +1 -1
  123. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +37 -4
  124. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +125 -29
  125. package/skills/global_config/plan-canvas/scripts/plan-canvas.js +196 -8
  126. package/skills/global_config/pr-recap/SKILL.md +47 -0
  127. package/skills/global_config/pr-recap/scripts/pr-recap.mjs +200 -0
  128. package/skills/global_config/quick-recap/SKILL.md +55 -0
  129. package/skills/global_config/stay-within-limits/SKILL.md +85 -0
  130. package/skills/global_config/triage/SKILL.md +3 -0
  131. package/skills/global_config/visual-edit/README.md +96 -0
  132. package/skills/global_config/visual-edit/SKILL.md +615 -0
  133. package/skills/global_config/visual-plan/README.md +93 -0
  134. package/skills/global_config/visual-plan/SKILL.md +544 -0
  135. package/skills/global_config/visual-plan/references/canvas.md +139 -0
  136. package/skills/global_config/visual-plan/references/connection.md +51 -0
  137. package/skills/global_config/visual-plan/references/document-quality.md +186 -0
  138. package/skills/global_config/visual-plan/references/exemplar.md +62 -0
  139. package/skills/global_config/visual-plan/references/local-files.md +99 -0
  140. package/skills/global_config/visual-plan/references/wireframe.md +319 -0
  141. package/skills/global_config/visual-recap/README.md +103 -0
  142. package/skills/global_config/visual-recap/SKILL.md +560 -0
  143. package/skills/global_config/visual-recap/references/connection.md +51 -0
  144. package/skills/global_config/visual-recap/references/local-files.md +99 -0
  145. package/skills/global_config/visual-recap/references/wireframe.md +319 -0
  146. package/skills/playbooks/pb-ci-fix/SKILL.md +49 -0
  147. package/skills/playbooks/pb-event-tracker/SKILL.md +45 -0
  148. package/skills/playbooks/pb-meeting-actions/SKILL.md +42 -0
  149. package/skills/playbooks/pb-project-new/SKILL.md +48 -0
  150. package/skills/playbooks/pb-week-plan/SKILL.md +45 -0
  151. package/assets/header-v4.jpg +0 -0
@@ -0,0 +1 @@
1
+ {"id":"architecture","label":"Architecture","description":"A system design with diagram, data model, endpoints, trade-off decision and implementation map.","useWhen":"You are designing or reshaping a service or system and need the structure, interfaces and trade-offs agreed before building.","hasBoard":false}
@@ -0,0 +1,195 @@
1
+ ---
2
+ title: Parcelly - Delivery Tracking Service
3
+ status: draft
4
+ needs-store: foundations
5
+ needs-ingest: store
6
+ needs-api: store
7
+ needs-notify: ingest
8
+ needs-launch: api, notify
9
+ ---
10
+
11
+ # Parcelly: Delivery Tracking Service
12
+
13
+ <Callout tone="note" title="How to use this template">
14
+
15
+ Replace the Parcelly example with your system: rewrite the goals and constraints, redraw the diagram, edit the data model and endpoints, and record your real trade-off in the Decision. Keep the implementation map aligned with the build order. All names here are invented.
16
+
17
+ </Callout>
18
+
19
+ ## Goal and constraints
20
+
21
+ Parcelly is a fictional service that tracks parcels for small online shops. Carriers push scan events, shops and customers read the current status.
22
+
23
+ - Show the latest status within 10 seconds of a carrier scan.
24
+ - Handle 300 events per second at peak, 20 million parcels stored.
25
+ - Keep a full event history for 12 months.
26
+
27
+ <Table title="Constraints" columns={["Constraint", "Value"]} rows={[
28
+ ["Team", "Three engineers, no dedicated ops"],
29
+ ["Budget", "Managed services only, under 1,500 per month"],
30
+ ["Availability", "99.9 percent for reads, writes may queue"],
31
+ ["Compliance", "Customer addresses deleted 90 days after delivery"],
32
+ ]} />
33
+
34
+ ## System overview
35
+
36
+ <Mermaid label="Components" source={"flowchart LR\n C[Carrier webhooks] --> G[Ingest gateway]\n G --> Q[(Event queue)]\n Q --> W[Event worker]\n W --> D[(Parcel database)]\n W --> N[Notifier]\n N --> E[Email and SMS provider]\n D --> A[Read API]\n A --> S[Shop dashboard]\n A --> T[Customer tracking page]"} />
37
+
38
+ ## Data model
39
+
40
+ <DataModel entities={[
41
+ { name: "parcel", fields: [
42
+ { name: "id", type: "uuid", note: "Primary key" },
43
+ { name: "shop_id", type: "uuid", note: "Owner shop" },
44
+ { name: "carrier_ref", type: "text", note: "Unique per carrier" },
45
+ { name: "status", type: "enum", note: "created, in_transit, out_for_delivery, delivered, failed" },
46
+ { name: "updated_at", type: "timestamptz" },
47
+ ] },
48
+ { name: "parcel_event", fields: [
49
+ { name: "id", type: "uuid" },
50
+ { name: "parcel_id", type: "uuid", note: "References parcel" },
51
+ { name: "kind", type: "text", note: "Carrier scan code" },
52
+ { name: "occurred_at", type: "timestamptz", note: "Carrier time, not receive time" },
53
+ { name: "payload", type: "jsonb", note: "Raw event, kept 12 months" },
54
+ ] },
55
+ ]} />
56
+
57
+ ## Endpoints
58
+
59
+ <Endpoint method="POST" path="/v1/carriers/{carrier}/events" params={[
60
+ { name: "carrier", in: "path", type: "string", description: "Carrier slug" },
61
+ { name: "X-Signature", in: "header", type: "string", description: "HMAC of the body" },
62
+ ]} examples={[
63
+ { label: "202 Accepted", code: "{ \"accepted\": 3 }" },
64
+ ]}>
65
+
66
+ Accepts a batch of scan events. Verifies the signature, writes to the queue and returns without waiting for processing.
67
+
68
+ </Endpoint>
69
+
70
+ <Endpoint method="GET" path="/v1/parcels/{id}" params={[
71
+ { name: "id", in: "path", type: "uuid", description: "Parcel id" },
72
+ ]} examples={[
73
+ { label: "200 OK", code: "{ \"id\": \"b1f0\", \"status\": \"out_for_delivery\", \"events\": [] }" },
74
+ ]}>
75
+
76
+ Returns the current status and the event history, newest first.
77
+
78
+ </Endpoint>
79
+
80
+ ## Decision
81
+
82
+ <Decision title="How do events reach the database?" question="Which delivery path do we use between the gateway and the parcel database?" options={[
83
+ { id: "queue", label: "Managed queue and worker", detail: "Absorbs carrier bursts, retries on failure, one more moving part.", recommended: true },
84
+ { id: "direct", label: "Gateway writes to the database directly", detail: "Fewest parts, but bursts hit the database and carrier retries duplicate rows." },
85
+ { id: "stream", label: "Event streaming platform", detail: "Strong replay, too heavy for three engineers." },
86
+ ]}>
87
+
88
+ Carriers retry aggressively when we answer slowly. A queue lets the gateway answer in milliseconds and the worker deduplicate by carrier reference and event time.
89
+
90
+ </Decision>
91
+
92
+ ## Trade-offs
93
+
94
+ <Table title="What we accept" columns={["Choice", "Gain", "Cost"]} rows={[
95
+ ["Queue between gateway and database", "Burst tolerance, retries", "Status can lag by a few seconds"],
96
+ ["Relational store with jsonb payload", "Simple queries, one system", "Payload size grows, needs the 12 month purge"],
97
+ ["Managed notification provider", "No deliverability work", "Per-message cost"],
98
+ ]} />
99
+
100
+ ## Build map
101
+
102
+ ### Foundations {#foundations}
103
+
104
+ Accounts, environments and CI.
105
+
106
+ <Checklist title="Foundations tasks" items={[
107
+ { label: "Staging and production environments", checked: true },
108
+ { label: "CI with migration check" },
109
+ ]} />
110
+
111
+ <ImplementationMap files={[
112
+ { path: "infra/environments.tf", change: "added", note: "Staging and production" },
113
+ { path: ".github/workflows/ci.yml", change: "added", note: "Test and migration dry run" },
114
+ ]} />
115
+
116
+ ### Parcel store {#store}
117
+
118
+ Schema, retention and purge.
119
+
120
+ <Checklist title="Store tasks" items={[
121
+ { label: "Tables parcel and parcel_event with indexes" },
122
+ { label: "Nightly purge: payload after 12 months, addresses after 90 days" },
123
+ ]} />
124
+
125
+ <ImplementationMap files={[
126
+ { path: "db/migrations/001_parcels.sql", change: "added", note: "parcel and parcel_event tables" },
127
+ { path: "jobs/purge.ts", change: "added", note: "Retention purge" },
128
+ ]} />
129
+
130
+ ### Ingest {#ingest}
131
+
132
+ Gateway and worker.
133
+
134
+ <Checklist title="Ingest tasks" items={[
135
+ { label: "Signature check per carrier" },
136
+ { label: "Worker with dedupe by carrier_ref and occurred_at" },
137
+ { label: "Dead letter queue and alert" },
138
+ ]} />
139
+
140
+ <ImplementationMap files={[
141
+ { path: "services/gateway/events.ts", change: "added", note: "Verify, enqueue, answer 202" },
142
+ { path: "services/worker/process-event.ts", change: "added", note: "Dedupe, update status" },
143
+ ]} />
144
+
145
+ ### Read API {#api}
146
+
147
+ Status and history reads.
148
+
149
+ <Checklist title="API tasks" items={[
150
+ { label: "GET /v1/parcels/{id} with cache of 5 seconds" },
151
+ { label: "Shop-scoped listing with cursor paging" },
152
+ ]} />
153
+
154
+ <ImplementationMap files={[
155
+ { path: "services/api/parcels.ts", change: "added", note: "Read handlers" },
156
+ ]} />
157
+
158
+ ### Notifications {#notify}
159
+
160
+ Email and SMS on status change.
161
+
162
+ <Checklist title="Notify tasks" items={[
163
+ { label: "Notify on out_for_delivery and delivered" },
164
+ { label: "Per-shop opt out" },
165
+ ]} />
166
+
167
+ <ImplementationMap files={[
168
+ { path: "services/worker/notify.ts", change: "added", note: "Template and provider call" },
169
+ ]} />
170
+
171
+ ### Launch {#launch}
172
+
173
+ <Checklist title="Launch tasks" items={[
174
+ { label: "Load test at 600 events per second" },
175
+ { label: "Onboard three pilot shops" },
176
+ ]} />
177
+
178
+ <AgentTrail />
179
+
180
+ ## Risks
181
+
182
+ <Table title="Risks" columns={["Risk", "Likelihood", "Mitigation"]} rows={[
183
+ ["Carrier sends events out of order", "High", "Order by occurred_at, never by receive time"],
184
+ ["Queue outage stops status updates", "Low", "Gateway buffers to disk for 10 minutes, alert on queue age"],
185
+ ["Provider rate limits notifications", "Medium", "Batch and back off, drop to email only"],
186
+ ]} />
187
+
188
+ ## Open questions
189
+
190
+ <QuestionForm title="Open Questions" questions={[
191
+ { title: "Do we offer a public tracking page without login?", mode: "single", options: [
192
+ { label: "Yes, with an unguessable id", recommended: true }, { label: "No, login only" } ] },
193
+ { title: "Retention for event payloads?", mode: "single", options: [
194
+ { label: "12 months", recommended: true }, { label: "6 months" }, { label: "24 months" } ] },
195
+ ]} />
@@ -0,0 +1,95 @@
1
+ # Parcelly: Delivery Tracking Service
2
+
3
+ > **How to use this template:** replace the Parcelly example with your system. Rewrite goals, diagram, data model, endpoints and the trade-off decision. All names here are invented.
4
+
5
+ **Status:** draft
6
+
7
+ ## 1. Goal and constraints
8
+
9
+ Parcelly is a fictional service that tracks parcels for small online shops. Carriers push scan events, shops and customers read the current status.
10
+
11
+ - Show the latest status within 10 seconds of a carrier scan.
12
+ - Handle 300 events per second at peak, 20 million parcels stored.
13
+ - Keep a full event history for 12 months.
14
+
15
+ | Constraint | Value |
16
+ |------------|-------|
17
+ | Team | Three engineers, no dedicated ops |
18
+ | Budget | Managed services only, under 1,500 per month |
19
+ | Availability | 99.9 percent for reads, writes may queue |
20
+ | Compliance | Customer addresses deleted 90 days after delivery |
21
+
22
+ ## 2. System overview
23
+
24
+ ```mermaid
25
+ flowchart LR
26
+ C[Carrier webhooks] --> G[Ingest gateway]
27
+ G --> Q[(Event queue)]
28
+ Q --> W[Event worker]
29
+ W --> D[(Parcel database)]
30
+ W --> N[Notifier]
31
+ N --> E[Email and SMS provider]
32
+ D --> A[Read API]
33
+ A --> S[Shop dashboard]
34
+ A --> T[Customer tracking page]
35
+ ```
36
+
37
+ ## 3. Data model
38
+
39
+ | Entity | Field | Type | Note |
40
+ |--------|-------|------|------|
41
+ | parcel | id | uuid | Primary key |
42
+ | parcel | shop_id | uuid | Owner shop |
43
+ | parcel | carrier_ref | text | Unique per carrier |
44
+ | parcel | status | enum | created, in_transit, out_for_delivery, delivered, failed |
45
+ | parcel | updated_at | timestamptz | |
46
+ | parcel_event | id | uuid | |
47
+ | parcel_event | parcel_id | uuid | References parcel |
48
+ | parcel_event | kind | text | Carrier scan code |
49
+ | parcel_event | occurred_at | timestamptz | Carrier time, not receive time |
50
+ | parcel_event | payload | jsonb | Raw event, kept 12 months |
51
+
52
+ ## 4. Endpoints
53
+
54
+ | Method | Path | Purpose |
55
+ |--------|------|---------|
56
+ | POST | `/v1/carriers/{carrier}/events` | Accept a batch of scan events, verify HMAC in `X-Signature`, enqueue, answer 202 |
57
+ | GET | `/v1/parcels/{id}` | Current status and event history, newest first |
58
+
59
+ ## 5. Decision
60
+
61
+ | Question | Options | Recommendation | Why |
62
+ |----------|---------|----------------|-----|
63
+ | How do events reach the database? | Managed queue and worker / direct write / streaming platform | **Managed queue and worker** | Carriers retry when we answer slowly; the queue lets the gateway answer in milliseconds and the worker deduplicate |
64
+
65
+ **Trade-offs accepted**
66
+
67
+ | Choice | Gain | Cost |
68
+ |--------|------|------|
69
+ | Queue between gateway and database | Burst tolerance, retries | Status can lag by a few seconds |
70
+ | Relational store with jsonb payload | Simple queries, one system | Payload grows, needs the 12 month purge |
71
+ | Managed notification provider | No deliverability work | Per-message cost |
72
+
73
+ ## 6. Build order
74
+
75
+ - [x] Staging and production environments
76
+ - [ ] CI with migration check
77
+ - [ ] Tables parcel and parcel_event with indexes
78
+ - [ ] Nightly purge: payload after 12 months, addresses after 90 days
79
+ - [ ] Gateway with signature check, worker with dedupe
80
+ - [ ] Read API with 5 second cache
81
+ - [ ] Notifications on out_for_delivery and delivered
82
+ - [ ] Load test at 600 events per second, onboard three pilot shops
83
+
84
+ ## 7. Risks
85
+
86
+ | Risk | Likelihood | Mitigation |
87
+ |------|------------|------------|
88
+ | Carrier sends events out of order | High | Order by occurred_at, never by receive time |
89
+ | Queue outage stops status updates | Low | Gateway buffers to disk for 10 minutes, alert on queue age |
90
+ | Provider rate limits notifications | Medium | Batch and back off, drop to email only |
91
+
92
+ ## 8. Open questions
93
+
94
+ - Public tracking page without login? Recommended: yes, with an unguessable id.
95
+ - Retention for event payloads? Recommended: 12 months.
@@ -0,0 +1 @@
1
+ {"id":"bugfix","label":"Bug fix","description":"Reproduction, root cause, fix plan, diff and regression test for one defect.","useWhen":"You are fixing a defect and want the cause, the fix and the proof written down before coding.","hasBoard":false}
@@ -0,0 +1,105 @@
1
+ ---
2
+ title: Fix duplicate invoice emails
3
+ status: draft
4
+ needs-fix: repro
5
+ needs-test: fix
6
+ needs-rollout: fix, test
7
+ ---
8
+
9
+ # Fix: duplicate invoice emails
10
+
11
+ <Callout tone="note" title="How to use this template">
12
+
13
+ Replace the Ledgerly example with your bug: rewrite the symptom, the reproduction steps, the root cause with real file paths, the Diff with the actual before and after, and the regression test. Keep the sections in this order, a reviewer reads top to bottom. All names here are invented.
14
+
15
+ </Callout>
16
+
17
+ ## Symptom
18
+
19
+ Ledgerly is a fictional invoicing service. Since release 2.14 some customers receive the same invoice email two or three times. Support counted 38 reports in nine days, all for invoices sent from the scheduled run at 06:00 UTC.
20
+
21
+ - Affected: scheduled invoices only, manual sends are fine.
22
+ - Impact: customer confusion, no double charge.
23
+ - Severity: medium, no data loss.
24
+
25
+ ## Reproduction {#repro}
26
+
27
+ <Table title="Steps" columns={["Step", "Action", "Expected", "Actual"]} rows={[
28
+ ["1", "Create an invoice scheduled for 06:00 UTC", "Status scheduled", "Status scheduled"],
29
+ ["2", "Start two scheduler workers", "Both idle", "Both idle"],
30
+ ["3", "Let the 06:00 tick fire", "One email", "Two emails"],
31
+ ["4", "Check the invoice row", "sent_at set once", "sent_at set twice, 40 ms apart"],
32
+ ]} />
33
+
34
+ Reproduces locally in about one run in three with two workers and an artificial 50 ms delay in the mail client.
35
+
36
+ ## Root cause
37
+
38
+ The scheduler selects due invoices and sends them in two separate steps. Two workers can select the same row before either marks it as sent.
39
+
40
+ <Mermaid label="Race between two workers" source={"sequenceDiagram\n participant W1 as Worker 1\n participant DB as Database\n participant W2 as Worker 2\n W1->>DB: SELECT due invoices\n W2->>DB: SELECT due invoices\n DB-->>W1: invoice 4012\n DB-->>W2: invoice 4012\n W1->>W1: send email\n W2->>W2: send email\n W1->>DB: UPDATE sent_at\n W2->>DB: UPDATE sent_at"} />
41
+
42
+ <Callout tone="warning" title="Why it appeared in 2.14">
43
+
44
+ Release 2.14 raised the worker count from one to three. With one worker the race could not happen, so the missing claim step went unnoticed for two years.
45
+
46
+ </Callout>
47
+
48
+ ## Fix plan {#fix}
49
+
50
+ Claim the row atomically before sending. The claim is a single UPDATE that only succeeds for one worker.
51
+
52
+ <Decision title="How do we claim an invoice?" question="Which mechanism prevents two workers from sending the same invoice?" options={[
53
+ { id: "claim", label: "Atomic claim with UPDATE ... WHERE status = 'scheduled'", detail: "One statement, no new infrastructure, safe across workers.", recommended: true },
54
+ { id: "lock", label: "Advisory lock per invoice", detail: "Works, but the lock must be released on every error path." },
55
+ { id: "single", label: "Back to one worker", detail: "Hides the bug and loses the throughput gain." },
56
+ ]}>
57
+
58
+ The atomic claim fixes the cause in the one place every send path goes through, and a crashed worker is recovered by a timeout on the claim.
59
+
60
+ </Decision>
61
+
62
+ <Diff filename="server/scheduler/send-due.ts" language="typescript" summary="Claim before send" before={"const due = await db.invoices.findDue(now);\nfor (const inv of due) {\n await mailer.send(inv);\n await db.invoices.markSent(inv.id, now);\n}"} after={"const due = await db.invoices.findDue(now);\nfor (const inv of due) {\n const claimed = await db.invoices.claim(inv.id, workerId, now);\n if (!claimed) continue;\n await mailer.send(inv);\n await db.invoices.markSent(inv.id, now);\n}"} />
63
+
64
+ <ImplementationMap files={[
65
+ { path: "server/scheduler/send-due.ts", change: "modified", note: "Claim each invoice before sending" },
66
+ { path: "server/db/invoices.ts", change: "modified", note: "claim(): UPDATE status to sending, only from scheduled" },
67
+ { path: "server/db/migrations/031_invoice_claim.sql", change: "added", note: "claimed_by and claimed_at columns" },
68
+ ]} />
69
+
70
+ ## Regression test {#test}
71
+
72
+ <Code filename="server/scheduler/send-due.test.ts" language="typescript" code={"test('two workers send an invoice once', async () => {\n const inv = await seedScheduledInvoice();\n await Promise.all([runWorker('w1'), runWorker('w2')]);\n expect(mailer.sent).toHaveLength(1);\n expect(await db.invoices.get(inv.id)).toMatchObject({ status: 'sent' });\n});"} />
73
+
74
+ <Checklist title="Test plan" items={[
75
+ { label: "Test fails on the current main branch" },
76
+ { label: "Test passes with the fix, 200 runs in a row" },
77
+ { label: "Crashed worker: claim expires after 5 minutes and the invoice is retried" },
78
+ { label: "Manual send path is unchanged" },
79
+ ]} />
80
+
81
+ ## Rollout {#rollout}
82
+
83
+ <Checklist title="Rollout tasks" items={[
84
+ { label: "Run migration 031 before deploying the code" },
85
+ { label: "Deploy and watch duplicate-send alert for one day" },
86
+ { label: "Reply to the 38 affected tickets" },
87
+ ]} />
88
+
89
+ <AgentTrail />
90
+
91
+ ## Risks
92
+
93
+ <Table title="Risks" columns={["Risk", "Mitigation"]} rows={[
94
+ ["A crashed worker leaves invoices stuck in sending", "Claim expires after 5 minutes, a sweep resets it to scheduled"],
95
+ ["Migration locks the invoices table", "Add nullable columns only, no backfill"],
96
+ ]} />
97
+
98
+ ## Open questions
99
+
100
+ <QuestionForm title="Open Questions" questions={[
101
+ { title: "Should customers who got duplicates receive an apology email?", mode: "single", options: [
102
+ { label: "No, a support reply is enough", recommended: true }, { label: "Yes, one short email" } ] },
103
+ { title: "Add an idempotency key to the mail provider call as well?", mode: "single", options: [
104
+ { label: "Yes, as a follow-up", recommended: true }, { label: "Not needed" } ] },
105
+ ]} />
@@ -0,0 +1,76 @@
1
+ # Fix: duplicate invoice emails
2
+
3
+ > **How to use this template:** replace the Ledgerly example with your bug. Rewrite the symptom, reproduction, root cause, fix and regression test. All names here are invented.
4
+
5
+ **Status:** draft **Severity:** medium, no data loss
6
+
7
+ ## 1. Symptom
8
+
9
+ Ledgerly is a fictional invoicing service. Since release 2.14 some customers receive the same invoice email two or three times. Support counted 38 reports in nine days, all for invoices sent by the scheduled run at 06:00 UTC.
10
+
11
+ ## 2. Reproduction
12
+
13
+ | Step | Action | Expected | Actual |
14
+ |------|--------|----------|--------|
15
+ | 1 | Create an invoice scheduled for 06:00 UTC | Status scheduled | Status scheduled |
16
+ | 2 | Start two scheduler workers | Both idle | Both idle |
17
+ | 3 | Let the 06:00 tick fire | One email | Two emails |
18
+ | 4 | Check the invoice row | sent_at set once | sent_at set twice, 40 ms apart |
19
+
20
+ Reproduces about one run in three with two workers and a 50 ms delay in the mail client.
21
+
22
+ ## 3. Root cause
23
+
24
+ The scheduler selects due invoices and sends them in two separate steps. Two workers can select the same row before either marks it as sent. Release 2.14 raised the worker count from one to three, which exposed the race.
25
+
26
+ ```mermaid
27
+ sequenceDiagram
28
+ participant W1 as Worker 1
29
+ participant DB as Database
30
+ participant W2 as Worker 2
31
+ W1->>DB: SELECT due invoices
32
+ W2->>DB: SELECT due invoices
33
+ DB-->>W1: invoice 4012
34
+ DB-->>W2: invoice 4012
35
+ W1->>W1: send email
36
+ W2->>W2: send email
37
+ W1->>DB: UPDATE sent_at
38
+ W2->>DB: UPDATE sent_at
39
+ ```
40
+
41
+ ## 4. Fix plan
42
+
43
+ | Option | Verdict |
44
+ |--------|---------|
45
+ | **Atomic claim with UPDATE ... WHERE status = 'scheduled'** | Recommended: one statement, no new infrastructure |
46
+ | Advisory lock per invoice | Lock must be released on every error path |
47
+ | Back to one worker | Hides the bug, loses throughput |
48
+
49
+ Change in `server/scheduler/send-due.ts`: call `db.invoices.claim(id, workerId, now)` before sending and skip the invoice when the claim fails. Add `claimed_by` and `claimed_at` columns in migration 031.
50
+
51
+ ## 5. Regression test
52
+
53
+ - [ ] Test fails on the current main branch
54
+ - [ ] Test passes with the fix, 200 runs in a row
55
+ - [ ] Crashed worker: claim expires after 5 minutes and the invoice is retried
56
+ - [ ] Manual send path is unchanged
57
+
58
+ Test: seed one scheduled invoice, run two workers at once, expect exactly one email and status sent.
59
+
60
+ ## 6. Rollout
61
+
62
+ - [ ] Run migration 031 before deploying the code
63
+ - [ ] Deploy and watch the duplicate-send alert for one day
64
+ - [ ] Reply to the 38 affected tickets
65
+
66
+ ## 7. Risks
67
+
68
+ | Risk | Mitigation |
69
+ |------|------------|
70
+ | A crashed worker leaves invoices stuck in sending | Claim expires after 5 minutes, a sweep resets it |
71
+ | Migration locks the invoices table | Nullable columns only, no backfill |
72
+
73
+ ## 8. Open questions
74
+
75
+ - Apology email for affected customers? Recommended: no, a support reply is enough.
76
+ - Add an idempotency key to the mail provider call? Recommended: yes, as a follow-up.
@@ -0,0 +1,81 @@
1
+ <DesignBoard title="Trailmark - Saved Routes storyboard" version={1} transitions={[
2
+ { from: "map", to: "save", label: "Tap Save" },
3
+ { from: "save", to: "list", label: "Save route" },
4
+ { from: "list", to: "detail", label: "Open a route" },
5
+ { from: "detail", to: "list", label: "Remove" },
6
+ ]}>
7
+ <Section title="1 Save and revisit" subtitle="From the map to a saved route. Low-fidelity sketches, not final UI.">
8
+ <Artboard id="map" label="Map" surface="mobile" x={80} y={120} width={320} height={480} order={1}>
9
+ <Screen surface="mobile" caption="Route selected on the map.">
10
+ <FrameScreen>
11
+ <StatusBar />
12
+ <Title text="Ridge Loop" />
13
+ <Text value="9.4 km, 3 h 10 min" tone="muted" />
14
+ <Box full dashed>
15
+ <Text value="Map preview" tone="muted" />
16
+ </Box>
17
+ <Row>
18
+ <Btn label="Start" primary />
19
+ <Btn label="Save" />
20
+ </Row>
21
+ </FrameScreen>
22
+ </Screen>
23
+ </Artboard>
24
+ <Artboard id="save" label="Save sheet" surface="mobile" x={480} y={120} width={320} height={480} order={2}>
25
+ <Screen surface="mobile" caption="Bottom sheet over the map.">
26
+ <FrameScreen>
27
+ <StatusBar />
28
+ <Title text="Save route" />
29
+ <Box>
30
+ <Text value="Name" tone="muted" />
31
+ <Text value="Ridge Loop" />
32
+ </Box>
33
+ <TaskRow title="Keep available offline" done />
34
+ <TaskRow title="Sync to my other devices" done />
35
+ <Divider />
36
+ <Btn label="Save route" primary />
37
+ <Btn label="Cancel" />
38
+ </FrameScreen>
39
+ </Screen>
40
+ </Artboard>
41
+ <Artboard id="list" label="Saved list" surface="mobile" x={880} y={120} width={320} height={480} order={3}>
42
+ <Screen surface="mobile" caption="Works in airplane mode.">
43
+ <FrameScreen>
44
+ <StatusBar />
45
+ <Title text="Saved routes" />
46
+ <Chips items={[{"label":"Recent","active":true},{"label":"Nearby"},{"label":"A-Z"}]} />
47
+ <Card>
48
+ <Text value="Ridge Loop" weight="bold" />
49
+ <Text value="9.4 km, synced" tone="muted" />
50
+ </Card>
51
+ <Card>
52
+ <Text value="Lake Shore Walk" weight="bold" />
53
+ <Text value="4.1 km, pending" tone="muted" />
54
+ </Card>
55
+ <Card>
56
+ <Text value="Quarry Climb" weight="bold" />
57
+ <Text value="12.8 km, synced" tone="muted" />
58
+ </Card>
59
+ </FrameScreen>
60
+ </Screen>
61
+ </Artboard>
62
+ <Artboard id="detail" label="Route detail" surface="mobile" x={1280} y={120} width={320} height={480} order={4}>
63
+ <Screen surface="mobile" caption="Remove is a text button, not a trash icon.">
64
+ <FrameScreen>
65
+ <StatusBar />
66
+ <Title text="Ridge Loop" />
67
+ <Box full dashed>
68
+ <Text value="Map preview" tone="muted" />
69
+ </Box>
70
+ <Row>
71
+ <Text value="9.4 km" weight="bold" />
72
+ <Text value="620 m up" tone="muted" />
73
+ </Row>
74
+ <Btn label="Start" primary />
75
+ <Btn label="Remove from saved" />
76
+ </FrameScreen>
77
+ </Screen>
78
+ </Artboard>
79
+ <Annotation targetId="list" title="Sync state">Each row shows synced or pending so a hiker knows what a second device will see.</Annotation>
80
+ </Section>
81
+ </DesignBoard>
@@ -0,0 +1 @@
1
+ {"id":"feature","label":"New feature","description":"A product feature with screens, a storyboard board, decisions, build map and open questions.","useWhen":"You are planning a user-facing feature that needs 3-4 screens and a clear build order.","hasBoard":true}