@drawbridge/drawbridge-agents 0.1.10 → 0.1.15

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/CLAUDE.md CHANGED
@@ -12,6 +12,7 @@
12
12
  @../conventions/drawbridge-packages.md
13
13
  @../conventions/sentry-sdk.md
14
14
  @../conventions/app-web-forms.md
15
+ @../conventions/app-web-gates.md
15
16
  @../conventions/docs-linkage.md
16
17
  @../conventions/superpowers-docs.md
17
18
  @../conventions/git-branching.md
@@ -0,0 +1,22 @@
1
+ # app-web gate helpers
2
+
3
+ Applies to `drawbridge-app-web` only.
4
+
5
+ UI gating goes through the shared helpers in `@/lib/helpers` — never inline the
6
+ underlying object walks:
7
+
8
+ - `hasCapability( organization, key, level = 'manage' )` — capability checks by
9
+ dot-path (`'campaigns.settings'`, `'brands'`). Never write
10
+ `organization?.capabilities?.x === 'manage'` inline; a missed optional chain
11
+ in that pattern has crashed pages before.
12
+ - `hasConnection( organization, slug )` — "org has an ACTIVE connection for
13
+ this slug". Reads `connectionItems` from the org payload (see the
14
+ organization-payload contract in `cross-repo-contracts.md`); never fetch
15
+ `/organization/:id/connections` just to gate UI.
16
+ - `hasRestriction( object, key )` — plan-limit restrictions on an org or
17
+ campaign.
18
+
19
+ Compose extras (`getPlanFeature(...).granted`, totals checks) explicitly at the
20
+ call site; the helpers stay single-purpose. Pass the org from the
21
+ `[ 'organization', id ]` query cache (or an `enabled : false` `useQuery` on
22
+ that key when the component must re-render as it refreshes).
@@ -26,6 +26,12 @@ skill) so it stops living only in tribal memory.
26
26
  `queue/index.js` together.
27
27
  - BullMQ jobs are keyed on the change-stream resume token as `jobId` for HA dedup across sync
28
28
  replicas. Reusing a static `jobId` with `removeOnComplete` makes re-enqueues no-op silently.
29
+ - Scraped-asset file reuse keys on `organization + meta.origin + meta.element` **plus
30
+ `meta.render` for pipeline-rendered marks** (sync `resolveAsset` writes/queries it; the field
31
+ and its index live in drawbridge-api `schema/file.js`). `origin` identifies the source asset,
32
+ `render` content-hashes the rendered output — drop `render` from either side and a rendering
33
+ fix never ships: reuse serves the first-ever render forever (the onemethod
34
+ black-on-black-logo-twice bug).
29
35
 
30
36
  ## Billing / metering
31
37
 
@@ -39,6 +45,14 @@ skill) so it stops living only in tribal memory.
39
45
  sync upserts the same invoice from three concurrent webhooks (finalized/payment_succeeded/voided)
40
46
  and relies on the index to reject the losing insert so the catch can redo it as an update. Drop
41
47
  the index → silent duplicate invoice rows; remove the catch → concurrent events throw + retry.
48
+ - The `contact` collection's **unique multikey index on `leads`** (drawbridge-api
49
+ `schema/contact.js`) and sync's contact resolution (`lib/contact.js` + `queue/contact.js`) are
50
+ one contract: a lead belongs to at most one contact. One submission enqueues two resolve jobs
51
+ (lead insert + totals-update events) that race; the index rejects the losing create and the
52
+ worker retries it into the update path. The merge path must delete duplicate contacts BEFORE
53
+ `$set`-ing the survivor (the expanded `leads` array collides with rows about to be deleted).
54
+ Drop the index → duplicate contacts, doubled segment members, double-billed `contact.create` +
55
+ `segment.join`; reorder the merge → every merge aborts E11000 and duplicates never heal.
42
56
 
43
57
  ## `@drawbridge/*` package coordination
44
58
 
@@ -54,3 +68,32 @@ skill) so it stops living only in tribal memory.
54
68
  - Import names against a package's **actual exports** — a missing export resolves to `undefined`
55
69
  and throws at call time, not at import. Check `drawbridge-docs/reference/packages.md` (the
56
70
  generated export surface) when unsure.
71
+
72
+ ## Shopify sales channel
73
+
74
+ - `@drawbridge/shopify` storefront `getProduct`/`getProductVariantsPage` throw the **exact
75
+ message `'Shopify product not found'`** when the Storefront API can't see a product
76
+ (unpublished from the Drawbridge channel / deleted); transport and GraphQL failures throw
77
+ `'Shopify storefront error: …'`. drawbridge-sync's product worker (`queue/product.js`)
78
+ string-matches the not-found message to send publish-this-product ResourceFeedback instead of
79
+ retrying. Reword the message → unpublished products retry-loop forever and merchants get no
80
+ feedback; drawbridge-api's shopifyProduct route relies on the not-found throw staying a throw.
81
+ - The `write_resource_feedbacks` and `read_publications` scopes live in drawbridge-shopify-app's
82
+ `shopify.app.*.toml` but power other repos: sync's product ResourceFeedback sends and the
83
+ embedded app's publishing section. Removing either → silent Shopify userErrors, no crash.
84
+ - `SHOPIFY_REQUIRED_SCOPES` (`@drawbridge/shopify` `lib/constants.js`) must stay in lockstep with
85
+ `[access_scopes]` in drawbridge-shopify-app's `shopify.app.*.toml` (both files). drawbridge-api
86
+ diffs each store's granted scopes against it to flag "update permissions" on the connections
87
+ page. A scope added to the toml but not the constant silently escapes the check; one added to
88
+ the constant but not the toml flags every store forever.
89
+
90
+ ## Organization payload (api ↔ app-web)
91
+
92
+ - `connectionItems` on the org payload comes only from drawbridge-api
93
+ `extend.organization({ connections : true })` (org GET + org menu opt in via
94
+ `hasOrganizationAccess`); app-web's `hasConnection` helper (`lib/helpers.js`) and api's
95
+ `lib/tasks.js connectionErrors` read it. Two invariants: the lookup's `$project`
96
+ (id/slug/status/title/shop/errors) is a **security boundary** — the org response spreads to the
97
+ browser, so adding a credential field ships connection tokens to every dashboard user; and api
98
+ deploys before app-web — the gate treats a missing field as "no connection" and silently hides
99
+ connection-gated UI (e.g. the campaign Shop product columns row).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawbridge/drawbridge-agents",
3
- "version": "0.1.10",
3
+ "version": "0.1.15",
4
4
  "description": "Shared agent-instruction content (rules, code style, conventions) for the drawbridge-* monorepo.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {