Skip to content

chore: recover PR #5 follow-up commits (E2E + shellcheck + CI) - #7

Merged
HongmingWang-Rabbit merged 6 commits into
mainfrom
chore/recover-pass2-tail
Apr 14, 2026
Merged

chore: recover PR #5 follow-up commits (E2E + shellcheck + CI)#7
HongmingWang-Rabbit merged 6 commits into
mainfrom
chore/recover-pass2-tail

Conversation

@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor

Context

PR #5 had 9 commits but only the first landed on main (GitHub squash-merge default). Six follow-up commits — E2E auth fixes, 100% helper test coverage, shared lib, shellcheck CI, docker-exec migration check — never made it.

This PR recovers them as a cherry-picked stack on top of main.

Contents

Commit Summary
208235b 100% coverage of PR #5 extracted Go helpers + ConfirmDialog singleButton test
27829a6 fix test_api.sh for Phase 30.1 bearer tokens + Phase 30.6 X-Workspace-ID
f9803ec comprehensive + activity E2Es + shared _lib.sh + CI smoke job
3130fe0 dead-code cleanup, lib polish, CI hardening (dev:dev creds, Go cache, migration assert)
c84b999 round-6 suggestions (Python-in-bash → file, docker exec, shellcheck CI job)
30b30b6 round-7 nits (narrowed exception, marketplace shellcheck action)

Test plan

  • `bash tests/e2e/test_api.sh` → 62/62 before this PR's CI runs
  • `bash tests/e2e/test_comprehensive_e2e.sh` → 67/67
  • `shellcheck tests/e2e/*.sh` → clean
  • Go race tests, 357 vitest, 96 jest all pass

🤖 Generated with Claude Code

Hongming Wang and others added 6 commits April 13, 2026 17:08
Follow-up to the quality-fixes-pass2 code review.

## Go: direct unit tests for PR #5 extracted helpers (~47 new tests)

a2a_proxy_test.go:
- resolveAgentURL: cache hit, cache-miss DB hit, not-found, null-URL,
  docker-rewrite guard
- dispatchA2A: build error, canvas timeout, agent timeout, success
- handleA2ADispatchError: context deadline, generic error, build error
- maybeMarkContainerDead: nil-provisioner, runtime=external short-circuits
- logA2AFailure, logA2ASuccess: activity_logs row content + status

delegation_test.go:
- bindDelegateRequest: valid / malformed / bad-UUID
- lookupIdempotentDelegation: no-key / no-match / failed-row-deleted / existing-pending
- insertDelegationRow: insertOK / insertHandledByIdempotent /
  insertTrackingUnavailable
- insertDelegationOutcome: zero-value is insertOutcomeUnknown sentinel

discovery_test.go:
- discoverWorkspacePeer: online / not-found / access-denied + 2 edges
- writeExternalWorkspaceURL: 3 cases
- discoverHostPeer: smoke test documents the unreachable-by-design path

activity_test.go:
- parseSessionSearchParams: defaults + custom limit/offset/q
- buildSessionSearchQuery: no-filters + with-query shapes
- scanSessionSearchRows: empty / single / multiple rows

Package coverage: 56.1% → 57.6%. Every helper extracted in PR #5 is
now at or near 100% line coverage (see PR notes for the 4 remaining
gaps, all blocked on provisioner interface mockability).

## Defensive enum zero-value fix

insertDelegationOutcome now starts with insertOutcomeUnknown=0 as a
sentinel so an un-initialized variable can't silently read as
"success". insertOK, insertHandledByIdempotent, insertTrackingUnavailable
shift to 1/2/3. No caller changes needed.

## Canvas: ConfirmDialog.singleButton test (5 cases)

canvas/src/components/__tests__/ConfirmDialog.test.tsx covers:
- default render (both buttons)
- singleButton hides Cancel
- singleButton: Escape still fires onCancel
- singleButton: backdrop-click still fires onCancel
- singleButton: onConfirm fires on click

vitest total: 352 → 357, all passing.

## Docstring clarity

ConfirmDialog.tsx: expanded singleButton prop comment to explicitly
instruct callers to pass the same handler for onConfirm/onCancel when
using it as an info toast (matches TemplatePalette usage).

## ErrorBoundary clipboard observability

.catch(() => {}) silently swallowed rejections. Now:
.catch((e) => console.warn("clipboard write failed:", e))
so permission-denied / insecure-context failures surface in the console.

## Verification

- go build ./... clean
- go vet ./... clean
- go test -race ./internal/... — all pass
- canvas npm run build — clean
- canvas npm test -- --run — 357/357 pass
- tests/e2e/test_api.sh — 46/62 pass; all 16 failures are pre-existing
  (token-auth enforcement + stale test workspaces + missing Docker
  network). None involve handlers touched in PR #5.
- Manual: platform + canvas running locally, title=Molecule AI,
  /workspaces returns [], /health returns ok. Identified + killed a
  stale Next.js server from the old Starfire-AgentTeam repo that was
  serving the old brand on IPv4 port 3000.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The script was stuck on pre-auth API expectations and hadn't been
updated when /registry heartbeat and /registry/discover tightened:

- Phase 30.1 (/registry/heartbeat, /registry/update-card): require
  Authorization: Bearer <token>. The token is returned in the register
  response as auth_token.
- Phase 30.6 (/registry/discover/:id, /registry/:id/peers): require
  X-Workspace-ID caller identity + bearer token on the caller.

Changes:
- Capture ECHO_TOKEN and SUM_TOKEN from /registry/register responses
- Thread Authorization: Bearer on every heartbeat + update-card call
- Assert the new 400 "X-Workspace-ID header is required" rejection for
  the no-caller discover path (previously asserted old success shape)
- Add bearer auth to sibling discover + /peers calls
- Pre-test cleanup: delete all workspaces at script start so count
  assertions are reproducible across back-to-back runs

Result: 62 passed, 0 failed (was 46/62).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Follow-up to the test_api.sh fix. Same Phase 30.1 + 30.6 staleness
existed in the other E2E scripts; same pattern applied.

## New tests/e2e/_lib.sh
Shared bash helpers so future scripts don't reimplement:
- e2e_extract_token — parse auth_token from register response
- e2e_register       — register + echo token
- e2e_heartbeat      — heartbeat with bearer auth
- e2e_cleanup_all_workspaces — pre-test state reset

## test_comprehensive_e2e.sh (14 fail -> 0 fail)
Root cause was deeper than test_api.sh: the script creates workspaces
at Section 2 but doesn't register them until Section 3. In between,
the platform provisioner spawns the Docker container, whose main.py
calls /registry/register first and claims the single-issue token.
The script's later register gets no auth_token back.

Fix: register each workspace immediately after POST /workspaces,
beating the container to the token. Empirically 5/5 wins in a tight
loop. PM/Dev/QA tokens captured at creation time; bearer auth threaded
through all heartbeat/update-card/discover/peers calls.

Removed the duplicate register calls in Section 3/4 that followed
(tokens already captured).

Result: 53/68 -> 67/67 (one duplicate check dropped).

## test_activity_e2e.sh
Same pattern applied on faith. Script still SKIPs cleanly when no
online agent is present; when an agent IS online, it now re-registers
it to mint a fresh bearer token and threads Authorization: Bearer on
the 3 heartbeat calls.

## test_api.sh refactor
Now sources _lib.sh and uses the shared helpers. No behavior change,
still 62/62.

## .github/workflows/ci.yml — new e2e-api job
Spins up Postgres 16 + Redis 7 as GitHub Actions services, builds the
platform binary, runs it in background with DATABASE_URL/REDIS_URL,
polls /health for 30s, then runs tests/e2e/test_api.sh. On failure
dumps platform.log for triage. 10-min job timeout.

This is the watchdog that would have caught Phase 30.1 auth drift
the day it landed. Picks test_api.sh not test_comprehensive_e2e.sh
because the latter depends on Docker-in-Docker for container
provisioning which is heavier than a PR gate should carry.

## Verification
- bash tests/e2e/test_api.sh                -> 62/62
- bash tests/e2e/test_comprehensive_e2e.sh  -> 67/67
- bash tests/e2e/test_activity_e2e.sh       -> cleanly SKIPs (no agent)
- go build ./...                            -> clean
- .github/workflows/ci.yml                  -> valid YAML, new job added

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Last sweep of code-review items before merging PR #5.

## _lib.sh cleanup

- Removed unused e2e_register and e2e_heartbeat helpers (dead code —
  no caller ever invoked them)
- Standardized on $BASE variable set via : "${BASE:=...}" so every
  script uses one name (was mixed $BASE / $e2e_base)
- e2e_extract_token now writes stderr warnings on JSON parse failure
  or missing auth_token, instead of silently returning empty. Previous
  behavior made downstream "missing workspace auth token" 401s much
  harder to diagnose

## Script cleanup

- test_api.sh, test_comprehensive_e2e.sh, test_activity_e2e.sh all
  drop the redundant `e2e_base + BASE="$e2e_base"` aliasing; sourcing
  _lib.sh sets BASE via : "${BASE:=...}" default

## CI hardening (.github/workflows/ci.yml)

- Postgres credentials now match .env.example (dev:dev — was
  molecule:molecule, caused confusion for local repros)
- Added Go module cache via actions/setup-go cache:true +
  cache-dependency-path: platform/go.sum. ~30s cold-run improvement
- New pre-E2E step asserts migrations actually ran by checking for
  the 'workspaces' table. Catches future migration-author mistakes
  before they surface as obscure E2E failures

## Follow-up issue

Filed #6 for the deterministic token-
mint admin endpoint. PR #5 uses an empirical "beat the container"
race (5/5 wins in benchmarks); issue #6 tracks the real fix for
any future CI load that invalidates the assumption.

## Verification

- bash tests/e2e/test_api.sh              -> 62/62
- bash tests/e2e/test_comprehensive_e2e.sh -> 67/67
- python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))" -> ok

## Operational note

Hourly PR-triage + issue-pickup cron scheduled this session (job id
0328bc8f, fires at :17 past each hour). Runtime reports it as
session-only despite durable:true — re-invoke via /loop or
CronCreate in a fresh session if needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All 5 suggestions from the latest review pass.

## tests/e2e/_extract_token.py (new)
Extracted the 14-line python-in-bash heredoc from _lib.sh into a real
Python file. Easier to edit, fewer escaping traps, same behavior.
Shell helper now just shells out to it.

## tests/e2e/_lib.sh
- Replaced inline python with: python3 "$(dirname "${BASH_SOURCE[0]}")/_extract_token.py"
- Removed redundant sys.exit(0) as part of the extraction

## Shellcheck-clean scripts (new CI job enforces)
- Removed dead captures: BEFORE_COUNT (test_activity_e2e.sh), ORIG_SKILLS,
  REIMPORT_SKILLS (test_api.sh), QA_TOKEN (test_comprehensive_e2e.sh)
- Renamed unused loop vars `i`, `j` -> `_` in 4 sites
- Added `# shellcheck disable=SC2046` on the two intentional word-splits
  in test_claude_code_e2e.sh (docker stop/rm of multiple container IDs)
- Removed a useless re-register of QA mid-script (was done in Section 2)

## CI (.github/workflows/ci.yml)
- Replaced `sudo apt-get install postgresql-client` + psql with a direct
  `docker exec` into the existing postgres:16 service container. Saves
  ~10-20s per CI run.
- Added new `shellcheck` job that lints tests/e2e/*.sh on every PR.
  Local: shellcheck --severity=warning returns 0 across all 5 scripts.

## Verification
- go test -race ./internal/handlers/... : pass
- mcp-server: 96/96 jest
- canvas: 357/357 vitest + clean build
- tests/e2e/test_api.sh: 62/62
- tests/e2e/test_comprehensive_e2e.sh: 67/67
- shellcheck tests/e2e/*.sh : clean
- CI YAML: valid

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- _extract_token.py: narrow `except Exception` to
  `except (json.JSONDecodeError, ValueError)`. Prevents swallowing
  KeyboardInterrupt in edge cases and documents intent clearly.
- ci.yml shellcheck job: switch to ludeeus/action-shellcheck@master
  (caches shellcheck binary across runs; saves the apt-get install).

Both changes verified locally: YAML parses, extract script still
extracts valid tokens and prints the stderr warning on malformed JSON.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@HongmingWang-Rabbit
HongmingWang-Rabbit merged commit cd3cf3c into main Apr 14, 2026
5 of 6 checks passed
@HongmingWang-Rabbit
HongmingWang-Rabbit deleted the chore/recover-pass2-tail branch April 14, 2026 00:11
molecule-ai Bot pushed a commit that referenced this pull request Apr 21, 2026
chore: recover PR #5 follow-up commits (E2E + shellcheck + CI)
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
Addresses both review subagents' REQUEST_CHANGES verdicts on
PR #1929:

Code review (correctness)
- #1: Move schedule seeding to AFTER provisionWorkspaceAuto
  succeeds so the scheduler never fires cron rows against a
  workspace whose backend never wired. Failed-backend workspaces
  no longer end up with orphan template_schedules rows.
- #2: seedTemplateSchedules now returns (seeded, skipped int) so
  the caller can observe partial-seed states; workspace.go Create
  logs the (seeded, skipped) pair when skipped > 0, surfacing
  silent partial-loss that the prior (int) return masked.

Security review (hostile-template defenses)
- #3 / #4: parseTemplateSchedules reads config.yaml through an
  io.LimitReader bounded by maxTemplateConfigYAMLBytes (1 MiB)
  and rejects files over the cap before yaml.Unmarshal runs.
  Defends against billion-laughs / anchor-explosion DoS.
- #3: schedules slice length capped at maxTemplateSchedules (100,
  10x the largest current production grid). Hostile template with
  50k schedules now rejected at parse time, not after 50k inserts.
- #3: cron_expr length capped at maxScheduleCronExprLen (128) per
  schedule; resolved prompt body capped at maxSchedulePromptBytes
  (16 KiB) per schedule. Oversized entries are skipped (counted
  as `skipped`) so one bad row doesn't break the rest.
- #3: Seed loop honours ctx.Err() so an aborted Create request
  stops further inserts rather than running to completion on a
  dead goroutine.
- #8: Schedule names quoted via %q in all log lines so CRLF in a
  hostile name can't injection-pollute stdout/Loki.

Tests
- TestParseTemplateSchedules_RejectsOversizeFile — gate against
  the LimitReader cap (1 MiB + 1 byte of '#').
- TestParseTemplateSchedules_RejectsTooManySchedules — gate
  against the schedule-count cap (maxTemplateSchedules + 1
  minimal entries).
- Full handlers test suite still green (17.4s).

Non-fix surface
- Code-review #3 (runtime-default fallback also seeds): runtime-
  default templates do not currently ship a schedules: block so
  this is benign in practice; documented behavior in the comment.
- Code-review #4 (files_dir in workspace-template config.yaml):
  not part of the current template_registry schema; flagged for
  follow-up if templates start declaring files_dir.
- Security-review #7 (cron prompt as agent self-message escalation
  vector): out of scope per security reviewer's own note; tracked
  separately. Will file an issue.

Verified locally:
  go vet ./...                 → clean
  go build ./...               → clean
  gofmt -d <changed files>     → clean
  go test ./internal/handlers/ → PASS (7 unit tests for parser,
                                  full suite 17.4s)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant