Skip to content

feat: auto_packaging toggle via Benchling App Config settings#395

Open
QuiltSimon wants to merge 2 commits into
392-configurabilityfrom
feature/auto-packaging-platform-config
Open

feat: auto_packaging toggle via Benchling App Config settings#395
QuiltSimon wants to merge 2 commits into
392-configurabilityfrom
feature/auto-packaging-platform-config

Conversation

@QuiltSimon

@QuiltSimon QuiltSimon commented Jul 3, 2026

Copy link
Copy Markdown

Summary

Adds a runtime auto_packaging toggle to the three-tier routing system, configured via Benchling App Configuration Items rather than AWS Secrets Manager.

What changes

  • New App Config setting: ["quilt", "settings", "auto_packaging"] — when false, entry events (v2.entry.*) and canvas lifecycle events (v2.canvas.initialized, v2.canvas.created) no longer automatically enqueue packaging work. The canvas renders with the Update Package button so scientists trigger packaging per-entry.
  • Defaults to true: existing behavior is preserved when the setting is absent or unrecognized.
  • Fail-open on config errors: if the Benchling App Config lookup fails, packaging stays enabled so an API hiccup never silently drops work.
  • Immediate toggle: the manifest now subscribes to v2-beta.app.configuration.updated, so toggling the value in the Benchling UI takes effect on the next event (no restart, no60s cache wait).
  • Button-driven packaging unaffected: the Update Package button always enqueues packaging regardless of the setting.

Bugfix included

Unknown canvas button interactions now return an explicit {"status": "ignored"} response instead of falling through to the packaging path. Previously, an unrecognized buttonId would enqueue unintended packaging work (with auto_packaging true) or silently re-render the canvas (with auto_packaging false).

Files to focus your review on

File Change
docker/src/routing_config.py RuntimeSettings.auto_packaging: bool | None, _bool_value() parser
docker/src/config.py Config.auto_packaging, resolve_auto_packaging() method
docker/src/app.py _resolve_auto_packaging(), _render_canvas_without_packaging(), unknown-button early return
docker/app-manifest.yaml Subscribe to v2-beta.app.configuration.updated
docker/scripts/app_config.py Seed default ["quilt", "settings", "auto_packaging"] as "true"
docker/tests/test_routing_config.py Settings parsing tests for true/false/unset/garbage
docker/tests/test_app.py Entry skipped, canvas renders without packaging, unknown button ignored

Relationship to PR #393

This supersedes the Secrets-Manager-based auto_packaging from #393. That PR routed configuration through BenchlingSecretData and sync-secrets.ts; this PR follows the platform-config-first principle discussed in the #engineering thread: all configurable behavior lives in the platform (Benchling App Config), not in AWS console configuration. Closing #393 in favor of this PR.

Test plan

  • docker/tests/test_routing_config.py: auto_packaging parses "true", "False", unset, garbage
  • docker/tests/test_app.py: entry event returns skipped when auto_packaging is off
  • docker/tests/test_app.py: canvas lifecycle renders without packaging when off
  • docker/tests/test_app.py: unknown button returns ignored without enqueuing packaging
  • Full suite: 414 passed, 37 deselected
  • Deploy to bench.dev.quilttest.com, toggle auto_packaging via Benchling UI, verify entry events no longer auto-package and Update Package button still works

Made with Cursor

Greptile Summary

Adds a runtime auto_packaging toggle controlled by the ["quilt", "settings", "auto_packaging"] Benchling App Config Item, wired through the existing three-tier routing/cache system. When disabled, entry and canvas lifecycle events render the canvas without enqueueing SQS work; the Update Package button is always unaffected. Also fixes a pre-existing bug where unrecognised canvas buttonId values would fall through to the packaging path.

  • routing_config.py: New _bool_value() parser and RuntimeSettings.auto_packaging: bool | None field, with clear None-as-unset semantics.
  • config.py: resolve_auto_packaging() reads the TTL cache and falls back to the startup-time value; apply_global_routing_config propagates the setting at startup.
  • app.py: _resolve_auto_packaging() wraps the config call with a fail-open exception handler; guards are inserted at the three relevant event paths; _render_canvas_without_packaging() handles the canvas-only render; unknown buttons now return {"status": "ignored"} instead of falling through.

Confidence Score: 4/5

Safe to merge; the only finding is a cosmetic dead-parameter in a helper function. Core logic, cache behaviour, and fail-open default are all sound.

The toggle logic is straightforward and well-isolated behind the existing TTL-cache layer. The unknown-button fix is clearly correct. The single comment is about an unused payload argument in _resolve_auto_packaging that doesn't affect runtime behaviour — removing it would improve clarity but nothing breaks with it present.

docker/src/app.py — minor cleanup of the unused parameter in _resolve_auto_packaging and its three call-sites.

Important Files Changed

Filename Overview
docker/src/app.py Adds _resolve_auto_packaging and _render_canvas_without_packaging helpers; inserts auto_packaging guards in /event and /canvas handlers; fixes unknown-button fall-through. The payload parameter in _resolve_auto_packaging is unused.
docker/src/config.py Adds auto_packaging field (default True), propagates it in apply_global_routing_config, and adds resolve_auto_packaging() which reads the TTL-cached routing config and falls back to the startup-time value.
docker/src/routing_config.py Adds auto_packaging: bool
docker/app-manifest.yaml Subscribes to v2-beta.app.configuration.updated; Benchling routing convention for v2-beta.app.* events should deliver it to /lifecycle (already handled), but the routing relies on Benchling's prefix conventions.
docker/tests/test_app.py Adds three new tests covering the auto_packaging disabled path for entry events, canvas lifecycle events, and the unknown-button early-return fix. Tests are clear and correctly mock resolve_auto_packaging.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming webhook event] --> B{Endpoint}
    B -->|canvas events| C[canvas handler]
    B -->|entry events| D[event handler]
    B -->|config.updated lifecycle| E[lifecycle handler]
    E --> E1[config.invalidate_routing_config]
    C --> F{userInteracted?}
    F -->|Yes| G{Known buttonId?}
    G -->|browse, page, back, metadata| H[Handle button action]
    G -->|update-package| I[handle_update_package - enqueue]
    G -->|unknown| J[Return ignored - NEW FIX]
    F -->|No - lifecycle event| K{_resolve_auto_packaging}
    K -->|False| L[render canvas without packaging Return 202]
    K -->|True| M[send updating canvas + publish_packaging_request Return 202]
    D --> N{event_type supported?}
    N -->|No| O[Return ignored]
    N -->|Yes| P{_resolve_auto_packaging}
    P -->|False| Q[Return skipped - NEW]
    P -->|True| R[publish_packaging_request - Return ACCEPTED]
    subgraph resolve[_resolve_auto_packaging closure]
        S[get_benchling_secrets] --> T[resolve_auto_packaging via TTL cache]
        T --> U{value in cache?}
        U -->|yes| V[return cached bool]
        U -->|no| W[return config.auto_packaging startup default]
        S -->|Exception| X[return True fail open]
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Incoming webhook event] --> B{Endpoint}
    B -->|canvas events| C[canvas handler]
    B -->|entry events| D[event handler]
    B -->|config.updated lifecycle| E[lifecycle handler]
    E --> E1[config.invalidate_routing_config]
    C --> F{userInteracted?}
    F -->|Yes| G{Known buttonId?}
    G -->|browse, page, back, metadata| H[Handle button action]
    G -->|update-package| I[handle_update_package - enqueue]
    G -->|unknown| J[Return ignored - NEW FIX]
    F -->|No - lifecycle event| K{_resolve_auto_packaging}
    K -->|False| L[render canvas without packaging Return 202]
    K -->|True| M[send updating canvas + publish_packaging_request Return 202]
    D --> N{event_type supported?}
    N -->|No| O[Return ignored]
    N -->|Yes| P{_resolve_auto_packaging}
    P -->|False| Q[Return skipped - NEW]
    P -->|True| R[publish_packaging_request - Return ACCEPTED]
    subgraph resolve[_resolve_auto_packaging closure]
        S[get_benchling_secrets] --> T[resolve_auto_packaging via TTL cache]
        T --> U{value in cache?}
        U -->|yes| V[return cached bool]
        U -->|no| W[return config.auto_packaging startup default]
        S -->|Exception| X[return True fail open]
    end
Loading

Reviews (1): Last reviewed commit: "feat(app-manifest): subscribe to configu..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

smkohnstamm and others added 2 commits July 3, 2026 10:40
Adds ["quilt", "settings", "auto_packaging"] to the Tier 1 App Config
namespace. When false, entry and canvas lifecycle events render/refresh
the canvas without enqueueing packaging work; scientists trigger
packaging via the canvas Update Package button. Defaults to true
(current behavior) when unset, and fails open if config lookup errors
so a Benchling API hiccup never drops packaging work.

Also fixes unknown canvas button interactions falling through to the
packaging path (previously enqueued unintended work).

Supersedes the Secrets-Manager-based auto_packaging from PR #393,
reimplemented on top of the three-tier routing system per the
platform-config-first direction discussed in #engineering.

Co-authored-by: Cursor <cursoragent@cursor.com>
Enables immediate config cache invalidation when an operator toggles a
["quilt"] App Config Item in the Benchling UI. Without this subscription
the configuration.updated lifecycle event is not delivered, so the
routing cache relies entirely on the TTL (60s) for invalidation.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread docker/src/app.py
Comment on lines +717 to +733
def _resolve_auto_packaging(payload: Payload) -> bool:
"""Resolve auto_packaging from Tier 1 App Config for this event.

Defaults to True (current behavior) when unset or when config lookup
fails, so a Benchling API hiccup never silently drops packaging work.
"""
assert benchling is not None and config is not None
try:
secrets = config.get_benchling_secrets()
return config.resolve_auto_packaging(benchling, secrets)
except Exception as exc:
logger.warning(
"Failed to resolve auto_packaging setting; defaulting to enabled",
error=str(exc),
error_type=type(exc).__name__,
)
return True

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 The payload parameter is declared but never read inside the function — all logic derives solely from config and benchling captured in the closure. Keeping a dead parameter is misleading: callers may assume the result is per-payload, and future authors may accidentally add per-payload logic while thinking the signature already supports it.

Suggested change
def _resolve_auto_packaging(payload: Payload) -> bool:
"""Resolve auto_packaging from Tier 1 App Config for this event.
Defaults to True (current behavior) when unset or when config lookup
fails, so a Benchling API hiccup never silently drops packaging work.
"""
assert benchling is not None and config is not None
try:
secrets = config.get_benchling_secrets()
return config.resolve_auto_packaging(benchling, secrets)
except Exception as exc:
logger.warning(
"Failed to resolve auto_packaging setting; defaulting to enabled",
error=str(exc),
error_type=type(exc).__name__,
)
return True
def _resolve_auto_packaging() -> bool:
"""Resolve auto_packaging from Tier 1 App Config for this event.
Defaults to True (current behavior) when unset or when config lookup
fails, so a Benchling API hiccup never silently drops packaging work.
"""
assert benchling is not None and config is not None
try:
secrets = config.get_benchling_secrets()
return config.resolve_auto_packaging(benchling, secrets)
except Exception as exc:
logger.warning(
"Failed to resolve auto_packaging setting; defaulting to enabled",
error=str(exc),
error_type=type(exc).__name__,
)
return True

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!

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