Skip to content

ci: connector runtime parity smoke gate#1037

Merged
buremba merged 5 commits into
mainfrom
feat/parity-smoke
May 26, 2026
Merged

ci: connector runtime parity smoke gate#1037
buremba merged 5 commits into
mainfrom
feat/parity-smoke

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented May 25, 2026

Incident this prevents

The worker Docker image (docker/worker/Dockerfile) shipped to prod missing COPY packages/core, so @lobu/connector-sdk's transitive @lobu/core import dangled and every connector feed sync crashed with ENOENT reading .../connector-sdk/node_modules/@lobu/core. All CI was green because:

  • ci.yml tests the monorepo workspace, where @lobu/core resolves fine, and
  • build-images.yml builds + pushes images but never runs them.

Fixed by a one-line COPY in #1035. This PR adds a gate that prevents that whole class of packaging/parity drift by running the built artifact and asserting its resolution/compile/execute graph actually holds.

Design: one shared self-check, two entrypoints (the parity invariant)

A single shared function runConnectorRuntimeSelfCheck() lives in packages/connector-worker/src/self-check/. Both the worker image and the built CLI call the same function over the same compile + default SubprocessExecutor path. The only permitted difference between the two surfaces is the set of connector-source discovery roots (genuinely environment-specific: monorepo packages/connectors/src vs the worker image layout vs an npm-installed CLI's bundled dist/connectors).

  • packages/connector-worker/src/bin.ts gains a self-check subcommand (alongside daemon). Worker image runs bun src/bin.ts self-check --json.
  • The CLI gains a hidden lobu connector runtime-self-check subcommand that statically imports the same function via a new ./self-check export on @lobu/connector-worker.

What the self-check asserts (artifact/packaging facts ONLY)

  1. @lobu/connector-sdk resolves and imports.
  2. @lobu/core resolves and imports through the same runtime resolver — the exact dangling edge that broke prod.
  3. Every EXTERNAL_RUNTIME_DEPS package (playwright/patchright, sharp, jimp) is resolvable.
  4. The bundled connector source directory is present + discoverable.
  5. All discovered connector definitions instantiate, each has key/name/version, and keys are unique (31 connectors today).
  6. A synthetic no-op connector (an inline in-repo fixture, not a real bundled connector) compiles via the real compileConnectorFromFile path and executes through executeCompiledConnector using the default SubprocessExecutor (no custom executor) — proving compile + fork + child-runner + SDK-externalized runtime resolution all line up.

What it explicitly does NOT do

No network, DB, gateway, or OAuth. It asserts no event content and no checkpoint semantics, and re-runs no business logic. It passes under docker run --network=none.

CI wiring

PR gate (.github/workflows/ci.yml) — path-filtered job modeled on mac-build-smoke + its filter job (paths: docker/**, packages/{connector-worker,connector-sdk,connectors,core,embeddings,cli}/**, root package.json, bun.lock, and the two workflow files). The job builds the worker image linux/amd64 only (load: true, push: false, GHA buildx cache scope=worker), runs docker run --rm --network=none <img> bun src/bin.ts self-check --json, then runs the CLI self-check on the host (make build-packagesnode packages/cli/bin/lobu.js connector runtime-self-check --json).

Main/deploy gate (.github/workflows/build-images.yml) — the same amd64 image self-check as a connector-parity-smoke job that build-worker depends on. Since build-app already needs: build-worker, a failing smoke blocks both the worker and app pushes. This matters because the chart applies one shared tag to app + worker and Flux's ImageUpdateAutomation would otherwise roll the app to a tag whose worker is broken. amd64-only, and it shares the buildx cache scope with build-worker so only the arm64 leg is new work at push time.

Validation

  • make build-packages (Node 22) — green.
  • Host CLI self-check — PASS, exit 0:
connector runtime self-check (cli): PASS
  connector source: .../packages/connectors/src (31 connectors)
  ✓ resolve:@lobu/connector-sdk: ok
  ✓ import:@lobu/connector-sdk: ok
  ✓ resolve:@lobu/core: ok
  ✓ import:@lobu/core: ok
  ✓ resolve:playwright: ok
  ✓ resolve:sharp: ok
  ✓ resolve:jimp: ok
  ✓ connector-source-dir: ok
  ✓ connectors-instantiate: 31 connectors instantiated with key/name/version.
  ✓ connector-keys-unique: 31 unique keys.
  ✓ synthetic-connector-subprocess-execute: compiled + executed via default SubprocessExecutor; emitted >=1 event.
  • Worker bun src/bin.ts self-check — PASS, exit 0 (same 11 checks, surface: worker).
  • Failure-path proof: temporarily moving packages/core/dist aside reproduces the prod bug — the self-check exits 1 with import:@lobu/connector-sdk, resolve:@lobu/core, import:@lobu/core, connectors-instantiate, and synthetic-connector-subprocess-execute all failing on the dangling @lobu/core. Restored.
  • Per-package tsc --noEmit for connector-worker and cli — clean. Pre-commit biome + root tsc — clean.

Docker is not available on this host, so the image leg was not run locally. That leg is validated by the new CI job itself — good dogfooding, since this PR touches the gated paths and the gate will run on this very PR.

Summary by CodeRabbit

  • Chores

    • Added CI/build smoke gates that run a connector-runtime parity check before publishing worker images, blocking downstream app builds if the check fails.
    • Improved build cache reuse between smoke check and worker builds to speed repeated validations.
  • New Features

    • Added a hidden self-check diagnostic in both CLI and worker image that verifies runtime/package parity, compiles and exercises a sample connector, and outputs JSON or human-readable results.
    • Exported a self-check entrypoint for runtime use.
  • Documentation

    • Updated guidance to allow specific runtime dynamic imports used by the self-check.

Review Change Stack

Guards against the packaging/parity drift that broke prod in #1035: the
worker Docker image shipped missing `COPY packages/core`, so
@lobu/connector-sdk's transitive @lobu/core import dangled and every
connector feed sync crashed at runtime — yet all CI was green because the
workspace tests resolve @lobu/core and build-images.yml builds + pushes
images but never RUNS them.

Add a single SHARED runtime self-check
(packages/connector-worker/src/self-check) executed from BOTH the built
CLI runtime (`lobu connector runtime-self-check`, hidden) and the worker
image (`bun src/bin.ts self-check`). Both entrypoints call the SAME
runConnectorRuntimeSelfCheck() over the same compile + default
SubprocessExecutor path (the parity invariant; only the connector-source
discovery roots differ). It asserts artifact/packaging facts only —
@lobu/connector-sdk + @lobu/core resolve+import, EXTERNAL_RUNTIME_DEPS
(playwright/sharp/jimp) resolve, the bundled connector source dir is
discoverable, all discovered connectors instantiate with unique
key/name/version, and a synthetic no-op connector compiles + runs through
the default SubprocessExecutor. No network/DB/gateway/OAuth; passes under
docker run --network=none.

CI wiring:
- ci.yml PR gate (path-filtered like mac-build-smoke): build worker image
  amd64 (load, no push), run the image self-check with --network=none, and
  run the CLI self-check on the host.
- build-images.yml main/deploy gate: same amd64 image self-check as a job
  that build-worker (and transitively build-app) depends on, so a failing
  smoke blocks BOTH worker and app pushes — Flux can't roll the shared tag
  to a broken worker. Shares the buildx cache scope with build-worker.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a connector runtime parity self-check module, exposes it from connector-worker, integrates it into worker and CLI entrypoints, and gates build/CI workflows (selectively via a filter) by running an amd64 worker image self-check and a host-side CLI self-check.

Changes

Connector runtime parity validation

Layer / File(s) Summary
Self-check module: types and core implementation
packages/connector-worker/src/self-check/index.ts
Defines SelfCheckEntry, SelfCheckResult, SelfCheckOptions; implements temp-file staging, connector discovery (collectConnectorSourceFiles), runtime class detection (findConnectorRuntimeClass), compilation & dynamic import/instantiation (instantiateConnector), synthetic connector compile+execution (runSyntheticConnector), orchestration (runConnectorRuntimeSelfCheck), and printSelfCheckResult.
Worker and CLI integration & package export
packages/connector-worker/src/bin.ts, packages/cli/src/commands/connector.ts, packages/cli/src/index.ts, packages/connector-worker/package.json
Worker bin: adds self-check command handling and help/examples; CLI: adds connectorRuntimeSelfCheckCommand and wires hidden connector runtime-self-check subcommand; package.json: exports ./self-checkdist/self-check/index.js + types.
Build and CI workflow gates
.github/workflows/build-images.yml, .github/workflows/ci.yml
build-images.yml: adds connector-parity-smoke (amd64-only build + containerized bun ... self-check --json, network disabled), gates build-worker on the smoke job, and uses scope=worker for Buildx cache sharing. ci.yml: adds connector-parity-smoke-filter to decide when to run and connector-parity-smoke to run both containerized worker self-check and host CLI self-check.
Docs allow-list update
AGENTS.md
Expands the "No new dynamic imports" allow-list to include connector-worker dynamic imports and documents the runtime-loaded connector bundle and resolution probing.

Sequence Diagrams

sequenceDiagram
  participant WorkerBin as worker bin.ts
  participant CLI as cli index.ts
  participant ConnectorCmd as connector.ts
  participant SelfCheckMod as self-check module
  
  WorkerBin->>WorkerBin: Check argv for 'self-check'
  WorkerBin->>SelfCheckMod: runConnectorRuntimeSelfCheck({ surface: 'worker' })
  SelfCheckMod-->>WorkerBin: SelfCheckResult
  WorkerBin->>WorkerBin: Output JSON or formatted result
  
  CLI->>CLI: Parse 'connector runtime-self-check'
  CLI->>ConnectorCmd: connectorRuntimeSelfCheckCommand({ json })
  ConnectorCmd->>SelfCheckMod: runConnectorRuntimeSelfCheck({ surface: 'cli' })
  SelfCheckMod-->>ConnectorCmd: SelfCheckResult
  ConnectorCmd->>ConnectorCmd: Output JSON or formatted result
Loading
sequenceDiagram
  participant Event as Workflow Trigger
  participant Filter as Filter Job
  participant Smoke as Smoke Job
  participant BuildWorker as build-worker Job
  participant BuildApp as build-app Job
  
  Event->>Filter: Check for relevant path changes
  Filter-->>Smoke: run=true or run=false
  Smoke->>Smoke: If enabled: build worker image + self-check
  Smoke-->>BuildWorker: Parity check result
  BuildWorker->>BuildWorker: Waits for smoke job
  BuildWorker-->>BuildApp: Worker image ready
  BuildApp->>BuildApp: Build app image (depends on worker)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • lobu-ai/lobu#1035: Worker Dockerfile change that fixes missing/copied package files needed for the self-check's @lobu/core import to succeed inside the image.
  • lobu-ai/lobu#929: Changes affecting connector source discovery and connector-worker bin env wiring that interact with runtime resolution used by the self-check.

Poem

🐰 I hopped through builds with a careful check,
Compiled a stub, then gave it a peck.
I import, I run, I listen for signs,
Report PASS/FAIL in JSON lines —
Now parity sleeps, no surprises on deck.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ci: connector runtime parity smoke gate' directly and specifically summarizes the main change: adding a CI-gated runtime parity smoke test for connector/packaging integrity.
Description check ✅ Passed The description is comprehensive and well-structured, covering the incident context, design rationale, self-check assertions, CI wiring, and validation results. However, it does not follow the provided repository template (missing explicit checklist items for test plan validation).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/parity-smoke

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread .github/workflows/ci.yml
Comment on lines +546 to +581
runs-on: ubuntu-latest
outputs:
run: ${{ steps.filter.outputs.run }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Decide whether to run the connector parity smoke
id: filter
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
if [ -z "${BASE_SHA:-}" ]; then
echo "run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
changed=$(git diff --name-only "$BASE_SHA"...HEAD)
if echo "$changed" | grep -E '^(docker/|packages/connector-worker/|packages/connector-sdk/|packages/connectors/|packages/core/|packages/embeddings/|packages/cli/|package\.json$|bun\.lock$|\.github/workflows/ci\.yml$|\.github/workflows/build-images\.yml$)' >/dev/null; then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "::notice::No paths affecting the connector runtime changed — skipping connector-parity-smoke."
fi

# Connector-runtime parity smoke gate. Guards against the packaging/parity
# drift that broke prod: the worker image shipped missing `COPY packages/core`,
# so `@lobu/connector-sdk`'s transitive `@lobu/core` import dangled and every
# feed sync crashed at runtime — yet CI was green because the workspace tests
# resolve `@lobu/core` and build-images.yml builds+pushes images but never RUNS
# them. This job RUNS the built artifact's self-check on BOTH surfaces (the
# worker image and the host CLI), which both call the SAME
# runConnectorRuntimeSelfCheck() over the same compile + default
# SubprocessExecutor path.
connector-parity-smoke:
Comment thread .github/workflows/ci.yml
Comment on lines +582 to +640
needs: connector-parity-smoke-filter
if: needs.connector-parity-smoke-filter.outputs.run == 'true'
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4

# Submodule needed so `bun install` keeps packages/owletto in the lockfile
# graph (a bare install without it can prune the entry). The CLI self-check
# itself doesn't need the web UI; the worker image build below doesn't use
# the submodule at all.
- uses: ./.github/actions/setup-submodule
with:
deploy-key: ${{ secrets.OWLETTO_WEB_DEPLOY_KEY }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# Build the worker image for amd64 only (no QEMU/arm64 — this is a smoke
# gate, not a release) and load it into the local daemon instead of
# pushing. Reuse the GHA buildx cache shared with build-images.yml so this
# leg doesn't pay a cold worker build.
- name: Build worker image (amd64, load, no push)
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
file: ./docker/worker/Dockerfile
load: true
push: false
tags: lobu-worker:parity-smoke
cache-from: type=gha,scope=worker
cache-to: type=gha,mode=max,scope=worker

# The real prod runtime contract: run the built image's self-check with NO
# network, so a green result can't depend on reaching anything external.
- name: Worker image self-check (docker run --network=none)
run: |
docker run --rm --network=none lobu-worker:parity-smoke \
bun src/bin.ts self-check --json

# Host (CLI) leg: same self-check function, exercised from the built CLI
# runtime. This PR touches the gated paths, so this leg dogfoods the gate.
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5

- uses: actions/setup-node@v4
with:
node-version: "22"

- name: Install dependencies
run: bun install

- name: Build workspace packages (CLI + deps)
run: make build-packages

- name: CLI runtime self-check (host)
run: node packages/cli/bin/lobu.js connector runtime-self-check --json
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 25, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 64.28571% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/cli/src/index.ts 64.28% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build-images.yml:
- Around line 47-55: The workflow uses floating tags for third-party actions and
leaves checkout credentials persistent; update the three action references
(actions/checkout@v4, docker/setup-buildx-action@v3,
docker/build-push-action@v6) to fixed commit SHAs and add persist-credentials:
false under the actions/checkout step to disable credential persistence; ensure
you replace each `@v`* tag with the corresponding commit SHA from the action's
repo and keep the same inputs otherwise.

In @.github/workflows/ci.yml:
- Around line 545-586: Add explicit least-privilege GitHub Actions permissions
for the connector parity jobs: modify the connector-parity-smoke-filter and
connector-parity-smoke job definitions (and the other parity job instance
referenced in the comment) to include a permissions block granting at minimum
contents: read (and no broader token scopes). Place the permissions entry under
each job key so the jobs do not inherit default token permissions.
- Around line 550-552: The checkout steps use unpinned action refs and leave
credentials persisted; update each actions/checkout usage (currently "uses:
actions/checkout@v4" with "fetch-depth: 0") to SHA-pin the action ref (use the
specific commit SHA instead of `@v4`) and add "persist-credentials: false" under
the checkout step; apply the same changes to the other occurrences noted (lines
referencing the same checkout step at the other job locations) so all parity CI
jobs are SHA-pinned and do not persist credentials.

In `@packages/connector-worker/src/self-check/index.ts`:
- Around line 200-213: The discovery code currently only accepts .ts files in
the two places where extname(entry.name) and extname(s.name) are checked; update
those filters to also allow compiled connector outputs (e.g., .js, .mjs, and
optionally .cjs) while still excluding type declaration files and maps (e.g.,
.d.ts, .map). Modify the checks in the branches that push into paths (the ones
referencing entry.name and s.name, and the paths array populated after
readdir/resolve) so they accept the compiled extensions when scanning dist-style
directories produced by builds.
- Around line 261-264: The dynamic runtime import in withCwdTempFile (callsite:
await import(pathToFileURL(tmpFile).href) in
packages/connector-worker/src/self-check/index.ts) lacks a measured-cost
justification in CLAUDE.md; add an entry to CLAUDE.md that documents the
measured-cost rationale for this specific dynamic-import pattern (reference the
same pattern/matching rationale used by child-runner.ts), include evidence of
controlled input (tmpFile is produced by withCwdTempFile),
security/attack-surface assessment, performance cost measurements or estimates,
and an explicit statement allowing this dynamic import for the
self-check/runtime-compiled bundle scenario so reviewers can validate it against
the dynamic-import allow-list.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 49bddf4f-8bcf-4e77-ab8d-ed5f4a073ec1

📥 Commits

Reviewing files that changed from the base of the PR and between 3d9175c and ec3fe4c.

📒 Files selected for processing (7)
  • .github/workflows/build-images.yml
  • .github/workflows/ci.yml
  • packages/cli/src/commands/connector.ts
  • packages/cli/src/index.ts
  • packages/connector-worker/package.json
  • packages/connector-worker/src/bin.ts
  • packages/connector-worker/src/self-check/index.ts

Comment on lines +47 to +55
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build worker image (amd64, load, no push)
uses: docker/build-push-action@v6
with:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin newly added Actions and disable checkout credential persistence.

Line 48, Line 51, and Line 54 use tag refs (@v*) instead of commit SHAs, and Line 48 leaves persist-credentials at default. This weakens CI supply-chain posture in the new gate job.

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 47-48: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 48-48: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 51-51: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 54-54: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-images.yml around lines 47 - 55, The workflow uses
floating tags for third-party actions and leaves checkout credentials
persistent; update the three action references (actions/checkout@v4,
docker/setup-buildx-action@v3, docker/build-push-action@v6) to fixed commit SHAs
and add persist-credentials: false under the actions/checkout step to disable
credential persistence; ensure you replace each `@v`* tag with the corresponding
commit SHA from the action's repo and keep the same inputs otherwise.

Comment thread .github/workflows/ci.yml
Comment on lines +545 to +586
connector-parity-smoke-filter:
runs-on: ubuntu-latest
outputs:
run: ${{ steps.filter.outputs.run }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Decide whether to run the connector parity smoke
id: filter
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
if [ -z "${BASE_SHA:-}" ]; then
echo "run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
changed=$(git diff --name-only "$BASE_SHA"...HEAD)
if echo "$changed" | grep -E '^(docker/|packages/connector-worker/|packages/connector-sdk/|packages/connectors/|packages/core/|packages/embeddings/|packages/cli/|package\.json$|bun\.lock$|\.github/workflows/ci\.yml$|\.github/workflows/build-images\.yml$)' >/dev/null; then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "::notice::No paths affecting the connector runtime changed — skipping connector-parity-smoke."
fi

# Connector-runtime parity smoke gate. Guards against the packaging/parity
# drift that broke prod: the worker image shipped missing `COPY packages/core`,
# so `@lobu/connector-sdk`'s transitive `@lobu/core` import dangled and every
# feed sync crashed at runtime — yet CI was green because the workspace tests
# resolve `@lobu/core` and build-images.yml builds+pushes images but never RUNS
# them. This job RUNS the built artifact's self-check on BOTH surfaces (the
# worker image and the host CLI), which both call the SAME
# runConnectorRuntimeSelfCheck() over the same compile + default
# SubprocessExecutor path.
connector-parity-smoke:
needs: connector-parity-smoke-filter
if: needs.connector-parity-smoke-filter.outputs.run == 'true'
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Set explicit minimal permissions for the new parity jobs.

The new connector-parity-smoke-filter and connector-parity-smoke jobs inherit default token permissions. Please set explicit least-privilege permissions (at minimum contents: read) for these jobs.

Also applies to: 582-640

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 546-581: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}

🪛 zizmor (1.25.2)

[warning] 550-552: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[warning] 545-580: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[error] 550-550: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 545 - 586, Add explicit
least-privilege GitHub Actions permissions for the connector parity jobs: modify
the connector-parity-smoke-filter and connector-parity-smoke job definitions
(and the other parity job instance referenced in the comment) to include a
permissions block granting at minimum contents: read (and no broader token
scopes). Place the permissions entry under each job key so the jobs do not
inherit default token permissions.

Comment thread .github/workflows/ci.yml
Comment on lines +550 to +552
- uses: actions/checkout@v4
with:
fetch-depth: 0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Harden new CI job steps: SHA-pin actions and set persist-credentials: false on checkout.

The newly added parity jobs introduce unpinned third-party action refs and checkout steps with credential persistence enabled. Tightening both is a low-cost hardening win.

Also applies to: 587-587, 598-599, 605-606, 625-626, 629-630

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 550-552: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 550-550: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 550 - 552, The checkout steps use
unpinned action refs and leave credentials persisted; update each
actions/checkout usage (currently "uses: actions/checkout@v4" with "fetch-depth:
0") to SHA-pin the action ref (use the specific commit SHA instead of `@v4`) and
add "persist-credentials: false" under the checkout step; apply the same changes
to the other occurrences noted (lines referencing the same checkout step at the
other job locations) so all parity CI jobs are SHA-pinned and do not persist
credentials.

Comment on lines +200 to +213
if (extname(entry.name) !== '.ts' || entry.name.endsWith('.d.ts'))
continue;
paths.push(entryPath);
continue;
}
if (entry.isDirectory()) {
if (entry.name === '__tests__' || entry.name.startsWith('_')) continue;
try {
const sub = await readdir(entryPath, { withFileTypes: true });
for (const s of sub.sort((a, b) => a.name.localeCompare(b.name))) {
if (!s.isFile()) continue;
if (extname(s.name) !== '.ts' || s.name.endsWith('.d.ts')) continue;
paths.push(resolve(entryPath, s.name));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Connector discovery currently ignores built JS connectors.

Line 200 and Line 211 only accept .ts, but your candidate roots include dist-style directories. In packaged/built layouts, connector files are typically .js/.mjs, so this can incorrectly fail connectors-instantiate.

Suggested fix
-      if (extname(entry.name) !== '.ts' || entry.name.endsWith('.d.ts'))
+      const topExt = extname(entry.name);
+      if (!['.ts', '.js', '.mjs', '.cjs'].includes(topExt) || entry.name.endsWith('.d.ts'))
         continue;
@@
-          if (extname(s.name) !== '.ts' || s.name.endsWith('.d.ts')) continue;
+          const subExt = extname(s.name);
+          if (!['.ts', '.js', '.mjs', '.cjs'].includes(subExt) || s.name.endsWith('.d.ts')) continue;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (extname(entry.name) !== '.ts' || entry.name.endsWith('.d.ts'))
continue;
paths.push(entryPath);
continue;
}
if (entry.isDirectory()) {
if (entry.name === '__tests__' || entry.name.startsWith('_')) continue;
try {
const sub = await readdir(entryPath, { withFileTypes: true });
for (const s of sub.sort((a, b) => a.name.localeCompare(b.name))) {
if (!s.isFile()) continue;
if (extname(s.name) !== '.ts' || s.name.endsWith('.d.ts')) continue;
paths.push(resolve(entryPath, s.name));
}
const topExt = extname(entry.name);
if (!['.ts', '.js', '.mjs', '.cjs'].includes(topExt) || entry.name.endsWith('.d.ts'))
continue;
paths.push(entryPath);
continue;
}
if (entry.isDirectory()) {
if (entry.name === '__tests__' || entry.name.startsWith('_')) continue;
try {
const sub = await readdir(entryPath, { withFileTypes: true });
for (const s of sub.sort((a, b) => a.name.localeCompare(b.name))) {
if (!s.isFile()) continue;
const subExt = extname(s.name);
if (!['.ts', '.js', '.mjs', '.cjs'].includes(subExt) || s.name.endsWith('.d.ts')) continue;
paths.push(resolve(entryPath, s.name));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/connector-worker/src/self-check/index.ts` around lines 200 - 213,
The discovery code currently only accepts .ts files in the two places where
extname(entry.name) and extname(s.name) are checked; update those filters to
also allow compiled connector outputs (e.g., .js, .mjs, and optionally .cjs)
while still excluding type declaration files and maps (e.g., .d.ts, .map).
Modify the checks in the branches that push into paths (the ones referencing
entry.name and s.name, and the paths array populated after readdir/resolve) so
they accept the compiled extensions when scanning dist-style directories
produced by builds.

Comment thread packages/connector-worker/src/self-check/index.ts Outdated
connector-worker has no direct @lobu/core dependency — it's consumed
transitively by @lobu/connector-sdk (logger/retry). The self-check
resolved @lobu/core from connector-worker's own location, which resolves
in the hoisted dev workspace but fails in the isolated-linker worker
image: a false negative (caught by the gate's own docker leg on #1037).

Resolve/import @lobu/core anchored at @lobu/connector-sdk (its real
consumer), reproducing the exact prod edge that dangled in #1035:
.../connector-sdk/node_modules/@lobu/core.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/connector-worker/src/self-check/index.ts`:
- Around line 48-50: There is a duplicate import of pathToFileURL from
'node:url' causing a module/typecheck error; update the import statements in
packages/connector-worker/src/self-check/index.ts by removing the redundant
import and consolidating the node:url imports so that fileURLToPath and
pathToFileURL are imported together (keeping extname, join, resolve imports from
'node:path' unchanged), i.e., ensure only one import line brings in
pathToFileURL and fileURLToPath.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c9a9d0fc-24c2-4294-9db2-968596c903a2

📥 Commits

Reviewing files that changed from the base of the PR and between ec3fe4c and 0b8e9c1.

📒 Files selected for processing (1)
  • packages/connector-worker/src/self-check/index.ts

Comment thread packages/connector-worker/src/self-check/index.ts Outdated
buremba added 2 commits May 25, 2026 03:01
The previous commit added a pathToFileURL import that already existed
(line 50), tripping TS2300 in make build-packages. pathToFileURL is
already imported; remove the duplicate.
Collapse the four hand-written async try/catch IIFEs into one async check()
helper (records returned detail or error); fold connector discovery and
key-uniqueness into the same pattern; trim duplicated narration while keeping
the non-obvious rationale (SDK-anchored core resolution, cwd temp file,
dynamic-import probes). No behavior change: 534 -> 413 lines, all 11 checks
still pass and the dangling-@lobu/core failure path still exits 1.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/connector-worker/src/self-check/index.ts`:
- Around line 167-175: The current try/catch around readdir in the connector
scan swallows errors so unreadable connector subdirectories (readdir failures)
are silently skipped; update the block in the self-check scan that calls
readdir(entryPath, { withFileTypes: true }) so that any error is propagated
(throw) or logged and causes the scan to fail instead of being ignored: remove
the empty catch, or rethrow the caught error, and ensure the caller of this loop
(or the function performing the scan) handles the thrown error so that
unreadable directories are reported as a failure of the connector discovery
check; keep using isConnectorFile, resolve and paths as-is when iterating
successful directory entries.
- Around line 297-340: The self-check currently resolves EXTERNAL_RUNTIME_DEPS
using createRequire(import.meta.url) (see require_ and the loop over
EXTERNAL_RUNTIME_DEPS) which anchors resolution to the self-check module
location; instead resolve externals the same way the child process will by
creating a require anchored at the cwd-staged tmp module used by
executor/child-runner.ts (i.e., build a staged module URL/path using
process.cwd() like the child-runner does and call
createRequire(stagedModuleUrl).resolve(dep) for each dep) so the check uses the
same resolution root as the forked importer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e005fe9-6434-4467-9cd1-80479ead4693

📥 Commits

Reviewing files that changed from the base of the PR and between dee11f9 and 3671999.

📒 Files selected for processing (1)
  • packages/connector-worker/src/self-check/index.ts

Comment thread packages/connector-worker/src/self-check/index.ts Outdated
Comment thread packages/connector-worker/src/self-check/index.ts
…e connector subdir

Address CodeRabbit review:
- Resolve EXTERNAL_RUNTIME_DEPS from a cwd-rooted require, matching how
  child-runner stages and resolves the compiled bundle, so the probe fails
  where a real connector would instead of off connector-worker's hoisted tree.
- Propagate readdir errors on connector subdirs (an unreadable subdir is a
  packaging defect for a parity gate) instead of silently skipping.
- Document the self-check + child-runner runtime-compiled-bundle imports in the
  AGENTS.md dynamic-import allow-list.
- Add least-privilege contents:read to the connector-parity-smoke job.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/connector-worker/src/self-check/index.ts (1)

154-177: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Discover compiled connector artifacts too.

firstExistingDir() can select .../connectors/dist or a packaged connectors directory, but isConnectorFile() still only accepts .ts. In those layouts the scan will miss .js/.mjs/.cjs connectors and fail connectors-instantiate even when the bundled artifacts are present.

Suggested fix
 async function collectConnectorSourceFiles(dirPath: string): Promise<string[]> {
 	const paths: string[] = [];
 	const isConnectorFile = (name: string) =>
-		extname(name) === ".ts" && !name.endsWith(".d.ts");
+		[".ts", ".js", ".mjs", ".cjs"].includes(extname(name)) &&
+		!name.endsWith(".d.ts") &&
+		!name.endsWith(".map");
 	for (const entry of await readdir(dirPath, { withFileTypes: true })) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/connector-worker/src/self-check/index.ts` around lines 154 - 177,
collectConnectorSourceFiles currently only treats files with extname ".ts" as
connector files (isConnectorFile), so when firstExistingDir selects a compiled
layout (e.g., connectors/dist or packaged connectors) .js/.mjs/.cjs artifacts
are ignored and the scan misses bundled connectors; update isConnectorFile
inside collectConnectorSourceFiles to accept compiled extensions (e.g., ".ts",
".js", ".mjs", ".cjs") while still excluding TypeScript declaration files (names
ending with ".d.ts"), so both the top-level file checks and the nested readdir
loop use the new predicate and return the resolved paths as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/connector-worker/src/self-check/index.ts`:
- Around line 154-177: collectConnectorSourceFiles currently only treats files
with extname ".ts" as connector files (isConnectorFile), so when
firstExistingDir selects a compiled layout (e.g., connectors/dist or packaged
connectors) .js/.mjs/.cjs artifacts are ignored and the scan misses bundled
connectors; update isConnectorFile inside collectConnectorSourceFiles to accept
compiled extensions (e.g., ".ts", ".js", ".mjs", ".cjs") while still excluding
TypeScript declaration files (names ending with ".d.ts"), so both the top-level
file checks and the nested readdir loop use the new predicate and return the
resolved paths as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 113b286c-7507-439a-9993-dbff4541154b

📥 Commits

Reviewing files that changed from the base of the PR and between 3671999 and ea2afdf.

📒 Files selected for processing (3)
  • .github/workflows/build-images.yml
  • AGENTS.md
  • packages/connector-worker/src/self-check/index.ts
✅ Files skipped from review due to trivial changes (1)
  • AGENTS.md

@buremba
Copy link
Copy Markdown
Member Author

buremba commented May 26, 2026

Addressed the CodeRabbit review in ea2afdf.

Fixed (valid findings):

  • External-dep resolution rootEXTERNAL_RUNTIME_DEPS now resolve through a require rooted at a cwd module path, matching how child-runner stages and resolves the compiled bundle (cwd node_modules), instead of createRequire(import.meta.url) anchored at connector-worker. In a hoisted monorepo these coincide, but in the worker image / npm layout they can diverge — so the probe now fails exactly where a real connector would.
  • Swallowed readdir error — an unreadable connector subdir is a packaging defect for a parity gate, so the error now propagates (recorded as a failed connectors-instantiate check) instead of being silently skipped.
  • Dynamic-import allow-list — documented self-check/index.ts + executor/child-runner.ts in the AGENTS.md allow-list (per the repo rule). Both load a freshly-compiled bundle from a runtime-computed temp path and probe runtime resolution — no static equivalent.

Intentionally skipped (with reasoning):

  • Accept .js/.mjs in connector discoverypackages/connectors ships src only ("./*": "./src/*.ts"); connectors are distributed as TypeScript and compiled at runtime, never as .js. The .ts-only filter is correct; accepting compiled extensions would be dead code.
  • SHA-pin actions / persist-credentials: false — the repo uses floating @vN tags across all workflows (29× checkout@v4) and sets neither anywhere; pinning only these two new jobs would be inconsistent noise.
  • Per-job permissions: in ci.yml — no job in ci.yml sets per-job permissions. I did add least-privilege contents: read to the connector-parity-smoke job in build-images.yml, since that workflow already uses per-job permission blocks (and the job pushes nothing).

Validation (Node 22): per-package tsc --noEmit clean for connector-worker + cli; make build-packages green; host CLI self-check PASS (exit 0); failure-path reproduced (hide @lobu/core/dist → FAIL on the dangling-@lobu/core edge → restore → PASS). CI: connector-parity-smoke green (real worker image + host CLI), and the earlier unit red was a flaky cancellation — green this run.

@buremba
Copy link
Copy Markdown
Member Author

buremba commented May 26, 2026

bug_free 88, simplicity 84, slop 0, bugs 0, 0 blockers

Typecheck/unit passed. [env] Integration failed because DATABASE_URL points at database "postgres" and the harness refused destructive setup; failing paths are untouched server tests. Explored CLI self-check and worker self-check from package cwd; both returned ok with 31 connectors.

Full verdict JSON
{
  "bug_free_confidence": 88,
  "bugs": 0,
  "slop": 0,
  "simplicity": 84,
  "blockers": [],
  "change_type": "test",
  "behavior_change_risk": "low",
  "tests_adequate": true,
  "suggested_fixes": [],
  "notes": "Typecheck/unit passed. [env] Integration failed because DATABASE_URL points at database \"postgres\" and the harness refused destructive setup; failing paths are untouched server tests. Explored CLI self-check and worker self-check from package cwd; both returned ok with 31 connectors.",
  "categories": {
    "src": 487,
    "tests": 0,
    "docs": 2,
    "config": 0,
    "deps": 4,
    "migrations": 0,
    "ci": 155,
    "generated": 0
  }
}

Local review gate — branch protection can require the pi-review commit status. See docs/REVIEW_SCHEMA.md.

@buremba buremba enabled auto-merge (squash) May 26, 2026 02:51
@buremba buremba merged commit e1fb354 into main May 26, 2026
23 checks passed
@buremba buremba deleted the feat/parity-smoke branch May 26, 2026 03:12
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.

3 participants