@drawbridge/drawbridge-agents 0.1.33 → 0.1.35

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.
@@ -21,13 +21,25 @@ need to retest. Use this right after merging the work to `develop` (which deploy
21
21
  (`type` / `resource_subtype` is a comment, not a system story), take the **most recent**
22
22
  one's `created_by`. This is the reassignee — do not hardcode a person. If there are no
23
23
  comments, ask the user who to assign to.
24
- 3. **Draft the comment.** A concise summary of what changed (not a changelog dump), then an
25
- explicit **Retest & verify** section: the exact steps and expected results to confirm the
26
- fix, and any data/setup needed. Ground the retest steps in the actual behaviour that
27
- changed. **Open the comment with an @-mention of the reassignee** — use `html_text` and
28
- start the body with `<a data-asana-gid="<their user gid>"/>` (Asana expands it into a
29
- proper mention). A comment without a mention may notify no one; reassignment alone is
30
- easy to miss.
24
+ 3. **Draft the comment — short.** Two parts, nothing else: what changed (and, if they
25
+ reported a bug, what actually caused it), then a **Retest** section with the steps and
26
+ the expected result. Ground the steps in the behaviour that actually changed.
27
+
28
+ **Hard limit: ~200 words, and never more than 6 short paragraphs/bullets.** These
29
+ comments have been far too long reading them costs the reader more than the fix did.
30
+ Cut: the investigation narrative, why the bug shipped, code/file/commit detail, anything
31
+ restating what they already reported, and any sentence that would not change what they
32
+ do next.
33
+
34
+ **Short is not partial.** Include everything they need in ONE comment — every scenario to
35
+ retest, any setup or data gotcha, and anything about the result that would otherwise look
36
+ wrong (an odd amount, a delay, a cosmetic leftover). A follow-up comment correcting or
37
+ completing this one is the failure this limit exists to prevent, so check the draft for
38
+ omissions *before* trimming words.
39
+
40
+ **Open with an @-mention of the reassignee** — use `html_text` and start the body with
41
+ `<a data-asana-gid="<their user gid>"/>` (Asana expands it into a proper mention). A
42
+ comment without a mention may notify no one; reassignment alone is easy to miss.
31
43
  4. **Confirm before writing.** This posts to an external system. Show the user the chosen
32
44
  assignee and the drafted comment and get a go-ahead, unless they've told you to proceed
33
45
  without asking.
@@ -42,5 +54,7 @@ need to retest. Use this right after merging the work to `develop` (which deploy
42
54
  - If the Asana MCP isn't connected, stop and tell the user to authorize it — don't fabricate a
43
55
  handoff.
44
56
  - Keep the comment user/QA-facing: what to test and expect, not internal implementation detail.
57
+ The reader is verifying behaviour, not reviewing the fix — if a sentence only makes sense to
58
+ someone who has read the diff, it does not belong in the comment.
45
59
  - Consider running `drawbridge-ship-feature` instead if docs/graph updates for this ship
46
60
  haven't been done yet — this skill is the final step of that flow.
@@ -54,6 +54,24 @@ skill) so it stops living only in tribal memory.
54
54
  index manually is not enough; until the boot collMod lands, every insert carrying the field
55
55
  fails Mongo 121 (not E11000), so keyed-idempotency writers silently drop their writes
56
56
  (2026-07-30 `notification.key` dev outage, Sentry DRAWBRIDGE-SYNC-32).
57
+ - **Absent keys become BSON null on validated writes** (api ↔ sync): neither app's MongoClient
58
+ sets `ignoreUndefined`, so a JS `undefined` property serializes as an explicit `null` — which
59
+ strict validators reject wherever the key is typed non-nullable (bare `bsonType : 'string'`).
60
+ Submission `fields` items legally lack `label` (nothing per-item is required), so any consumer
61
+ copying them into a **validated** write must include keys conditionally
62
+ (`...( typeof label === 'string' && { label } )`), never `label : maybeUndefined`. Violated by
63
+ sync `buildContact` until 2026-08-22: contact CREATE is the one validated write in the upsert
64
+ (updates bypass), so one label-less lead failed the insert with Mongo 121 and killed the org's
65
+ entire nightly segment sync (Sentry DRAWBRIDGE-SYNC-32, prod).
66
+ - **Share-page freshness is invalidation-driven, not TTL-driven** (sync ↔ api ↔ share). The api
67
+ render cache (`lib/render-cache.js`, 5 min TTL) and share's HTML cache (SWR, 60 s soft) are
68
+ both busted ONLY through sync's seam: `RENDER_AFFECTING` in
69
+ `lib/render-affecting-collections.js` → `render-invalidate` worker →
70
+ `invalidateSubdomainPageKeys`. Any collection baked into the public page payload MUST be in
71
+ that allowlist or its edits sit stale for the full TTL with no error anywhere — `product` was
72
+ missing until 2026-08-13 (Shopify renames took 5+ min to reach share pages). Product scope is
73
+ special: the doc carries no campaign/org refs, so the worker resolves featuring campaigns via
74
+ `campaign.products[]`, per-page org (canonical product docs can span orgs).
57
75
  - Scraped-asset file reuse keys on `organization + meta.origin + meta.element` **plus
58
76
  `meta.render` for pipeline-rendered marks** (sync `resolveAsset` writes/queries it; the field
59
77
  and its index live in drawbridge-api `schema/file.js`). `origin` identifies the source asset,
@@ -245,6 +263,32 @@ skill) so it stops living only in tribal memory.
245
263
  parsed milliseconds — `frameMilliseconds` in api `route/organization-file.js`, `lib/frames.js`
246
264
  in app-web.
247
265
 
266
+ ## File uploads (utils ↔ api ↔ sync ↔ app-web)
267
+
268
+ - `drawbridge-utils/lib/upload.js` is the ONLY allowlist of what may be uploaded, and now also
269
+ owns `maximumUploadBytes` + `uploadPartSize()`. api validates against it, sync mimes-checks
270
+ against it, app-web derives its `accept` attribute, "Accepted formats" row and uploader
271
+ restrictions from it. Adding a format = edit + publish utils + bump the pin in all three; a
272
+ hand-written copy anywhere is how a format gets accepted by one side and rejected by another.
273
+ - **app-web must compute the part size itself**, not read it from the create response — Uppy's
274
+ `MultipartUploader` builds its chunk array in its constructor, *before* `createMultipartUpload`
275
+ is called. Both sides call the same `uploadPartSize()` so the boundaries cannot disagree.
276
+ - **Never derive a file's extension from the mime subtype.** That works only where subtype ==
277
+ extension; an iPhone `.mov` is `video/quicktime`, so the naive split sends `quicktime` and the
278
+ upload 400s before a byte moves. app-web `fileParts()` in `lib/helpers.js`.
279
+ - **api and sync deploy together for multipart.** The presigned routes create the `file` doc
280
+ BEFORE the bytes (`status: 'pending'`, `bytes: 0`), so without sync's update-side
281
+ `fileBytesDelta` the file counts as ZERO toward `organization.totals.storage` forever — the
282
+ storage restriction passes indefinitely and we under-bill, with nothing erroring.
283
+ - **Spaces bucket CORS must set `ExposeHeaders: [ ETag ]`** for the dashboard origin. Without it
284
+ `@uppy/aws-s3`'s `uploadPartBytes` cannot read the part ETag and returns without resolving or
285
+ rejecting — the upload hangs forever with no error, no retry and no timeout.
286
+ - Abandoned multipart parts are BILLED by Spaces while invisible to bucket listings, so the
287
+ `file.sweep` job in drawbridge-sync (hourly, 6h window) is not optional. Golden Retriever's
288
+ `expires` in app-web must match that window.
289
+ - `sizes.original` must be set when a multipart upload completes, not at create — the resize
290
+ worker reads it as its source, and a file without it is shaped unlike every other file.
291
+
248
292
  ## Import surfaces
249
293
 
250
294
  - Import names against a package's **actual exports** — a missing export resolves to `undefined`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawbridge/drawbridge-agents",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "description": "Shared agent-instruction content (rules, code style, conventions) for the drawbridge-* monorepo.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {