@drawbridge/drawbridge-agents 0.1.93 → 0.1.94

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.
@@ -43,7 +43,7 @@ Wait for both answers. Never guess either name. The developer still assigns the
43
43
  - nothing about who reviews or tests: both were answered at line 1;
44
44
  - **a package publish, if any subtask names `@drawbridge/components` or `@drawbridge/drawbridge-utils`**, as a statement, not a question: this release publishes a package; the developer named above publishes it. The package is built first and gets its own pull request to the package's `main`; the end list (line 5) hands the developer the merge and the tag to push; the skill then bumps the pin in every consumer, and the app pull requests open after that, once the publish is done. Line 4 step 2 is the exact sequence.
45
45
 
46
- **Publishing is settled; never ask about it.** The developer publishes every package by merging its version bump to `main` and pushing the tag `v<version>` (`git checkout main && git pull && git tag v<version> && git push origin v<version>`); GitHub Actions (`.github/workflows/publish.yml`, since Butterscotch 2026-10-23, task 1218041740718540) builds, runs the tests and publishes through npm trusted publishing, so no npm token exists anywhere and the registry shows the publisher as "GitHub Actions" (proven with stripe 0.1.56). The workflow refuses a tag that differs from `package.json` or is not on `main`. The hand path, `git pull && npm run build` on `main` with the developer's one-time password, still works as a fallback. Agents hold no npm token and never will. So the skill never asks who publishes, whether to publish, whether it may publish, or how: it writes the one command into the end list as an instruction to the developer and waits for `npm view` to show the version (Darren, 2026-10-22: "I get a lot of questions in the release about npm publishing etc, lets make sure that it's clear that the developers do the publishing so that these go away").
46
+ **Publishing is settled; never ask about it.** The developer publishes every package by merging its version bump to `main`: GitHub Actions (`.github/workflows/publish.yml`, on merge for every package since Cherry, 2026-10-27) builds, runs the tests, publishes through npm trusted publishing when the version is new to npm, and tags the commit `v<version>`, so no npm token exists anywhere and the registry shows the publisher as "GitHub Actions" (proven with stripe 0.1.56). A merge that keeps the version publishes nothing. The hand path, `git pull && npm run build` on `main` with the developer's one-time password, still works as a fallback. Agents hold no npm token and never will. So the skill never asks who publishes, whether to publish, whether it may publish, or how: it writes the one command into the end list as an instruction to the developer and waits for `npm view` to show the version (Darren, 2026-10-22: "I get a lot of questions in the release about npm publishing etc, lets make sure that it's clear that the developers do the publishing so that these go away").
47
47
 
48
48
  Wait for the yes. Nothing below runs before it. From the yes to the end list, the run is unattended: the next section says what that means.
49
49
 
@@ -76,7 +76,7 @@ All work on the release branches against the **Local** database. Develop and its
76
76
  2. **Package first, and the publish never blocks.** Every subtask that touches `@drawbridge/drawbridge-utils` or `@drawbridge/components` is built before any app subtask, so the package is finished and its pull request ready when the end list goes out, and the build never pauses for it (Darren, 2026-10-22: "I had to wait a while for the publishing code to be written"; "the publish step includes merging each npm package to main, and bumping the pin(s)"). The whole package step, per package, utils before components (components peer-depends on utils):
77
77
  1. Build the package subtasks on `feature/<id>` in that package, tests first, `npm test` and `npm run test:app` (components) green, the two review passes.
78
78
  2. Bump the package's own version in its `package.json` (patch), commit, push, and open its pull request to the package's `main` (`gh pr create --base main …`), assigned to the developer from line 2. Nothing is posted to Asana yet; the link goes into the end list.
79
- 3. Write the publish instruction into the Parked section, addressed to the developer from line 1, as one numbered item with the exact words: merge that pull request, then from the package checkout: `git checkout main && git pull && git tag v<version> && git push origin v<version>`; the Publish workflow does the rest, and `npm view <package> version` shows it within a few minutes (by hand if the workflow fails: `npm run build` on `main`). It is an instruction, not a question, and it goes out at line 5, never now. No question about publishing is ever put to anyone (line 2).
79
+ 3. Write the publish instruction into the Parked section, addressed to the developer from line 1, as one numbered item with the exact words: merge that pull request to `main`; the Publish workflow does the rest, and `npm view <package> version` shows it within a few minutes (by hand if the workflow fails: `npm run build` on `main`). It is an instruction, not a question, and it goes out at line 5, never now. No question about publishing is ever put to anyone (line 2).
80
80
  4. **Keep going** on the app subtasks. For one that needs the new package code, `npm link` the local package checkout into the app worktree so it is written and tested against it now.
81
81
  5. When `npm view <package> version` shows the new version (after the person has published from the end list): bump the pin to that exact version in **every consumer in the family**, not only the release's (Darren, 2026-10-22: "Once a package is published, all consumer pins should also be updated"; pins are version-exact family-wide, `conventions/drawbridge-packages.md`). One command does it: `npx drawbridge-agents-bump <package> <version>` (from any family checkout; `--dry` lists the consumers first) finds every consumer, keeps each one's field, installs the exact version so the lockfile names it, commits "<package> <version>" and pushes each repo's current branch. Before running it, the release's own consumers must have their `feature/<id>` branch checked out (the worktrees), so they take the bump there, while every other consumer takes a pin-only commit on its integration branch. Unlink first where a worktree was linked; run the tests after. A sibling package bumped this way is itself published by the same steps before the apps that depend on it.
82
82
  6. No app pull request opens while a pin still names a version the registry does not have.
@@ -1 +1 @@
1
- v22.23.1
1
+ v22.23.4
package/README.md CHANGED
@@ -31,7 +31,7 @@ claude/
31
31
  mcp.json → .mcp.json project-level MCP servers (Sentry, Asana)
32
32
  editorconfig → .editorconfig family-wide editor defaults (tabs, lf, utf-8)
33
33
  npmrc → .npmrc save-exact=true (exact-pin convention)
34
- nvmrc → .nvmrc family-standard node version (v22.23.1)
34
+ nvmrc → .nvmrc family-standard node version (v22.23.4)
35
35
 
36
36
  bin/
37
37
  sync-claude.js ← drawbridge-agents-sync — mirrors templates + graphify preflight
@@ -37,7 +37,11 @@ skill) so it stops living only in tribal memory.
37
37
  `status:'pending'` + the `business` details, sync's `queue/identity.js` verify job is the
38
38
  ONLY writer of `'verified'` (after Twilio Toll-Free Verification approval, via the utils
39
39
  twilio transport — it also renders and uploads the opt-in evidence at submission), and
40
- sync's `queue/notification.js` sends from `'verified'` rows only. An api route (or script)
40
+ sync's `queue/notification.js` sends lead-facing SMS ONLY from the organization's own
41
+ `'verified'` row, on the platform's Twilio keys: no merchant Twilio connection is consulted,
42
+ and no row cancels the send (`no-sms-number`), never falling back to the platform number.
43
+ api's draw route requires the same verified row before a winner text (since Cherry, release
44
+ 1219789739362144). An api route (or script)
41
45
  minting `'verified'` ships a number Twilio blocks outright — it looks live and delivers
42
46
  nothing. (Collection renamed `networking` → `identity` 2026-09-03, pre-data; the plan
43
47
  feature key stays `organization:networking` because custom-plan documents can store
@@ -90,13 +94,17 @@ skill) so it stops living only in tribal memory.
90
94
  (`lib/buffer.js 'twilio.message.inbound'`). The ONLY reader on any send path is sync
91
95
  `lib/suppression.js canSend()` — a new lead-facing send path must call it, or opted-out
92
96
  recipients get contacted (TCPA/CAN-SPAM exposure). Lead-facing = the notification carries
93
- `lead`/`workflow`/`connection`; those sends also REQUIRE an org connection (no system-sender
94
- fallback), while system mail (OTC codes, member invites, security notices) keeps the system
97
+ `lead`/`workflow`/`connection`; lead-facing SMS also REQUIRES the organization's own verified
98
+ number (no system-number fallback), while system mail (OTC codes, member invites, security notices) keeps the system
95
99
  sender and is never suppressed. **System mail is identified by `audience`, not by whether an
96
100
  organization is present** — `queue/identity.js` sends member mail WITH an org, and
97
101
  `stream/transaction.js` sends AI-balance notices with none. Inbound STOP flows webhooks
98
- (`POST /connection/:id/twilio`, signature-verified against the URL built from
99
- `APP_CLIENT_WEBHOOKS_URI`) → `buffer` doc → sync drain → platform-global suppression row;
102
+ (`POST /connection/drawbridge/sms`, signature-verified against the URL built from
103
+ `APP_CLIENT_WEBHOOKS_URI`) → `buffer` doc → sync drain (utils drawbridge `inbound.process`,
104
+ given `read`) → a suppression row scoped to the organization whose number received it (from
105
+ the `To` number's `identity` row; only the platform's own number writes `organization : null`).
106
+ **STOP is per organization since Cherry**, like an email unsubscribe; rows written before it
107
+ carry `organization : null` and still block everyone;
100
108
  api registers the number's SmsUrl at twilio connect (`lib/twilio.js`) and `scripts.js
101
109
  registerTwilioWebhooks` backfills — a deployed webhooks route with unregistered numbers hears
102
110
  nothing, silently. **Since Butterscotch (release 1219790090574059; sync 762fdd7, Darren
@@ -125,10 +133,10 @@ skill) so it stops living only in tribal memory.
125
133
  same tag; break the round-trip and consent lookups stop matching. api mirrors ONLY the
126
134
  lead-facing branch (`identityDefaults.from`). Detail: drawbridge-docs `reference/providers.md`.
127
135
  - **Adding an email/SMS vendor is a two-repo change** (api ↔ sync). A connection is deliverable
128
- only if sync implements a sender for its slug — `lib/email.js` (`{ mailchimp, sendgrid }`) and
129
- `lib/sms.js` (`{ twilio }`), which `queue/notification.js` reads **directly** as its guard and
130
- org-scoped lookup; never re-list the slugs as literals, or the guard drifts from the senders
131
- that exist. Adding a vendor to drawbridge-api `lib/connections.js` with `group : 'email'`/`'sms'`
136
+ only if sync implements a sender for its slug — `smsSenders` in `queue/notification.js`, which
137
+ since Cherry guards only a legacy notification pinned to a connection (lead-facing SMS sends
138
+ from the org's verified number; email from the platform or the org's verified domain); never
139
+ re-list the slugs as literals, or the guard drifts from the senders that exist. Adding a vendor to drawbridge-api `lib/connections.js` with `group : 'email'`/`'sms'`
132
140
  makes it selectable throughout api and app-web, but with no sync sender every send is
133
141
  cancelled, not delivered — merchant sees a workflow that ran and no message. The miss is
134
142
  detectable, not silent: `notification.reason = 'connection-slug-mismatch'` plus an
@@ -170,7 +178,19 @@ skill) so it stops living only in tribal memory.
170
178
  and its index live in drawbridge-api `schema/file.js`). `origin` identifies the source asset,
171
179
  `render` content-hashes the rendered output — drop `render` from either side and a rendering
172
180
  fix never ships: reuse serves the first-ever render forever (the onemethod
173
- black-on-black-logo-twice bug).
181
+ black-on-black-logo-twice bug). Since Cherry a captured video's origin IS its storage address,
182
+ `scrape/<id>/videos/<digest>-original`, owned by that scrape (sync `scrape.mediaKey`; images
183
+ under `scrape/<id>/images/`): moving one means rewriting `data.gallery.video.origin`, the
184
+ cached `data.files` entry and every file's `meta.origin` to the same string in one step, or the
185
+ next generation re-trims and duplicates the clip (api migration 1219789739362144 `scrape`).
186
+ - **A workflow's campaigns are read only through utils `workflowCampaigns( workflow )`** (utils ↔
187
+ sync ↔ api ↔ components, since Cherry). `trigger.filters.campaigns` is a list of ids, empty =
188
+ every campaign; the helper falls back to a legacy `campaign` string. sync (`firesForCampaign`,
189
+ `pauseWorkflowsForCampaign`), the utils digest/notify steps and the api's save and read all go
190
+ through it. **Sync deploys BEFORE the api**: the new api writes `campaigns` and drops `campaign`
191
+ on every save, and an old sync reads only `campaign`, so it fires every narrowed workflow for
192
+ every campaign. The migration converting `campaign` → `campaigns` runs after both are live.
193
+ Saving refuses another organization's campaign id.
174
194
 
175
195
  ## Billing / metering
176
196
 
@@ -329,6 +349,15 @@ skill) so it stops living only in tribal memory.
329
349
  - The api's `POST /oauth/google/callback` returns `redirectTo` in its 400 body when it read the state
330
350
  row before failing, so app-web can keep the resume target across `/auth?error=oauth`. A
331
351
  `oauth.state.invalid` failure cannot — that row is what carried the target.
352
+ - **Sign-out ends a token family** (utils ↔ api ↔ app-web, since Cherry). Every token of one
353
+ sign-in shares `token.family` (utils `saveToken`, api `issueTokenPair`/`silentRefresh`); sign-out
354
+ calls utils `revokeFamily` and clears the api's session cache for each access hash. app-web's
355
+ `lib/signout.js` sends the refresh token in the body: Bearer while the session cookie lives, the
356
+ dashboard's Basic client credentials once it has lapsed. The api schema declaring `family` and
357
+ `pair` must have **booted** before the api runs utils ≥0.0.223 (only the api writes tokens, so
358
+ sync may run it first), and the api deploys before app-web. NEVER
359
+ pass a missing family to `revokeFamily` (it refuses; a query on a missing family would revoke
360
+ every older token). A rotated refresh token expires 60 s after rotation, so the TTL deletes it.
332
361
  - Google sign-in NEVER auto-links (`profile/disconnect-google` must stick): the callback signs in
333
362
  only on an exact google account row; an email-only match is refused with `code: google.not.linked`
334
363
  in the error body, which app-web's `/api/auth/google` maps to `/auth?error=google-not-linked`
@@ -521,6 +550,13 @@ renaming one orphans what is already enqueued and starts a parallel stream nobod
521
550
  with no error. `bullmq-identity.test.js` in sync spells out the routing table so moving a
522
551
  step between queues requires editing it deliberately.
523
552
 
553
+ **A hook asks for a retry by throwing `retry : true` (utils ↔ sync, since Cherry).** Sync's
554
+ step runner (`lib/step-runner.js`) re-runs the same step after 5, 10 and 15 seconds with a new job
555
+ id, writing no step row and no issue until the retries are spent; utils' Send SMS hook throws it
556
+ when the entry's submission is not written yet (the api's thin ingest writes the lead first). A
557
+ utils that throws it ahead of a sync without the runner change turns every retry into a failed
558
+ step with an issue: sync deploys first.
559
+
524
560
  **`grant_refused` is minted in utils, keyed on in sync (utils ↔ sync).** Only the two
525
561
  dead-grant paths in drawbridge-utils — a refused refresh (`connections/oauth.js`) and an
526
562
  unrenewable expiry (`connections/token.js`) — set `error.code = 'grant_refused'`; a network
@@ -9,24 +9,20 @@ on GitHub and `@drawbridge/shopify` deprecated on npm (2026-09-13, "use
9
9
 
10
10
  ## Publishing
11
11
 
12
- **A publish is a tag push (since 2026-10-23).** Merge the version bump to `main`, then from
13
- the package checkout `git checkout main && git pull && git tag v<version> && git push origin
14
- v<version>`. Every package's `.github/workflows/publish.yml` runs on `v*` tags: it refuses a
15
- tag that differs from `package.json` or is not on `main`, runs `npm ci`, `npx tsup` and
16
- `node --test`, then `npm publish --ignore-scripts` through npm trusted publishing (OIDC,
17
- `id-token: write`, npm 11). No npm token is stored anywhere; the registry records the
18
- publisher as "GitHub Actions" (first proven with stripe 0.1.56, run 39975659406). The
19
- trusted publisher for each package is configured on npmjs.com by Darren. A developer pushes
20
- the tag; an agent never does.
21
-
22
- **drawbridge-agents publishes on merge (since 2026-10-23).** Merging a version bump to its
23
- `main` publishes it and tags `v<version>`; a merge that keeps the version publishes nothing.
24
- The session that merged then waits for `npm view @drawbridge/drawbridge-agents version` to
25
- show it and runs `npx drawbridge-agents-bump @drawbridge/drawbridge-agents <version>`, leaving
26
- out growth and any repo checked out on another release's branch (docs often is: bump docs on
27
- `main` in a worktree). No GitHub key is involved (Darren, 2026-10-23: merging to main should
28
- "publish agents and trigger the pin bump"). Merging is publishing, so whoever merges to agents
29
- `main` decides. The other packages still publish on the tag push above.
12
+ **A publish is a merge to `main` (every package since Cherry, 2026-10-27; agents since
13
+ 2026-10-23).** Each package's `.github/workflows/publish.yml` runs on every push to `main`: when
14
+ the `package.json` version is already on npm it publishes nothing; when it is new it runs
15
+ `npm ci`, the package's build (`npx tsup`, plus `node scripts/esm-extensions.js` for components,
16
+ `npx tsup ./index.js` for mongodb and redis) and `node --test`, then `npm publish
17
+ --ignore-scripts` through npm trusted publishing (OIDC, `id-token: write`, npm 11) and tags the
18
+ commit `v<version>` itself. No npm token is stored anywhere; the registry records the publisher
19
+ as "GitHub Actions" (first proven with stripe 0.1.56, run 39975659406). The trusted publisher
20
+ for each package is configured on npmjs.com by Darren. Merging is publishing, so whoever merges
21
+ a version bump to a package's `main` decides it ships (Darren, 2026-10-27: "all npm connected
22
+ packages should publish the same way"). The session that merged waits for `npm view <package>
23
+ version` to show it and runs `npx drawbridge-agents-bump <package> <version>`, leaving out growth
24
+ and any repo checked out on another release's branch (docs often is: bump docs on `main` in a
25
+ worktree).
30
26
 
31
27
  The hand path below still works as a fallback when the workflow fails.
32
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawbridge/drawbridge-agents",
3
- "version": "0.1.93",
3
+ "version": "0.1.94",
4
4
  "description": "Shared agent-instruction content (rules, code style, conventions) for the drawbridge-* monorepo.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {