Repository navigation
e2e: keep the sidecar session open at pin 692c3106 (CL-7472) - #663
Merged
TheGreatAxios merged 20 commits intoSep 6, 2026
Conversation
The native workflow deploy route now requires sourceOfferingIds/ defaultSourceOfferingId resolved against the tenant's own catalog, rather than a raw provider/baseURL/apiKey source chain. Update workflowDeployBody accordingly, and extract the zero-cost noop catalog chain smoke-webhook.test.ts already built (an anthropic- plugin provider pointed at the hub's noop-inference endpoint) into a shared seedNoopCatalogOffering harness helper. Every deploying e2e suite now seeds or resolves a real catalog offering before deploying: walking-skeleton keeps a never-real placeholder host (assertNeverRealProvider still guards it), chat resolves the offering seedCatalog already planted rather than seeding a second chain, and heartbeat/workbench-digest/smoke-webhook seed the noop chain via the shared helper. cl-6324-launch-proof's section-mode deploy now reuses the offering id the proof already resolved when it narrowed the bench catalog to its pinned model.
seedTenant currently deploys every default workflow with a bare provider/baseURL/apiKey source, but the hub's deploy route now requires sourceOfferingIds/defaultSourceOfferingId resolved from the tenant's catalog. Update the seeding, onboarding, and hub config test suites to expect the new offering-id based deploy body and provider/model-only ModelSource shape ahead of the implementation change.
ensureDeployment posted a bare {provider, baseURL, apiKey, model}
source, but the hub's deploy route requires sourceOfferingIds and a
defaultSourceOfferingId resolved from the tenant's catalog, so seeding
a connect flow's "assistant" workflow 400'd. seedTenant now resolves
the tenant's real catalog offerings (sorted by priority, lowest wins
the default) for a normal workflow, and plants a dedicated noop
catalog offering for a modelSource-pinned one (heartbeat), pointed at
the hub's own noop-inference endpoint. ModelSource shrinks to a bare
provider/model pair — the baseURL/apiKey it used to carry only mattered
for the old deploy body, and callers that still need a key for seeding
a catalog (system-seed's root tenant, config's env-key auto-plant) keep
it on their own local type instead.
Another workflowDeployBody caller CI's typecheck job caught: this stack seeds one catalog offering per real inference target already, so track each one's id and pass it through to the deploy body instead of the raw provider/baseURL/apiKey shape.
…' into cl-7471-generation-zero
The allocation service's initial sidecar_allocation and workflow_probe rows start at generation 0 (@intx/db enforces generation >= 0, not > 0), so the first ensure() for a freshly allocated sidecar must be accepted at generation 0. These tests cover that acceptance, later generations, non-integer/negative rejection, and that stale-generation fencing still rejects an older generation once a newer one has been observed.
@intx/db's sidecar_allocation and workflow_probe tables both check generation >= 0 and the allocation service records the first row for an allocation at generation 0, but validateEnsureRequest() and validateDestroyRequest() in packages/sandbox-sidecar rejected anything that wasn't strictly positive, and the on-disk fencing schema in state-store.ts required generation > 0. The first ensure() for any newly allocated sidecar was rejected with 'generation must be a positive integer', failing every deploy. Accept generation >= 0 in both validators and the state-store schema; stale-generation fencing in observeEnsure()/observeDestroy() already compares against the stored generation rather than assuming positivity, so it is unaffected.
…t-against-interchange-main-before-the' into cl-7461-local
… signature Covers the new GET .../catalog/resolved-offerings route (denied grant, and both directly-owned and ancestor-inherited offerings sorted by priority) and updates seeding/onboarding test fixtures to the route's response shape ahead of the implementation change.
…owned ones resolveRealSourceOfferingIds listed only offerings a tenant owns directly, missing anything inherited from an ancestor tenant — the hub's own workflow deployer already resolves that wider, ancestor-inheriting view via listVisibleOfferings. Adds GET /api/tenants/:tenantId/catalog/resolved-offerings (mounted from apps/hub, implemented in @corbits/inference-catalog so the deployer and this route share one source of truth) and points seed at it instead. Also drops the two long-dead unused parameters from onboarding's modelSourceFor, and exports ensureNoopCatalogOffering from @corbits/seeding for the e2e harness to reuse.
…' into cl-7472-sidecar-ws
At the interchange pin this branch stacks on (692c3106), the hub authenticates a sidecar's register/reconnect frame only against a sidecar row that resolves to a live sidecar_allocation (or workflow_probe) row. Upstream dropped the older "shared" credential scope this repo's e2e harness used to provision a single, standalone sidecar identity for, so a provisionSidecar-only row now connects and is rejected with "invalid token" in a loop rather than ever dialing in. This test pins that negative so a future re-vendor that silently restores (or relies on) the old scope surfaces here first.
Every e2e suite dial-in relied on a single, harness-started sidecar process registered against a bare `provisionSidecar` identity row. At pin 692c3106, the hub only accepts a register/reconnect frame from a sidecar whose identity resolves to a live sidecar_allocation (or workflow_probe) row; a standalone identity with neither now fails the handshake and gets rejected with "invalid token" every ~3s, forever (chat mint 500s, deploys never dial in — CI run 34042907014). The hub's own process-provisioner already spawns a dedicated, correctly-allocated sidecar process per deployment on demand — the same path production already takes for a real workflow run or a minted agent chat. Every affected suite (walking skeleton, chat, heartbeat, workbench-digest, smoke-chat, smoke-webhook, greeting-delivery, channel-reconnect, local-rip, and the isolation suite) now lets that happen instead of dialing in its own sidecar: the existing 502/500 retry-until-ready loops already cover the wait, and diagnostics that used to read the one sidecar process's output now read the hub's — the process-provisioner backend spawns each sidecar with its stdio inherited from the hub, so the hub's own captured output already carries every spawned sidecar's log lines. `provisionSidecar`/`startSidecar` stay in the harness (documented as non-authenticating at this pin) for the handful of standalone proof scripts not yet migrated off a directly-controlled sidecar process.
The e2e job's hub/sidecar output was never captured, so the CI run that surfaced the sidecar disconnect loop (34042907014) had no sidecar-side log to read past the transcript. Point E2E_LOG_DIR at a runner-temp directory and upload it on every run, pass or fail.
TheGreatAxios
changed the base branch from
cl-7471-generation-zero
to
cl-7107-repin-and-re-vendor-audit-against-interchange-main-before-the
September 6, 2026 16:22
TheGreatAxios
merged commit Sep 6, 2026
0816d1d
into
cl-7107-repin-and-re-vendor-audit-against-interchange-main-before-the
6 of 7 checks passed
TheGreatAxios
added a commit
that referenced
this pull request
Sep 7, 2026
* Vendor @intx/* trees at 692c3106 with the ledger deltas re-applied
Re-copy every vendor/intx/* package from faremeter/interchange
origin/main at 692c3106 ("Remove the orphaned sidecar challenge
signer"), 90 commits past the v0.3.0 tag, and re-apply the workbench
deltas the ledger records.
Restores the vendoring convention the tree had before this branch:
upstream test files are not vendored, because they import the
unvendored @intx/inference-testing and @intx/test-harness test
packages. Only the ten tests that cover a workbench delta are kept.
The previous pass copied all 242 upstream test files, which is what
made typecheck fail on those imports.
Deltas retired at this pin:
- workflow-host CL-6448's credential thread is now half native:
upstream passes the run's live credential-material cell into every
body spawn as `credentialMaterial`. The `credentialWiring` field
stays, because it additionally carries the per-step
`resolveStepGrants` a body's tool bundles need and upstream's cell
does not.
Deltas this pin strands, recorded in VENDORED.md rather than
re-authored: the CL-6324 session-service deltas (the adopted deploy
front, the per-run `sourceRef` threading) and CL-7191's
`UserMessageParams.correlationId` hung off upstream's
`deployWorkflowFromSource`, which this pin deletes in favour of the
catalog-offering plus provisioner-selection path. session-service.ts
is vendored pristine.
Workbench db migrations are renumbered to 0089-0092 behind upstream's
new 0087_drop_sidecar_placement and 0088_thick_sprite; the drizzle
journal keeps our original `when` values, which still sort after
upstream's.
The kill-date registry moves in the same commit, as the vendored-tree
hash rule requires.
* Add the sidecar modules the 692c3106 pin requires
The re-pinned @intx/hub-agent and @intx/workflow-host expect the host
to supply a child grant filter, per-step credential capabilities, and
a workflow-run record writer. Derived from upstream's own apps/sidecar
at the pin, which is never npm-published.
* sidecar: align with the 692c3106 host contracts
- Drop `signEd25519` from the orchestrator's `SidecarCryptoOps`: the
pin removes the orphaned sidecar challenge signer, so the field no
longer exists. The sidecar's own supervisor and identity vault keep
calling `@intx/crypto` directly.
- Thread the spawn seam's `depth` / `maxChildSpawnDepth` and the
native `credentialMaterial` argument through the child runtime.
- Attach a `cause` to the SIDECAR_TOOL_REGISTRIES JSON parse failure
and stop pre-seeding `dirtyExists`, both of which lint flags.
* Update docs: repin the vendoring ledger to 692c3106
Move every ledger row and VENDORED-FROM commit line to 692c3106, roll
the kill dates, and correct the rows whose deltas changed: the db
migration renumbering, workflow-host's now-native credential thread,
and hub-sessions' stranded adopted-deploy front.
Adds a section recording the two deltas this pin strands and the
ruling each needs: the CL-6324 adopted deploy front (upstream deleted
the `deployWorkflowFromSource` seam it hung off) and
packages/sidecar-placement (upstream replaced the placement model with
sidecar capabilities and registration-order provisioner selection).
* sidecar: resolve inference credentials from the run's material cell
Interchange unified inference and tool credentials onto one material
cell: an `InferenceSource` names a `credentialId` instead of carrying
an inline `apiKey`, and the reactor fills the request's secret at send
time through `BaseEnv.readCurrentMaterial`. Without that resolver the
agent's fail-closed default refuses every inference call.
Add `stepInferenceMaterialResolver`, which reads the step's live
`CredentialWiring.materialRef` by credential id -- fresh per call, so a
`credentials-updated` frame mid-step is picked up, and failing closed
when the credential was revoked or never delivered -- and wire it into
the per-step env beside the existing tool-credential slot. A step with
no credential wiring omits the key and keeps the platform default.
Test fixtures move from `apiKey` to `credentialId`. Two comments
claiming the on-disk deployment and run records embed source API keys
are corrected: a record now names a credentialId, not a secret. The
0o600 mode stays, since the record still carries the run's routing
identity.
* sidecar: assert malformed credential deliveries at the wire boundary
The multistep credentials router no longer re-validates the delivery it
is handed. It cannot be handed a malformed one: hub-link's
`handleMessage` narrows every inbound frame through `HubFrame` (which
unions `CredentialsUpdateFrame`) and answers a malformed frame with an
error frame before any router is consulted.
So the test asserts the boundary's contract -- the frame schema rejects
the shape -- rather than asking our adapter to defend against a shape
that can never reach it.
* provisioners: declare sidecar capabilities
`SidecarProvisioner` now requires a `capabilities` declaration, which
the hub's capability policy matches a deployment's requirements
against. Every workbench provisioner declares the empty set, which
matches any deployment that states no requirement -- the behaviour each
had before Interchange replaced the placement model with capability
selection. The field is required rather than defaulted so a backend
that grows a real capability has to say so at its construction site.
* folded-runs: move the launch-source fixtures onto credentialId
`InferenceSource` names a `credentialId` at this pin instead of carrying
an inline `apiKey`, so a caller-supplied sources override built the old
way now fails `parseSourcesOverride`'s arktype narrow.
* Delete sidecar-placement and the Capacity settings section (CL-7107)
Interchange handles sidecar placement itself as of upstream 692c3106: the
`SidecarPlacementRequirement` model this root-bucket module was built on is
gone, replaced by declared sidecar capabilities plus registration-order
provisioner selection, and upstream's own migration 0087 drops the table.
A per-tenant placement flag can no longer be honored by the platform, so
the module and the product surface that wrote it are removed rather than
re-pointed at a contract that no longer exists.
Removed: `packages/sidecar-placement` (package, its route mount in
apps/hub, its workspace dependency and project reference), the Capacity
settings section (`capacity-api.ts`, `capacity-section.tsx` and their
suites), its nav id, probe state and strings, and the adoption doc. No
workbench-owned migration existed for it -- the module read and wrote
`TenantConfig.sidecarPlacement` on the tenant row directly.
* Retire the vendored deltas the 692c3106 pin strands (CL-7107)
`hub-sessions/src/session-service.ts` is pristine at this pin, so its
co-located test -- which existed only to cover the retired CL-6324
`deployAdoptedCodeSourcedWorkflow` front -- goes with the front it
covered. Upstream replaced that front with the catalog-offering plus
provisioner-selection path, and vendored trees here carry tests only for
their own deltas.
`hub-api`'s deploy-bearer mirror keeps its delta but rebases its suite
onto the native route deps: `createWorkflowRoutes` now takes a
`workflowAllocationService` rather than a `sessionService`, so the fake
stands in for `prepareProvisionedDeployment` and the request body carries
`sourceOfferingIds`/`defaultSourceOfferingId` instead of inline resolved
sources. The middleware itself is unchanged -- what the suite proves,
that a bearer-authenticated deploy reaches the same handler and the same
`requireGrant`/asset-scoping gates a human session does, is unchanged
too.
* Update docs: retire the stranded-deltas section from the ledger (CL-7107)
The "Deltas this pin strands" section recorded two pending owner rulings.
Both have landed: sidecar placement is Interchange's own concern now (the
module and its Capacity surface are deleted), and the CL-6324 adopted
deploy front is retired outright rather than re-authored, leaving
`session-service.ts` pristine. The hub-sessions row and its VENDORED-FROM
line now describe only deltas that still exist, and the recorded tree
hashes match the trees.
* Add tests for the shipped backends' capability declarations
`SidecarProvisioner` requires a `capabilities` declaration at this pin,
and Interchange's capability policy fails a deployment closed when no
registered backend declares what it requires. Pin the isolation ladder
the three shipped backends declare -- process, container, vm -- so a
backend cannot quietly widen or narrow what it claims to isolate.
* provisioners: declare the isolation each backend actually gives
The empty declaration every workbench provisioner carried matched only a
deployment that required nothing, which made capability selection inert:
a deployment asking for VM isolation was as welcome on the hub's own
child process as on a remote sandbox.
Declare the real ladder instead, in one shared place. Isolation is
cumulative -- a container also isolates the process, a VM also isolates
the container -- so a backend names the rung it reaches and
`sidecarCapabilityDeclarations` marks everything at or below it available
and everything above it blocked. `process` blocks container and VM
isolation, `docker` blocks VM isolation, `e2b` blocks nothing, and all
three declare `runtime:sidecar`.
This also supplies the declaration the `process` backend was missing
outright, which the required field caught the moment the two changes met.
* Update docs: what each sidecar backend declares it can do
Records the isolation ladder against the backend table it belongs
next to, and drops the dangling link to the placement-adoption
analysis this branch deleted along with `packages/sidecar-placement`.
* sidecar tests: drop signEd25519 from the key-store deps
`AgentKeyStoreDeps` no longer takes a signer at the 692c3106 pin -- the
key store signs through its own crypto provider -- so passing one is a
rejected excess property in three suites and dead weight in the fourth,
which only escaped the error by spreading the bag through a variable.
* launch fixtures: name the credential instead of carrying its secret
An `InferenceSource` names its credential by id at this pin rather than
carrying the decrypted key, and `DefinitionSourceResolution` returns the
material separately in `materials`. Move both launch-source suites onto
that shape.
This narrows what the deployed-sources digest can detect, and the tests
now say so: re-pointing a source at a different credential still changes
the digest, but rotating a secret in place no longer does, because the
secret travels in the deploy's credential-material cell. The drift check
that relied on the old behaviour is CL-6534's to resettle.
* provisioners: sync tsconfig references for the capability declarations
`@corbits/sandbox-sidecar` gained an `@intx/types` dependency for the
shared capability vocabulary, and `check:tsconfig-references` requires
the project references to match a package's declared dependencies.
Also reflows the provisioner doc's new table to prettier's width.
* Add tests for RFC Message-ID thread dispatch and header correlation
Covers the contract CL-7104 introduces: a dispatched timeline row goes
out under its own RFC 5322 Message-ID, names its thread parentage in
In-Reply-To / References, and is the only thing a reply is correlated
back through.
- a root-feed dispatch carries the row's Message-ID and threads under
nothing; the header resolves back to exactly one row
- a dispatch from inside a sub-thread carries the full References chain,
oldest ancestor first, with In-Reply-To as its tail
- two turns pending against one agent address stay told apart by their
Message-IDs rather than by which was asked most recently
- a question answer is dispatched as a reply to that card's own
Message-ID, and never to another live question's
- a reply naming no parent is reported and lands on the root thread
instead of being attached to a guess
* Thread turn dispatch by RFC Message-ID; correlate replies by header
Chat is a mail thread, so a message dispatched to an agent now travels as
one. Every timeline row dispatched as mail carries a real RFC 5322
Message-ID — <{row id}@{mail domain}>, derived from the row's own primary
key and stamped back onto it in a new mail_message_id column — and names
its thread parentage in In-Reply-To and References, built per RFC 5322
from the chat.threads parent chain.
Correlation is those headers and nothing else. MailContent's replyTo,
which carried a bare workbench id into the In-Reply-To slot, is gone with
no alias; so is the correlationId parameter threaded from
SendWorkbenchMessageInput through the turn queue, dispatchTurn, the
platform port and adapter, and into folded-runs' mail path, where the
native UserMessageParams has no such field and silently dropped it. The
folded-runs sender now passes inReplyTo and references through natively
instead.
A parked message_response gate is armed on the question card's own
Message-ID rather than a private question id: the card is posted with
that header, the answer is dispatched as a reply naming it, and an answer
to a different card names a different id.
On the inbound side, the orchestrator no longer treats a mail's reply-to
address as an originating-room hint, and a reply that names no parent is
reported through reportError with room and agent context and appended to
the root thread rather than posting unattached.
The message_response gate's own resolution still reads
Interchange-Correlation-ID inside vendor/intx/inference's reactor, which
this change does not touch; wiring that to In-Reply-To needs an upstream
change and is tracked separately.
* Update docs: correlation is mail headers, and the reply bridge is retiring
CHAT.md's threading section now states the rule directly — a dispatched
row's own Message-ID, In-Reply-To and References from the thread parent
chain, no correlation id and no reply-to address — and says what happens
to a reply that names no parent. The reply-bridge section and the
GLOSSARY row both note the bridge is being retired now that a reply can
travel on the same transport its dispatch went out on.
* chat: drop duplicate mail-id test stubs after rebase
The rebase onto origin/main doubled stampMailMessageId and
findByMailMessageId on the fake store; keep one of each.
* Add tests for native provisioned launch
The shared launcher writes rendered source onto the instance asset at
the default ref and calls prepareProvisionedDeployment. It must not
mint a per-run sourceRef or insert chat-owned agent_session rows.
* Add native provisioned launcher in @corbits/workflows
populateAsset at DEFAULT_ASSET_REF then prepareProvisionedDeployment,
mirroring the native POST /workflows/deployments sequence. Callers
render the tree first; this module never imports agent-runtime.
* hub: wire allocation service to the 692c3106 plugin split
Pass deploymentPlugins and probePlugins (same registry), hubWebSocketUrl,
and sidecarAllocationRouter. Initialize interrupted probes on boot. Drop
the session-service deploy-source wrapper — those methods are gone and
the provisioned path already records source on workflow_run_launch_spec.
* workflows: drop the Workbench launch wrapper; Interchange launches
* hub: authoring deploy uses Interchange prepareProvisionedDeployment
* Chat invite, wake, and relaunch provision via Interchange
Workbench agents are native provisioned deployments: invite and wake
call prepareProvisionedDeployment onto the definition asset and record
the returned run id, address, and allocation. Inference sources are
catalog offering ids. Chat no longer mints the run row.
* Webhook triggers provision via Interchange prepareProvisionedDeployment
launchWebhookTrigger renders the agent runtime onto the definition
asset, prepares a provisioned deployment, and sends one native
user message. Folded-run launch and mail are gone.
* Morning-brief: drop the folded-runs deploy-source import
The catalog workflow no longer goes through the retired
deployWorkflowFromSource front.
* Hub: wire webhook launches onto prepareProvisionedDeployment
Pass the allocation service and persist the workbench_launch row
after Interchange prepares, instead of the folded-run extra hook.
* One-shot runner provisions via Interchange prepareProvisionedDeployment
* Move definition projection helpers into @corbits/workflows
* Artifacts-hub resolves run scope from workflow_run address
* Run-scope lists top-level runs without a folded_run marker
* Delete @corbits/folded-runs
Launch, wake, and outbound mail go through Interchange prepareProvisionedDeployment and sendUserMessage. Chat records the returned run id, address, and offering-id digest, and threads turns by the timeline row's Message-ID. Remaining helpers (cipher tag, crypto cache, pin ports, sendRunMail) live in @corbits/chat. Wake is a terminal workflow_run status, not a folded_run marker. Hard cutover: no marker table, no backfill.
* Delete folded-run-one-shot and the catalog pin-gate
The one-shot wait-loop now lives in @corbits/agent-directory as
runOneShotPrompt and still calls Interchange prepareProvisionedDeployment.
Catalog instantiate no longer 409s credential-binding workflows now that
the native front takes credentialCipher.
* Record vendored hashes; drop the folded-runs kill-date
The 692c3106 re-pin changed vendor/intx/inference and vendor/intx/types.
packages/folded-runs is gone, so its kill-date row is gone too.
* Describe native Interchange agent launch in the docs
Workbench invites by calling prepareProvisionedDeployment. Chat records
returned ids. Replies reach humans through persistMail into mailbox.
* Drop leftover deploy-source recording and pin-stale hub lookups
This pin has no lookupPublicKey, getDeployRef, or orchestrator
agentRepoStore, and no deployWorkflowFromSource for a session-service
wrapper to record.
* Provision from the existing definition asset HEAD
Invite, wake, webhook, and one-shot no longer rewrite the definition
asset with a per-run source tree. They resolve HEAD and pass that SHA
to Interchange prepareProvisionedDeployment.
* isolation: give the afterAll shutdown hook headroom past the harness's own kill fallback (#653)
hub.shutdown() stops the sidecar then the hub in sequence, and each
stop() gives its process up to 5s to exit on SIGTERM before escalating
to SIGKILL. A graceful-but-slow exit on either process alone can
approach Bun's 5000ms default hook timeout, and two of them back to
back can exceed it outright, which is what the isolation CI job hit:
the hook timed out at ~5001ms with every real assertion passing.
Raise the hook's timeout to 20s so a slow-but-clean shutdown on a
loaded CI runner has room to finish.
* Sweep leftover folded-run references after the native launch cutover (CL-7463) (#655)
* hub: point stale folded-runs comments at the packages that replaced it
Several comments in apps/hub still named the deleted @corbits/folded-runs
package and its FoldedRunsDeps type. Point them at createHubChatPlatform's
CreateHubChatPlatformDeps, @corbits/workflows, and @corbits/run-scope,
which now own the referenced behavior.
* web: retire stale folded-run terminology in top-level-run comments
Several comments described the scope-routes filter using the deleted
@corbits/folded-runs package and a nonexistent folded_run marker table.
Reworded them around the actual predicate (top-level vs non-top-level
run) and pointed the package references at @corbits/run-scope.
* sidecar: point lazy-wake comments at chat's wakeByAddress, not folded-runs
The deferred-to-wake restore path's comments still named the deleted
@corbits/folded-runs package's wakeFoldedRun/deployAtHead. Point them at
@corbits/chat's wakeByAddress, which now backs the lazy-wake port.
* agent-directory: drop folded-run terminology from client filter docs
purposeAgentInstances' excludeRunIds no longer has a real caller in this
repo (apps/web now scopes server-side via @corbits/run-scope), and the
README pointed at a deleted @corbits/chat-ui helper. Rewrote the
comments, README, and test names around the actual excludeRunIds
contract.
* chat: point stale folded-runs function names at their real replacements
schema.ts and test comments still named the deleted @corbits/folded-runs
package (inferenceSourcesDigest, findFoldedRunByAddress, wakeFoldedRun,
sendFoldedMailWithReclaimRetry) — none of which exist any more. Point
them at the actual in-package names: offeringDigest, the real
address→run resolution, wakeByAddress, sendRunMailWithReclaimRetry.
* run-key-history: drop reference to a nonexistent folded-runs lookup
reconnect.ts claimed the hub wires this alongside @corbits/folded-runs'
lookupFoldedRunReconnectKey, which no longer exists anywhere. Also
pointed the drizzle test's scope-routes cross-reference at
@corbits/run-scope.
* misc: point remaining stale folded-runs cross-references at real owners
describe.ts, credential-error.ts, the seeding launchability test,
collections' README, and the code-review/last-30-days-research workflow
comments still named the deleted @corbits/folded-runs package or its
files. Pointed each at where the described behavior actually lives now
(@corbits/chat, @corbits/workflows, or vendor/intx/hub-sessions).
* db-setup: drop the dead folded_runs schema from the reset allowlist
No package creates a folded_runs Postgres schema any more (the package
was deleted); also renamed the run-scope drizzle test's isolation
schema names off the same dead term.
* Seeding: deploy default workflows by catalog offering ids (CL-7461) (#657)
* Add tests for deploying default workflows by catalog offering ids
seedTenant currently deploys every default workflow with a bare
provider/baseURL/apiKey source, but the hub's deploy route now requires
sourceOfferingIds/defaultSourceOfferingId resolved from the tenant's
catalog. Update the seeding, onboarding, and hub config test suites to
expect the new offering-id based deploy body and provider/model-only
ModelSource shape ahead of the implementation change.
* Seeding: deploy default workflows by catalog offering ids
ensureDeployment posted a bare {provider, baseURL, apiKey, model}
source, but the hub's deploy route requires sourceOfferingIds and a
defaultSourceOfferingId resolved from the tenant's catalog, so seeding
a connect flow's "assistant" workflow 400'd. seedTenant now resolves
the tenant's real catalog offerings (sorted by priority, lowest wins
the default) for a normal workflow, and plants a dedicated noop
catalog offering for a modelSource-pinned one (heartbeat), pointed at
the hub's own noop-inference endpoint. ModelSource shrinks to a bare
provider/model pair — the baseURL/apiKey it used to carry only mattered
for the old deploy body, and callers that still need a key for seeding
a catalog (system-seed's root tenant, config's env-key auto-plant) keep
it on their own local type instead.
* Fix formatting: prettier
* Add tests for resolved catalog offerings and modelSourceFor's trimmed signature
Covers the new GET .../catalog/resolved-offerings route (denied grant,
and both directly-owned and ancestor-inherited offerings sorted by
priority) and updates seeding/onboarding test fixtures to the route's
response shape ahead of the implementation change.
* Seed deploys against a tenant's resolved catalog offerings, not just owned ones
resolveRealSourceOfferingIds listed only offerings a tenant owns
directly, missing anything inherited from an ancestor tenant — the
hub's own workflow deployer already resolves that wider,
ancestor-inheriting view via listVisibleOfferings. Adds
GET /api/tenants/:tenantId/catalog/resolved-offerings (mounted from
apps/hub, implemented in @corbits/inference-catalog so the deployer and
this route share one source of truth) and points seed at it instead.
Also drops the two long-dead unused parameters from
onboarding's modelSourceFor, and exports ensureNoopCatalogOffering from
@corbits/seeding for the e2e harness to reuse.
* Fix remaining modelSourceFor call sites for its trimmed signature
play.ts, greeting-delivery.test.ts, and local-rip.test.ts each still
called modelSourceFor with the apiKey/baseURLOverride arguments the
previous commit dropped; CI's typecheck job caught the three sites
this repo's own tsc -p packages/onboarding pass didn't cover.
* e2e: deploy workflows by catalog offering ids (CL-7460) (#662)
* Add test for workflowDeployBody's catalog-offering shape
* e2e: deploy workflows by catalog offering ids
The native workflow deploy route now requires sourceOfferingIds/
defaultSourceOfferingId resolved against the tenant's own catalog,
rather than a raw provider/baseURL/apiKey source chain. Update
workflowDeployBody accordingly, and extract the zero-cost noop
catalog chain smoke-webhook.test.ts already built (an anthropic-
plugin provider pointed at the hub's noop-inference endpoint) into
a shared seedNoopCatalogOffering harness helper.
Every deploying e2e suite now seeds or resolves a real catalog
offering before deploying: walking-skeleton keeps a never-real
placeholder host (assertNeverRealProvider still guards it), chat
resolves the offering seedCatalog already planted rather than
seeding a second chain, and heartbeat/workbench-digest/smoke-webhook
seed the noop chain via the shared helper. cl-6324-launch-proof's
section-mode deploy now reuses the offering id the proof already
resolved when it narrowed the bench catalog to its pinned model.
* Fix formatting
* Add tests for deploying default workflows by catalog offering ids
seedTenant currently deploys every default workflow with a bare
provider/baseURL/apiKey source, but the hub's deploy route now requires
sourceOfferingIds/defaultSourceOfferingId resolved from the tenant's
catalog. Update the seeding, onboarding, and hub config test suites to
expect the new offering-id based deploy body and provider/model-only
ModelSource shape ahead of the implementation change.
* Seeding: deploy default workflows by catalog offering ids
ensureDeployment posted a bare {provider, baseURL, apiKey, model}
source, but the hub's deploy route requires sourceOfferingIds and a
defaultSourceOfferingId resolved from the tenant's catalog, so seeding
a connect flow's "assistant" workflow 400'd. seedTenant now resolves
the tenant's real catalog offerings (sorted by priority, lowest wins
the default) for a normal workflow, and plants a dedicated noop
catalog offering for a modelSource-pinned one (heartbeat), pointed at
the hub's own noop-inference endpoint. ModelSource shrinks to a bare
provider/model pair — the baseURL/apiKey it used to carry only mattered
for the old deploy body, and callers that still need a key for seeding
a catalog (system-seed's root tenant, config's env-key auto-plant) keep
it on their own local type instead.
* longevity-sim: deploy workflows by catalog offering ids
Another workflowDeployBody caller CI's typecheck job caught: this
stack seeds one catalog offering per real inference target already,
so track each one's id and pass it through to the deploy body
instead of the raw provider/baseURL/apiKey shape.
* Fix formatting: prettier
* Add tests for resolved catalog offerings and modelSourceFor's trimmed signature
Covers the new GET .../catalog/resolved-offerings route (denied grant,
and both directly-owned and ancestor-inherited offerings sorted by
priority) and updates seeding/onboarding test fixtures to the route's
response shape ahead of the implementation change.
* Seed deploys against a tenant's resolved catalog offerings, not just owned ones
resolveRealSourceOfferingIds listed only offerings a tenant owns
directly, missing anything inherited from an ancestor tenant — the
hub's own workflow deployer already resolves that wider,
ancestor-inheriting view via listVisibleOfferings. Adds
GET /api/tenants/:tenantId/catalog/resolved-offerings (mounted from
apps/hub, implemented in @corbits/inference-catalog so the deployer and
this route share one source of truth) and points seed at it instead.
Also drops the two long-dead unused parameters from
onboarding's modelSourceFor, and exports ensureNoopCatalogOffering from
@corbits/seeding for the e2e harness to reuse.
* Fix remaining modelSourceFor call sites for its trimmed signature
play.ts, greeting-delivery.test.ts, and local-rip.test.ts each still
called modelSourceFor with the apiKey/baseURLOverride arguments the
previous commit dropped; CI's typecheck job caught the three sites
this repo's own tsc -p packages/onboarding pass didn't cover.
* e2e harness: reuse @corbits/seeding's noop catalog offering chain
harness.ts's seedNoopCatalogOffering duplicated the exact chain
@corbits/seeding's ensureNoopCatalogOffering already plants (CL-7461).
Every deploying suite that pointed the offering at the hub's own real
noop-inference endpoint (heartbeat, smoke-webhook, workbench-digest)
now calls the shared helper instead; walking-skeleton keeps its own
local copy, since it deliberately points the offering at an
unreachable placeholder host rather than the hub's noop-inference
endpoint, which the shared helper does not support.
* sandbox-sidecar: accept the allocation service's initial generation (CL-7471) (#659)
* Add test for workflowDeployBody's catalog-offering shape
* e2e: deploy workflows by catalog offering ids
The native workflow deploy route now requires sourceOfferingIds/
defaultSourceOfferingId resolved against the tenant's own catalog,
rather than a raw provider/baseURL/apiKey source chain. Update
workflowDeployBody accordingly, and extract the zero-cost noop
catalog chain smoke-webhook.test.ts already built (an anthropic-
plugin provider pointed at the hub's noop-inference endpoint) into
a shared seedNoopCatalogOffering harness helper.
Every deploying e2e suite now seeds or resolves a real catalog
offering before deploying: walking-skeleton keeps a never-real
placeholder host (assertNeverRealProvider still guards it), chat
resolves the offering seedCatalog already planted rather than
seeding a second chain, and heartbeat/workbench-digest/smoke-webhook
seed the noop chain via the shared helper. cl-6324-launch-proof's
section-mode deploy now reuses the offering id the proof already
resolved when it narrowed the bench catalog to its pinned model.
* Fix formatting
* Add tests for deploying default workflows by catalog offering ids
seedTenant currently deploys every default workflow with a bare
provider/baseURL/apiKey source, but the hub's deploy route now requires
sourceOfferingIds/defaultSourceOfferingId resolved from the tenant's
catalog. Update the seeding, onboarding, and hub config test suites to
expect the new offering-id based deploy body and provider/model-only
ModelSource shape ahead of the implementation change.
* Seeding: deploy default workflows by catalog offering ids
ensureDeployment posted a bare {provider, baseURL, apiKey, model}
source, but the hub's deploy route requires sourceOfferingIds and a
defaultSourceOfferingId resolved from the tenant's catalog, so seeding
a connect flow's "assistant" workflow 400'd. seedTenant now resolves
the tenant's real catalog offerings (sorted by priority, lowest wins
the default) for a normal workflow, and plants a dedicated noop
catalog offering for a modelSource-pinned one (heartbeat), pointed at
the hub's own noop-inference endpoint. ModelSource shrinks to a bare
provider/model pair — the baseURL/apiKey it used to carry only mattered
for the old deploy body, and callers that still need a key for seeding
a catalog (system-seed's root tenant, config's env-key auto-plant) keep
it on their own local type instead.
* longevity-sim: deploy workflows by catalog offering ids
Another workflowDeployBody caller CI's typecheck job caught: this
stack seeds one catalog offering per real inference target already,
so track each one's id and pass it through to the deploy body
instead of the raw provider/baseURL/apiKey shape.
* Fix formatting: prettier
* sandbox-sidecar: add tests for generation-0 ensure/destroy requests
The allocation service's initial sidecar_allocation and workflow_probe
rows start at generation 0 (@intx/db enforces generation >= 0, not >
0), so the first ensure() for a freshly allocated sidecar must be
accepted at generation 0. These tests cover that acceptance, later
generations, non-integer/negative rejection, and that stale-generation
fencing still rejects an older generation once a newer one has been
observed.
* sandbox-sidecar: accept the allocation service's initial generation
@intx/db's sidecar_allocation and workflow_probe tables both check
generation >= 0 and the allocation service records the first row for
an allocation at generation 0, but validateEnsureRequest() and
validateDestroyRequest() in packages/sandbox-sidecar rejected anything
that wasn't strictly positive, and the on-disk fencing schema in
state-store.ts required generation > 0. The first ensure() for any
newly allocated sidecar was rejected with 'generation must be a
positive integer', failing every deploy.
Accept generation >= 0 in both validators and the state-store schema;
stale-generation fencing in observeEnsure()/observeDestroy() already
compares against the stored generation rather than assuming positivity,
so it is unaffected.
* sandbox-sidecar: fix formatting in provisioner test
* Web: drop the Myra title pre-check; the server's definition dedupe is the one path (CL-7466) (#660)
* Add tests for the Myra land path with no client-side title lookup
Replace the by-title stub expectations in myra-workbench.test.ts and
default-agent-workbench.test.ts with assertions that ensure() issues
exactly the definition-keyed POST /workbenches create, with no
kind=workbench or kind=chat list call in front of it.
* Drop the client-side title pre-check in front of the Myra land create
createDefaultAgentWorkbench used to list workbench and chat kinds and
reuse a title match (plus convert a legacy workbench-kind row) before
ever reaching the server's create. That duplicated the dedupe
POST /workbenches already does by definitionId (findExistingAgentChat
in packages/chat), and left two logics that could disagree. Delete the
pre-check: the client always issues the definition-keyed create and
trusts the server's find-or-reopen.
Drops findWorkbenchByTitle, isWorkbenchTitleMatch, and
apps/web's findMyraWorkbench/isMyraWorkbenchTitle, which existed only
to serve that pre-check.
* Format myra-workbench.test.ts to match prettier's line-wrap rules
* e2e: keep the sidecar session open at pin 692c3106 (CL-7472) (#663)
* Add test for workflowDeployBody's catalog-offering shape
* e2e: deploy workflows by catalog offering ids
The native workflow deploy route now requires sourceOfferingIds/
defaultSourceOfferingId resolved against the tenant's own catalog,
rather than a raw provider/baseURL/apiKey source chain. Update
workflowDeployBody accordingly, and extract the zero-cost noop
catalog chain smoke-webhook.test.ts already built (an anthropic-
plugin provider pointed at the hub's noop-inference endpoint) into
a shared seedNoopCatalogOffering harness helper.
Every deploying e2e suite now seeds or resolves a real catalog
offering before deploying: walking-skeleton keeps a never-real
placeholder host (assertNeverRealProvider still guards it), chat
resolves the offering seedCatalog already planted rather than
seeding a second chain, and heartbeat/workbench-digest/smoke-webhook
seed the noop chain via the shared helper. cl-6324-launch-proof's
section-mode deploy now reuses the offering id the proof already
resolved when it narrowed the bench catalog to its pinned model.
* Fix formatting
* Add tests for deploying default workflows by catalog offering ids
seedTenant currently deploys every default workflow with a bare
provider/baseURL/apiKey source, but the hub's deploy route now requires
sourceOfferingIds/defaultSourceOfferingId resolved from the tenant's
catalog. Update the seeding, onboarding, and hub config test suites to
expect the new offering-id based deploy body and provider/model-only
ModelSource shape ahead of the implementation change.
* Seeding: deploy default workflows by catalog offering ids
ensureDeployment posted a bare {provider, baseURL, apiKey, model}
source, but the hub's deploy route requires sourceOfferingIds and a
defaultSourceOfferingId resolved from the tenant's catalog, so seeding
a connect flow's "assistant" workflow 400'd. seedTenant now resolves
the tenant's real catalog offerings (sorted by priority, lowest wins
the default) for a normal workflow, and plants a dedicated noop
catalog offering for a modelSource-pinned one (heartbeat), pointed at
the hub's own noop-inference endpoint. ModelSource shrinks to a bare
provider/model pair — the baseURL/apiKey it used to carry only mattered
for the old deploy body, and callers that still need a key for seeding
a catalog (system-seed's root tenant, config's env-key auto-plant) keep
it on their own local type instead.
* longevity-sim: deploy workflows by catalog offering ids
Another workflowDeployBody caller CI's typecheck job caught: this
stack seeds one catalog offering per real inference target already,
so track each one's id and pass it through to the deploy body
instead of the raw provider/baseURL/apiKey shape.
* Fix formatting: prettier
* sandbox-sidecar: add tests for generation-0 ensure/destroy requests
The allocation service's initial sidecar_allocation and workflow_probe
rows start at generation 0 (@intx/db enforces generation >= 0, not >
0), so the first ensure() for a freshly allocated sidecar must be
accepted at generation 0. These tests cover that acceptance, later
generations, non-integer/negative rejection, and that stale-generation
fencing still rejects an older generation once a newer one has been
observed.
* sandbox-sidecar: accept the allocation service's initial generation
@intx/db's sidecar_allocation and workflow_probe tables both check
generation >= 0 and the allocation service records the first row for
an allocation at generation 0, but validateEnsureRequest() and
validateDestroyRequest() in packages/sandbox-sidecar rejected anything
that wasn't strictly positive, and the on-disk fencing schema in
state-store.ts required generation > 0. The first ensure() for any
newly allocated sidecar was rejected with 'generation must be a
positive integer', failing every deploy.
Accept generation >= 0 in both validators and the state-store schema;
stale-generation fencing in observeEnsure()/observeDestroy() already
compares against the stored generation rather than assuming positivity,
so it is unaffected.
* sandbox-sidecar: fix formatting in provisioner test
* Add tests for resolved catalog offerings and modelSourceFor's trimmed signature
Covers the new GET .../catalog/resolved-offerings route (denied grant,
and both directly-owned and ancestor-inherited offerings sorted by
priority) and updates seeding/onboarding test fixtures to the route's
response shape ahead of the implementation change.
* Seed deploys against a tenant's resolved catalog offerings, not just owned ones
resolveRealSourceOfferingIds listed only offerings a tenant owns
directly, missing anything inherited from an ancestor tenant — the
hub's own workflow deployer already resolves that wider,
ancestor-inheriting view via listVisibleOfferings. Adds
GET /api/tenants/:tenantId/catalog/resolved-offerings (mounted from
apps/hub, implemented in @corbits/inference-catalog so the deployer and
this route share one source of truth) and points seed at it instead.
Also drops the two long-dead unused parameters from
onboarding's modelSourceFor, and exports ensureNoopCatalogOffering from
@corbits/seeding for the e2e harness to reuse.
* Add a regression test pinning the shared-sidecar auth gap (CL-7472)
At the interchange pin this branch stacks on (692c3106), the hub
authenticates a sidecar's register/reconnect frame only against a
sidecar row that resolves to a live sidecar_allocation (or
workflow_probe) row. Upstream dropped the older "shared" credential
scope this repo's e2e harness used to provision a single, standalone
sidecar identity for, so a provisionSidecar-only row now connects and
is rejected with "invalid token" in a loop rather than ever dialing
in. This test pins that negative so a future re-vendor that silently
restores (or relies on) the old scope surfaces here first.
* e2e: stop pre-provisioning a shared sidecar identity (CL-7472)
Every e2e suite dial-in relied on a single, harness-started sidecar
process registered against a bare `provisionSidecar` identity row.
At pin 692c3106, the hub only accepts a register/reconnect frame from
a sidecar whose identity resolves to a live sidecar_allocation (or
workflow_probe) row; a standalone identity with neither now fails the
handshake and gets rejected with "invalid token" every ~3s, forever
(chat mint 500s, deploys never dial in — CI run 34042907014).
The hub's own process-provisioner already spawns a dedicated,
correctly-allocated sidecar process per deployment on demand — the
same path production already takes for a real workflow run or a
minted agent chat. Every affected suite (walking skeleton, chat,
heartbeat, workbench-digest, smoke-chat, smoke-webhook,
greeting-delivery, channel-reconnect, local-rip, and the isolation
suite) now lets that happen instead of dialing in its own sidecar:
the existing 502/500 retry-until-ready loops already cover the wait,
and diagnostics that used to read the one sidecar process's output
now read the hub's — the process-provisioner backend spawns each
sidecar with its stdio inherited from the hub, so the hub's own
captured output already carries every spawned sidecar's log lines.
`provisionSidecar`/`startSidecar` stay in the harness (documented as
non-authenticating at this pin) for the handful of standalone proof
scripts not yet migrated off a directly-controlled sidecar process.
* ci: upload hub/sidecar e2e logs as a build artifact (CL-7472)
The e2e job's hub/sidecar output was never captured, so the CI run
that surfaced the sidecar disconnect loop (34042907014) had no
sidecar-side log to read past the transcript. Point E2E_LOG_DIR at a
runner-temp directory and upload it on every run, pass or fail.
* e2e: lint fixes
* e2e: seeded offerings are launchable at pin 692c3106 (CL-7473) (#664)
* Add test for workflowDeployBody's catalog-offering shape
* e2e: deploy workflows by catalog offering ids
The native workflow deploy route now requires sourceOfferingIds/
defaultSourceOfferingId resolved against the tenant's own catalog,
rather than a raw provider/baseURL/apiKey source chain. Update
workflowDeployBody accordingly, and extract the zero-cost noop
catalog chain smoke-webhook.test.ts already built (an anthropic-
plugin provider pointed at the hub's noop-inference endpoint) into
a shared seedNoopCatalogOffering harness helper.
Every deploying e2e suite now seeds or resolves a real catalog
offering before deploying: walking-skeleton keeps a never-real
placeholder host (assertNeverRealProvider still guards it), chat
resolves the offering seedCatalog already planted rather than
seeding a second chain, and heartbeat/workbench-digest/smoke-webhook
seed the noop chain via the shared helper. cl-6324-launch-proof's
section-mode deploy now reuses the offering id the proof already
resolved when it narrowed the bench catalog to its pinned model.
* Fix formatting
* Add tests for deploying default workflows by catalog offering ids
seedTenant currently deploys every default workflow with a bare
provider/baseURL/apiKey source, but the hub's deploy route now requires
sourceOfferingIds/defaultSourceOfferingId resolved from the tenant's
catalog. Update the seeding, onboarding, and hub config test suites to
expect the new offering-id based deploy body and provider/model-only
ModelSource shape ahead of the implementation change.
* Seeding: deploy default workflows by catalog offering ids
ensureDeployment posted a bare {provider, baseURL, apiKey, model}
source, but the hub's deploy route requires sourceOfferingIds and a
defaultSourceOfferingId resolved from the tenant's catalog, so seeding
a connect flow's "assistant" workflow 400'd. seedTenant now resolves
the tenant's real catalog offerings (sorted by priority, lowest wins
the default) for a normal workflow, and plants a dedicated noop
catalog offering for a modelSource-pinned one (heartbeat), pointed at
the hub's own noop-inference endpoint. ModelSource shrinks to a bare
provider/model pair — the baseURL/apiKey it used to carry only mattered
for the old deploy body, and callers that still need a key for seeding
a catalog (system-seed's root tenant, config's env-key auto-plant) keep
it on their own local type instead.
* longevity-sim: deploy workflows by catalog offering ids
Another workflowDeployBody caller CI's typecheck job caught: this
stack seeds one catalog offering per real inference target already,
so track each one's id and pass it through to the deploy body
instead of the raw provider/baseURL/apiKey shape.
* Fix formatting: prettier
* sandbox-sidecar: add tests for generation-0 ensure/destroy requests
The allocation service's initial sidecar_allocation and workflow_probe
rows start at generation 0 (@intx/db enforces generation >= 0, not >
0), so the first ensure() for a freshly allocated sidecar must be
accepted at generation 0. These tests cover that acceptance, later
generations, non-integer/negative rejection, and that stale-generation
fencing still rejects an older generation once a newer one has been
observed.
* sandbox-sidecar: accept the allocation service's initial generation
@intx/db's sidecar_allocation and workflow_probe tables both check
generation >= 0 and the allocation service records the first row for
an allocation at generation 0, but validateEnsureRequest() and
validateDestroyRequest() in packages/sandbox-sidecar rejected anything
that wasn't strictly positive, and the on-disk fencing schema in
state-store.ts required generation > 0. The first ensure() for any
newly allocated sidecar was rejected with 'generation must be a
positive integer', failing every deploy.
Accept generation >= 0 in both validators and the state-store schema;
stale-generation fencing in observeEnsure()/observeDestroy() already
compares against the stored generation rather than assuming positivity,
so it is unaffected.
* sandbox-sidecar: fix formatting in provisioner test
* Add tests for resolved catalog offerings and modelSourceFor's trimmed signature
Covers the new GET .../catalog/resolved-offerings route (denied grant,
and both directly-owned and ancestor-inherited offerings sorted by
priority) and updates seeding/onboarding test fixtures to the route's
response shape ahead of the implementation change.
* Seed deploys against a tenant's resolved catalog offerings, not just owned ones
resolveRealSourceOfferingIds listed only offerings a tenant owns
directly, missing anything inherited from an ancestor tenant — the
hub's own workflow deployer already resolves that wider,
ancestor-inheriting view via listVisibleOfferings. Adds
GET /api/tenants/:tenantId/catalog/resolved-offerings (mounted from
apps/hub, implemented in @corbits/inference-catalog so the deployer and
this route share one source of truth) and points seed at it instead.
Also drops the two long-dead unused parameters from
onboarding's modelSourceFor, and exports ensureNoopCatalogOffering from
@corbits/seeding for the e2e harness to reuse.
* Add a regression test pinning the shared-sidecar auth gap (CL-7472)
At the interchange pin this branch stacks on (692c3106), the hub
authenticates a sidecar's register/reconnect frame only against a
sidecar row that resolves to a live sidecar_allocation (or
workflow_probe) row. Upstream dropped the older "shared" credential
scope this repo's e2e harness used to provision a single, standalone
sidecar identity for, so a provisionSidecar-only row now connects and
is rejected with "invalid token" in a loop rather than ever dialing
in. This test pins that negative so a future re-vendor that silently
restores (or relies on) the old scope surfaces here first.
* e2e: stop pre-provisioning a shared sidecar identity (CL-7472)
Every e2e suite dial-in relied on a single, harness-started sidecar
process registered against a bare `provisionSidecar` identity row.
At pin 692c3106, the hub only accepts a register/reconnect frame from
a sidecar whose identity resolves to a live sidecar_allocation (or
workflow_probe) row; a standalone identity with neither now fails the
handshake and gets rejected with "invalid token" every ~3s, forever
(chat mint 500s, deploys never dial in — CI run 34042907014).
The hub's own process-provisioner already spawns a dedicated,
correctly-allocated sidecar process per deployment on demand — the
same path production already takes for a real workflow run or a
minted agent chat. Every affected suite (walking skeleton, chat,
heartbeat, workbench-digest, smoke-chat, smoke-webhook,
greeting-delivery, channel-reconnect, local-rip, and the isolation
suite) now lets that happen instead of dialing in its own sidecar:
the existing 502/500 retry-until-ready loops already cover the wait,
and diagnostics that used to read the one sidecar process's output
now read the hub's — the process-provisioner backend spawns each
sidecar with its stdio inherited from the hub, so the hub's own
captured output already carries every spawned sidecar's log lines.
`provisionSidecar`/`startSidecar` stay in the harness (documented as
non-authenticating at this pin) for the handful of standalone proof
scripts not yet migrated off a directly-controlled sidecar process.
* ci: upload hub/sidecar e2e logs as a build artifact (CL-7472)
The e2e job's hub/sidecar output was never captured, so the CI run
that surfaced the sidecar disconnect loop (34042907014) had no
sidecar-side log to read past the transcript. Point E2E_LOG_DIR at a
runner-temp directory and upload it on every run, pass or fail.
* e2e: lint fixes
* Add a test pinning the noop catalog offering's model name
Exports NOOP_CATALOG_MODEL from the e2e harness and adds a unit test
asserting seedNoopCatalogOffering plants a catalog model with that
exact canonicalName, so a future edit can't silently drift the
planted (provider, model) pair away from what a suite's workflow
fixture declares as its inference preference.
* walking-skeleton e2e: match the seeded catalog offering's model
The suite's echo workflow declared an inference preference of
anthropic:claude-sonnet-5, while seedNoopCatalogOffering plants a
catalog offering for anthropic:noop. At the re-vendored intx pin, the
deploy-time capability walk only auto-approves the (provider, model)
pair a step's own agent declares (@intx/workflow-deploy's
pickStepInferenceSource), so the mismatch left neither the preferred
source nor the deploy's defaultSource resolvable against an approved
grant, and the deploy 409ed invalid_workflow before the run could
ever start. Naming the same (provider, model) the seeded offering
serves — like every other e2e suite already does — lets the deploy
resolve a source it can actually approve.
* walking-skeleton e2e: match the seeded catalog offering's model
The suite's echo workflow declared an inference preference of
anthropic:claude-sonnet-5, while seedPlaceholderCatalogOffering
plants a catalog offering for anthropic:noop. The deploy-time
capability walk only auto-approves the (provider, model) pair a
step's own agent declares (@intx/workflow-deploy's
pickStepInferenceSource), so the mismatch left neither the preferred
source nor the deploy's defaultSource resolvable against an approved
grant, and the deploy 409ed invalid_workflow before the run could
ever start. Naming PLACEHOLDER_MODEL — the same (provider, model)
the seeded offering serves — lets the deploy resolve a source it can
actually approve.
* walking-skeleton: drop the literal-asserting test
Per the owner's 2026-09-06 ruling to test only non-obvious owned
logic and never literals, remove the unit test asserting
seedPlaceholderCatalogOffering names PLACEHOLDER_MODEL.
* Chat: workbench_launch lookups are tenant-scoped (CL-7474) (#665)
* Add test for workflowDeployBody's catalog-offering shape
* e2e: deploy workflows by catalog offering ids
The native workflow deploy route now requires sourceOfferingIds/
defaultSourceOfferingId resolved against the tenant's own catalog,
rather than a raw provider/baseURL/apiKey source chain. Update
workflowDeployBody accordingly, and extract the zero-cost noop
catalog chain smoke-webhook.test.ts already built (an anthropic-
plugin provider pointed at the hub's noop-inference endpoint) into
a shared seedNoopCatalogOffering harness helper.
Every deploying e2e suite now seeds or resolves a real catalog
offering before deploying: walking-skeleton keeps a never-real
placeholder host (assertNeverRealProvider still guards it), chat
resolves the offering seedCatalog already planted rather than
seeding a second chain, and heartbeat/workbench-digest/smoke-webhook
seed the noop chain via the shared helper. cl-6324-launch-proof's
section-mode deploy now reuses the offering id the proof already
resolved when it narrowed the bench catalog to its pinned model.
* Fix formatting
* Add tests for deploying default workflows by catalog offering ids
seedTenant currently deploys every default workflow with a bare
provider/baseURL/apiKey source, but the hub's deploy route now requires
sourceOfferingIds/defaultSourceOfferingId resolved from the tenant's
catalog. Update the seeding, onboarding, and hub config test suites to
expect the new offering-id based deploy body and provider/model-only
ModelSource shape ahead of the implementation change.
* Seeding: deploy default workflows by catalog offering ids
ensureDeployment posted a bare {provider, baseURL, apiKey, model}
source, but the hub's deploy route requires sourceOfferingIds and a
defaultSourceOfferingId resolved from the tenant's catalog, so seeding
a connect flow's "assistant" workflow 400'd. seedTenant now resolves
the tenant's real catalog offerings (sorted by priority, lowest wins
the default) for a normal workflow, and plants a dedicated noop
catalog offering for a modelSource-pinned one (heartbeat), pointed at
the hub's own noop-inference endpoint. ModelSource shrinks to a bare
provider/model pair — the baseURL/apiKey it used to carry only mattered
for the old deploy body, and callers that still need a key for seeding
a catalog (system-seed's root tenant, config's env-key auto-plant) keep
it on their own local type instead.
* longevity-sim: deploy workflows by catalog offering ids
Another workflowDeployBody caller CI's typecheck job caught: this
stack seeds one catalog offering per real inference target already,
so track each one's id and pass it through to the deploy body
instead of the raw provider/baseURL/apiKey shape.
* Fix formatting: prettier
* sandbox-sidecar: add tests for generation-0 ensure/destroy requests
The allocation service's initial sidecar_allocation and workflow_probe
rows start at generation 0 (@intx/db enforces generation >= 0, not >
0), so the first ensure() for a freshly allocated sidecar must be
accepted at generation 0. These tests cover that acceptance, later
generations, non-integer/negative rejection, and that stale-generation
fencing still rejects an older generation once a newer one has been
observed.
* sandbox-sidecar: accept the allocation service's initial generation
@intx/db's sidecar_allocation and workflow_probe tables both check
generation >= 0 and the allocation service records the first row for
an allocation at generation 0, but validateEnsureRequest() and
validateDestroyRequest() in packages/sandbox-sidecar rejected anything
that wasn't strictly positive, and the on-disk fencing schema in
state-store.ts required generation > 0. The first ensure() for any
newly allocated sidecar was rejected with 'generation must be a
positive integer', failing every deploy.
Accept generation >= 0 in both validators and the state-store schema;
stale-generation fencing in observeEnsure()/observeDestroy() already
compares against the stored generation rather than assuming positivity,
so it is unaffected.
* sandbox-sidecar: fix formatting in provisioner test
* Add tests for resolved catalog offerings and modelSourceFor's trimmed signature
Covers the new GET .../catalog/resolved-offerings route (denied grant,
and both directly-owned and ancestor-inherited offerings sorted by
priority) and updates seeding/onboarding test fixtures to the route's
response shape ahead of the implementation change.
* Seed deploys against a tenant's resolved catalog offerings, not just owned ones
resolveRealSourceOfferingIds listed only offerings a tenant owns
directly, missing anything inherited from an ancestor tenant — the
hub's own workflow deployer already resolves that wider,
ancestor-inheriting view via listVisibleOfferings. Adds
GET /api/tenants/:tenantId/catalog/resolved-offerings (mounted from
apps/hub, implemented in @corbits/inference-catalog so the deployer and
this route share one source of truth) and points seed at it instead.
Also drops the two long-dead unused parameters from
onboarding's modelSourceFor, and exports ensureNoopCatalogOffering from
@corbits/seeding for the e2e harness to reuse.
* Add a regression test pinning the shared-sidecar auth gap (CL-7472)
At the interchange pin this branch stacks on (692c3106), the hub
authenticates a sidecar's register/reconnect frame only against a
sidecar row that resolves to a live sidecar_allocation (or
workflow_probe) row. Upstream dropped the older "shared" credential
scope this repo's e2e harness used to provision a single, standalone
sidecar identity for, so a provisionSidecar-only row now connects and
is rejected with "invalid token" in a loop rather than ever dialing
in. This test pins that negative so a future re-vendor that silently
restores (or relies on) the old scope surfaces here first.
* e2e: stop pre-provisioning a shared sidecar identity (CL-7472)
Every e2e suite dial-in relied on a single, harness-started sidecar
process registered against a bare `provisionSidecar` identity row.
At pin 692c3106, the hub only accepts a register/reconnect frame from
a sidecar whose identity resolves to a live sidecar_allocation (or
workflow_probe) row; a standalone identity with neither now fails the
handshake and gets rejected with "invalid token" every ~3s, forever
(chat mint 500s, deploys never dial in — CI run 34042907014).
The hub's own process-provisioner already spawns a dedicated,
correctly-allocated sidecar process per deployment on demand — the
same path production already takes for a real workflow run or a
minted agent chat. Every affected suite (walking skeleton, chat,
heartbeat, workbench-digest, smoke-chat, smoke-webhook,
greeting-delivery, channel-reconnect, local-rip, and the isolation
suite) now lets that happen instead of dialing in its own sidecar:
the existing 502/500 retry-until-ready loops already cover the wait,
and diagnostics that used to read the one sidecar process's output
now read the hub's — the process-provisioner backend spawns each
sidecar with its stdio inherited from the hub, so the hub's own
captured output already carries every spawned sidecar's log lines.
`provisionSidecar`/`startSidecar` stay in the harness (documented as
non-authenticating at this pin) for the handful of standalone proof
scripts not yet migrated off a directly-controlled sidecar process.
* ci: upload hub/sidecar e2e logs as a build artifact (CL-7472)
The e2e job's hub/sidecar output was never captured, so the CI run
that surfaced the sidecar disconnect loop (34042907014) had no
sidecar-side log to read past the transcript. Point E2E_LOG_DIR at a
runner-temp directory and upload it on every run, pass or fail.
* Add tests for tenant-scoped workbench_launch address resolution (CL-7474)
* Chat: workbench_launch address/stableId lookups are tenant-scoped
readBindingByAddress and resolveLiveByStableId took an address or
stable id and resolved whichever workbench_launch row matched it,
regardless of which tenant that row belonged to. Every caller that
already knows its own tenant (sendMail's requireLive, and
refreshAgentInstanceFromDefinition) now passes it through and gets
undefined instead of another tenant's launch if the two ever
disagree. The one legitimate tenant-agnostic caller — the event
stream handler discovering which tenant an inbound address even
belongs to before it has one to check against — keeps working by
simply omitting the new optional parameter.
Also fixes offeringDigest: it joined catalog offering ids with a
NUL byte, which is not valid UTF-8 to Postgres the moment a tenant's
catalog carries more than one visible offering ("invalid byte
sequence for encoding \"UTF8\": 0x00"), permanently failing the
workbench_launch insert for every such launch. This is what was
failing 4 of scripts/e2e/chat.test.ts's cases under per-allocation
sidecars, where seedCatalog now produces more than one offering.
* Chat: run prettier on the tenant-scoping change
* Chat: tenant-scoped binding reads require the tenant; the one unscoped read is named
* Chat: run prettier on the tenant-scoping test file
* Deliver first-turn mail once the provisioned sidecar is routable (CL-7476) (#667)
* Add test for workflowDeployBody's catalog-offering shape
* e2e: deploy workflows by catalog offering ids
The native workflow deploy route now requires sourceOfferingIds/
defaultSourceOfferingId resolved against the tenant's own catalog,
rather than a raw provider/baseURL/apiKey source chain. Update
workflowDeployBody accordingly, and extract the zero-cost noop
catalog chain smoke-webhook.test.ts already built (an anthropic-
plugin provider pointed at the hub's noop-inference endpoint) into
a shared seedNoopCatalogOffering harness helper.
Every deploying e2e suite now seeds or resolves a real catalog
offering before deploying: walking-skeleton keeps a never-real
placeholder host (assertNeverRealProvider still guards it), chat
resolves the offering seedCatalog already planted rather than
seeding a second chain, and heartbeat/workbench-digest/smoke-webhook
seed the noop chain via the shared helper. cl-6324-launch-proof's
section-mode deploy now reuses the offering id the proof already
resolved when it narrowed the bench catalog to its pinned model.
* Fix formatting
* Add tests for deploying default workflows by catalog offering ids
seedTenant currently deploys every default workflow with a bare
provider/baseURL/apiKey source, but the hub's deploy route now requires
sourceOfferingIds/defaultSourceOfferingId resolved from the tenant's
catalog. Update the seeding, onboarding, and hub config test suites to
expect the new offering-id based deploy body and provider/model-only
ModelSource shape ahead of the implementation change.
* Seeding: deploy default workflows by catalog offering ids
ensureDeployment posted a bare {provider, baseURL, apiKey, model}
source, but the hub's deploy route requires sourceOfferingIds and a
defaultSourceOfferingId resolved from the tenant's catalog, so seeding
a connect flow's "assistant" workflow 400'd. seedTenant now resolves
the tenant's real catalog offerings (sorted by priority, lowest wins
the default) for a normal workflow, and plants a dedicated noop
catalog offering for a modelSource-pinned one (heartbeat), pointed at
the hub's own noop-inference endpoint. ModelSource shrinks to a bare
provider/model pair — the baseURL/apiKey it used to carry only mattered
for the old deploy body, and callers that still need a key for seeding
a catalog (system-seed's root tenant, config's env-key auto-plant) keep
it on their own local type instead.
* longevity-sim: deploy workflows by catalog offering ids
Another workflowDeployBody caller CI's typecheck job caught: this
stack seeds one catalog offering per real inference target already,
so track each one's id and pass it through to the deploy body
instead of the raw provider/baseURL/apiKey shape.
* Fix formatting: prettier
* sandbox-sidecar: add tests for generation-0 ensure/destroy requests
The allocation service's initial sidecar_allocation and workflow_probe
rows start at generation 0 (@intx/db enforces generation >= 0, not >
0), so the first ensure() for a freshly allocated sidecar must be
accepted at generation 0. These tests cover that acceptance, later
generations, non-integer/negative rejection, and that stale-generation
fencing still rejects an older generation once a newer one has been
observed.
* sandbox-sidecar: accept the allocation service's initial generation
@intx/db's sidecar_allocation and workflow_probe tables both check
generation >= 0 and the allocation service records the first row for
an allocation at generation 0, but validateEnsureRequest() and
validateDestroyRequest() in packages/sandbox-sidecar rejected anything
that wasn't strictly positive, and the on-disk fencing schema in
state-store.ts required generation > 0. The first ensure() for any
newly allocated sidecar was rejected with 'generation must be a
positive integer', failing every deploy.
Accept generation >= 0 in both validators and the state-store schema;
stale-generation fen…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
CI run 34042907014 (PR #659) showed every e2e suite that needs a live run failing after deploy: the hub logged
Connected to hubimmediately followed byDisconnected from hub, repeating every ~3s, with chat mint answering 500 and deploys never dialing in.At pin
692c3106, upstream Interchange dropped the older "shared" credential scope a sidecar identity could carry (vendor/intx/db/migrations/0088_thick_sprite.sqldropssidecar.credential_scopeand deletes every'shared'row; seevendor/intx/hub-sessions/src/ws/sidecar-token-authenticator.ts'screateSidecarCredentialResolver). Every sidecar identity must now resolve to a livesidecar_allocation(orworkflow_probe) row to authenticate a register/reconnect frame (vendor/intx/hub-sessions/src/ws/sidecar-handler.ts'sauthenticateHandshake/handleAllocatedRegister).scripts/e2e/harness.ts'sprovisionSidecaronly ever wrote a baresidecaridentity row with no allocation — the "shared" scope this repo's e2e suites relied on for one, harness-started sidecar process to host every deployment. At this pin that identity never authenticates: it connects and is rejected withinvalid tokenin a loop, forever (apps/hub/src/index.ts'ssidecarWsHandlerclosing the socket on every attempt).Fix
The hub's own process-provisioner (
packages/process-provisioner) already spawns a dedicated, correctly-allocated sidecar process per deployment on demand — the same path production takes for a real workflow run or a minted agent chat, wired in unconditionally byapps/hub/src/index.ts. Every affected e2e suite (walking skeleton, chat, heartbeat, workbench-digest, smoke-chat, smoke-webhook, greeting-delivery, channel-reconnect, local-rip, and the isolation suite) now lets that happen instead of dialing in its own pre-provisioned sidecar: the existing 502/500 retry-until-ready loops already cover the wait, and diagnostics that used to read the one sidecar process's output now read the hub's —process-backend.tsspawns each sidecar with its stdio inherited from the hub, so the hub's own captured output already carries every spawned sidecar's log lines.provisionSidecar/startSidecarstay inscripts/e2e/harness.ts(now documented as non-authenticating at this pin) for the handful of standalone proof scripts (scripts/e2e/cl-6324-launch-proof.ts,cl-6329-turn-swap-proof.ts,cl-6451-single-run-proof.ts,play.ts,browser/walkthrough.ts,packages/evals,packages/longevity-sim) not yet migrated off a directly-controlled sidecar process — those still hit the same defect and are out of scope for this ticket.A new regression test (
scripts/e2e/harness.test.ts) pins the negative directly: aprovisionSidecar-only row never authenticates against the real vendored resolver, so a future re-vendor that silently restores (or a caller that starts relying on) the old scope surfaces here first.Also:
E2E_LOG_DIRis now set for the CIe2ejob and its directory uploaded as a build artifact on every run (pass or fail), so the next sidecar-side failure is diagnosable without a repro.Stacks on #659 → #651 → #657.
Resolves CL-7472.
Verify
bunx tsc --noEmit -p apps/sidecar,-p apps/hub— clean (pre-existing unrelated errors inapps/sidecartest files andSidecarDeployRoutertypings left untouched)bun test apps/sidecar/src apps/hub/src scripts/e2e/harness.test.ts— greenscripts/e2e/walking-skeleton.test.tslocally: the sidecar disconnect loop is gone; it now progresses past deploy into an unrelated, pre-existing inference-catalog/offering-approval 409 from another branch in this stackscripts/e2e/chat.test.tslocally: 6/10 pass (up from 0/10); remaining failures are a separate, pre-existing cross-tenantworkbench_launchbug exposed once concurrent per-allocation sidecars replace the single shared one — filed separately, out of scope here