Skip to content

feat: multi-bucket routing (pkg_bucket_map) and auto_packaging toggle#393

Closed
QuiltSimon wants to merge 5 commits into
mainfrom
feature/multi-bucket-auto-packaging
Closed

feat: multi-bucket routing (pkg_bucket_map) and auto_packaging toggle#393
QuiltSimon wants to merge 5 commits into
mainfrom
feature/multi-bucket-auto-packaging

Conversation

@QuiltSimon

@QuiltSimon QuiltSimon commented Jul 3, 2026

Copy link
Copy Markdown

Context for reviewers

Sergey and Ernie — you wrote the original benchling-webhook. This PR adds two new optional BenchlingSecret parameters requested during the leadership call for the Pioneering Medicines multi-bucket workflow. Everything is backward-compatible: existing secrets without the new keys behave exactly as today.


What changed and why

pkg_bucket_map — multi-bucket routing

Why: PM needs different experiment types stored in different S3 buckets. The current code hard-codes user_bucket for everything.

How it works:

  • BenchlingSecret gains an optional pkg_bucket_map key — a JSON object mapping Benchling folder IDs (lib_...) or project IDs (src_...) to S3 bucket names, e.g. {"lib_abc123": "bucket-a", "src_def456": "bucket-b"}.
  • Routing precedence: folder ID first, then project ID, then user_bucket fallback. Logic lives in the new module docker/src/bucket_router.py.
  • Applied consistently across: S3 uploads in _process_export, input.json registry metadata, SQS packaging messages, canvas package links/QuiltSync URIs/footer, and package-event canvas refreshes. The SQS consumer also accepts events from all mapped buckets, not just user_bucket.
  • Routing is evaluated per event. Re-foldering an entry re-routes future packaging; existing packages stay in the bucket they were written to.

Constraint to be aware of (Sergey): Mapped buckets must already be registered with the Quilt stack — the task role's BucketWritePolicy and the per-bucket Athena <bucket>_packages-view only cover buckets the stack knows about. No new IAM or CloudFormation changes are needed in this PR; that's an operator concern when they configure the map.

auto_packaging — disable automatic packaging

Why: PM also wants a mode where entry events don't immediately kick off packaging (they're high-frequency and not all represent a "finished" state). Instead, a lab scientist manually triggers packaging via the canvas "Update Package" button when they're ready.

How it works:

  • BenchlingSecret gains an optional auto_packaging key (string "true"/"false", default "true").
  • When "false": entry lifecycle events (v2.entry.created, v2.entry.updated.fields, v2.entry.updated.reviewRecord) return status: skipped without touching SQS. Canvas init/creation events render the full canvas (package links, Browse/Update buttons) without starting an export.
  • The explicit "Update Package" button path is unchanged and always works regardless of this flag.

Architecture decisions you should weigh in on

Ernie — reversibility and blast radius:

  • Both parameters are optional with safe defaults. Removing them from a secret restores exact prior behavior. Blast radius on misconfiguration: worst case a package goes to the wrong bucket (still a Quilt-tracked bucket, still accessible via catalog — just under a different bucket tab). Not data loss.
  • auto_packaging=false is a positive opt-in, not a default-change. No existing deployment is affected unless the operator explicitly adds the key.
  • The "fence" is in the secret schema: pkg_bucket_map values that name non-stack buckets fail silently at write time (IAM AccessDenied on S3 put) rather than at parse time. Worth discussing whether we want a validation step at secret-load time that checks bucket reachability.

Sergey — AWS/infra specifics:

  • No new AWS resources. The task role's existing BucketWritePolicy (a managed IAM policy attached at deploy time) already covers all stack-registered buckets, so multi-bucket writes work without an IAM change if the buckets are stack-registered.
  • The SQS filter now accepts events from {user_bucket} ∪ {all pkg_bucket_map values} instead of just user_bucket. The EventBridge pattern itself is unchanged — filtering happens in the consumer at runtime, not at the SQS/EventBridge level.
  • Secrets are cached with a 60s TTL. A pkg_bucket_map change takes effect within ~60s without a restart.

Bug found and fixed during review

Greptile flagged (and we fixed before requesting review): execute_workflow previously resolved target_bucket twice from two independent Benchling API calls — once inside _process_export for S3 writes, and once at Step 5 for the SQS message. If an entry was re-foldered between those calls, files would land in bucket-A while the SQS source_prefix pointed at bucket-B. Fixed in the last commit: bucket is now resolved once from the Step 1 entry_data and threaded into _process_export as a parameter.


Files to focus your review on

File What to look at
docker/src/bucket_router.py New module — routing logic and key normalization
docker/src/entry_packager.py Single bucket resolution threaded through execute_workflow_process_export_send_to_sqs
docker/src/sqs_consumer.py Multi-bucket accept filter; bucket param threaded to refresh_canvas_for_package_event
docker/src/secrets_manager.py Parsing and validation of the two new secret keys
docker/src/app.py auto_packaging gates in _handle_event_impl and _handle_canvas_impl
docker/src/canvas.py Bucket resolved from pkg_bucket_map in __init__; propagates to PackageQuery and PackageFileFetcher

Test coverage

  • 467 Python tests pass (24 new: 11 bucket-router, 10 secret-param validation, 2 app-level auto-packaging end-to-end, 1 canvas routing parity)
  • 557 TypeScript tests pass
  • lint/typecheck clean

Not yet tested (needs a live dev stack)

  • Two-bucket map: verify mapped-folder entry lands in bucket-a, unmapped in user_bucket, canvas links point at the right catalog URL
  • auto_packaging=false: entry events skipped, canvas renders with buttons, Update Package still packages

smkohnstamm and others added 3 commits July 2, 2026 22:24
Two new optional secret parameters, both runtime-configurable in AWS
Secrets Manager (no redeploy needed):

- pkg_bucket_map: JSON object mapping Benchling folder IDs (lib_...) or
  project IDs (src_...) to S3 bucket names. Entries route by folder
  first, then project; unmapped entries fall back to user_bucket.
  Routing applies to exports, SQS packaging messages, canvas package
  links, and package-event canvas refreshes. The package-event consumer
  accepts events from all mapped buckets.

- auto_packaging (default "true"): when "false", entry lifecycle events
  are acknowledged without triggering packaging, and canvas creation
  renders the canvas without starting an export. Packaging runs only
  from the explicit "Update Package" canvas button.

Both are also settable via the CLI profile config (packages.bucketMap /
packages.autoPackaging) and synced by sync-secrets.

Co-authored-by: Cursor <cursoragent@cursor.com>
…kager)

Co-authored-by: Cursor <cursoragent@cursor.com>
…nt routing)

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread docker/src/app.py
Comment on lines +946 to +963
if not config.auto_packaging:
# Auto-packaging disabled: render the canvas (package links +
# "Update Package" button) but do not enqueue a packaging
# workflow. Packaging runs only from the explicit
# "Update Package" button (handled above).
_render_canvas_without_packaging(payload)

logger.info(
"Canvas rendered without packaging (auto-packaging disabled)",
canvas_id=payload.canvas_id,
entry_id=payload.entry_id,
event_type=payload.event_type,
)

return JSONResponse(
{"status": "ACCEPTED", "message": "Canvas rendered (auto-packaging disabled)"},
status_code=202,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 auto_packaging=False silently re-renders canvas for unknown button interactions

The if not config.auto_packaging: check is reached not only for v2.canvas.initialized but also whenever a v2.canvas.userInteracted event arrives with an unrecognized button_id (none of the startswith guards return early). When auto_packaging=True that falls through to publish_packaging_request — a pre-existing issue. But with auto_packaging=False, the unknown button now silently re-renders the canvas instead. This may be acceptable as a safe fallback, but it's worth confirming the intent since it means any typo'd or future buttonId value silently re-renders rather than logging "unknown" and returning.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! Is re-rendering the canvas the desired behavior for unrecognized button IDs when auto_packaging=False, or should the unknown-button warning still be returned?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — fixed in 5a8be45. Added a return immediately after the unknown-button warning so the auto_packaging gate (and the publish_packaging_request path above it) is only reachable by canvas lifecycle events (initialized, created, etc.), not by unrecognized button interactions. Also cleans up the pre-existing issue where an unknown button with auto_packaging=True would trigger packaging.

…cess_export

Closes the race Greptile flagged: previously execute_workflow resolved the
target bucket twice — once inside _process_export (from its own entry fetch
for S3 writes) and once at Step 5 (from the Step 1 fetch for the SQS message).
If an entry was re-foldered between those two Benchling API calls, S3 files
would land in bucket-A while the SQS source_prefix pointed at bucket-B.

Now target_bucket is resolved once from the Step 1 entry_data and passed into
_process_export as an optional argument, so S3 writes and the SQS registry
field are always derived from the same snapshot.

Co-authored-by: Cursor <cursoragent@cursor.com>
@QuiltSimon
QuiltSimon requested review from drernie and sir-sigurd July 3, 2026 13:42
… through

With auto_packaging=False an unknown button_id was silently re-rendering
the canvas; with auto_packaging=True it was incorrectly triggering packaging.
Neither is the right behavior for an unrecognized button. Return 200/ignored
immediately after the warning so the auto_packaging gate is only reached by
canvas lifecycle events (initialized, created, etc.).

Co-authored-by: Cursor <cursoragent@cursor.com>
@QuiltSimon

Copy link
Copy Markdown
Author

Superseded by #395.

After discussion in the #engineering thread, we agreed that all configurable capability should live in the platform (Benchling App Config Items), not in AWS Secrets Manager. PR #395 adds the auto_packaging toggle via ["quilt", "settings", "auto_packaging"] on top of Ernie's three-tier routing system, with the unknown-button bugfix included.

Closing this PR. The Secrets Manager routing code (pkg_bucket_map, bucket_router.py) from this PR is dropped in favor of Ernie's routing_config.py approach.

@QuiltSimon QuiltSimon closed this Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants