fix(workspace): replace asyncio.get_event_loop().run_until_complete with asyncio.run() (#307) #498

Merged
core-lead merged 8 commits from fix/307-async-rebase into main 2026-05-11 15:37:40 +00:00

8 Commits

Author SHA1 Message Date
fullstack-engineer 631d1bae5f fix(workspace): replace asyncio.get_event_loop().run_until_complete with asyncio.run() (#307)
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 6s
cascade-list-drift-gate / check (pull_request) Successful in 11s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 9s
Harness Replays / detect-changes (pull_request) Successful in 12s
sop-tier-check / tier-check (pull_request) Successful in 13s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 14s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 21s
E2E API Smoke Test / detect-changes (pull_request) Successful in 21s
CI / Detect changes (pull_request) Successful in 22s
Harness Replays / Harness Replays (pull_request) Successful in 7s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 24s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 26s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 8s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 6s
CI / Canvas (Next.js) (pull_request) Successful in 7s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 2m33s
audit-force-merge / audit (pull_request) Successful in 27s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Failing after 4m35s
E2E Staging External Runtime / E2E Staging External Runtime (pull_request) Successful in 5m17s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 5m38s
CI / Python Lint & Test (pull_request) Failing after 7m33s
CI / Platform (Go) (pull_request) Failing after 13m9s
test_a2a_tools_inbox_wrappers.py's _run() helper used
asyncio.get_event_loop().run_until_complete() to run coroutines from
sync test methods. When pytest-asyncio is active in OTHER test files in
the same suite, get_event_loop() can return the shared pytest-asyncio
loop, and run_until_complete() raises "loop already running" errors.

Fix: replace with asyncio.run(), which creates a fresh loop each call.

Result (full suite, 14→0 for inbox wrappers):
  Without fix: 10 failures (6 TestToolWaitForMessage + 4 delegation)
  With fix:    4 failures (all pre-existing delegation polling)

Closes #307.
2026-05-11 15:34:18 +00:00
core-be bad69295ca fix(workspace): auto-suffix duplicate names on POST /workspaces (closes 500 on double-click)
The Canvas template-deploy path returned HTTP 500 with raw pq error
when a user clicked a template card twice in quick succession. Root
cause: migration 20260506000000 added the partial-unique index
`workspaces_parent_name_uniq` on (COALESCE(parent_id, sentinel), name)
WHERE status != 'removed' to close TOCTOU on /org/import (#2872). The
org-import handler resolves the constraint via ON CONFLICT DO NOTHING
+ idempotent re-select. The Canvas Create handler did not — it
bubbled the pq violation as a generic 500.

Fix: auto-suffix the user-typed name on collision via a small retry
helper that pins on SQLSTATE 23505 + constraint name (so unrelated
unique indexes still fail loud), retries with " (2)", " (3)" up to
N=20, and threads the actually-persisted name back into the response
+ broadcast payload (so the canvas displays what the DB actually
holds). Exhaustion maps to a clean 409 Conflict instead of a 500.

#2872 protection is preserved unchanged — the index stays in place,
and /org/import's ON CONFLICT path is unaffected. The bundle-import
INSERT (handlers/bundle.go) is a separate code path and is not
touched here; if it surfaces the same UX issue a follow-up can adopt
the same helper.

Verification (against running localhost:8080 platform):

  Three back-to-back POSTs with name="ManualVerify-1778459812":
    POST #1 -> 201, id=db2dacf7-…, persisted name="ManualVerify-1778459812"
    POST #2 -> 201, id=f468083d-…, persisted name="ManualVerify-1778459812 (2)"
    POST #3 -> 201, id=5f5ae905-…, persisted name="ManualVerify-1778459812 (3)"
  Log lines: "name collision auto-suffix \"…\" -> \"… (N)\""

Tests:
- workspace_create_name_test.go — 4 unit tests via sqlmock pin the
  retry contract (happy path no-suffix, single-collision -> " (2)",
  non-retryable error pass-through, exhaustion -> errWorkspaceNameExhausted).
- workspace_create_name_integration_test.go — 2 real-Postgres tests
  (build tag `integration`) confirm the partial-unique index
  behaviour AND the WHERE status != 'removed' tombstone exemption.
- Watch-it-fail confirmed: temporarily removing the
  `fmt.Sprintf("%s (%d)", baseName, attempt+1)` candidate-naming
  line makes TestInsertWorkspaceWithNameRetry_SecondAttemptSuffixed
  fail with the expected argument-mismatch from sqlmock.

Pre-existing test failures in handlers/ (TestExecuteDelegation_…,
TestMCPHandler_CommitMemory_GlobalScope_Blocked) reproduce on
unmodified staging and are NOT caused by this change.
2026-05-11 15:34:18 +00:00
infra-runtime-be 2581db713e fix(workspace): OFFSEC-003 sanitize read_delegation_results()
Adds _sanitize_a2a.py (from PR #346) and integrates sanitize_a2a_result()
into read_delegation_results() so peer-supplied summary and response_preview
fields are escaped before being injected into the agent prompt.

Output is wrapped in [A2A_RESULT_FROM_PEER]...[/A2A_RESULT_FROM_PEER]
boundary markers so content after the block is clearly not from a peer.

Fixes:
- test_a2a_executor.py: correct mock patch path to executor_helpers
- test_executor_helpers.py: fix boundary-injection test assertion to match
  _strip_closed_blocks behaviour (closes marker, removes following text)

Follow-up to PR #346 (OFFSEC-003 boundary escape) which noted
"read_delegation_results() path still needs sanitization" as a gap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 15:33:52 +00:00
fullstack-engineer 0434f5e7fd fix(workspace): inject plugins_registry into sys.modules before loading adapters (closes #296)
Plugin adapters in molecule-skill-* repos do:
  from plugins_registry.builtins import AgentskillsAdaptor as Adaptor

But _load_module_from_path() used exec_module() with a fresh module
namespace that did NOT have plugins_registry or its submodules in sys.modules,
causing:
  ModuleNotFoundError: No module named 'plugins_registry'

Fix: before exec_module(), import and register plugins_registry + all three
submodules (builtins, protocol, raw_drop) in sys.modules so adapter imports
resolve correctly.  Follows the Option 1 recommendation from issue #296.

Also adds test_resolve_plugin.py verifying the fix for both the
AgentskillsAdaptor import and the full InstallContext/resolve/protocol import.

Closes #296.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 15:32:59 +00:00
fullstack-engineer 3b729a970a fix(a2a): handle string-form errors in delegate_task
The A2A proxy can return three error shapes:
  {"error": "plain string"}
  {"error": {"message": "...", "code": ...}}
  {"error": {"message": {"nested": "object"}}}   ← value at .message is a string

builtin_tools/a2a_tools.py:72 called data["error"].get("message")
without guarding against error being a string, which raised:
  AttributeError: 'str' object has no attribute 'get'

This broke every delegation attempt through the legacy a2a_tools path
(the LangChain-wrapped version used by adapter templates). The
SSOT parser a2a_response.py already handled string errors; the
legacy inline sniffer in a2a_tools.py did not.

Fix: branch on isinstance(err, dict/str/other) before calling .get().

Also update both publish-workflow files to remove the dead
`staging` branch trigger — trunk-based migration (PR #109,
2026-05-08) removed the staging branch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 15:32:59 +00:00
integration-tester 103b76b0a7 chore: restore manifest.json after trigger test 2026-05-11 15:32:33 +00:00
integration-tester 77e0bb719f chore: trigger publish workflow [Integration Tester 2026-05-10T08:45Z] 2026-05-11 15:32:33 +00:00
integration-tester c0fc11c562 chore: staging trigger commit from Integration Tester 2026-05-11 15:32:33 +00:00