@drawbridge/drawbridge-agents 0.1.38 → 0.1.40
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.
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: drawbridge-add-connection
|
|
3
|
+
description: Use when adding a new vendor connection (a new manifest in drawbridge-utils) — runs the auth-type assessment FIRST so the integration is built the way the vendor's own docs recommend, then walks the build against the documented checklist and its guards.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add a connection
|
|
7
|
+
|
|
8
|
+
A connection is one manifest file in `drawbridge-utils/lib/connections/`, and the
|
|
9
|
+
build half is documented — `drawbridge-docs/reference/connection-hooks.md`,
|
|
10
|
+
"Adding a connection". This skill exists for what comes BEFORE the file: deciding
|
|
11
|
+
what kind of connection to build, from the vendor's documentation rather than
|
|
12
|
+
from whichever auth mechanism is easiest to wire.
|
|
13
|
+
|
|
14
|
+
The failure it prevents is real: Attentive was first built as pasted API keys
|
|
15
|
+
because keys worked, and their docs turned out to frame private-app keys as
|
|
16
|
+
"integrate your own account" — the public OAuth app was the documented path for
|
|
17
|
+
platforms like Drawbridge. The rebuild cost an afternoon; the assessment costs
|
|
18
|
+
ten minutes.
|
|
19
|
+
|
|
20
|
+
## When to use
|
|
21
|
+
|
|
22
|
+
Any new vendor manifest, and any auth-type change to an existing one (keys →
|
|
23
|
+
OAuth is a rebuild of the auth surface, not a tweak).
|
|
24
|
+
|
|
25
|
+
## Steps
|
|
26
|
+
|
|
27
|
+
1. **Fetch the vendor's developer docs and answer these in writing, with the
|
|
28
|
+
URL of the page that answers each.** Never from memory — the Klaviyo consent
|
|
29
|
+
url was once written from memory, pointed at their API host, and stalled
|
|
30
|
+
every merchant on a blank page.
|
|
31
|
+
|
|
32
|
+
- What auth mechanisms exist, and **who does the vendor say each is for**?
|
|
33
|
+
The distinction that matters is *your own account* versus *accounts outside
|
|
34
|
+
your organization*. Drawbridge is always the second: merchants connect
|
|
35
|
+
THEIR account to OUR platform.
|
|
36
|
+
- Is there a partner / public-app / marketplace program? What does
|
|
37
|
+
registration take, and what credentials does it issue?
|
|
38
|
+
- For OAuth: authorize url, token url, how client credentials are sent
|
|
39
|
+
(form fields vs Basic header), whether PKCE is required, token lifetime,
|
|
40
|
+
whether refresh tokens exist, scope names.
|
|
41
|
+
|
|
42
|
+
2. **Pick the `auth.type` from the answers, not from convenience:**
|
|
43
|
+
|
|
44
|
+
| vendor's shape | type |
|
|
45
|
+
|---|---|
|
|
46
|
+
| installed from the vendor's app store, never redirects back | `install` |
|
|
47
|
+
| registered app, merchant consents on the vendor's screen | `oauth` |
|
|
48
|
+
| the vendor offers ONLY per-account credentials | `keys` |
|
|
49
|
+
| no third party behind it at all | `generated` |
|
|
50
|
+
|
|
51
|
+
`keys` when OAuth exists is a decision someone must make out loud — usually
|
|
52
|
+
as an explicitly-flagged interim while a partner registration is pending, and
|
|
53
|
+
the manifest header says so.
|
|
54
|
+
|
|
55
|
+
3. **If the app is not yet registered, build it dormant.** `requires` names the
|
|
56
|
+
client env vars that will exist after registration; until they land on a
|
|
57
|
+
deployment, the connection is offered nowhere. Shipping the manifest complete
|
|
58
|
+
and inert beats waiting — adding the env vars becomes the launch.
|
|
59
|
+
|
|
60
|
+
4. **Record the assessment in the manifest header**: every cited fact, every
|
|
61
|
+
contradiction between the vendor's own pages (cite both and say which one the
|
|
62
|
+
code models), and — for OAuth — the questions only a live registration
|
|
63
|
+
answers: does consent echo `state` back, what is the real token lifetime,
|
|
64
|
+
does one app accept both environments' redirect urls.
|
|
65
|
+
|
|
66
|
+
5. **Build against the checklist** in
|
|
67
|
+
`drawbridge-docs/reference/connection-hooks.md` → "Adding a connection". Its
|
|
68
|
+
guards are the enforcement: the directory-registration test, the
|
|
69
|
+
feature-grant test, `build()` itself. Trust the named failures over your
|
|
70
|
+
memory of the steps.
|
|
71
|
+
|
|
72
|
+
## Notes
|
|
73
|
+
|
|
74
|
+
- The manifest is the only place vendor facts live. If the assessment finds a
|
|
75
|
+
fact that must hold across repos, record it with `drawbridge-record-contract`.
|
|
76
|
+
- The picker/search question is part of the assessment: if the vendor's list
|
|
77
|
+
endpoint filters by name server-side, the field must NOT declare
|
|
78
|
+
`search : false` — Attentive was the first vendor that could, and a test pins
|
|
79
|
+
that it stays searchable.
|
|
@@ -9,7 +9,7 @@ skill) so it stops living only in tribal memory.
|
|
|
9
9
|
|
|
10
10
|
- `OAUTH_TOKEN_HMAC_KEY` must be identical in **drawbridge-api** and **drawbridge-sync** — both
|
|
11
11
|
`assertEnv` it at boot. A mismatch silently breaks realtime token verification.
|
|
12
|
-
- `
|
|
12
|
+
- `HMAC_UNSUBSCRIBE_TOKEN_KEY` must be identical in **drawbridge-api** and **drawbridge-sync** —
|
|
13
13
|
both `assertEnv` it at boot. Sync signs the unsubscribe tokens it builds into notification
|
|
14
14
|
emails (`lib/suppression.js buildToken`); api verifies them on the public `/unsubscribe/:token`
|
|
15
15
|
routes (`lib/unsubscribe.js`). A mismatch makes every unsubscribe link 404 as "invalid or
|
|
@@ -343,6 +343,34 @@ skill) so it stops living only in tribal memory.
|
|
|
343
343
|
- `sizes.original` must be set when a multipart upload completes, not at create — the resize
|
|
344
344
|
worker reads it as its source, and a file without it is shaped unlike every other file.
|
|
345
345
|
|
|
346
|
+
## Connection manifests are the single declaration (utils ↔ api ↔ sync ↔ webhooks)
|
|
347
|
+
|
|
348
|
+
A connection is **one file** in `drawbridge-utils/lib/connections/`. Everything else
|
|
349
|
+
derives from it: the builder catalog and `enums.step.type` in api, the queue routing table
|
|
350
|
+
and hook registry in sync, the inbound receiver in webhooks, the connections page in
|
|
351
|
+
app-web. Adding a vendor is that file plus its import in `index.js` — a test reads the
|
|
352
|
+
directory and fails if the two disagree, because a manifest nothing imports is a vendor
|
|
353
|
+
that exists on disk and in nobody's catalog.
|
|
354
|
+
|
|
355
|
+
**`STEPS` and `RETIRED` in `contract.js` must stay in step with `enums.step.type` in the
|
|
356
|
+
api.** That enum is the `$jsonSchema` validator on the workflow collection: a type absent
|
|
357
|
+
from it is rejected by the DATABASE with "Document failed validation" naming no field. The
|
|
358
|
+
enum DERIVES from `STEPS` and UNIONS its own legacy map — union rather than replace, or
|
|
359
|
+
stored documents carrying a retired type become unwritable, including by the migration that
|
|
360
|
+
would retire them.
|
|
361
|
+
|
|
362
|
+
**A hook lives in drawbridge-sync only if it needs Drawbridge's own database, sockets or
|
|
363
|
+
queues.** Everything else belongs on the manifest. `lib/hooks/` in sync mirrors
|
|
364
|
+
`lib/connections/` in utils, one file per connection. A hook declared `{}` means "supported,
|
|
365
|
+
implemented in the repo holding the dependencies"; if that registration is missing the
|
|
366
|
+
runner resolves nothing and the step is SKIPPED with a `succeeded` record — silent, so
|
|
367
|
+
`step-handler-coverage.test.js` asserts both directions.
|
|
368
|
+
|
|
369
|
+
**Job identity is production state.** Step types, queue names and jobIds are live in Redis;
|
|
370
|
+
renaming one orphans what is already enqueued and starts a parallel stream nobody consumes,
|
|
371
|
+
with no error. `bullmq-identity.test.js` in sync spells out the routing table so moving a
|
|
372
|
+
step between queues requires editing it deliberately.
|
|
373
|
+
|
|
346
374
|
## Import surfaces
|
|
347
375
|
|
|
348
376
|
- Import names against a package's **actual exports** — a missing export resolves to `undefined`
|
package/conventions/rules.md
CHANGED
|
@@ -6,3 +6,9 @@
|
|
|
6
6
|
3. Don't touch unrelated code. If a file or function is not directly part of the current task, do not modify it, even if you think it could be improved.
|
|
7
7
|
|
|
8
8
|
4. Flag uncertainty explicitly. If you are not confident about an approach or technical detail, say so before proceeding. Confidence without certainty causes more damage than admitting a gap.
|
|
9
|
+
|
|
10
|
+
5. Read the code before you have an opinion about it. Never describe what a component, handler or helper does — what props it takes, what it cannot do, whether two things are the same — on the strength of a grep or a partial read. Open the whole file first. A grep tells you a string is present; it cannot tell you what surrounds it, what the other variant does, or which prop already solves the problem you are about to declare unsolvable.
|
|
11
|
+
|
|
12
|
+
This is not a style preference, it is the difference between advice and noise. Asserting a limitation that isn't real sends the reader to check your work, and asserting two implementations are identical when one carries a field the other drops ships a regression. Both have happened.
|
|
13
|
+
|
|
14
|
+
In practice: before claiming a component can't do something, read the component. Before calling two implementations equivalent, read both ends to end. Before proposing a new endpoint or field to work around a gap, confirm the gap exists in the code rather than in your sample of it.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Workflow step naming
|
|
2
|
+
|
|
3
|
+
Step `type` strings follow **`step.<domain>[.<resource>].<verb>`** — dots, never dashes.
|
|
4
|
+
|
|
5
|
+
The resource segment appears when the domain acts on more than one thing, and is omitted
|
|
6
|
+
when it acts on one. Both forms are correct:
|
|
7
|
+
|
|
8
|
+
step.shopify.order.record four — shopify manages orders, customers,
|
|
9
|
+
step.shopify.customer.insert discounts, products, tokens
|
|
10
|
+
step.shopify.token.refresh
|
|
11
|
+
|
|
12
|
+
step.email.send three — email sends one thing
|
|
13
|
+
step.email.notify
|
|
14
|
+
step.sms.send
|
|
15
|
+
step.segment.sync
|
|
16
|
+
|
|
17
|
+
State it as a flat four segments and the three-segment ones read as drift, and someone
|
|
18
|
+
tries to "correct" them. They are not drift.
|
|
19
|
+
|
|
20
|
+
**The last segment is a verb.** `step.webhook.outgoing` breaks that — `outgoing` is a
|
|
21
|
+
direction — and should have been `step.webhook.send`. **Do not rename it**: webhooks are
|
|
22
|
+
shipped and step slugs are stored in workflow documents. Known exception, not a pattern.
|
|
23
|
+
|
|
24
|
+
## Renaming
|
|
25
|
+
|
|
26
|
+
**Stored step slugs cannot be renamed once merchants hold them.** A rename is only free
|
|
27
|
+
while a slug has no workflows anywhere, which in practice means before it reaches `main`.
|
|
28
|
+
Check both environments before assuming — dev counts prove nothing about production.
|
|
29
|
+
|
|
30
|
+
## Adding a step
|
|
31
|
+
|
|
32
|
+
**This used to be a six-place ritual.** The type string had to appear identically in the
|
|
33
|
+
api's `enums.step.type`, its workflow catalog, its `stepSettingsShapes`, sync's
|
|
34
|
+
`stepTypeToQueueName`, sync's handler registry, and app-web's builder — and a mismatch in
|
|
35
|
+
any one failed silently, saving the step and then dropping the work at run time.
|
|
36
|
+
|
|
37
|
+
It is now **two places**, and both are declarations rather than copies:
|
|
38
|
+
|
|
39
|
+
1. `drawbridge-utils/lib/connections/contract.js` — the type and its label in `STEPS`
|
|
40
|
+
2. `drawbridge-utils/lib/connections/<vendor>.js` — the step, and the hook it points at
|
|
41
|
+
|
|
42
|
+
Everything else DERIVES: the builder catalog, `enums.step.type`, `stepSettingsShapes`,
|
|
43
|
+
sync's routing table. `build()` refuses the manifest at import if the declaration and the
|
|
44
|
+
hook disagree, and `step-handler-coverage.test.js` in drawbridge-sync fails if the body is
|
|
45
|
+
missing.
|
|
46
|
+
|
|
47
|
+
**A step that does real work must declare a `hook`.** The runner resolves hooks by
|
|
48
|
+
declaration, so a step declaring none is *skipped* — the step doc reads `succeeded` with a
|
|
49
|
+
null message and the work silently stops. That shipped once, for `segment.sync`.
|
|
50
|
+
|
|
51
|
+
## Labels
|
|
52
|
+
|
|
53
|
+
**One label, in `STEPS`.** The enum label and the builder's `key` used to be two strings
|
|
54
|
+
for one step and they drifted — `step.sms.send` was "Send SMS" in the enum and "Send Twilio
|
|
55
|
+
SMS" in the builder. The enum now derives from `STEPS`, so there is nothing to keep in
|
|
56
|
+
step.
|
|
57
|
+
|
|
58
|
+
The label lives on the vocabulary rather than on a vendor because a step type belongs to
|
|
59
|
+
the CAPABILITY: Klaviyo and Mailchimp both do `contacts.sync`, and a label taken from
|
|
60
|
+
either would show a merchant two identically-named entries. A manifest's own `key` is the
|
|
61
|
+
INSTANCE label — "Sync contact to Acme Co" — which is a different sentence for a different
|
|
62
|
+
place.
|
|
63
|
+
|
|
64
|
+
## Retiring a type
|
|
65
|
+
|
|
66
|
+
`RETIRED` in `contract.js` maps each retired type to what it became. Stored documents carry
|
|
67
|
+
the old names until a backfill runs, and the runner resolves a manifest **by type** — so
|
|
68
|
+
without that map a legacy step finds no manifest and is silently skipped. `enums.step.type`
|
|
69
|
+
keeps every retired key so those documents stay writable, including by the migration that
|
|
70
|
+
retires them. A type leaves the map a release AFTER its backfill, never with it.
|
package/package.json
CHANGED