@drawbridge/drawbridge-agents 0.1.93 → 0.1.95
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/.claude-template/skills/drawbridge-asana-comment/SKILL.md +13 -5
- package/.claude-template/skills/drawbridge-asana-ship-handoff/SKILL.md +3 -2
- package/.claude-template/skills/drawbridge-release/SKILL.md +10 -10
- package/.root-template/nvmrc +1 -1
- package/README.md +1 -1
- package/conventions/cross-repo-contracts.md +46 -10
- package/conventions/drawbridge-packages.md +14 -18
- package/conventions/migrations.md +16 -10
- package/conventions/rules.md +1 -1
- package/package.json +1 -1
|
@@ -23,11 +23,11 @@ bulleted section beside it. In order:
|
|
|
23
23
|
1. What changed, in plain past tense. Numbered.
|
|
24
24
|
2. What they need before the first step: a permission to grant, a setting to switch on, a
|
|
25
25
|
record that has to exist. One item each.
|
|
26
|
-
3. What to do and what to expect. One step per
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
path.
|
|
26
|
+
3. What to do and what to expect. One step per CHECK: what they do, then what they must see,
|
|
27
|
+
in one item, each sentence one action (rules.md rule 9). Setup they already know how to do
|
|
28
|
+
(make a workflow, connect a vendor, open a second tab) is one line, never a click per line.
|
|
29
|
+
Every step starts with the click path from where they already are (Admin, Connections, then
|
|
30
|
+
the store; Shopify admin, Apps, then Drawbridge). "Open the app" is not a path.
|
|
31
31
|
4. Anything that would otherwise look wrong to them. Numbered too.
|
|
32
32
|
5. A result already confirmed, an explanation, a billing note: numbered too.
|
|
33
33
|
|
|
@@ -50,6 +50,14 @@ anything in the result that would look wrong without warning. A second comment c
|
|
|
50
50
|
completing the first is the real failure. Structure is what keeps a complete comment readable,
|
|
51
51
|
so restructure before you cut content.
|
|
52
52
|
|
|
53
|
+
**Fifteen steps is the ceiling.** Darren, 2026-10-28, on a 52-step retest (task
|
|
54
|
+
1219064026230883): "It's insane to expect someone to go through this list." Count the steps
|
|
55
|
+
before the read-back. Over fifteen, restructure: fold setup into one line, drop every click
|
|
56
|
+
that is not the thing being tested, and write a cycle that repeats on another screen
|
|
57
|
+
(disconnect, look, reconnect, look) once per screen, as one step naming what changes there.
|
|
58
|
+
Still over fifteen means the change is more than one handoff: split it by story, one list each.
|
|
59
|
+
Never drop a check to hit the count.
|
|
60
|
+
|
|
53
61
|
**The stop test:** if it does not fit on a phone screen without scrolling, restructure it. Do
|
|
54
62
|
not trim facts to hit a length.
|
|
55
63
|
|
|
@@ -42,8 +42,9 @@ humanizer pass, claim verification and the read-back. Don't restate or work arou
|
|
|
42
42
|
|
|
43
43
|
On top of those:
|
|
44
44
|
|
|
45
|
-
- **One numbered list and nothing outside it**, one
|
|
46
|
-
|
|
45
|
+
- **One numbered list and nothing outside it**, one check per line (what they do, then what
|
|
46
|
+
they must see), fifteen lines at most, so they can reply "3 is wrong" and you both know what
|
|
47
|
+
they mean. Item 1 is what changed, plain past tense, with
|
|
47
48
|
the cause if they reported a bug. Then prerequisites, then the steps, each with what they
|
|
48
49
|
should see, then what won't change and what you didn't test, all numbered. No sentence
|
|
49
50
|
above or below the list.
|
|
@@ -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
|
|
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,12 +76,12 @@ 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
|
|
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.
|
|
83
83
|
3. **Per subtask**, on the release branch: read the story and the reference page, `graphify explain` and `graphify affected` on the symbols the story names (the graph is the map; a grep is only for reading a file the graph pointed at), failing test first where a suite exists, the change, `npm test` in that repo. One commit per subtask, its message starting with the subtask id.
|
|
84
|
-
4. **Migration.** If any subtask moves data, ONE file, `drawbridge-api/scripts/migration-<id>.js`, exporting `({ controller }) => async ({ dryRun, organization, ...args }) => counts`, holding every data step. It uses the controller only; a dry run refuses controller writes but cannot refuse a native `collection.drop()` or `dropIndex()`, so a step that drops anything branches on `dryRun` itself and reports what it would drop. Anything a person must do around the run (export a collection and read it back before a drop
|
|
84
|
+
4. **Migration.** If any subtask moves data, ONE file, `drawbridge-api/scripts/migration-<id>.js`, exporting `({ controller }) => async ({ dryRun, organization, ...args }) => counts`, holding every data step. It uses the controller only; a dry run refuses controller writes but cannot refuse a native `collection.drop()` or `dropIndex()`, so a step that drops anything branches on `dryRun` itself and reports what it would drop. Anything a person must do around the run (export a collection and read it back before a drop) is written into the release doc's Migrations section as the developer's steps, not into the file. Prove it on Local: `npm run migrate -- <id> --dry --env local`, then `--env local`. The runner prints the counts and writes `runs/<env>-<id>-<time>.json`; nothing copies them anywhere.
|
|
85
85
|
5. **Walk every touched app on Local**: `npm run walk -- --local <path or url>` from drawbridge-tests (dashboard at `localhost:3000`; sync must be running for anything live). Record what was seen; a label quoted later comes from this walk.
|
|
86
86
|
6. **QA agent on Local.** Dispatch `drawbridge-qa` with `target : local`, once per subtask that has a story (story paths, the SUBTASK id, the organization id: `APP_TEST_ORG_ID` in `drawbridge-tests/.env`, the e2e organization `fixtures/e2e-org.md` describes, present on Local because Local is a copy of dev). A subtask spanning many stories gets one report listing them all. Fix every fail on the release branch, redispatch, until every `drawbridge-tests/results/qa/<subtask id>.json` reads `fails : 0`. The fixes are commits in the same pull request; that is why the agent runs here and not on dev (Darren: "all agent work is done locally so the work and fixes get committed as part of the PR").
|
|
87
87
|
**Inbound webhooks are the exception.** Every Local Environment points `APP_CLIENT_WEBHOOKS_URI` at `http://localhost:4002`, which Shopify, Stripe and Twilio cannot reach, so a change to how a webhook is received or handled is marked in the Scope table as "webhooks: tested on dev" and the QA person checks it on dev in line 7.
|
|
@@ -96,7 +96,7 @@ Only when every subtask that can be built has been built: each committed, `npm t
|
|
|
96
96
|
|
|
97
97
|
1. One pull request per repo: `gh pr create --base develop --head feature/<id> --title "Release <id>: <task name>" --assignee <developer> --reviewer <developer> --body <the Scope table, the per-subtask commits, the QA reports' paths, and the migration's dev command if there is one>`. Components and utils target `main`.
|
|
98
98
|
2. Every pull request body ends with the lane rule in one line: "Review any time, on the feature branch locally. Merge only when develop is free: one release on develop at a time, so this merges once the release before it is approved and cut to its release/ branch." Fill the Branches table in `releases/<id>.md` with the PR links; commit.
|
|
99
|
-
3. **How to test, on every subtask, for both readers.** Append to each subtask's description, under its Story lines, two headings (Darren, 2026-10-22: "steps for a developer to test via code, or locally, and a QA person's steps"). First, "Test locally (developer)": one numbered list with the branch to check out, the exact commands (`npm test` in the repo, the test file that proves the change, `npm run migrate -- <id> --dry --env local` when there is a migration, `npm run walk -- --local <path>` for a screen), and for a screen: "Open an organization with the following features: <what this subtask needs, e.g. a connected Shopify store, a giveaway with a published prize>" on `http://localhost:3000`, then the path inside it by screen name, and the result each command must print or show. Never a fixed organization id. Then, "Test on dev (QA)": one numbered list with the dev app to open first (dashboard `https://dev.dashboard.trydrawbridge.com/organizations`, then "Open an organization with the following features: <what this subtask needs>" and the screens by name from there, never a fixed organization id, a share page `https://<slug>.dev.drwbrdg.com/...`, the website `https://dev.trydrawbridge.com/...`, the components app `https://components.trydrawbridge.com/...`), then one
|
|
99
|
+
3. **How to test, on every subtask, for both readers.** Append to each subtask's description, under its Story lines, two headings (Darren, 2026-10-22: "steps for a developer to test via code, or locally, and a QA person's steps"). First, "Test locally (developer)": one numbered list with the branch to check out, the exact commands (`npm test` in the repo, the test file that proves the change, `npm run migrate -- <id> --dry --env local` when there is a migration, `npm run walk -- --local <path>` for a screen), and for a screen: "Open an organization with the following features: <what this subtask needs, e.g. a connected Shopify store, a giveaway with a published prize>" on `http://localhost:3000`, then the path inside it by screen name, and the result each command must print or show. Never a fixed organization id. Then, "Test on dev (QA)": one numbered list with the dev app to open first (dashboard `https://dev.dashboard.trydrawbridge.com/organizations`, then "Open an organization with the following features: <what this subtask needs>" and the screens by name from there, never a fixed organization id, a share page `https://<slug>.dev.drwbrdg.com/...`, the website `https://dev.trydrawbridge.com/...`, the components app `https://components.trydrawbridge.com/...`), then one step per check (what the tester does, then what they must see), fifteen steps at most (`drawbridge-asana-comment`; Darren, 2026-10-28, on a 52-step list: "It's insane to expect someone to go through this list"). Every label in it is quoted from the Local walk and the QA reports of line 4, never from memory (`conventions/walk-the-app.md`), with dev URLs in place of localhost. A migration subtask says what the tester checks after the developer's dev run. The words go through `humanizer`. Both lists are written here, after the build, because only then are the commands and labels known (Darren, 2026-10-22: "include steps on how to test in the respective development apps"). The pull request body and the release doc's QA table point at the subtasks for these steps rather than repeating them.
|
|
100
100
|
4. Attach every QA report's screenshots to the release task (`node lib/asana-attach.js <release id> <png>` from drawbridge-tests, one per scenario, named `<subtask id>-<scenario>.png`), because the reports under `results/qa/` are gitignored and exist only on the machine that ran them. Then put the PR links on the release task as ONE numbered list (`drawbridge-asana-comment`): this is the first Asana comment of the release, and the package pull request's link rides in it too. Assign the task to the developer and move it to Review (`POST /sections/1219762451512990/addTask`) only once every pull request is open, parked items included.
|
|
101
101
|
5. Stop. Report the PR links.
|
|
102
102
|
|
|
@@ -104,7 +104,7 @@ Only when every subtask that can be built has been built: each committed, `npm t
|
|
|
104
104
|
|
|
105
105
|
The skill does none of this. For the record, so the skill can answer "what is the next step":
|
|
106
106
|
|
|
107
|
-
- Line 6, the developer: reviews the diff, runs the branch locally, approves and merges, but merges only when develop is free. Develop holds one release at a time: the review happens whenever the developer likes, on the feature branch locally, and the approved release waits in Review until the one before it is cut at line 8 (Darren, 2026-10-23: "Develop can only go one at a time"; "The Review slot may queue up once the devs have reviewed a release but that's fine"). Merging means the developer has QA'd the branch locally and it is ready (Darren, 2026-10-23). Then the merge, the dev migration, the move to QA and the QA assignment, in that order. Dev deploys itself. If there is a migration: `npm run migrate -- <id> --dry --env dev` from the api checkout on `develop`, read the printed result, then without `--dry`. The developer may have an agent run those two dev commands on their explicit instruction in their own session (Darren, 2026-10-22); the skill never runs them on its own
|
|
107
|
+
- Line 6, the developer: reviews the diff, runs the branch locally, approves and merges, but merges only when develop is free. Develop holds one release at a time: the review happens whenever the developer likes, on the feature branch locally, and the approved release waits in Review until the one before it is cut at line 8 (Darren, 2026-10-23: "Develop can only go one at a time"; "The Review slot may queue up once the devs have reviewed a release but that's fine"). Merging means the developer has QA'd the branch locally and it is ready (Darren, 2026-10-23). Then the merge, the dev migration, the move to QA and the QA assignment, in that order. Dev deploys itself. If there is a migration: `npm run migrate -- <id> --dry --env dev` from the api checkout on `develop`, read the printed result, then without `--dry`. The developer may have an agent run those two dev commands on their explicit instruction in their own session (Darren, 2026-10-22); the skill never runs them on its own. Then moves the task to QA and assigns the QA person in Asana.
|
|
108
108
|
- Line 7, the QA person: tests on dev. The band already names the release.
|
|
109
109
|
- Line 8, the QA person: moves the task to Approved. Then the developer cuts the release, below.
|
|
110
110
|
|
|
@@ -125,12 +125,12 @@ The cut stages the approved release for production and frees develop for the nex
|
|
|
125
125
|
|
|
126
126
|
## Line 9: promote, as the developer's checklist
|
|
127
127
|
|
|
128
|
-
**Production is done by a developer, by hand: every push to `main`, every deploy, the migration** (Darren: "All production deployment and migrations will be done manually, you are only to aid a developer deploying to production"). The skill hands over one step at a time, waits for "done", verifies by reading, records, then hands over the next.
|
|
128
|
+
**Production is done by a developer, by hand: every push to `main`, every deploy, the migration** (Darren: "All production deployment and migrations will be done manually, you are only to aid a developer deploying to production"). The one exception is the migration, which an agent runs when the developer tells it to (step 4). The skill hands over one step at a time, waits for "done", verifies by reading, records, then hands over the next.
|
|
129
129
|
|
|
130
|
-
1.
|
|
131
|
-
2.
|
|
130
|
+
1. **The Atlas snapshot, first.** Before any "Finish release", push to `main`, deploy or migration, hand over the snapshot of the production cluster, taken at the start of the window (avoid 00:00 and 02:00 UTC, sync's scheduled jobs). Wait for "done". Nothing below is handed over until the snapshot exists, and its time goes in the release doc's Promote section (Darren, 2026-10-26: "the snapshot should be run before any production deployment release is done, so deployments, migrations, etc. It should be the first thing").
|
|
131
|
+
2. Confirm the task sits in Approved and `release/<id>` exists in every app in the Branches table (line 8); refuse otherwise.
|
|
132
132
|
3. Per app with a `release/<id>` branch, in this order unless the release doc says otherwise (dashboard, api, sync, share, webhooks, website): hand over the client's "Finish release" with the tag `<id>`, which merges `release/<id>` into `main` and into develop and tags it, then the push of `main`, develop and the tag, then the Deploy click in DigitalOcean (prod apps deploy from `main` with deploy-on-push off). Plain git for the finish: `git checkout main && git pull && git merge --no-ff release/<id>`, `git tag <id>`, `git checkout develop && git pull && git merge --no-ff release/<id>`, `git push origin main develop <id>`. The merge into develop keeps develop's band: develop emptied or replaced `release.json` after the cut and the release branch did not touch it. An app with no `release/<id>` is not promoted. Verify by reading: the deployment ACTIVE through the DigitalOcean MCP server registered in Claude Code (`apps-get-deployment-status`; the release doctor checks it is there, and no machine needs doctl), `/health` reporting `main`'s new commit (the merge commit, whose code is the cut commit plus any fix made on the release branch). Note the commit in the release doc's Promote section.
|
|
133
|
-
4. Migration, after the api is ACTIVE and before sync: from the developer's api checkout on `main` at the promoted commit, with the api's two-line `.env.production` beside `migrate.js`: `npm run migrate -- <id> --env prod --dry --sha <promoted api commit>`, read the result, then without `--dry`.
|
|
133
|
+
4. Migration, after the api is ACTIVE and before sync: from the developer's api checkout on `main` at the promoted commit, with the api's two-line `.env.production` beside `migrate.js`: `npm run migrate -- <id> --env prod --dry --sha <promoted api commit>`, read the result, then without `--dry`. The production cluster is IP-restricted: before the first local run, the developer adds their own IP range to its Network Access list in Atlas, or the runner cannot connect. When the developer tells it to, the agent runs both commands in the developer's session: it reads the dry run's counts file (`runs/prod-<id>-<time>.json`, never the console), checks the release doc's Migrations steps are done (exports read back, for example), shows the developer what the real run will do, and runs it only on their yes (Darren, 2026-10-26: "The agent does when the developer tells it to"). Only a machine without `.env.production` uses the fallback, the deployed api container's Console in the DigitalOcean dashboard. Then `git rm drawbridge-api/scripts/migration-<id>.js`, committed on develop. `main` takes the removal with the next release's finish; never push develop to `main`, which would ship the next release unapproved.
|
|
134
134
|
5. Hand over the verification list from the release doc's Promote section; the developer reports what they saw; the skill fills it in.
|
|
135
135
|
|
|
136
136
|
## Line 10: Released
|
|
@@ -141,7 +141,7 @@ The cut stages the approved release for production and frees develop for the nex
|
|
|
141
141
|
|
|
142
142
|
## What the skill never does
|
|
143
143
|
|
|
144
|
-
- Push `main`, cut or finish a `release/` branch, deploy production, write to the production database, publish a package, merge a pull request, assign the QA person or move the task to QA or Approved. Those are a developer's or a tester's lines. On dev the skill writes nothing of its own either (the refresh only READS dev to copy it into Local)
|
|
144
|
+
- Push `main`, cut or finish a `release/` branch, deploy production, write to the production database, publish a package, merge a pull request, assign the QA person or move the task to QA or Approved. Those are a developer's or a tester's lines. On dev the skill writes nothing of its own either (the refresh only READS dev to copy it into Local). A migration, on dev or on production, runs on a developer's explicit instruction, by the developer or by an agent they ask; the production one only after the snapshot.
|
|
145
145
|
- Ask anything about npm publishing: who, whether, how, or for permission. The developer publishes, always, by pushing the version tag; the skill hands them the one command in the end list and waits for the registry.
|
|
146
146
|
- Name a reviewer, a tester or a release scope that nobody told it; the people are asked at line 1, the scope at line 2. The window is the developer's at line 9; the skill asks for it never.
|
|
147
147
|
- Name an organization in a test list. A developer or a tester opens one that has the features the list names; nobody can guess which they will use.
|
package/.root-template/nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v22.23.
|
|
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.
|
|
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
|
|
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`;
|
|
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
|
|
99
|
-
`APP_CLIENT_WEBHOOKS_URI`) → `buffer` doc → sync drain
|
|
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 — `
|
|
129
|
-
|
|
130
|
-
org
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`node --test`, then `npm publish
|
|
17
|
-
`id-token: write`, npm 11)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
|
@@ -61,16 +61,22 @@ quietly that dev served stale code the whole time.
|
|
|
61
61
|
only place that needs this, and locally it's there"):
|
|
62
62
|
`npm run migrate -- <release asana id> --env prod --dry --sha <promoted commit>`,
|
|
63
63
|
then the same without `--dry`. The runner refuses production off `main`, at
|
|
64
|
-
the wrong commit, or with `scripts/`, `lib/` or `migrate.js` modified.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
the wrong commit, or with `scripts/`, `lib/` or `migrate.js` modified.
|
|
65
|
+
**The production cluster is IP-restricted**: before the first local run,
|
|
66
|
+
each developer adds their own IP range to the production cluster's Network
|
|
67
|
+
Access list in Atlas, or the runner cannot connect. Only a machine without
|
|
68
|
+
`.env.production` uses the fallback, the deployed api container (the api
|
|
69
|
+
component's Console in the DigitalOcean dashboard, then the
|
|
70
|
+
`op run --environment "$OP_ENVIRONMENT_ID" -- node migrate.js …` form; nobody
|
|
71
|
+
installs doctl for this; the image carries `migrate.js` and `scripts/`, there
|
|
72
|
+
is no git, so `--sha` is proven against `APP_COMMIT_HASH`).
|
|
73
|
+
An agent runs a migration only when the developer tells it to, in the
|
|
74
|
+
developer's own session: the two `--env dev` commands (Darren, 2026-10-22),
|
|
75
|
+
and the production ones from the developer's checkout once the Atlas snapshot
|
|
76
|
+
is done (Darren, 2026-10-26: "The agent does when the developer tells it
|
|
77
|
+
to"). It reads the dry run's counts file and shows the developer what the
|
|
78
|
+
real run will do, and it waits for the developer's yes before the real run.
|
|
79
|
+
Never unasked.
|
|
74
80
|
- **The runner records nothing but its counts file**, and nothing else records
|
|
75
81
|
a run either: a developer runs the migration on dev after the pull request
|
|
76
82
|
merges and reads the printed result, and runs it on production from their
|
package/conventions/rules.md
CHANGED
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
|
|
38
38
|
Why: a confident sentence with nothing behind it reads exactly like a verified one, and the reader builds on it. It has sent a tester hunting for a badge that did not exist, described a repo as missing code that was in another package, and promised an edit that had not happened. Every one of those was cheaper to ask about than to correct afterwards.
|
|
39
39
|
|
|
40
|
-
9. Write in the spirit of ASD-STE100. One idea per sentence, with an active verb, in about 20 words. A
|
|
40
|
+
9. Write in the spirit of ASD-STE100. One idea per sentence, with an active verb, in about 20 words. A sentence carries one action: "Open the campaign. Click Draw." never "Open the campaign and click Draw". A retest step is one check, the sentences for what they do and one for what they must see, and a retest list has at most fifteen steps (the `drawbridge-asana-comment` skill). A condition or warning comes before the step it applies to, never after. One word has one meaning for the whole reply: do not swap in synonyms for variety, and write "must" for a requirement and "we recommend" for advice, never "should". The STE dictionary is not in force; use the words the team uses (rule 7).
|
|
41
41
|
|
|
42
42
|
Why: STE exists so that a reader who skims under pressure cannot misread a step. That is Jaime reading a retest list and Darren reading a question. Darren, 2026-10-21, asked for the spirit of STE and not the letter.
|
package/package.json
CHANGED