@drawbridge/drawbridge-agents 0.1.40 → 0.1.47
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.
|
@@ -31,6 +31,15 @@ skill) so it stops living only in tribal memory.
|
|
|
31
31
|
|
|
32
32
|
- All socket emits from sync go through `drawbridge-sync/lib/emit.js` — never call
|
|
33
33
|
`io.to().emit()` directly from a worker.
|
|
34
|
+
- An `sms` identity row is **never created `verified`**: api's purchase route writes
|
|
35
|
+
`status:'pending'` + the `business` details, sync's `queue/identity.js` verify job is the
|
|
36
|
+
ONLY writer of `'verified'` (after Twilio Toll-Free Verification approval, via the utils
|
|
37
|
+
twilio transport — it also renders and uploads the opt-in evidence at submission), and
|
|
38
|
+
sync's `queue/notification.js` sends from `'verified'` rows only. An api route (or script)
|
|
39
|
+
minting `'verified'` ships a number Twilio blocks outright — it looks live and delivers
|
|
40
|
+
nothing. (Collection renamed `networking` → `identity` 2026-09-03, pre-data; the plan
|
|
41
|
+
feature key stays `organization:networking` because custom-plan documents can store
|
|
42
|
+
granted keys — that string is data, never rename it without a migration.)
|
|
34
43
|
- Cascade cleanup of dependent rows belongs in a sync change-stream listener, not inline in the
|
|
35
44
|
API route that triggered the delete (see `cascade-cleanup.md`).
|
|
36
45
|
- A collection with no `queue/<collection>.js` handler is watched but its events are **silently
|
|
@@ -134,9 +143,33 @@ skill) so it stops living only in tribal memory.
|
|
|
134
143
|
would have zeroed every invoice once the products were archived.
|
|
135
144
|
- **The v2 pricing-plan layer (`bpp_*`, billing cadences) is never touched by script.** Canceling a
|
|
136
145
|
billing cadence is terminal and unrecoverable. Dashboard, by hand, only.
|
|
146
|
+
- **The Shopify meter handle is a config contract no code validates** (shopify ↔ Partner
|
|
147
|
+
Dashboard ↔ sync/utils). `SHOPIFY_USAGE_ORDERS_EVENT_HANDLE` in `@drawbridge/shopify`
|
|
148
|
+
(`drawbridge-orders`) must exactly match — case-sensitive, handle not display name — a usage
|
|
149
|
+
meter on the App Pricing plan version each merchant has APPROVED (meters are never
|
|
150
|
+
retroactive). A mismatch, a missing meter, or a merchant on a pre-meter plan still 202s every
|
|
151
|
+
event, logs it as a plain app event, and bills nothing; no error surfaces anywhere in code
|
|
152
|
+
(the 19/99 write-off). Any change to the handle constant, the Partner Dashboard pricing
|
|
153
|
+
config, or a plan version requires verifying a fresh order classifies as a billing event in
|
|
154
|
+
Dev Dashboard → Logs (type "App billing event"). The trace key both sides share is
|
|
155
|
+
`billed.transaction` = the event's `reference` = `drawbridge-orders.<orderId>`. Detail:
|
|
156
|
+
drawbridge-docs `reference/shopify-app.md` (Billing).
|
|
137
157
|
- **A subscription must open with the plan item.** It used to open with the metered actions price
|
|
138
158
|
and attach the plan afterwards with `always_invoice`. With no metered item, Stripe rejects an
|
|
139
159
|
itemless subscription, so the plan goes in at creation — changing the first invoice's shape.
|
|
160
|
+
- **Ending a subscription is written twice, from ONE implementation** (stripe ↔ api ↔ sync).
|
|
161
|
+
`subscription.markTerminal` in **drawbridge-stripe** (`lib/subscription.js`, takes the caller's
|
|
162
|
+
db controller + session like `reconcile`) clears the org's `subscription` pointer, demotes to
|
|
163
|
+
`unsubscribed` under `status : { $nin : [ 'suspended', 'unsubscribed' ] }`, `$pull`s the org's
|
|
164
|
+
`type : 'method'` errors and flips the sub doc to `canceled`. Both callers run it: sync's
|
|
165
|
+
`markSubscriptionTerminal` (`lib/buffer.js`, on `customer.subscription.deleted` and terminal
|
|
166
|
+
`subscription.updated`) and drawbridge-api's admin cancel
|
|
167
|
+
(`route/organization-subscription.js` `handlers.delete.cancel`), which mirrors it inline so the
|
|
168
|
+
page isn't stale for a webhook it just caused. **The mirror runs FIRST and consumes the status
|
|
169
|
+
guard**, so the webhook's pass then matches nothing — anything one caller does outside
|
|
170
|
+
`markTerminal` therefore never happens on the admin path. Never re-inline these writes: the
|
|
171
|
+
hand-copied version drifted, and a stale card decline outlived its subscription and permanently
|
|
172
|
+
blocked the org from resubscribing (Asana 1217947409527192).
|
|
140
173
|
- **Invoice line descriptions must keep the word "actions":** `isActionsLine` matches `/actions/i`
|
|
141
174
|
to count billed quantity for the billed-vs-ledger reconcile. A copy edit that drops it silently
|
|
142
175
|
breaks the audit. Detail: drawbridge-docs `reference/billing.md`.
|
|
@@ -371,6 +404,21 @@ renaming one orphans what is already enqueued and starts a parallel stream nobod
|
|
|
371
404
|
with no error. `bullmq-identity.test.js` in sync spells out the routing table so moving a
|
|
372
405
|
step between queues requires editing it deliberately.
|
|
373
406
|
|
|
407
|
+
**`grant_refused` is minted in utils, keyed on in sync (utils ↔ sync).** Only the two
|
|
408
|
+
dead-grant paths in drawbridge-utils — a refused refresh (`connections/oauth.js`) and an
|
|
409
|
+
unrenewable expiry (`connections/token.js`) — set `error.code = 'grant_refused'`; a network
|
|
410
|
+
failure never does. Sync's step-runner errors the connection on that code during a
|
|
411
|
+
`lifecycle.health` step (the shell mints the token, so the hook can't). Minting the code
|
|
412
|
+
anywhere transient flips healthy connections to error on a blip; renaming it in either repo
|
|
413
|
+
means dead grants retry into Sentry forever (the Shopify 08-16 incident, ecosystem-wide).
|
|
414
|
+
|
|
415
|
+
**The SMS inbound URL is one string two repos must agree on (api ↔ sync ↔ webhooks).**
|
|
416
|
+
`APP_CLIENT_WEBHOOKS_URI + '/connection/drawbridge/sms'` is written to each purchased
|
|
417
|
+
number by api (`route/organization-networking.js`) and re-asserted by sync's daily identity
|
|
418
|
+
sweep (`queue/identity.js`), which re-points any number whose Twilio config drifts. Change
|
|
419
|
+
the path in one place and the sweep "repairs" every number to the wrong URL — inbound SMS
|
|
420
|
+
(STOP included) goes dark with all rows reading healthy.
|
|
421
|
+
|
|
374
422
|
## Import surfaces
|
|
375
423
|
|
|
376
424
|
- Import names against a package's **actual exports** — a missing export resolves to `undefined`
|
|
@@ -8,7 +8,12 @@ drawbridge-docs/superpowers/plans/YYYY-MM-DD-<slug>.md
|
|
|
8
8
|
drawbridge-docs/superpowers/specs/YYYY-MM-DD-<slug>.md
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**THE ONE EXCEPTION IS drawbridge-growth** (Darren, repeatedly, most recently 2026-09-01:
|
|
12
|
+
"Don't add growth docs to docs"): growth is its own system — its plans and specs live in
|
|
13
|
+
`drawbridge-growth/docs/superpowers/{plans,specs}/` and must **never** be written to
|
|
14
|
+
drawbridge-docs. The guard hook exempts any `drawbridge-growth*` path for this reason.
|
|
15
|
+
|
|
16
|
+
For every other repo, this is the **only** allowed home for these working docs. Rules:
|
|
12
17
|
|
|
13
18
|
- Do **not** create or keep superpowers plans/specs (or `docs/superpowers/`, `docs/plans/`)
|
|
14
19
|
inside any other repo. When you finalize a plan or spec, write it under
|
|
@@ -47,6 +47,11 @@ const run = () => {
|
|
|
47
47
|
// drawbridge-docs is the canonical home — anything there is fine.
|
|
48
48
|
if (resolved.split('/').includes('drawbridge-docs')) process.exit(0)
|
|
49
49
|
|
|
50
|
+
// GROWTH IS THE EXCEPTION (Darren, repeatedly, most recently 2026-09-01):
|
|
51
|
+
// growth is its own system and its docs stay in drawbridge-growth — never
|
|
52
|
+
// in drawbridge-docs. Any growth checkout or worktree passes.
|
|
53
|
+
if (/\/drawbridge-growth[^/]*\//.test(resolved + '/')) process.exit(0)
|
|
54
|
+
|
|
50
55
|
const hit = FORBIDDEN.find((seg) => resolved.includes('/' + seg + '/') || resolved.endsWith('/' + seg))
|
|
51
56
|
if (!hit) process.exit(0)
|
|
52
57
|
|
package/package.json
CHANGED