diff --git a/VENDORED.md b/VENDORED.md index fd0e30abe..e2d2b8a2e 100644 --- a/VENDORED.md +++ b/VENDORED.md @@ -25,37 +25,49 @@ never a convenience. | Vendored path | What was copied | Upstream repo @ commit | Why not a published package | Owner | Kill date | Kill-date test | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------------- | | `apps/sidecar` | Derived from upstream's own `apps/sidecar`: 11 shared modules, of which `signing-keypair.ts` is near-verbatim and the rest (`index.ts`, `config.ts`, `tool-materialization.ts`, `workflow-run-pack-client.ts`, …) are substantially rewritten, plus workbench-only modules. A living fork, not a frozen copy, so this row carries no tree hash. | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | An app is never npm-published, so no publish can cover the execution host; retired by consuming an upstream-published host, or by renewing this row deliberately | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/authz` | `@intx/authz` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/crypto` | `@intx/crypto` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/hub-common` | `@intx/hub-common` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it, or by an upstream `inference.usage`-carrying event stream (CL-5879, whichever lands first) | sawyer | 2026-09-05 | `check:killdates` | -| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/inference-catalog` | `@intx/inference-catalog` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `5d2aa94a` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/log` | `@intx/log` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/pack-transport` | `@intx/pack-transport` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/storage-isogit` | `@intx/storage-isogit` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/tool-packaging` | `@intx/tool-packaging` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `59f5e7b9` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/authz` | `@intx/authz` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/crypto` | `@intx/crypto` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/hub-common` | `@intx/hub-common` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it, or by an upstream `inference.usage`-carrying event stream (CL-5879, whichever lands first) | sawyer | 2026-09-05 | `check:killdates` | +| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/inference-catalog` | `@intx/inference-catalog` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/log` | `@intx/log` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/pack-transport` | `@intx/pack-transport` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/storage-isogit` | `@intx/storage-isogit` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/tool-packaging` | `@intx/tool-packaging` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | +| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `4ed8baf4` | npm 0.2.2 predates the folded model; retired by the next @intx npm publish covering it | sawyer | 2026-09-14 | `check:killdates` | -The pinned commit `59f5e7b9` is the tip of upstream `main` as of 2026-08-18, -a plain main-tip bump from `55c4431e`. The 32 commits it adds are the -source-format workflow line — workflow definitions and tool packages -resolved from a hub asset's git tree rather than a packed tarball, with a -monorepo-aware closure resolver, an atomic materialized asset store, and -pack-boundary limits (object count, per-object inflation, symlink and -submodule rejection). No published `@intx/*` version yet covers any -vendored path: npm still tops out at `0.2.2`, which predates the folded -model, so every row below stays vendored. +The pinned commit `4ed8baf4` is the tip of upstream `main` as of 2026-08-19, +a plain main-tip bump from `59f5e7b9`. The 45 commits it adds retire +`workflow.json`: a deployed workflow's definition is no longer serialized +into the deploy tree and re-read on the sidecar, it is evaluated from the +deployment's own source closure and re-verified in-child against the +approved wire hash. Source-ref is now the only deploy lineage — the +live-authored and instance deploy chains (`createWorkflowDeployOrchestrator`, +`SessionService.deploySingleStepAtHead`, `deployInstanceAtHead`, +`wrapHarnessAsSingleStepWorkflow`) are deleted upstream, `childWorkflow` +became an owned inline import resolved in memory, run grants derive from a +persisted grant-walk snapshot, and the child spawn adapters lost their +deploy-ref arguments (`createInMemorySpawnChild` / +`createInMemorySpawnSuspendableChild`). Eleven rows below carry trees that +are byte-identical at both commits; they move to the new pin so the ledger +records one commit rather than a mix. No published `@intx/*` version yet +covers any vendored path: npm still tops out at `0.2.2`, which predates the +folded model, so every row below stays vendored. + +`apps/sidecar` stays pinned at `59f5e7b9`: workbench's execution host has +not yet been converted off the retired lineage (see CL-6324), so its row +records the last upstream commit its fork was reconciled against. Local modifications (all `vendor/intx/*` rows): each package's exports map is repointed from the upstream `intx-src` resolve condition to direct @@ -103,13 +115,9 @@ subscribed instead of ending the whole run, so one bad turn does not kill a long-lived section. The gate is read live off `primitive.onBodyFailure` at both the steady-state drive loop and the crash-recovery resume plan in `runtime/run.ts`, mirroring how `awaitSignal.onTimeout` is read live rather -than defaulted at construction. This delta targets the current pin -(`59f5e7b9`) and re-applies against the re-pinned tree once PR #59 lands — -see `docs/revendor-inventory.md`. `vendor/intx/inference-catalog` (CL-6280) is -pinned separately at `5d2aa94a`, a later `main` tip than the other twenty -rows' `59f5e7b9`, since that commit is where the package's folded -provider/model catalog first landed upstream; its own local modification -also repoints the `./models` subpath's exports, not just the root export. +than defaulted at construction. `vendor/intx/inference-catalog`'s own local +modification also repoints the `./models` subpath's exports, not just the +root export. Each package's `VENDORED-FROM` file restates its own delta. ### Un-vendoring `vendor/intx` diff --git a/docs/revendor-inventory.md b/docs/revendor-inventory.md index 9b3cbdfbe..346379f57 100644 --- a/docs/revendor-inventory.md +++ b/docs/revendor-inventory.md @@ -319,9 +319,9 @@ non-fatal to the section. Covered by `vendor/intx/workflow/src/runtime/run.test.ts` — the intended home `runlocal/run-local.ts:8` already names for this layer's coverage. -**This delta targets the current pin** (`59f5e7b9`). Upstream's `486a6b1b` -changed `run.ts` after this pin; the delta above re-diffs against the -re-pinned tree once PR #59 lands. +**Re-applied at `4ed8baf4`.** Upstream's `486a6b1b` reshaped `run.ts` across +the re-pin; the delta re-diffed onto the new tree without conflict and its +four tests stay green. **Retire when** upstream ships a non-fatal body-failure edge on `onTrigger` (or the whole source-format workflow line reaches a published `@intx/*` npm @@ -357,15 +357,15 @@ see below), but the two adapters that wiring needs now exist. - `vendor/intx/workflow-host/src/adapters/{action-invoker,effect-ledger,run-blobs}.ts` (+ their tests), copied from gtm-workbench's `packages/workflow-host/src/ - adapters/`. Important provenance note carried over from the port scope +adapters/`. Important provenance note carried over from the port scope report: **these three files are gtm-OWNED, not upstream Interchange.** Upstream `faremeter/interchange`'s own `packages/workflow-host/src/ - adapters/` (at the pinned `59f5e7b9`) has none of them — only blob- +adapters/` (at the pinned `59f5e7b9`) has none of them — only blob- substrate/repo-store/spawn-child/step-invoker. So there is no upstream publish this sub-delta tracks toward; it is copy-with-attribution from a sibling internal fork, not a normal "vendored until the next npm publish" entry. The ordinary `VENDORED.md` kill date on the `vendor/intx/ - workflow-host` row does not really apply to this slice — treat it as +workflow-host` row does not really apply to this slice — treat it as retired only once workbench builds its own equivalent inline (dropping the gtm-origin copy) or gtm's fork itself gets folded into a real publish, whichever happens first. @@ -388,12 +388,8 @@ see below), but the two adapters that wiring needs now exist. **What did NOT land (deliberately, time-boxed):** the run-child wiring (`resolveActionHandler` bindings field, `effects`/`invokeAction` construction -in `buildRuntimeEnv`, `loopFns`) is a separate final commit on this branch, -explicitly marked in its message as needing re-application after the -in-flight `vendor/intx` re-pin lands (a sibling change was re-pinning -concurrently with this port) — `buildRuntimeEnv` is exactly the kind of -generated/copied vendor surface a re-pin silently regenerates, so the wiring -diff would otherwise be dropped on the floor. The app-seam wiring in +in `buildRuntimeEnv`, `loopFns`) landed as a separate commit and has since +been re-applied onto the `4ed8baf4` tree unchanged. The app-seam wiring in `apps/sidecar/src/workflow-substrate-factory/index.ts` (building the registry and passing it as `resolveActionHandler`) is likewise deferred to that same follow-up, since it depends on the run-child binding field existing first. @@ -402,3 +398,178 @@ follow-up, since it depends on the run-child binding field existing first. src/adapters/blob-substrate.ts` already has inline (private `writeBlob`/ `readBlob` helpers) rather than reconciling the two into one shared helper — left as a known follow-up per the port scope report, not a blocker. + +## CL-6324 re-pin: `59f5e7b9` → `4ed8baf4` (the workflow.json retirement) + +The vendored trees are re-copied at upstream `main` tip `4ed8baf4` +(2026-08-19, 45 commits on). `VENDORED.md` is the pin of record. This +section is the map of what the bump costs on the workbench side, because +the app-side conversion does **not** land with it. + +### What landed cleanly + +- All 21 `vendor/intx/*` rows re-copied. Only nine trees actually changed + (`hub-sessions`, `workflow-host`, `workflow`, `workflow-deploy`, `types`, + `hub-api`, `db`, `agent`, `hub-agent`); the other twelve are byte-identical + at both commits and were re-pinned so the ledger records one commit rather + than a mix (this also collapses `inference-catalog`'s separate `5d2aa94a` + pin). +- Every workbench-local delta re-applied unchanged — upstream subsumed none + of them, and none of the five files they touch was modified upstream in the + 45 commits: the `inference.usage` forward, `ownsWorkflowRunRepo`, + `hasConversationText`, and the `needs-you` approval-route carve-out. Their + tests pass. +- `packages/folded-runs`' `wrapHarnessAsSingleStepWorkflow` call moved onto + `buildSingleStepAgentDefinition`, which survives the deletion. + +### What the bump breaks, and why it is one migration + +Upstream retired the on-disk `workflow.json`. A deployed workflow's +definition is no longer serialized into the deploy tree and re-read by the +sidecar; it is evaluated from the deployment's own **source closure** and +re-verified in-child against the approved wire hash. Source-ref is now the +only deploy lineage, and the live-authored and instance chains are deleted: +`createWorkflowDeployOrchestrator`, `SessionService.deploySingleStepAtHead`, +`SessionService.deployInstanceAtHead`, `wrapHarnessAsSingleStepWorkflow`, +`createWorkflowSpawnChild`, `createWorkflowSpawnSuspendableChild`, +`loadVerifiedWorkflowDefinition`, and the `definition` field on the deploy +frame. `SpawnTimeEnv` drops `referencedDefinitionHashes` and gains +`closurePackageDir`; `RunWorkflowChildBindings` drops +`workflowDefinitionRepoId`. + +Workbench has no code-sourced deploy front. Every run it launches — chat, +tasks, routines, agent lifecycle — goes through `packages/folded-runs`' +`deployAtHead`, which synthesizes a single-step definition in memory from a +system prompt plus tool-package pins and hands it to `deploySingleStepAtHead`. +The new front (`deployWorkflowFromSource` / `installAndApproveWorkflowSource` +/ `deployPreparedCodeSourcedWorkflow`) takes a registry `name@range` pin or +an asset tarball and resolves a dependency closure from it. Converting means +giving a folded run a real source package, not renaming a call. + +That is why the remaining breakage cannot be split by tree: +`hub-sessions` (deploy front), `workflow-deploy` (orchestrator), `types` +(deploy frame), `workflow-host` (child definition load), `db` (frozen +approval bundle, migrations 0082/0083) and `hub-api` (run trigger) all move +together, and `apps/sidecar` reads the frame both sides write. Leaving any +one on the old pin leaves the frame contract split down the middle. + +Open conversion sites, all blocked on that one decision: + +| Site | What it needs | +| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| `packages/folded-runs/src/launch.ts` (`deployAtHead`), `wake.ts` | A code-sourced deploy for the folded single-step run — the root blocker. | +| `apps/sidecar/src/workflow-host-wiring/index.ts`, `asset-materialization.ts` | Stop writing `workflow.json` and stop reading `projection.definition`; stage the closure instead. | +| `apps/sidecar/src/workflow-substrate-factory/index.ts`, `child-runtime.ts`, `config.ts` | Drop `WORKFLOW_DEFINITION_REPO_ID`/`_REF`; in-memory child spawn; `closurePackageDir` plumbing. | +| `apps/sidecar/src/workflow-deployment-record.ts` | Drop `referencedDefinitionHashes`; carry the grant-walk snapshot. | + +Upstream's own diff over the same span is the reference implementation: +`apps/sidecar/src/workflow-substrate-factory.ts` and +`workflow-host-wiring.ts` at `4ed8baf4` show every one of these conversions +against the same contracts, and `apps/sidecar`'s `VENDORED.md` row stays at +`59f5e7b9` until workbench's fork is reconciled with them. + +### Why the conversion is one chain, not four independent sites + +Re-basing the re-pin onto post-merge `main` (133 commits, including the +`#58` workflow-host adapters and the `#68` `onTrigger` edge, both re-applied +onto the re-vendored trees and green) leaves the four sites above still red, +and they cannot be taken in any other order than this one: + +1. **`packages/folded-runs`' `deployAtHead` must become a code-sourced + deploy.** `deployWorkflowFromSource` / + `deployPreparedCodeSourcedWorkflow` take a `WorkflowDefinitionSource` + (`vendor/intx/types/src/workflow-sources.ts`) — an npm `registry` pin, an + asset `tarball` selected by a `name@range` pin, or an asset `source` tree + at a `commitSha`. Workbench has no caller of any of them today: grepping + `apps/`, `packages/`, `workflows/`, and `scripts/` for + `deployWorkflowFromSource` returns nothing. So this is not a call-site + rename — it is a new deploy pipeline (mint the `workflow`-kind definition + asset, seed the source asset's tarball, configure + `toolPackageRegistries`, supply the attachment resolver and asset + service). +2. **A folded run's definition is per-run, a source package is not.** The + synthesized definition varies by `systemPrompt`, `trigger.to` + (the run's own mail address), resolved inference sources, and the + `credentialBindings` folded in for `@corbits/mcp-tools`. A seeded tarball + is static, so the varying half has to move out of the definition body and + into deploy-time config the entry module reads. That is a design change + to the folded-run contract, not a mechanical port. +3. **Only then can the sidecar convert.** `SpawnTimeEnv` drops + `referencedDefinitionHashes` and gains `closurePackageDir` + (`vendor/intx/workflow-host/src/child/env-bootstrap.ts:126`), and + `createInMemorySpawnChild` / `createInMemorySpawnSuspendableChild` + (`adapters/spawn-child.ts:115,220`) take a `bodies: ReadonlyMap` lifted from the re-evaluated closure — not a deploy + ref. There is no closure dir to stage and no bodies map to lift until a + deploy actually ships a source closure, so swapping the spawners ahead of + step 1 could only be done by re-deriving the bodies from the retired + in-memory definition: precisely the fallback shim this conversion is not + allowed to build. + +The sidecar's three remaining red suites +(`test/workflow-substrate-factory-suspendable-child.test.ts` and siblings, +failing on the deleted `createWorkflowSpawnChild` export) are the visible +tail of that same chain. + +### Section deploys on the new rails (CL-6329's prerequisite) + +Phase 1.3's turn=run swap needs the converted front to deploy an `onTrigger` +SECTION definition carrying `referencedDefinitions`, not just the single-step +folded agent. Checked against the re-vendored trees, and the answer is that +the new front handles this **natively and unconditionally** — it is strictly +more natural here than on the retired path: + +- `deployCodeSourcedWorkflow` + (`vendor/intx/hub-sessions/src/session-service.ts:745`) calls + `enumerateInertOnTriggerBodies(projection)` on **every** code-sourced + deploy. It resolves each body step's inference source through the same + `pickStepInferenceSource` + operator-approval gate the top-level steps use, + recomputes each body's wire hash from the frozen inert body, and ships the + set as `referencedDefinitions` on the deploy frame. +- There is no caller opt-in and no second entry point. A definition with no + inline section simply enumerates zero bodies and the field is omitted. + +**So the launch API needs no single-step/section branch.** Which shape gets +deployed is entirely a property of what the source package's +`interchange.workflow` entry module evaluates to. One front, one set of +parameters (source, entry, pin, `definitionAssetId`, config); the section-ness +is downstream of it. A converted `deployAtHead` that exposes a +"deploy a section" flag would be modelling a distinction the platform does +not have. + +Two corrections to the seams as cited from the old pin: + +- `vendor/intx/workflow-deploy/src/orchestrator.ts:1020` no longer exists — + the re-pin cut that file from 1094 lines to 319 (`createWorkflowDeployOrchestrator` + is deleted). Body enumeration now lives in `hub-sessions`' code-sourced + deploy, and the pure structural half in + `workflow-deploy/src/inert-ontrigger-bodies.ts`. +- `vendor/intx/workflow/src/ontrigger-bodies.ts:43` (`onTriggerBodyRef`) is + intact and is now the single owner of the `__` scheme, + shared by the hub's inert enumerator and the in-child rewrite. + +#### `onBodyFailure` survives the new rails, but only because of where it is read + +The CL-6326 delta adds `onBodyFailure` to the LIVE `onTrigger` primitive and +reads it live in `runtime/run.ts`. The inert projector does **not** carry it: +`projectOnTrigger` (`vendor/intx/workflow/src/live-inert-projector.ts:376`) +is an explicit field whitelist — `kind`, `id`, `on`, `body`, `drainBehavior`, +`after` — and `InertOnTrigger` has no such field. + +On the code-sourced rails this is harmless, and in fact load-bearing in our +favour. The child EVALUATES the pinned closure to a live definition and runs +the runtime against that (`run-child.ts:645-649`); the inert projection is +only the approval/hash surface. So: + +- `onBodyFailure` reaches the runtime, because the entry module authored it + and the child re-evaluates the entry module. +- `onBodyFailure` is invisible to `computeLiveDefinitionHash`, because the + hash is taken over the projection that drops it — so the field cannot + cause a re-verify divergence. + +The consequence worth stating plainly: the delta is **only** sound on a path +where the executing definition is re-evaluated from source. Anything that +ships the projection as the executable definition would silently drop the +policy and take the default `"end"` — a section that dies on its first failed +turn, with no error. That is one more reason the conversion cannot be +half-taken. diff --git a/packages/folded-runs/src/launch.ts b/packages/folded-runs/src/launch.ts index 6ac2a2cc2..0a3b5c458 100644 --- a/packages/folded-runs/src/launch.ts +++ b/packages/folded-runs/src/launch.ts @@ -28,7 +28,7 @@ import { generateId } from "@intx/hub-common"; import { InferenceSource } from "@intx/types/runtime"; import type { WireGrantRule } from "@intx/types/grant-wire"; import { - wrapHarnessAsSingleStepWorkflow, + buildSingleStepAgentDefinition, type FoldedBody, } from "@intx/workflow-deploy"; import { defineWorkflow, step, type Selector } from "@intx/workflow"; @@ -279,15 +279,23 @@ export async function deployAtHead( }; const deployContent = { systemPrompt: params.foldedBody.systemPrompt }; // A folded run is a conversation: its one step must service every - // inbound mail as another turn, never complete after the first. The - // platform's `deployInstanceAtHead` wraps the agent as a step with the - // default trigger budget of 1 (batch), which is exactly what made every - // chat go silent after its first real reply — so the folded launch - // builds the same single-step workflow itself, with the budget - // declared, and deploys it through the same head deploy. + // inbound mail as another turn, never complete after the first. A wrap + // with the platform's default trigger budget of 1 (batch) is exactly what + // made every chat go silent after its first real reply — so the folded + // launch builds the single-step agent itself, with the budget declared, + // and deploys it through the same head deploy. The launch pins its tools + // as packages rather than factories, so the step agent carries none. const foldedSteps = { [FOLDED_STEP_ID]: step({ - agent: wrapHarnessAsSingleStepWorkflow({ config, deployContent }), + agent: buildSingleStepAgentDefinition({ + id: config.agentId, + systemPrompt: deployContent.systemPrompt, + inferencePreferences: config.sources.map((source) => ({ + provider: source.provider, + model: source.model, + })), + toolFactories: [], + }), triggers: "unbounded", ...(params.stepInput !== undefined ? { input: params.stepInput } : {}), }), diff --git a/scripts/checks/kill-dates.txt b/scripts/checks/kill-dates.txt index d47883bc0..e7e1f4bb9 100644 --- a/scripts/checks/kill-dates.txt +++ b/scripts/checks/kill-dates.txt @@ -14,27 +14,27 @@ # drift: editing a vendored tree means updating this hash and the # package's VENDORED-FROM delta line in the same change. apps/sidecar | sawyer | 2026-09-14 -vendor/intx/agent | sawyer | 2026-09-14 | 478b17dfc4e71da8c7b15b51bdf74d6476459e58c7a251aa3eaabfdda6276d45 -vendor/intx/authz | sawyer | 2026-09-14 | 38d6760b35a9ce4ccbadc07b1d4d1d24fbe50dbde2200179009035b7c08bf2ec -vendor/intx/crypto | sawyer | 2026-09-14 | 98cec0405dec9eadc8daeae161231b9bc980cb076daf43ef4f617a3b1c1d7096 -vendor/intx/db | sawyer | 2026-09-14 | 0841456d5d983773847e442af0813db12705978e866adebe6d3dceed1a6952a8 -vendor/intx/harness | sawyer | 2026-09-14 | 70e6b3dccca2d596c3992911e4515da7f08c2d5df8815c582ea970671c9424ce -vendor/intx/hub-agent | sawyer | 2026-09-14 | 4426f2436a79e98e29f9ba562895e7d909cb685117af16f0fdb7eaa6a2bfcdbb -vendor/intx/hub-api | sawyer | 2026-09-14 | f821fb1204ba73892d9f0b5f40fcd4ccdbf14b33ce52f077031f053efd31189b -vendor/intx/hub-common | sawyer | 2026-09-14 | adf9027bab1c7ebfb627b739c22fd1ce1aeec826ba12c4288564f843bee788f1 -vendor/intx/hub-sessions | sawyer | 2026-09-05 | e3c2e39bfbb8d483271bf2030ecf8c920927a3a3a0ac32b6f25fb47795abab1a -vendor/intx/inference | sawyer | 2026-09-14 | a4092dc6a43afb1870fe1e47fa6d27433cab25f7ab857ce386c37c71c5610717 -vendor/intx/inference-catalog | sawyer | 2026-09-14 | 7b8fdcc0357d40f265609ddd8b75f517d94d4e0662617e9f9c59aba6e5bfe5cd -vendor/intx/log | sawyer | 2026-09-14 | b91343965c0feea11051c7f62a7b71e218330c299ef78ba9ef35e663527e6bc3 -vendor/intx/mail-memory | sawyer | 2026-09-14 | 7cd5416cec904d904cf3f8e183b7417cd1bfee85f800031d85fc0d302ce3d744 -vendor/intx/mime | sawyer | 2026-09-14 | 28fbfaf77bd90eeaa2c58735dc8583df7a7fd85327d6489c153b93cdb56b5b76 -vendor/intx/pack-transport | sawyer | 2026-09-14 | 81f230269ae916111bd7242698f4e40c4eb49934cebea47fb16d80dd78e13348 -vendor/intx/storage-isogit | sawyer | 2026-09-14 | 26f30a4fd27645a620bded1e9359fbb8d87d20f759b9e02c52e213efb459ca73 -vendor/intx/tool-packaging | sawyer | 2026-09-14 | 47f29256729105eebab38b23b7326ec9fadc9ebcb9ab460eeba44f94a86d7e45 -vendor/intx/types | sawyer | 2026-09-14 | 29d8a7b2589979a04a38706e40e14491a4c57856d6368f74e944c386afed2ef7 -vendor/intx/workflow | sawyer | 2026-09-14 | 81d7ff7b8cfde64ab8b7422f393b5f46c9e98a8af341685f099b01c71ea17197 -vendor/intx/workflow-deploy | sawyer | 2026-09-14 | de72de087e7b499d42b69b7eae6185a4c5448c34a02d3c21badf7c6d0ad3b66d -vendor/intx/workflow-host | sawyer | 2026-09-14 | 2cc2fc754bf195ee0eab4d36e7e2ff0d7600f5111dcb5ddc7ae3e4412412d0bb +vendor/intx/agent | sawyer | 2026-09-14 | 797f8aa6a6fb3986c3c8c3e4df396e1378294b21411a24089e8ddb19d5df304e +vendor/intx/authz | sawyer | 2026-09-14 | 9d760ef9b5037cead31a2224821e5913c8e7eb66d851271d36a473d4a0e68337 +vendor/intx/crypto | sawyer | 2026-09-14 | 7280b002b09da04b81a53d413580c88c8786f02310408a67f1187b9ca519aba5 +vendor/intx/db | sawyer | 2026-09-14 | 8c8f5379799d6549daba0237db0a830f91fed4ccdbcdde015b645f4d714781bc +vendor/intx/harness | sawyer | 2026-09-14 | af9b270a297ae1dc6d8684da9005ec9d3d6220d1679e5f569086ef313e17f371 +vendor/intx/hub-agent | sawyer | 2026-09-14 | 6402193dfe48dce3525c9b233bd6974e566df57ff5bc209128633af92abe8b17 +vendor/intx/hub-api | sawyer | 2026-09-14 | 7d82a625c852b9e9bb13fd59e71c6c45be792bcbb9ebb5994586e97840dc66c1 +vendor/intx/hub-common | sawyer | 2026-09-14 | 0e2d71d4754713538d7fd6451c8648c6b277390abfc888e605499fc004ce0349 +vendor/intx/hub-sessions | sawyer | 2026-09-05 | daaf9b2626e3fe66c530d025621c2067ac05716846deb9864c1a3400f6518b29 +vendor/intx/inference | sawyer | 2026-09-14 | f91ac6a6b9621888276c5d2c90bd8a0ff8f9c6d3ce3ad67dd3ba57fdd9c01b0f +vendor/intx/inference-catalog | sawyer | 2026-09-14 | 6e2ef3af83eafafdf1b773725afcb724cbb712604266919ecd1d67d50ff8016a +vendor/intx/log | sawyer | 2026-09-14 | 17ba64f2ff751b640dd2db9eb034450876c435f43641b022fbc4a2e9aa9da04d +vendor/intx/mail-memory | sawyer | 2026-09-14 | 7bac2d26cddc55f3c209ae8090391fac2d9d915bbbedfa9ff387a518c5690d0e +vendor/intx/mime | sawyer | 2026-09-14 | c5e923b712e16ec8ce2cdc9e1f7fd63b124acff4f986731723e11556678b8cae +vendor/intx/pack-transport | sawyer | 2026-09-14 | 94578a75112059d31960abdc0b12175f6955cff8a18f5cb549c3cee151525749 +vendor/intx/storage-isogit | sawyer | 2026-09-14 | a89b58687b8738620ce664e81a99250cba7b3bbaddbe0904661778fafef8d586 +vendor/intx/tool-packaging | sawyer | 2026-09-14 | a4f446a5712f906986ddc02b3a9fb133018d15ac661052026527263d942d0249 +vendor/intx/types | sawyer | 2026-09-14 | 21833d272f619f31371e80d752e22bdf8e1d31839169d7faec71240fb2db1139 +vendor/intx/workflow | sawyer | 2026-09-14 | 7d880c3c7911018a6e71e4e2b6d3ad3de496b3acc48b4479d4dae3d9a60428dc +vendor/intx/workflow-deploy | sawyer | 2026-09-14 | ee75c87a3f8141eaa83068ec29731f064b7f27ef108919aac81419755b9bc1e3 +vendor/intx/workflow-host | sawyer | 2026-09-14 | 74fa8ba65faba279a654fb83e31b3b6d29780ea799ea32a32b297af6857e0e95 packages/folded-runs | sawyer | 2026-11-01 diff --git a/vendor/intx/agent/VENDORED-FROM b/vendor/intx/agent/VENDORED-FROM index e398c8fbf..4150ea18d 100644 --- a/vendor/intx/agent/VENDORED-FROM +++ b/vendor/intx/agent/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/agent) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/agent/src/definition.ts b/vendor/intx/agent/src/definition.ts index 017aaa4f7..afd33044f 100644 --- a/vendor/intx/agent/src/definition.ts +++ b/vendor/intx/agent/src/definition.ts @@ -55,6 +55,19 @@ export interface AgentDefinition { readonly systemPrompt: string; readonly director?: DirectorRef; readonly toolFactories: readonly AnnotatedToolFactory[]; + /** + * Tool-package names whose `definePlugin` factories this agent uses + * (`["@intx/tools-lsp"]`). Unlike a tool factory -- which the agent + * imports and places in `toolFactories`, so it is agent-visible -- a + * plugin package contributes NO agent-visible factory: its plugin + * factory reaches the agent only through `env.plugins`, wired by the + * host. This explicit per-agent list is therefore the only way per-step + * plugin scoping and the plugin's contributed tool grants can be known + * from the definition alone. The field is part of the hashed wire + * surface (the live->inert projector carries it), so a tampered plugin + * set fails re-verify. Absent when the agent uses no plugins. + */ + readonly plugins?: readonly string[]; readonly capabilities: readonly string[]; readonly inference: { readonly sources: readonly InferencePreference[]; @@ -143,6 +156,8 @@ export interface DefineAgentConfig< readonly systemPrompt: string; readonly director?: DirectorRef; readonly tools: Factories; + /** Plugin-package names this agent uses; see `AgentDefinition.plugins`. */ + readonly plugins?: readonly string[]; readonly capabilities: readonly string[]; readonly inference: { readonly sources: readonly InferencePreference[]; @@ -174,6 +189,7 @@ export function defineAgent< toolFactories, capabilities: config.capabilities, inference: config.inference, + ...(config.plugins !== undefined ? { plugins: config.plugins } : {}), ...(config.description !== undefined ? { description: config.description } : {}), diff --git a/vendor/intx/agent/src/tool.ts b/vendor/intx/agent/src/tool.ts index 3a6fe2493..d4467b7c2 100644 --- a/vendor/intx/agent/src/tool.ts +++ b/vendor/intx/agent/src/tool.ts @@ -274,6 +274,20 @@ export const PLUGIN_MARKER: unique symbol = Symbol.for("@intx/agent.plugin"); export interface AnnotatedPluginMeta { readonly id: string; readonly requires: readonly string[]; + /** + * Static declaration of the tool names this plugin contributes at + * runtime, so a caller can enumerate the plugin's tool grant surface + * WITHOUT instantiating it (which for a plugin like LSP would start a + * language-server subprocess). A plugin adds its tools indirectly -- it + * hands a host-defined shape to the tool package that consumes + * `env.plugins`, which then registers the plugin's tools under its own + * bundle -- so the plugin's contributed tool names are otherwise + * invisible until run time. The deploy-time capability walk reads this + * field to authorize a plugin-contributed tool the same way it + * authorizes a factory-declared tool. Empty when the plugin contributes + * no standalone tool (middleware-only plugins). + */ + readonly definitions: readonly ToolDeclaration[]; readonly [PLUGIN_MARKER]: true; } @@ -328,12 +342,21 @@ export function definePlugin< >(opts: { id: string; requires?: readonly string[]; + /** + * Static declaration of the tool names this plugin contributes at run + * time. Omit for a middleware-only plugin that adds no standalone tool. + * See `AnnotatedPluginMeta.definitions`. + */ + definitions?: readonly ToolDeclaration[]; factory: PluginFactory; }): AnnotatedPluginFactory { validateNamespacedId(opts.id); const requires = Object.freeze([ ...(opts.requires ?? []), ]) as readonly string[]; + const definitions = Object.freeze([ + ...(opts.definitions ?? []), + ]) as readonly ToolDeclaration[]; const wrapped: PluginFactory = ( env, ) => { @@ -346,6 +369,7 @@ export function definePlugin< return Object.assign(wrapped, { id: opts.id, requires, + definitions, [PLUGIN_MARKER]: true as const, }); } diff --git a/vendor/intx/authz/VENDORED-FROM b/vendor/intx/authz/VENDORED-FROM index eb3fdc75f..e21e8c489 100644 --- a/vendor/intx/authz/VENDORED-FROM +++ b/vendor/intx/authz/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/authz) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/crypto/VENDORED-FROM b/vendor/intx/crypto/VENDORED-FROM index 5fb42d1f5..b832310b3 100644 --- a/vendor/intx/crypto/VENDORED-FROM +++ b/vendor/intx/crypto/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/crypto) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/db/VENDORED-FROM b/vendor/intx/db/VENDORED-FROM index 620d4e688..896f95d0b 100644 --- a/vendor/intx/db/VENDORED-FROM +++ b/vendor/intx/db/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/db) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/db/migrations/0082_blue_black_queen.sql b/vendor/intx/db/migrations/0082_blue_black_queen.sql new file mode 100644 index 000000000..0f4ca863e --- /dev/null +++ b/vendor/intx/db/migrations/0082_blue_black_queen.sql @@ -0,0 +1 @@ +ALTER TABLE "workflow_definition_version" ADD COLUMN "grant_snapshot" jsonb; \ No newline at end of file diff --git a/vendor/intx/db/migrations/0083_replace_launch_spec_snapshot_with_frozen_bundle.sql b/vendor/intx/db/migrations/0083_replace_launch_spec_snapshot_with_frozen_bundle.sql new file mode 100644 index 000000000..fede4a2f2 --- /dev/null +++ b/vendor/intx/db/migrations/0083_replace_launch_spec_snapshot_with_frozen_bundle.sql @@ -0,0 +1,3 @@ +ALTER TABLE "workflow_run_launch_spec" ADD COLUMN "frozen_approval_bundle" jsonb NOT NULL;--> statement-breakpoint +ALTER TABLE "workflow_run_launch_spec" DROP COLUMN "definition_snapshot";--> statement-breakpoint +ALTER TABLE "workflow_run_launch_spec" DROP COLUMN "definition_hash"; \ No newline at end of file diff --git a/vendor/intx/db/migrations/meta/0082_snapshot.json b/vendor/intx/db/migrations/meta/0082_snapshot.json new file mode 100644 index 000000000..f8752fb12 --- /dev/null +++ b/vendor/intx/db/migrations/meta/0082_snapshot.json @@ -0,0 +1,4102 @@ +{ + "id": "d956c545-4e86-4d86-a3f8-d408c98eea2e", + "prevId": "83d02430-cec6-45a5-a75b-dbaf491fa4bc", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_trust": { + "name": "federation_trust", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_tenant_id": { + "name": "target_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "federation_trust_tenant_id_tenant_id_fk": { + "name": "federation_trust_tenant_id_tenant_id_fk", + "tableFrom": "federation_trust", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_trust_target_tenant_id_tenant_id_fk": { + "name": "federation_trust_target_tenant_id_tenant_id_fk", + "tableFrom": "federation_trust", + "tableTo": "tenant", + "columnsFrom": ["target_tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "federation_trust_tenant_id_target_tenant_id_unique": { + "name": "federation_trust_tenant_id_target_tenant_id_unique", + "nullsNotDistinct": false, + "columns": ["tenant_id", "target_tenant_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tenant": { + "name": "tenant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tenant_parent_id_tenant_id_fk": { + "name": "tenant_parent_id_tenant_id_fk", + "tableFrom": "tenant", + "tableTo": "tenant", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tenant_slug_unique": { + "name": "tenant_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + }, + "tenant_domain_unique": { + "name": "tenant_domain_unique", + "nullsNotDistinct": false, + "columns": ["domain"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal": { + "name": "principal", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ref_id": { + "name": "ref_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "principal_tenant_id_tenant_id_fk": { + "name": "principal_tenant_id_tenant_id_fk", + "tableFrom": "principal", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "principal_tenant_id_kind_ref_id_unique": { + "name": "principal_tenant_id_kind_ref_id_unique", + "nullsNotDistinct": false, + "columns": ["tenant_id", "kind", "ref_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_role": { + "name": "agent_role", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_role_agent_id_workflow_definition_id_fk": { + "name": "agent_role_agent_id_workflow_definition_id_fk", + "tableFrom": "agent_role", + "tableTo": "workflow_definition", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_role_role_id_role_id_fk": { + "name": "agent_role_role_id_role_id_fk", + "tableFrom": "agent_role", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agent_role_agent_id_role_id_pk": { + "name": "agent_role_agent_id_role_id_pk", + "columns": ["agent_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_role": { + "name": "principal_role", + "schema": "", + "columns": { + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "principal_role_principal_id_principal_id_fk": { + "name": "principal_role_principal_id_principal_id_fk", + "tableFrom": "principal_role", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "principal_role_role_id_role_id_fk": { + "name": "principal_role_role_id_role_id_fk", + "tableFrom": "principal_role", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "principal_role_principal_id_role_id_pk": { + "name": "principal_role_principal_id_role_id_pk", + "columns": ["principal_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.role": { + "name": "role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "role_tenant_id_tenant_id_fk": { + "name": "role_tenant_id_tenant_id_fk", + "tableFrom": "role", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.grant": { + "name": "grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "grant_tenant_id_tenant_id_fk": { + "name": "grant_tenant_id_tenant_id_fk", + "tableFrom": "grant", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grant_role_id_role_id_fk": { + "name": "grant_role_id_role_id_fk", + "tableFrom": "grant", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grant_principal_id_principal_id_fk": { + "name": "grant_principal_id_principal_id_fk", + "tableFrom": "grant", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "grant_target_exactly_one": { + "name": "grant_target_exactly_one", + "value": "num_nonnulls(\"grant\".\"principal_id\", \"grant\".\"role_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.approval": { + "name": "approval", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_address": { + "name": "agent_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_definition": { + "name": "tool_definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "tool_arguments": { + "name": "tool_arguments", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_tenant_status_idx": { + "name": "approval_tenant_status_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_anchor_run_idx": { + "name": "approval_anchor_run_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_tenant_id_tenant_id_fk": { + "name": "approval_tenant_id_tenant_id_fk", + "tableFrom": "approval", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "approval_anchor_run_id_workflow_run_id_fk": { + "name": "approval_anchor_run_id_workflow_run_id_fk", + "tableFrom": "approval", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "approval_run_id_workflow_run_id_fk": { + "name": "approval_run_id_workflow_run_id_fk", + "tableFrom": "approval", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "approval_correlation_id_unique": { + "name": "approval_correlation_id_unique", + "nullsNotDistinct": false, + "columns": ["correlation_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.signal_correlation": { + "name": "signal_correlation", + "schema": "", + "columns": { + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_address": { + "name": "agent_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signal_name": { + "name": "signal_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signal_id": { + "name": "signal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "signal_correlation_tenant_id_tenant_id_fk": { + "name": "signal_correlation_tenant_id_tenant_id_fk", + "tableFrom": "signal_correlation", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "signal_correlation_anchor_run_id_workflow_run_id_fk": { + "name": "signal_correlation_anchor_run_id_workflow_run_id_fk", + "tableFrom": "signal_correlation", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "signal_correlation_run_id_workflow_run_id_fk": { + "name": "signal_correlation_run_id_workflow_run_id_fk", + "tableFrom": "signal_correlation", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider": { + "name": "provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin": { + "name": "plugin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_base_url": { + "name": "api_base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_info_url": { + "name": "user_info_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "provider_tenant_id_tenant_id_fk": { + "name": "provider_tenant_id_tenant_id_fk", + "tableFrom": "provider", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_tenant_name": { + "name": "provider_tenant_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "default_scopes": { + "name": "default_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_client_tenant_id_tenant_id_fk": { + "name": "oauth_client_tenant_id_tenant_id_fk", + "tableFrom": "oauth_client", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_client_provider_id_provider_id_fk": { + "name": "oauth_client_provider_id_provider_id_fk", + "tableFrom": "oauth_client", + "tableTo": "provider", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_tenant_provider": { + "name": "oauth_client_tenant_provider", + "nullsNotDistinct": false, + "columns": ["tenant_id", "provider_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_secret": { + "name": "refresh_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_tenant_id_tenant_id_fk": { + "name": "credential_tenant_id_tenant_id_fk", + "tableFrom": "credential", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_principal_id_principal_id_fk": { + "name": "credential_principal_id_principal_id_fk", + "tableFrom": "credential", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "credential_provider_id_provider_id_fk": { + "name": "credential_provider_id_provider_id_fk", + "tableFrom": "credential", + "tableTo": "provider", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_oauth_client_id_oauth_client_id_fk": { + "name": "credential_oauth_client_id_oauth_client_id_fk", + "tableFrom": "credential", + "tableTo": "oauth_client", + "columnsFrom": ["oauth_client_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "credential_tenant_name": { + "name": "credential_tenant_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.asset": { + "name": "asset", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creator_principal_id": { + "name": "creator_principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "asset_tenant_id_tenant_id_fk": { + "name": "asset_tenant_id_tenant_id_fk", + "tableFrom": "asset", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "asset_creator_principal_id_principal_id_fk": { + "name": "asset_creator_principal_id_principal_id_fk", + "tableFrom": "asset", + "tableTo": "principal", + "columnsFrom": ["creator_principal_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "asset_tenant_kind_name": { + "name": "asset_tenant_kind_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "kind", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction": { + "name": "transaction", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "wallet_id": { + "name": "wallet_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipient_id": { + "name": "recipient_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_id": { + "name": "sender_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "transaction_wallet_id_wallet_id_fk": { + "name": "transaction_wallet_id_wallet_id_fk", + "tableFrom": "transaction", + "tableTo": "wallet", + "columnsFrom": ["wallet_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transaction_run_id_workflow_run_id_fk": { + "name": "transaction_run_id_workflow_run_id_fk", + "tableFrom": "transaction", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wallet": { + "name": "wallet", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backend_type": { + "name": "backend_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "balance": { + "name": "balance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wallet_tenant_id_tenant_id_fk": { + "name": "wallet_tenant_id_tenant_id_fk", + "tableFrom": "wallet", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.offering": { + "name": "offering", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing": { + "name": "pricing", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "offering_agent_id_workflow_definition_id_fk": { + "name": "offering_agent_id_workflow_definition_id_fk", + "tableFrom": "offering", + "tableTo": "workflow_definition", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "offering_tenant_id_tenant_id_fk": { + "name": "offering_tenant_id_tenant_id_fk", + "tableFrom": "offering", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model": { + "name": "model", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_name": { + "name": "canonical_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_tenant_id_tenant_id_fk": { + "name": "model_tenant_id_tenant_id_fk", + "tableFrom": "model", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_tenant_canonical_name": { + "name": "model_tenant_canonical_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "canonical_name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_offering": { + "name": "model_offering", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "deployment_tags": { + "name": "deployment_tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "capabilities": { + "name": "capabilities", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "quirks": { + "name": "quirks", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_offering_tenant_id_tenant_id_fk": { + "name": "model_offering_tenant_id_tenant_id_fk", + "tableFrom": "model_offering", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_offering_model_id_model_id_fk": { + "name": "model_offering_model_id_model_id_fk", + "tableFrom": "model_offering", + "tableTo": "model", + "columnsFrom": ["model_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_offering_provider_id_model_provider_id_fk": { + "name": "model_offering_provider_id_model_provider_id_fk", + "tableFrom": "model_offering", + "tableTo": "model_provider", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_offering_tenant_model_provider": { + "name": "model_offering_tenant_model_provider", + "nullsNotDistinct": false, + "columns": ["tenant_id", "model_id", "provider_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_pricing": { + "name": "model_pricing", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "offering_id": { + "name": "offering_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_token_price": { + "name": "input_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output_token_price": { + "name": "output_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_read_token_price": { + "name": "cache_read_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_write_token_price": { + "name": "cache_write_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thinking_token_price": { + "name": "thinking_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "per_request_fee": { + "name": "per_request_fee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "per_image_fee": { + "name": "per_image_fee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "per_audio_fee": { + "name": "per_audio_fee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_from": { + "name": "effective_from", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_pricing_tenant_id_tenant_id_fk": { + "name": "model_pricing_tenant_id_tenant_id_fk", + "tableFrom": "model_pricing", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_pricing_offering_id_model_offering_id_fk": { + "name": "model_pricing_offering_id_model_offering_id_fk", + "tableFrom": "model_pricing", + "tableTo": "model_offering", + "columnsFrom": ["offering_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_pricing_offering_currency_effective_from": { + "name": "model_pricing_offering_currency_effective_from", + "nullsNotDistinct": false, + "columns": ["offering_id", "currency", "effective_from"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_provider": { + "name": "model_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin": { + "name": "plugin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wallet_id": { + "name": "wallet_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_provider_tenant_id_tenant_id_fk": { + "name": "model_provider_tenant_id_tenant_id_fk", + "tableFrom": "model_provider", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_provider_credential_id_credential_id_fk": { + "name": "model_provider_credential_id_credential_id_fk", + "tableFrom": "model_provider", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "model_provider_wallet_id_wallet_id_fk": { + "name": "model_provider_wallet_id_wallet_id_fk", + "tableFrom": "model_provider", + "tableTo": "wallet", + "columnsFrom": ["wallet_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_provider_tenant_name": { + "name": "model_provider_tenant_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "name"] + } + }, + "policies": {}, + "checkConstraints": { + "model_provider_auth_xor": { + "name": "model_provider_auth_xor", + "value": "(\"model_provider\".\"credential_id\" is not null) <> (\"model_provider\".\"wallet_id\" is not null)" + } + }, + "isRLSEnabled": false + }, + "public.sidecar": { + "name": "sidecar", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash_sha256": { + "name": "token_hash_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "credential_scope": { + "name": "credential_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'shared'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'online'" + }, + "last_heartbeat": { + "name": "last_heartbeat", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sidecar_token_hash_sha256_unique": { + "name": "sidecar_token_hash_sha256_unique", + "nullsNotDistinct": false, + "columns": ["token_hash_sha256"] + } + }, + "policies": {}, + "checkConstraints": { + "sidecar_credential_scope_check": { + "name": "sidecar_credential_scope_check", + "value": "\"sidecar\".\"credential_scope\" in ('shared', 'allocated')" + } + }, + "isRLSEnabled": false + }, + "public.sidecar_allocation": { + "name": "sidecar_allocation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioner_id": { + "name": "provisioner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioner_api_version": { + "name": "provisioner_api_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provisioner_binding_fingerprint": { + "name": "provisioner_binding_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sidecar_id": { + "name": "sidecar_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "placement_sharing": { + "name": "placement_sharing", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sidecar_reuse": { + "name": "sidecar_reuse", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'never'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "ensure_accepted_generation": { + "name": "ensure_accepted_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconciliation_lease_id": { + "name": "reconciliation_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reconciliation_lease_expires_at": { + "name": "reconciliation_lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ensure_attempts": { + "name": "ensure_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "destroy_attempts": { + "name": "destroy_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "connect_deadline": { + "name": "connect_deadline", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_message": { + "name": "failure_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sidecar_allocation_anchor_run_idx": { + "name": "sidecar_allocation_anchor_run_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sidecar_allocation_active_sidecar_idx": { + "name": "sidecar_allocation_active_sidecar_idx", + "columns": [ + { + "expression": "sidecar_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"sidecar_allocation\".\"status\" in ('provisioning', 'allocated', 'replacing', 'releasing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "sidecar_allocation_sidecar_idx": { + "name": "sidecar_allocation_sidecar_idx", + "columns": [ + { + "expression": "sidecar_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sidecar_allocation_reconciliation_idx": { + "name": "sidecar_allocation_reconciliation_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"sidecar_allocation\".\"status\" in ('pending', 'provisioning', 'allocated', 'replacing', 'releasing') and \"sidecar_allocation\".\"next_attempt_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sidecar_allocation_anchor_run_id_workflow_run_id_fk": { + "name": "sidecar_allocation_anchor_run_id_workflow_run_id_fk", + "tableFrom": "sidecar_allocation", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sidecar_allocation_tenant_id_tenant_id_fk": { + "name": "sidecar_allocation_tenant_id_tenant_id_fk", + "tableFrom": "sidecar_allocation", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "sidecar_allocation_sidecar_id_sidecar_id_fk": { + "name": "sidecar_allocation_sidecar_id_sidecar_id_fk", + "tableFrom": "sidecar_allocation", + "tableTo": "sidecar", + "columnsFrom": ["sidecar_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "sidecar_allocation_status_check": { + "name": "sidecar_allocation_status_check", + "value": "\"sidecar_allocation\".\"status\" in ('pending', 'provisioning', 'allocated', 'replacing', 'releasing', 'released', 'failed')" + }, + "sidecar_allocation_placement_check": { + "name": "sidecar_allocation_placement_check", + "value": "\"sidecar_allocation\".\"placement_sharing\" = 'exclusive'" + }, + "sidecar_allocation_generation_check": { + "name": "sidecar_allocation_generation_check", + "value": "\"sidecar_allocation\".\"generation\" >= 0" + }, + "sidecar_allocation_accepted_generation_check": { + "name": "sidecar_allocation_accepted_generation_check", + "value": "\"sidecar_allocation\".\"ensure_accepted_generation\" is null or \"sidecar_allocation\".\"ensure_accepted_generation\" <= \"sidecar_allocation\".\"generation\"" + } + }, + "isRLSEnabled": false + }, + "public.agent_session": { + "name": "agent_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_session_tenant_id_tenant_id_fk": { + "name": "agent_session_tenant_id_tenant_id_fk", + "tableFrom": "agent_session", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_session_agent_id_workflow_definition_id_fk": { + "name": "agent_session_agent_id_workflow_definition_id_fk", + "tableFrom": "agent_session", + "tableTo": "workflow_definition", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "agent_session_principal_id_principal_id_fk": { + "name": "agent_session_principal_id_principal_id_fk", + "tableFrom": "agent_session", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_asset": { + "name": "session_asset", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mount_path": { + "name": "mount_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "asset_pack_sha": { + "name": "asset_pack_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_commit_sha": { + "name": "source_commit_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "materialized_at": { + "name": "materialized_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "session_asset_pack_sha_idx": { + "name": "session_asset_pack_sha_idx", + "columns": [ + { + "expression": "asset_pack_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "session_asset_instance_id_mount_path_pk": { + "name": "session_asset_instance_id_mount_path_pk", + "columns": ["instance_id", "mount_path"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inference_turn": { + "name": "inference_turn", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inference_turn_instance_id_started_at_idx": { + "name": "inference_turn_instance_id_started_at_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inference_turn_session_id_agent_session_id_fk": { + "name": "inference_turn_session_id_agent_session_id_fk", + "tableFrom": "inference_turn", + "tableTo": "agent_session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inference_turn_tenant_id_tenant_id_fk": { + "name": "inference_turn_tenant_id_tenant_id_fk", + "tableFrom": "inference_turn", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_mail": { + "name": "session_mail", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "raw": { + "name": "raw", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "session_mail_instance_id_created_at_idx": { + "name": "session_mail_instance_id_created_at_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_mail_session_id_created_at_idx": { + "name": "session_mail_session_id_created_at_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_mail_session_id_agent_session_id_fk": { + "name": "session_mail_session_id_agent_session_id_fk", + "tableFrom": "session_mail", + "tableTo": "agent_session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_mail_tenant_id_tenant_id_fk": { + "name": "session_mail_tenant_id_tenant_id_fk", + "tableFrom": "session_mail", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.turn_part": { + "name": "turn_part", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "turn_part_turn_id_inference_turn_id_fk": { + "name": "turn_part_turn_id_inference_turn_id_fk", + "tableFrom": "turn_part", + "tableTo": "inference_turn", + "columnsFrom": ["turn_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "turn_part_session_id_agent_session_id_fk": { + "name": "turn_part_session_id_agent_session_id_fk", + "tableFrom": "turn_part", + "tableTo": "agent_session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.git_token": { + "name": "git_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash_sha256": { + "name": "token_hash_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ref_pattern": { + "name": "ref_pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actions": { + "name": "actions", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "git_token_user_id_name_active_idx": { + "name": "git_token_user_id_name_active_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"git_token\".\"revoked_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "git_token_tenant_id_tenant_id_fk": { + "name": "git_token_tenant_id_tenant_id_fk", + "tableFrom": "git_token", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "git_token_user_id_user_id_fk": { + "name": "git_token_user_id_user_id_fk", + "tableFrom": "git_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "git_token_principal_id_principal_id_fk": { + "name": "git_token_principal_id_principal_id_fk", + "tableFrom": "git_token", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "git_token_token_hash_sha256_unique": { + "name": "git_token_token_hash_sha256_unique", + "nullsNotDistinct": false, + "columns": ["token_hash_sha256"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_definition": { + "name": "workflow_definition", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_principal_id": { + "name": "creator_principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "asset_id": { + "name": "asset_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wire_hash": { + "name": "wire_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_requirements": { + "name": "grant_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "model_requirements": { + "name": "model_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_bindings": { + "name": "credential_bindings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "current_version": { + "name": "current_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'deployed'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_definition_tenant_idx": { + "name": "workflow_definition_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_definition_asset_wire_hash_idx": { + "name": "workflow_definition_asset_wire_hash_idx", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "wire_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_definition_tenant_id_tenant_id_fk": { + "name": "workflow_definition_tenant_id_tenant_id_fk", + "tableFrom": "workflow_definition", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_definition_creator_principal_id_principal_id_fk": { + "name": "workflow_definition_creator_principal_id_principal_id_fk", + "tableFrom": "workflow_definition", + "tableTo": "principal", + "columnsFrom": ["creator_principal_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_definition_asset_id_asset_id_fk": { + "name": "workflow_definition_asset_id_asset_id_fk", + "tableFrom": "workflow_definition", + "tableTo": "asset", + "columnsFrom": ["asset_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_definition_version": { + "name": "workflow_definition_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "definition_id": { + "name": "definition_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "approved_wire_hash": { + "name": "approved_wire_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_snapshot": { + "name": "grant_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_definition_version_definition_idx": { + "name": "workflow_definition_version_definition_idx", + "columns": [ + { + "expression": "definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_definition_version_definition_id_workflow_definition_id_fk": { + "name": "workflow_definition_version_definition_id_workflow_definition_id_fk", + "tableFrom": "workflow_definition_version", + "tableTo": "workflow_definition", + "columnsFrom": ["definition_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workflow_definition_version_definition_version": { + "name": "workflow_definition_version_definition_version", + "nullsNotDistinct": false, + "columns": ["definition_id", "version"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_run": { + "name": "workflow_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "definition_id": { + "name": "definition_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sidecar_id": { + "name": "sidecar_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kernel_id": { + "name": "kernel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_preferences": { + "name": "model_preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_run_definition_idx": { + "name": "workflow_run_definition_idx", + "columns": [ + { + "expression": "definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_address_idx": { + "name": "workflow_run_address_idx", + "columns": [ + { + "expression": "address", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_run\".\"address\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_run_definition_id_workflow_definition_id_fk": { + "name": "workflow_run_definition_id_workflow_definition_id_fk", + "tableFrom": "workflow_run", + "tableTo": "workflow_definition", + "columnsFrom": ["definition_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_anchor_run_id_workflow_run_id_fk": { + "name": "workflow_run_anchor_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_tenant_id_tenant_id_fk": { + "name": "workflow_run_tenant_id_tenant_id_fk", + "tableFrom": "workflow_run", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_principal_id_principal_id_fk": { + "name": "workflow_run_principal_id_principal_id_fk", + "tableFrom": "workflow_run", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_run_sidecar_id_sidecar_id_fk": { + "name": "workflow_run_sidecar_id_sidecar_id_fk", + "tableFrom": "workflow_run", + "tableTo": "sidecar", + "columnsFrom": ["sidecar_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_run_dispatch": { + "name": "workflow_run_dispatch", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'mail'" + }, + "raw_message": { + "name": "raw_message", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "step_grants": { + "name": "step_grants", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "acknowledged_generation": { + "name": "acknowledged_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "delivery_lease_id": { + "name": "delivery_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delivery_lease_expires_at": { + "name": "delivery_lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_message": { + "name": "failure_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "settled_at": { + "name": "settled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_run_dispatch_anchor_message_idx": { + "name": "workflow_run_dispatch_anchor_message_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_dispatch_delivery_idx": { + "name": "workflow_run_dispatch_delivery_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_run_dispatch\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_dispatch_anchor_status_idx": { + "name": "workflow_run_dispatch_anchor_status_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_run_dispatch_anchor_run_id_workflow_run_id_fk": { + "name": "workflow_run_dispatch_anchor_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run_dispatch", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workflow_run_dispatch_status_check": { + "name": "workflow_run_dispatch_status_check", + "value": "\"workflow_run_dispatch\".\"status\" in ('pending', 'acknowledged', 'settled', 'failed')" + }, + "workflow_run_dispatch_kind_check": { + "name": "workflow_run_dispatch_kind_check", + "value": "\"workflow_run_dispatch\".\"kind\" in ('mail', 'signal')" + }, + "workflow_run_dispatch_attempt_count_check": { + "name": "workflow_run_dispatch_attempt_count_check", + "value": "\"workflow_run_dispatch\".\"attempt_count\" >= 0" + }, + "workflow_run_dispatch_acknowledged_generation_check": { + "name": "workflow_run_dispatch_acknowledged_generation_check", + "value": "\"workflow_run_dispatch\".\"acknowledged_generation\" is null or \"workflow_run_dispatch\".\"acknowledged_generation\" >= 0" + }, + "workflow_run_dispatch_acknowledged_state_check": { + "name": "workflow_run_dispatch_acknowledged_state_check", + "value": "\"workflow_run_dispatch\".\"status\" <> 'acknowledged' or \"workflow_run_dispatch\".\"acknowledged_generation\" is not null" + }, + "workflow_run_dispatch_pending_schedule_check": { + "name": "workflow_run_dispatch_pending_schedule_check", + "value": "\"workflow_run_dispatch\".\"status\" <> 'pending' or \"workflow_run_dispatch\".\"next_attempt_at\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.workflow_run_launch_spec": { + "name": "workflow_run_launch_spec", + "schema": "", + "columns": { + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_domain": { + "name": "deployment_domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_authority_principal_id": { + "name": "source_authority_principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition_snapshot": { + "name": "definition_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "definition_hash": { + "name": "definition_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_offering_ids": { + "name": "source_offering_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "default_source_offering_id": { + "name": "default_source_offering_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deploy_content": { + "name": "deploy_content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "tool_package_pins": { + "name": "tool_package_pins", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_run_launch_spec_anchor_run_id_workflow_run_id_fk": { + "name": "workflow_run_launch_spec_anchor_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run_launch_spec", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_launch_spec_source_authority_principal_id_principal_id_fk": { + "name": "workflow_run_launch_spec_source_authority_principal_id_principal_id_fk", + "tableFrom": "workflow_run_launch_spec", + "tableTo": "principal", + "columnsFrom": ["source_authority_principal_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_run_execution": { + "name": "workflow_run_execution", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_run_execution_run_id_id_idx": { + "name": "workflow_run_execution_run_id_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_execution_status_idx": { + "name": "workflow_run_execution_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_run_execution_run_id_workflow_run_id_fk": { + "name": "workflow_run_execution_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run_execution", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/vendor/intx/db/migrations/meta/0083_snapshot.json b/vendor/intx/db/migrations/meta/0083_snapshot.json new file mode 100644 index 000000000..5f6864b9a --- /dev/null +++ b/vendor/intx/db/migrations/meta/0083_snapshot.json @@ -0,0 +1,4096 @@ +{ + "id": "b84ce2f6-fec4-4c5e-8938-0ca0f32eb128", + "prevId": "d956c545-4e86-4d86-a3f8-d408c98eea2e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_trust": { + "name": "federation_trust", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_tenant_id": { + "name": "target_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "federation_trust_tenant_id_tenant_id_fk": { + "name": "federation_trust_tenant_id_tenant_id_fk", + "tableFrom": "federation_trust", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_trust_target_tenant_id_tenant_id_fk": { + "name": "federation_trust_target_tenant_id_tenant_id_fk", + "tableFrom": "federation_trust", + "tableTo": "tenant", + "columnsFrom": ["target_tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "federation_trust_tenant_id_target_tenant_id_unique": { + "name": "federation_trust_tenant_id_target_tenant_id_unique", + "nullsNotDistinct": false, + "columns": ["tenant_id", "target_tenant_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tenant": { + "name": "tenant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tenant_parent_id_tenant_id_fk": { + "name": "tenant_parent_id_tenant_id_fk", + "tableFrom": "tenant", + "tableTo": "tenant", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tenant_slug_unique": { + "name": "tenant_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + }, + "tenant_domain_unique": { + "name": "tenant_domain_unique", + "nullsNotDistinct": false, + "columns": ["domain"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal": { + "name": "principal", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ref_id": { + "name": "ref_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "principal_tenant_id_tenant_id_fk": { + "name": "principal_tenant_id_tenant_id_fk", + "tableFrom": "principal", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "principal_tenant_id_kind_ref_id_unique": { + "name": "principal_tenant_id_kind_ref_id_unique", + "nullsNotDistinct": false, + "columns": ["tenant_id", "kind", "ref_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_role": { + "name": "agent_role", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_role_agent_id_workflow_definition_id_fk": { + "name": "agent_role_agent_id_workflow_definition_id_fk", + "tableFrom": "agent_role", + "tableTo": "workflow_definition", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_role_role_id_role_id_fk": { + "name": "agent_role_role_id_role_id_fk", + "tableFrom": "agent_role", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agent_role_agent_id_role_id_pk": { + "name": "agent_role_agent_id_role_id_pk", + "columns": ["agent_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_role": { + "name": "principal_role", + "schema": "", + "columns": { + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "principal_role_principal_id_principal_id_fk": { + "name": "principal_role_principal_id_principal_id_fk", + "tableFrom": "principal_role", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "principal_role_role_id_role_id_fk": { + "name": "principal_role_role_id_role_id_fk", + "tableFrom": "principal_role", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "principal_role_principal_id_role_id_pk": { + "name": "principal_role_principal_id_role_id_pk", + "columns": ["principal_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.role": { + "name": "role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "role_tenant_id_tenant_id_fk": { + "name": "role_tenant_id_tenant_id_fk", + "tableFrom": "role", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.grant": { + "name": "grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "grant_tenant_id_tenant_id_fk": { + "name": "grant_tenant_id_tenant_id_fk", + "tableFrom": "grant", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grant_role_id_role_id_fk": { + "name": "grant_role_id_role_id_fk", + "tableFrom": "grant", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grant_principal_id_principal_id_fk": { + "name": "grant_principal_id_principal_id_fk", + "tableFrom": "grant", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "grant_target_exactly_one": { + "name": "grant_target_exactly_one", + "value": "num_nonnulls(\"grant\".\"principal_id\", \"grant\".\"role_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.approval": { + "name": "approval", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_address": { + "name": "agent_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_definition": { + "name": "tool_definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "tool_arguments": { + "name": "tool_arguments", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_tenant_status_idx": { + "name": "approval_tenant_status_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_anchor_run_idx": { + "name": "approval_anchor_run_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_tenant_id_tenant_id_fk": { + "name": "approval_tenant_id_tenant_id_fk", + "tableFrom": "approval", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "approval_anchor_run_id_workflow_run_id_fk": { + "name": "approval_anchor_run_id_workflow_run_id_fk", + "tableFrom": "approval", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "approval_run_id_workflow_run_id_fk": { + "name": "approval_run_id_workflow_run_id_fk", + "tableFrom": "approval", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "approval_correlation_id_unique": { + "name": "approval_correlation_id_unique", + "nullsNotDistinct": false, + "columns": ["correlation_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.signal_correlation": { + "name": "signal_correlation", + "schema": "", + "columns": { + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_address": { + "name": "agent_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signal_name": { + "name": "signal_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signal_id": { + "name": "signal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "signal_correlation_tenant_id_tenant_id_fk": { + "name": "signal_correlation_tenant_id_tenant_id_fk", + "tableFrom": "signal_correlation", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "signal_correlation_anchor_run_id_workflow_run_id_fk": { + "name": "signal_correlation_anchor_run_id_workflow_run_id_fk", + "tableFrom": "signal_correlation", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "signal_correlation_run_id_workflow_run_id_fk": { + "name": "signal_correlation_run_id_workflow_run_id_fk", + "tableFrom": "signal_correlation", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider": { + "name": "provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin": { + "name": "plugin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_base_url": { + "name": "api_base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_info_url": { + "name": "user_info_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "provider_tenant_id_tenant_id_fk": { + "name": "provider_tenant_id_tenant_id_fk", + "tableFrom": "provider", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_tenant_name": { + "name": "provider_tenant_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "default_scopes": { + "name": "default_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_client_tenant_id_tenant_id_fk": { + "name": "oauth_client_tenant_id_tenant_id_fk", + "tableFrom": "oauth_client", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_client_provider_id_provider_id_fk": { + "name": "oauth_client_provider_id_provider_id_fk", + "tableFrom": "oauth_client", + "tableTo": "provider", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_tenant_provider": { + "name": "oauth_client_tenant_provider", + "nullsNotDistinct": false, + "columns": ["tenant_id", "provider_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_secret": { + "name": "refresh_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_tenant_id_tenant_id_fk": { + "name": "credential_tenant_id_tenant_id_fk", + "tableFrom": "credential", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_principal_id_principal_id_fk": { + "name": "credential_principal_id_principal_id_fk", + "tableFrom": "credential", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "credential_provider_id_provider_id_fk": { + "name": "credential_provider_id_provider_id_fk", + "tableFrom": "credential", + "tableTo": "provider", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_oauth_client_id_oauth_client_id_fk": { + "name": "credential_oauth_client_id_oauth_client_id_fk", + "tableFrom": "credential", + "tableTo": "oauth_client", + "columnsFrom": ["oauth_client_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "credential_tenant_name": { + "name": "credential_tenant_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.asset": { + "name": "asset", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creator_principal_id": { + "name": "creator_principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "asset_tenant_id_tenant_id_fk": { + "name": "asset_tenant_id_tenant_id_fk", + "tableFrom": "asset", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "asset_creator_principal_id_principal_id_fk": { + "name": "asset_creator_principal_id_principal_id_fk", + "tableFrom": "asset", + "tableTo": "principal", + "columnsFrom": ["creator_principal_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "asset_tenant_kind_name": { + "name": "asset_tenant_kind_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "kind", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction": { + "name": "transaction", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "wallet_id": { + "name": "wallet_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipient_id": { + "name": "recipient_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_id": { + "name": "sender_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "transaction_wallet_id_wallet_id_fk": { + "name": "transaction_wallet_id_wallet_id_fk", + "tableFrom": "transaction", + "tableTo": "wallet", + "columnsFrom": ["wallet_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transaction_run_id_workflow_run_id_fk": { + "name": "transaction_run_id_workflow_run_id_fk", + "tableFrom": "transaction", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wallet": { + "name": "wallet", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backend_type": { + "name": "backend_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "balance": { + "name": "balance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wallet_tenant_id_tenant_id_fk": { + "name": "wallet_tenant_id_tenant_id_fk", + "tableFrom": "wallet", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.offering": { + "name": "offering", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing": { + "name": "pricing", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "offering_agent_id_workflow_definition_id_fk": { + "name": "offering_agent_id_workflow_definition_id_fk", + "tableFrom": "offering", + "tableTo": "workflow_definition", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "offering_tenant_id_tenant_id_fk": { + "name": "offering_tenant_id_tenant_id_fk", + "tableFrom": "offering", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model": { + "name": "model", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_name": { + "name": "canonical_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_tenant_id_tenant_id_fk": { + "name": "model_tenant_id_tenant_id_fk", + "tableFrom": "model", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_tenant_canonical_name": { + "name": "model_tenant_canonical_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "canonical_name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_offering": { + "name": "model_offering", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "deployment_tags": { + "name": "deployment_tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "capabilities": { + "name": "capabilities", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "quirks": { + "name": "quirks", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_offering_tenant_id_tenant_id_fk": { + "name": "model_offering_tenant_id_tenant_id_fk", + "tableFrom": "model_offering", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_offering_model_id_model_id_fk": { + "name": "model_offering_model_id_model_id_fk", + "tableFrom": "model_offering", + "tableTo": "model", + "columnsFrom": ["model_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_offering_provider_id_model_provider_id_fk": { + "name": "model_offering_provider_id_model_provider_id_fk", + "tableFrom": "model_offering", + "tableTo": "model_provider", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_offering_tenant_model_provider": { + "name": "model_offering_tenant_model_provider", + "nullsNotDistinct": false, + "columns": ["tenant_id", "model_id", "provider_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_pricing": { + "name": "model_pricing", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "offering_id": { + "name": "offering_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_token_price": { + "name": "input_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output_token_price": { + "name": "output_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_read_token_price": { + "name": "cache_read_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_write_token_price": { + "name": "cache_write_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thinking_token_price": { + "name": "thinking_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "per_request_fee": { + "name": "per_request_fee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "per_image_fee": { + "name": "per_image_fee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "per_audio_fee": { + "name": "per_audio_fee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_from": { + "name": "effective_from", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_pricing_tenant_id_tenant_id_fk": { + "name": "model_pricing_tenant_id_tenant_id_fk", + "tableFrom": "model_pricing", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_pricing_offering_id_model_offering_id_fk": { + "name": "model_pricing_offering_id_model_offering_id_fk", + "tableFrom": "model_pricing", + "tableTo": "model_offering", + "columnsFrom": ["offering_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_pricing_offering_currency_effective_from": { + "name": "model_pricing_offering_currency_effective_from", + "nullsNotDistinct": false, + "columns": ["offering_id", "currency", "effective_from"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_provider": { + "name": "model_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin": { + "name": "plugin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wallet_id": { + "name": "wallet_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_provider_tenant_id_tenant_id_fk": { + "name": "model_provider_tenant_id_tenant_id_fk", + "tableFrom": "model_provider", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_provider_credential_id_credential_id_fk": { + "name": "model_provider_credential_id_credential_id_fk", + "tableFrom": "model_provider", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "model_provider_wallet_id_wallet_id_fk": { + "name": "model_provider_wallet_id_wallet_id_fk", + "tableFrom": "model_provider", + "tableTo": "wallet", + "columnsFrom": ["wallet_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_provider_tenant_name": { + "name": "model_provider_tenant_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "name"] + } + }, + "policies": {}, + "checkConstraints": { + "model_provider_auth_xor": { + "name": "model_provider_auth_xor", + "value": "(\"model_provider\".\"credential_id\" is not null) <> (\"model_provider\".\"wallet_id\" is not null)" + } + }, + "isRLSEnabled": false + }, + "public.sidecar": { + "name": "sidecar", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash_sha256": { + "name": "token_hash_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "credential_scope": { + "name": "credential_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'shared'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'online'" + }, + "last_heartbeat": { + "name": "last_heartbeat", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sidecar_token_hash_sha256_unique": { + "name": "sidecar_token_hash_sha256_unique", + "nullsNotDistinct": false, + "columns": ["token_hash_sha256"] + } + }, + "policies": {}, + "checkConstraints": { + "sidecar_credential_scope_check": { + "name": "sidecar_credential_scope_check", + "value": "\"sidecar\".\"credential_scope\" in ('shared', 'allocated')" + } + }, + "isRLSEnabled": false + }, + "public.sidecar_allocation": { + "name": "sidecar_allocation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioner_id": { + "name": "provisioner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioner_api_version": { + "name": "provisioner_api_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provisioner_binding_fingerprint": { + "name": "provisioner_binding_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sidecar_id": { + "name": "sidecar_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "placement_sharing": { + "name": "placement_sharing", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sidecar_reuse": { + "name": "sidecar_reuse", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'never'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "ensure_accepted_generation": { + "name": "ensure_accepted_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconciliation_lease_id": { + "name": "reconciliation_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reconciliation_lease_expires_at": { + "name": "reconciliation_lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ensure_attempts": { + "name": "ensure_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "destroy_attempts": { + "name": "destroy_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "connect_deadline": { + "name": "connect_deadline", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_message": { + "name": "failure_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sidecar_allocation_anchor_run_idx": { + "name": "sidecar_allocation_anchor_run_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sidecar_allocation_active_sidecar_idx": { + "name": "sidecar_allocation_active_sidecar_idx", + "columns": [ + { + "expression": "sidecar_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"sidecar_allocation\".\"status\" in ('provisioning', 'allocated', 'replacing', 'releasing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "sidecar_allocation_sidecar_idx": { + "name": "sidecar_allocation_sidecar_idx", + "columns": [ + { + "expression": "sidecar_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sidecar_allocation_reconciliation_idx": { + "name": "sidecar_allocation_reconciliation_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"sidecar_allocation\".\"status\" in ('pending', 'provisioning', 'allocated', 'replacing', 'releasing') and \"sidecar_allocation\".\"next_attempt_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sidecar_allocation_anchor_run_id_workflow_run_id_fk": { + "name": "sidecar_allocation_anchor_run_id_workflow_run_id_fk", + "tableFrom": "sidecar_allocation", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sidecar_allocation_tenant_id_tenant_id_fk": { + "name": "sidecar_allocation_tenant_id_tenant_id_fk", + "tableFrom": "sidecar_allocation", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "sidecar_allocation_sidecar_id_sidecar_id_fk": { + "name": "sidecar_allocation_sidecar_id_sidecar_id_fk", + "tableFrom": "sidecar_allocation", + "tableTo": "sidecar", + "columnsFrom": ["sidecar_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "sidecar_allocation_status_check": { + "name": "sidecar_allocation_status_check", + "value": "\"sidecar_allocation\".\"status\" in ('pending', 'provisioning', 'allocated', 'replacing', 'releasing', 'released', 'failed')" + }, + "sidecar_allocation_placement_check": { + "name": "sidecar_allocation_placement_check", + "value": "\"sidecar_allocation\".\"placement_sharing\" = 'exclusive'" + }, + "sidecar_allocation_generation_check": { + "name": "sidecar_allocation_generation_check", + "value": "\"sidecar_allocation\".\"generation\" >= 0" + }, + "sidecar_allocation_accepted_generation_check": { + "name": "sidecar_allocation_accepted_generation_check", + "value": "\"sidecar_allocation\".\"ensure_accepted_generation\" is null or \"sidecar_allocation\".\"ensure_accepted_generation\" <= \"sidecar_allocation\".\"generation\"" + } + }, + "isRLSEnabled": false + }, + "public.agent_session": { + "name": "agent_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_session_tenant_id_tenant_id_fk": { + "name": "agent_session_tenant_id_tenant_id_fk", + "tableFrom": "agent_session", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_session_agent_id_workflow_definition_id_fk": { + "name": "agent_session_agent_id_workflow_definition_id_fk", + "tableFrom": "agent_session", + "tableTo": "workflow_definition", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "agent_session_principal_id_principal_id_fk": { + "name": "agent_session_principal_id_principal_id_fk", + "tableFrom": "agent_session", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_asset": { + "name": "session_asset", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mount_path": { + "name": "mount_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "asset_pack_sha": { + "name": "asset_pack_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_commit_sha": { + "name": "source_commit_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "materialized_at": { + "name": "materialized_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "session_asset_pack_sha_idx": { + "name": "session_asset_pack_sha_idx", + "columns": [ + { + "expression": "asset_pack_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "session_asset_instance_id_mount_path_pk": { + "name": "session_asset_instance_id_mount_path_pk", + "columns": ["instance_id", "mount_path"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inference_turn": { + "name": "inference_turn", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inference_turn_instance_id_started_at_idx": { + "name": "inference_turn_instance_id_started_at_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inference_turn_session_id_agent_session_id_fk": { + "name": "inference_turn_session_id_agent_session_id_fk", + "tableFrom": "inference_turn", + "tableTo": "agent_session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inference_turn_tenant_id_tenant_id_fk": { + "name": "inference_turn_tenant_id_tenant_id_fk", + "tableFrom": "inference_turn", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_mail": { + "name": "session_mail", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "raw": { + "name": "raw", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "session_mail_instance_id_created_at_idx": { + "name": "session_mail_instance_id_created_at_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_mail_session_id_created_at_idx": { + "name": "session_mail_session_id_created_at_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_mail_session_id_agent_session_id_fk": { + "name": "session_mail_session_id_agent_session_id_fk", + "tableFrom": "session_mail", + "tableTo": "agent_session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_mail_tenant_id_tenant_id_fk": { + "name": "session_mail_tenant_id_tenant_id_fk", + "tableFrom": "session_mail", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.turn_part": { + "name": "turn_part", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "turn_part_turn_id_inference_turn_id_fk": { + "name": "turn_part_turn_id_inference_turn_id_fk", + "tableFrom": "turn_part", + "tableTo": "inference_turn", + "columnsFrom": ["turn_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "turn_part_session_id_agent_session_id_fk": { + "name": "turn_part_session_id_agent_session_id_fk", + "tableFrom": "turn_part", + "tableTo": "agent_session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.git_token": { + "name": "git_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash_sha256": { + "name": "token_hash_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ref_pattern": { + "name": "ref_pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actions": { + "name": "actions", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "git_token_user_id_name_active_idx": { + "name": "git_token_user_id_name_active_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"git_token\".\"revoked_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "git_token_tenant_id_tenant_id_fk": { + "name": "git_token_tenant_id_tenant_id_fk", + "tableFrom": "git_token", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "git_token_user_id_user_id_fk": { + "name": "git_token_user_id_user_id_fk", + "tableFrom": "git_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "git_token_principal_id_principal_id_fk": { + "name": "git_token_principal_id_principal_id_fk", + "tableFrom": "git_token", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "git_token_token_hash_sha256_unique": { + "name": "git_token_token_hash_sha256_unique", + "nullsNotDistinct": false, + "columns": ["token_hash_sha256"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_definition": { + "name": "workflow_definition", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_principal_id": { + "name": "creator_principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "asset_id": { + "name": "asset_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wire_hash": { + "name": "wire_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_requirements": { + "name": "grant_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "model_requirements": { + "name": "model_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_bindings": { + "name": "credential_bindings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "current_version": { + "name": "current_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'deployed'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_definition_tenant_idx": { + "name": "workflow_definition_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_definition_asset_wire_hash_idx": { + "name": "workflow_definition_asset_wire_hash_idx", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "wire_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_definition_tenant_id_tenant_id_fk": { + "name": "workflow_definition_tenant_id_tenant_id_fk", + "tableFrom": "workflow_definition", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_definition_creator_principal_id_principal_id_fk": { + "name": "workflow_definition_creator_principal_id_principal_id_fk", + "tableFrom": "workflow_definition", + "tableTo": "principal", + "columnsFrom": ["creator_principal_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_definition_asset_id_asset_id_fk": { + "name": "workflow_definition_asset_id_asset_id_fk", + "tableFrom": "workflow_definition", + "tableTo": "asset", + "columnsFrom": ["asset_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_definition_version": { + "name": "workflow_definition_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "definition_id": { + "name": "definition_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "approved_wire_hash": { + "name": "approved_wire_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_snapshot": { + "name": "grant_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_definition_version_definition_idx": { + "name": "workflow_definition_version_definition_idx", + "columns": [ + { + "expression": "definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_definition_version_definition_id_workflow_definition_id_fk": { + "name": "workflow_definition_version_definition_id_workflow_definition_id_fk", + "tableFrom": "workflow_definition_version", + "tableTo": "workflow_definition", + "columnsFrom": ["definition_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workflow_definition_version_definition_version": { + "name": "workflow_definition_version_definition_version", + "nullsNotDistinct": false, + "columns": ["definition_id", "version"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_run": { + "name": "workflow_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "definition_id": { + "name": "definition_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sidecar_id": { + "name": "sidecar_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kernel_id": { + "name": "kernel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_preferences": { + "name": "model_preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_run_definition_idx": { + "name": "workflow_run_definition_idx", + "columns": [ + { + "expression": "definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_address_idx": { + "name": "workflow_run_address_idx", + "columns": [ + { + "expression": "address", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_run\".\"address\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_run_definition_id_workflow_definition_id_fk": { + "name": "workflow_run_definition_id_workflow_definition_id_fk", + "tableFrom": "workflow_run", + "tableTo": "workflow_definition", + "columnsFrom": ["definition_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_anchor_run_id_workflow_run_id_fk": { + "name": "workflow_run_anchor_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_tenant_id_tenant_id_fk": { + "name": "workflow_run_tenant_id_tenant_id_fk", + "tableFrom": "workflow_run", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_principal_id_principal_id_fk": { + "name": "workflow_run_principal_id_principal_id_fk", + "tableFrom": "workflow_run", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_run_sidecar_id_sidecar_id_fk": { + "name": "workflow_run_sidecar_id_sidecar_id_fk", + "tableFrom": "workflow_run", + "tableTo": "sidecar", + "columnsFrom": ["sidecar_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_run_dispatch": { + "name": "workflow_run_dispatch", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'mail'" + }, + "raw_message": { + "name": "raw_message", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "step_grants": { + "name": "step_grants", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "acknowledged_generation": { + "name": "acknowledged_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "delivery_lease_id": { + "name": "delivery_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delivery_lease_expires_at": { + "name": "delivery_lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_message": { + "name": "failure_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "settled_at": { + "name": "settled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_run_dispatch_anchor_message_idx": { + "name": "workflow_run_dispatch_anchor_message_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_dispatch_delivery_idx": { + "name": "workflow_run_dispatch_delivery_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_run_dispatch\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_dispatch_anchor_status_idx": { + "name": "workflow_run_dispatch_anchor_status_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_run_dispatch_anchor_run_id_workflow_run_id_fk": { + "name": "workflow_run_dispatch_anchor_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run_dispatch", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workflow_run_dispatch_status_check": { + "name": "workflow_run_dispatch_status_check", + "value": "\"workflow_run_dispatch\".\"status\" in ('pending', 'acknowledged', 'settled', 'failed')" + }, + "workflow_run_dispatch_kind_check": { + "name": "workflow_run_dispatch_kind_check", + "value": "\"workflow_run_dispatch\".\"kind\" in ('mail', 'signal')" + }, + "workflow_run_dispatch_attempt_count_check": { + "name": "workflow_run_dispatch_attempt_count_check", + "value": "\"workflow_run_dispatch\".\"attempt_count\" >= 0" + }, + "workflow_run_dispatch_acknowledged_generation_check": { + "name": "workflow_run_dispatch_acknowledged_generation_check", + "value": "\"workflow_run_dispatch\".\"acknowledged_generation\" is null or \"workflow_run_dispatch\".\"acknowledged_generation\" >= 0" + }, + "workflow_run_dispatch_acknowledged_state_check": { + "name": "workflow_run_dispatch_acknowledged_state_check", + "value": "\"workflow_run_dispatch\".\"status\" <> 'acknowledged' or \"workflow_run_dispatch\".\"acknowledged_generation\" is not null" + }, + "workflow_run_dispatch_pending_schedule_check": { + "name": "workflow_run_dispatch_pending_schedule_check", + "value": "\"workflow_run_dispatch\".\"status\" <> 'pending' or \"workflow_run_dispatch\".\"next_attempt_at\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.workflow_run_launch_spec": { + "name": "workflow_run_launch_spec", + "schema": "", + "columns": { + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_domain": { + "name": "deployment_domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_authority_principal_id": { + "name": "source_authority_principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "frozen_approval_bundle": { + "name": "frozen_approval_bundle", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source_offering_ids": { + "name": "source_offering_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "default_source_offering_id": { + "name": "default_source_offering_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deploy_content": { + "name": "deploy_content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "tool_package_pins": { + "name": "tool_package_pins", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_run_launch_spec_anchor_run_id_workflow_run_id_fk": { + "name": "workflow_run_launch_spec_anchor_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run_launch_spec", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_launch_spec_source_authority_principal_id_principal_id_fk": { + "name": "workflow_run_launch_spec_source_authority_principal_id_principal_id_fk", + "tableFrom": "workflow_run_launch_spec", + "tableTo": "principal", + "columnsFrom": ["source_authority_principal_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_run_execution": { + "name": "workflow_run_execution", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_run_execution_run_id_id_idx": { + "name": "workflow_run_execution_run_id_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_execution_status_idx": { + "name": "workflow_run_execution_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_run_execution_run_id_workflow_run_id_fk": { + "name": "workflow_run_execution_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run_execution", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/vendor/intx/db/migrations/meta/_journal.json b/vendor/intx/db/migrations/meta/_journal.json index 75a358077..0a9f4afac 100644 --- a/vendor/intx/db/migrations/meta/_journal.json +++ b/vendor/intx/db/migrations/meta/_journal.json @@ -568,6 +568,20 @@ "when": 1786665226312, "tag": "0081_workflow_definition_content_hash_and_approved_wire_hash", "breakpoints": true + }, + { + "idx": 82, + "version": "7", + "when": 1787066175571, + "tag": "0082_blue_black_queen", + "breakpoints": true + }, + { + "idx": 83, + "version": "7", + "when": 1787096121244, + "tag": "0083_replace_launch_spec_snapshot_with_frozen_bundle", + "breakpoints": true } ] } diff --git a/vendor/intx/db/src/index.ts b/vendor/intx/db/src/index.ts index b9a9f3f61..4c1896e22 100644 --- a/vendor/intx/db/src/index.ts +++ b/vendor/intx/db/src/index.ts @@ -59,6 +59,7 @@ export { } from "./sidecar-allocation-store"; export { createWorkflowDefinitionStore, + loadFrozenGrantSnapshot, resolveDefinitionIdForAsset, type WorkflowDefinitionRollbackResult, type WorkflowDefinitionSelector, diff --git a/vendor/intx/db/src/parse-row.ts b/vendor/intx/db/src/parse-row.ts index d8656f503..a085a14f8 100644 --- a/vendor/intx/db/src/parse-row.ts +++ b/vendor/intx/db/src/parse-row.ts @@ -17,7 +17,7 @@ import { workflowDefinitionVersionStatuses, } from "@intx/types"; import { WireGrantRule } from "@intx/types/grant-wire"; -import { RepoAction } from "@intx/types/sidecar"; +import { FrozenApprovalBundle, RepoAction } from "@intx/types/sidecar"; import { ToolPackagePinArray } from "@intx/types/tool-packages"; import type { @@ -217,7 +217,7 @@ export function parseWorkflowRunLaunchSpecRow( assertLaunchSpecSources(sourceOfferingIds, row.defaultSourceOfferingId); return { ...row, - definitionSnapshot: JSONObject.assert(row.definitionSnapshot), + frozenApprovalBundle: FrozenApprovalBundle.assert(row.frozenApprovalBundle), sourceOfferingIds, deployContent: JSONObject.assert(row.deployContent), toolPackagePins: diff --git a/vendor/intx/db/src/schema/workflow-definitions.ts b/vendor/intx/db/src/schema/workflow-definitions.ts index 3f77a9982..57cbdee38 100644 --- a/vendor/intx/db/src/schema/workflow-definitions.ts +++ b/vendor/intx/db/src/schema/workflow-definitions.ts @@ -104,6 +104,12 @@ export const workflowDefinitionVersion = pgTable( // and read back during re-verify to detect drift. Null before approval is // a legitimate state, so the column takes no NOT NULL constraint. approvedWireHash: text("approved_wire_hash"), + // Serializable projection of the deploy-time capability walk, recorded + // at approval so a run materializes grants from it instead of re-reading + // and re-walking a workflow.json blob. Validated as GrantWalkSnapshot at + // parse time. Null before approval is a legitimate state, so the column + // takes no NOT NULL constraint. + grantSnapshot: jsonb("grant_snapshot"), createdAt: timestamp("created_at").notNull().defaultNow(), }, (t) => [ diff --git a/vendor/intx/db/src/schema/workflow-run-launch-spec.ts b/vendor/intx/db/src/schema/workflow-run-launch-spec.ts index c842302ec..388af0c61 100644 --- a/vendor/intx/db/src/schema/workflow-run-launch-spec.ts +++ b/vendor/intx/db/src/schema/workflow-run-launch-spec.ts @@ -19,8 +19,7 @@ export const workflowRunLaunchSpec = pgTable("workflow_run_launch_spec", { sourceAuthorityPrincipalId: text("source_authority_principal_id") .notNull() .references(() => principal.id, { onDelete: "restrict" }), - definitionSnapshot: jsonb("definition_snapshot").notNull(), - definitionHash: text("definition_hash").notNull(), + frozenApprovalBundle: jsonb("frozen_approval_bundle").notNull(), sourceOfferingIds: jsonb("source_offering_ids").notNull(), defaultSourceOfferingId: text("default_source_offering_id").notNull(), deployContent: jsonb("deploy_content").notNull(), diff --git a/vendor/intx/db/src/workflow-definition-store.ts b/vendor/intx/db/src/workflow-definition-store.ts index da6743019..686ce8d4e 100644 --- a/vendor/intx/db/src/workflow-definition-store.ts +++ b/vendor/intx/db/src/workflow-definition-store.ts @@ -1,5 +1,7 @@ import { and, eq } from "drizzle-orm"; +import { GrantWalkSnapshot } from "@intx/types"; + import type { DB, DBExecutor } from "./client"; import { workflowDefinition, @@ -10,6 +12,12 @@ import { parseWorkflowDefinitionRow } from "./parse-row"; type DBHandle = DB["db"]; type ParsedWorkflowDefinition = ReturnType; +// The version `ensureWorkflowDefinitionForAsset` projects for a fresh +// definition, and therefore the row the approval freeze stamps the grant +// snapshot onto. Hand-coupled to the ensure helper's initial version; if that +// helper ever projects a different version this must follow. +const FROZEN_VERSION = "1"; + /** * The selector that keys a workflow definition's identity: the asset it * projects and the content hash of its wire projection. A single asset backs @@ -48,6 +56,32 @@ export async function resolveDefinitionIdForAsset( return row?.id ?? null; } +/** + * Read the deploy-approved grant-walk snapshot frozen onto a definition's + * version row, validated as a `GrantWalkSnapshot` at this boundary. Returns + * `null` when the version row is absent or its `grantSnapshot` column is still + * `null` -- the "not yet approved" state, mirroring `approvedWireHash`. The + * caller fails closed on `null`; it never substitutes an empty grant set. + */ +export async function loadFrozenGrantSnapshot( + db: DBExecutor, + definitionId: string, +): Promise { + const row = await db + .select({ grantSnapshot: workflowDefinitionVersion.grantSnapshot }) + .from(workflowDefinitionVersion) + .where( + and( + eq(workflowDefinitionVersion.definitionId, definitionId), + eq(workflowDefinitionVersion.version, FROZEN_VERSION), + ), + ) + .limit(1) + .then((rows) => rows[0]); + if (row === undefined || row.grantSnapshot === null) return null; + return GrantWalkSnapshot.assert(row.grantSnapshot); +} + export type WorkflowDefinitionRollbackResult = | { ok: true; definition: ParsedWorkflowDefinition } | { ok: false; reason: "definition_not_found" | "version_not_found" }; diff --git a/vendor/intx/harness/VENDORED-FROM b/vendor/intx/harness/VENDORED-FROM index b6d638bf1..191ecb1b0 100644 --- a/vendor/intx/harness/VENDORED-FROM +++ b/vendor/intx/harness/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/harness) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. drops the unvendored @intx/inference-testing devDependency. diff --git a/vendor/intx/hub-agent/VENDORED-FROM b/vendor/intx/hub-agent/VENDORED-FROM index 8b9e85056..ecfe5e27f 100644 --- a/vendor/intx/hub-agent/VENDORED-FROM +++ b/vendor/intx/hub-agent/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/hub-agent) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed; drops the unvendored @intx/test-harness devDependency (tests/lib is not vendored, same precedent as vendor/intx/harness dropping @intx/inference-testing). diff --git a/vendor/intx/hub-agent/src/ws/hub-link.ts b/vendor/intx/hub-agent/src/ws/hub-link.ts index 248d1cf87..def792e1d 100644 --- a/vendor/intx/hub-agent/src/ws/hub-link.ts +++ b/vendor/intx/hub-agent/src/ws/hub-link.ts @@ -450,11 +450,12 @@ export type WorkflowRunPackApplier = (args: { /** * The inert answer a probe execution produces, lifted off the * `workflow.probe.result` frame: the workflow's needs-surface projection, the - * inert grant set derived from it, and the projection's content hash. + * inert grant set derived from it, the un-flattened grant walk snapshot the set + * is derived from, and the projection's content hash. */ export type WorkflowProbeResult = Pick< WorkflowProbeResultFrame, - "projection" | "grants" | "wireHash" + "projection" | "grants" | "grantWalkSnapshot" | "wireHash" >; /** @@ -1336,6 +1337,7 @@ export function createHubLink(config: HubLinkConfig): HubLink { requestId: frame.requestId, projection: result.projection, grants: result.grants, + grantWalkSnapshot: result.grantWalkSnapshot, wireHash: result.wireHash, }); } catch (err) { diff --git a/vendor/intx/hub-api/VENDORED-FROM b/vendor/intx/hub-api/VENDORED-FROM index cc33e002f..65d92f686 100644 --- a/vendor/intx/hub-api/VENDORED-FROM +++ b/vendor/intx/hub-api/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/hub-api) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. adds a @types/ssri devDependency that bun's isolated linker does not hoist from tool-packaging the way upstream's install does; approval param routes exclude the reserved segment `needs-you` so hosts can mount a sibling /approvals/needs-you list without /:approvalId capturing it. diff --git a/vendor/intx/hub-api/src/app.ts b/vendor/intx/hub-api/src/app.ts index 81fad6627..437ca9f2c 100644 --- a/vendor/intx/hub-api/src/app.ts +++ b/vendor/intx/hub-api/src/app.ts @@ -287,7 +287,6 @@ export function mountHubRoutes( sidecarRouter, eventCollectors, repoStore, - assetService, ...(workflowDispatchService !== undefined ? { workflowDispatchService } : {}), @@ -308,13 +307,10 @@ export function mountHubRoutes( createWorkflowDefinitionRoutes({ db, requireGrant }), ); - // The workflow deploy + signal + listing surface needs the asset - // service to hydrate a workflow definition from its workflow.json, and - // the run-observe routes read the workflow-run repo through the repo - // store. Gate on both being present; the XOR throw above keeps - // assetService and repoStore moving as a unit, so this also narrows - // both away from null for the route factory. - if (assetService !== null && repoStore !== null) { + // The workflow deploy + signal + listing surface reads the workflow-run + // repo through the repo store (its run-observe routes and the mail-send + // trigger's terminal-state read). Gate on the repo store being present. + if (repoStore !== null) { app.route( "/api/tenants/:tenantId/workflows", createWorkflowRoutes({ @@ -327,7 +323,6 @@ export function mountHubRoutes( ? { workflowDispatchService } : {}), sidecarRouter, - assetService, repoStore, grantStore, requireGrant, diff --git a/vendor/intx/hub-api/src/routes/runs.ts b/vendor/intx/hub-api/src/routes/runs.ts index d47cf4497..97f830105 100644 --- a/vendor/intx/hub-api/src/routes/runs.ts +++ b/vendor/intx/hub-api/src/routes/runs.ts @@ -28,7 +28,6 @@ import { findRoutableById, resolveRunIdForSession, runRowToRoutableRecord, - type AssetService, type EventCollectorRegistry, type RepoStore, type RoutableRecord, @@ -151,13 +150,11 @@ export type CreateRunRoutesDeps = { sidecarRouter: SidecarRouter; eventCollectors: EventCollectorRegistry; // The workflow-run substrate that backs the durable run-event log the - // turns/events routes read and the workflow asset the mail-send trigger - // hydrates grants from. Both are null when the hub runs without the deploy - // surface (the app.ts XOR keeps assetService and repoStore moving as a unit); - // the substrate-backed routes then answer 503 rather than fabricating state, + // turns/events routes read and the run-event state the mail-send trigger + // reads. It is null when the hub runs without the deploy surface; the + // substrate-backed routes then answer 503 rather than fabricating state, // since createRunRoutes mounts unconditionally. repoStore: RepoStore | null; - assetService: AssetService | null; // The durable dispatch queue an exclusive deployment's trigger enqueues onto. // Absent when the hub runs without durable dispatch; the trigger then 503s an // exclusive send, exactly as the deployment Trigger route does. @@ -172,7 +169,6 @@ export function createRunRoutes({ sidecarRouter, eventCollectors, repoStore, - assetService, workflowDispatchService, grantStore, conditionRegistry, @@ -187,14 +183,12 @@ export function createRunRoutes({ repoStore !== null ? createWorkflowRunReader(repoStore) : null; // The mail-send trigger fires the run through its workflow-native Trigger - // path. It needs both the workflow asset (grant hydration) and the run-event - // substrate (terminal-state read), which the app.ts XOR moves as a unit, so a - // null on either leaves the trigger null and the mail-send route answers 503. + // path. It needs the run-event substrate (terminal-state read), so a null + // repoStore leaves the trigger null and the mail-send route answers 503. const triggerWorkflowRun = - repoStore !== null && assetService !== null + repoStore !== null ? createWorkflowRunTrigger({ db, - assetService, grantStore, sidecarRouter, ...(workflowDispatchService !== undefined diff --git a/vendor/intx/hub-api/src/routes/workflows.ts b/vendor/intx/hub-api/src/routes/workflows.ts index 82b95d1c5..cb94ba89f 100644 --- a/vendor/intx/hub-api/src/routes/workflows.ts +++ b/vendor/intx/hub-api/src/routes/workflows.ts @@ -24,16 +24,14 @@ import { } from "@intx/types"; import { InferenceSource } from "@intx/types/runtime"; import type { HarnessConfig } from "@intx/types/runtime"; -import { ToolPackagePinArray } from "@intx/types/tool-packages"; +import { WorkflowDefinitionSource } from "@intx/types/workflow-sources"; import { createWorkflowRunReader, ExclusiveWorkflowPlacementError, resolveWorkflowSidecarPlacement, - type AssetService, type RepoStore, type SessionService, type SidecarRouter, - type WorkflowDefinition, type WorkflowAllocationService, type WorkflowDispatchService, } from "@intx/hub-sessions"; @@ -47,7 +45,6 @@ import { import type { TenantEnv } from "../context"; import { idResource, type RequireGrant } from "../middleware/grant"; import { - hydrateDefinition, lockDispatchableAllocation, lockWorkflowRunState, } from "../run-grant-materialization"; @@ -64,15 +61,19 @@ import { WorkflowRunTriggerResponse, } from "../workflow-run-trigger"; -// Request body for the general workflow deploy. The workflow definition -// is hydrated from `assetId`'s `workflow.json`; the caller supplies the -// inference sources the per-step agents launch against (full credential -// resolution is the agent-instance path's concern, not this one). +// Request body for the general workflow deploy. The definition is CODE-SOURCED: +// `source` names where its bytes come from and `entry` the `interchange.workflow` +// module the sidecar evaluates; the hub installs + probes + gates + freezes it +// and deploys by source-ref. The caller supplies the inference chain the +// per-step agents launch against. `pin` selects the definition package for the +// `registry` and asset-`tarball` variants (asset-`source` selects by +// `packageName`). The `source` union is validated at this boundary. const DeployWorkflow = type({ - assetId: "string", + source: WorkflowDefinitionSource, + entry: "string > 0", sources: InferenceSource.array(), defaultSource: "string", - "toolPackages?": ToolPackagePinArray, + "pin?": "string > 0", }); // Request body for signal delivery. `signalId` is caller-supplied and @@ -187,7 +188,6 @@ export type CreateWorkflowRoutesDeps = { workflowAllocationService?: WorkflowAllocationService; workflowDispatchService?: WorkflowDispatchService; sidecarRouter: SidecarRouter; - assetService: AssetService; repoStore: RepoStore; grantStore: GrantStore; requireGrant: RequireGrant; @@ -199,7 +199,6 @@ export function createWorkflowRoutes({ workflowAllocationService, workflowDispatchService, sidecarRouter, - assetService, repoStore, grantStore, requireGrant, @@ -208,7 +207,6 @@ export function createWorkflowRoutes({ const runReader = createWorkflowRunReader(repoStore); const triggerWorkflowRun = createWorkflowRunTrigger({ db, - assetService, grantStore, sidecarRouter, ...(workflowDispatchService !== undefined @@ -236,7 +234,7 @@ export function createWorkflowRoutes({ tags: ["Workflows"], summary: "Deploy a workflow", description: - "Hydrates a workflow definition from its workflow asset's workflow.json and deploys it through the general multi-step workflow deploy path. Returns the deployment record.", + "Installs, probes, gates, and freezes a code-sourced workflow definition from its `source`/`entry`, then deploys it by source-ref. Returns the deployment record.", responses: { 201: { description: "Workflow deployed", @@ -251,11 +249,13 @@ export function createWorkflowRoutes({ content: { "application/json": { schema: resolver(ErrorResponse) } }, }, 409: { - description: "Workflow definition could not be hydrated", + description: + "Workflow definition invalid, exclusive placement unavailable on this Hub, or exclusive prepare rejected the source chain", content: { "application/json": { schema: resolver(ErrorResponse) } }, }, 500: { - description: "Deployment projection row missing after deploy", + description: + "Deployment projection row missing after deploy, or exclusive prepare failed unexpectedly", content: { "application/json": { schema: resolver(ErrorResponse) } }, }, 502: { @@ -269,9 +269,28 @@ export function createWorkflowRoutes({ const tenant = c.get("tenant"); const body = c.req.valid("json"); + // The deployment anchors its frozen `workflow_definition` to a + // `workflow`-kind asset. An asset-sourced deploy projects the definition + // over the very asset it sources from; a registry-sourced deploy has no + // backing asset for the definition, so this route (which anchors every + // deployment to a workflow asset) does not support it yet. + if (body.source.kind !== "asset") { + return c.json( + { + error: { + code: "unsupported_source", + message: + "Registry-sourced workflow deploys are not yet supported on this route", + }, + }, + 400, + ); + } + const definitionAssetId = body.source.assetId; + const assetRow = await db.query.asset.findFirst({ where: and( - eq(asset.id, body.assetId), + eq(asset.id, definitionAssetId), eq(asset.tenantId, tenant.id), eq(asset.kind, "workflow"), ), @@ -285,24 +304,6 @@ export function createWorkflowRoutes({ ); } - let definition: WorkflowDefinition; - try { - definition = await hydrateDefinition(assetService, assetRow.id); - } catch (err) { - return c.json( - { - error: { - code: "invalid_workflow", - message: - err instanceof Error - ? err.message - : "Failed to hydrate workflow definition", - }, - }, - 409, - ); - } - const [firstSource] = body.sources; if (firstSource === undefined) { return c.json( @@ -316,36 +317,12 @@ export function createWorkflowRoutes({ ); } - // A single-step deploy pins its full ordered inference chain and the - // reactor activates the head, so the default source must be the chain - // head. Reject a contradictory ordering here at the edge with a - // caller-facing message rather than letting it fall through to the - // orchestrator's internal invariant guard, which speaks in reactor terms. - // Multi-step deploys select a source per step, so their deploy-wide - // ordering is unconstrained and this check does not apply. - if ( - definition.stepOrder.length === 1 && - firstSource.id !== body.defaultSource - ) { - return c.json( - { - error: { - code: "invalid_workflow", - message: - "defaultSource must be the first entry in sources: a single-step deploy runs the default at the head of its pinned chain", - }, - }, - 409, - ); - } - + // Placement is now a pure tenant-config concern, decided BEFORE any + // definition is installed: a code-sourced deploy never hydrates a live + // definition to read declared placement off. let placement; try { - placement = await resolveWorkflowSidecarPlacement( - db, - tenant.id, - definition, - ); + placement = await resolveWorkflowSidecarPlacement(db, tenant.id); } catch (err) { return c.json( { @@ -363,25 +340,21 @@ export function createWorkflowRoutes({ const anchorRunId = generateId("workflowRun"); const sessionId = generateId("session"); - const config: HarnessConfig = { - sessionId, - agentId: deriveRunAgentId({ runId: anchorRunId }), - tenantId: tenant.id, - principalId: c.get("principal").id, - agentAddress: deriveRunAddress({ - runId: anchorRunId, - domain: tenant.domain, - }), - systemPrompt: "", - tools: [], - grants: [], - sources: body.sources, - defaultSource: body.defaultSource, - }; + const agentAddress = deriveRunAddress({ + runId: anchorRunId, + domain: tenant.domain, + }); let deployedId: string; let deploymentStatus = "deployed"; if (placement?.sharing === "exclusive") { + // Exclusive placement freezes the code-sourced approval on shared + // capacity NOW and defers the deploy to a dedicated allocation. The + // route only records the intent and returns a pending deployment; + // `deployReadyAllocation` deploys the frozen bundle once the sidecar is + // provisioned. (Exclusive is dormant in-tree -- no provisioner is + // registered -- so `prepareExclusiveDeployment` fails closed unless a + // tenant config requests it AND an operator build wires a provisioner.) if (workflowAllocationService === undefined) { return c.json( { @@ -400,7 +373,9 @@ export function createWorkflowRoutes({ tenantId: tenant.id, anchorRunId, deploymentDomain: tenant.domain, - definition, + source: body.source, + entry: body.entry, + ...(body.pin !== undefined ? { pin: body.pin } : {}), definitionAssetId: assetRow.id, placement, sessionId, @@ -408,21 +383,21 @@ export function createWorkflowRoutes({ sourceOfferingIds: body.sources.map((source) => source.id), defaultSourceOfferingId: body.defaultSource, deployContent: { systemPrompt: "" }, - ...(body.toolPackages !== undefined - ? { toolPackagePins: body.toolPackages } - : {}), }); deployedId = prepared.anchorRunId; deploymentStatus = prepared.status; } catch (err) { + // The shared-capacity probe/gate ran inside prepare: an unapproved + // definition is a client/definition error, not an infra failure. + if (err instanceof WorkflowDefinitionInvalidError) { + return c.json( + { error: { code: "invalid_workflow", message: err.message } }, + 409, + ); + } if (err instanceof ExclusiveWorkflowPlacementError) { return c.json( - { - error: { - code: err.code, - message: err.message, - }, - }, + { error: { code: err.code, message: err.message } }, 409, ); } @@ -440,32 +415,37 @@ export function createWorkflowRoutes({ ); } } else { + const config: HarnessConfig = { + sessionId, + agentId: deriveRunAgentId({ runId: anchorRunId }), + tenantId: tenant.id, + principalId: c.get("principal").id, + agentAddress, + systemPrompt: "", + tools: [], + grants: [], + sources: body.sources, + defaultSource: body.defaultSource, + }; try { - const result = await sessionService.deployWorkflowDefinition({ + const result = await sessionService.deployWorkflowFromSource({ tenantId: tenant.id, anchorRunId, deploymentDomain: tenant.domain, - definition, + agentAddress, + source: body.source, + entry: body.entry, + ...(body.pin !== undefined ? { pin: body.pin } : {}), definitionAssetId: assetRow.id, config, - deployContent: { systemPrompt: "" }, - ...(body.toolPackages !== undefined - ? { toolPackagePins: body.toolPackages } - : {}), }); deployedId = result.anchorRunId; } catch (err) { - // A single-step deploy whose source chain is invalid (head is not the - // default source, or a chain source the operator never approved) is a - // client/definition error, not a sidecar-reachability failure. + // An install/gate rejection or an unapproved/mis-ordered source chain + // is a client/definition error, not a sidecar-reachability failure. if (err instanceof WorkflowDefinitionInvalidError) { return c.json( - { - error: { - code: "invalid_workflow", - message: err.message, - }, - }, + { error: { code: "invalid_workflow", message: err.message } }, 409, ); } diff --git a/vendor/intx/hub-api/src/run-grant-materialization.ts b/vendor/intx/hub-api/src/run-grant-materialization.ts index 81041fc28..419dd3341 100644 --- a/vendor/intx/hub-api/src/run-grant-materialization.ts +++ b/vendor/intx/hub-api/src/run-grant-materialization.ts @@ -16,7 +16,6 @@ import { and, asc, eq } from "drizzle-orm"; import { alias } from "drizzle-orm/pg-core"; -import { type } from "arktype"; import { asset, @@ -28,26 +27,16 @@ import { workflowRun, } from "@intx/db/schema"; import type { DB, DBExecutor } from "@intx/db"; -import { createWorkflowRunStore } from "@intx/db"; +import { createWorkflowRunStore, loadFrozenGrantSnapshot } from "@intx/db"; import type { GrantStore, GrantRule } from "@intx/types/authz"; import { - GrantRequirement, isSidecarAllocationDispatchable, type GrantEffect, + type GrantRequirement, + type GrantWalkSnapshot, } from "@intx/types"; import { RunGrantsFrame } from "@intx/types/sidecar"; -import { - workflowDefinitionEnvelopeSchema, - WORKFLOW_JSON_PATH, - type AssetService, - type MailTriggeredRunGrantsResult, - type WorkflowDefinition, -} from "@intx/hub-sessions"; -import { - walkCapabilities, - type CapabilityWalkResult, -} from "@intx/workflow-deploy"; -import { createDefaultDirectorRegistry } from "@intx/agent"; +import { type MailTriggeredRunGrantsResult } from "@intx/hub-sessions"; import { deriveRunPrincipalId, generateId } from "@intx/hub-common"; import { @@ -55,8 +44,6 @@ import { type MaterializedGrantRow, } from "./grant-materialization"; -const GrantRequirements = GrantRequirement.array(); - // The `tool:` rows carry BARE tool names: the walk reads inline // `agent.toolFactories`, which have no bundle context. A workflow child gates // each tool call on `tool:`, and every runnable step tool is a @@ -72,44 +59,50 @@ const TOOL_GRANT_PREFIX = "tool:"; const EFFECT_GRANT_PREFIX = "effect:"; /** - * Project the capability walk into the run's runtime grant rows -- the - * `tool:` and `effect:` grants the runtime enforces fail-closed. + * Project the frozen grant-walk snapshot into the run's runtime grant rows -- + * the `tool:` and `effect:` grants the runtime enforces fail-closed. * Every distinct grant string across all steps becomes one creator-origin * `grant` row with `action: invoke`. The run's runtime authority is - * definition-pure for the deployment's stable top-level run, so the walk - * output alone determines it. + * definition-pure for the deployment's stable top-level run, so the snapshot + * alone determines it. * * Tool grants carry the effect the tool's static declaration requested (`ask` - * for approval-gated tools, `allow` otherwise) via the walk's `grantEffects` - * map. A tool in more than one step is emitted once; when two steps disagree + * for approval-gated tools, `allow` otherwise) via each step's `grantEffects` + * record. A tool in more than one step is emitted once; when two steps disagree * on its effect, `ask` wins over `allow` so an approval-gated declaration is * never silently downgraded. * * Effect grants are always `allow` -- the `effect.requires` set names the * capability floor an action needs, with no per-effect ask/allow distinction, - * so they are NOT routed through the `grantEffects` map (which covers tool + * so they are NOT routed through the `grantEffects` record (which covers tool * grants only). An `effect:` in more than one step is emitted once. */ export function deriveRunRuntimeGrantRows( - walk: CapabilityWalkResult, + snapshot: GrantWalkSnapshot, tenantId: string, runPrincipalId: string, now: Date, ): MaterializedGrantRow[] { const effectByResource = new Map(); - for (const declarations of walk.perStep.values()) { - for (const grant of declarations.grants) { + for (const step of snapshot.perStep) { + // The snapshot serializes each step's tool-grant-to-effect map as a plain + // object; rehydrate it to a `Map` so the lookup below matches the walk's + // original access pattern. + const grantEffects = new Map( + Object.entries(step.grantEffects), + ); + for (const grant of step.grants) { if (grant.startsWith(TOOL_GRANT_PREFIX)) { - // Every `tool:` grant the walk emits carries a `grantEffects` + // Every `tool:` grant the snapshot emits carries a `grantEffects` // entry (the tool-mark floor: `ask` for an approval-gated tool, - // `allow` otherwise). A missing entry means the walk's `grants` + // `allow` otherwise). A missing entry means the snapshot's `grants` // and `grantEffects` maps have diverged -- a defaulted `allow` // here would silently DOWNGRADE an `ask` tool below its floor, // defeating the approval gate. Fail loudly instead. - const effect = declarations.grantEffects.get(grant); + const effect = grantEffects.get(grant); if (effect === undefined) { throw new Error( - `deriveRunRuntimeGrantRows: tool grant ${JSON.stringify(grant)} has no grantEffects entry; the capability walk must emit an effect for every tool grant`, + `deriveRunRuntimeGrantRows: tool grant ${JSON.stringify(grant)} has no grantEffects entry; the grant-walk snapshot must carry an effect for every tool grant`, ); } const existing = effectByResource.get(grant); @@ -146,41 +139,6 @@ export function deriveRunRuntimeGrantRows( return rows; } -/** - * Read and hydrate the workflow definition from a workflow asset's - * `workflow.json`. Validates the structural envelope at this boundary, - * mirroring the workflow-host child's `loadWorkflowDefinition`: the - * per-primitive narrows live in the runtime layer that consumes the - * definition, so the envelope check plus the documented narrow is the - * canonical hydration shape. - */ -export async function hydrateDefinition( - assetService: AssetService, - assetId: string, -): Promise { - const raw = await assetService.readAssetBlob({ - assetId, - path: WORKFLOW_JSON_PATH, - }); - let parsed: unknown; - try { - parsed = JSON.parse(new TextDecoder().decode(raw)); - } catch (cause) { - throw new Error( - `workflow asset ${assetId} ${WORKFLOW_JSON_PATH} is not valid JSON`, - { cause }, - ); - } - const validated = workflowDefinitionEnvelopeSchema(parsed); - if (validated instanceof type.errors) { - throw new Error( - `workflow asset ${assetId} ${WORKFLOW_JSON_PATH} failed envelope validation: ${validated.summary}`, - ); - } - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- envelope schema enforces structural shape; per-primitive narrows live in the runtime layer that consumes the definition, matching loadWorkflowDefinition in @intx/workflow-host - return validated as unknown as WorkflowDefinition; -} - /** * Project a materialized run grant row into the `run.grants` wire shape -- * the same `WireGrantRule` encoding the `agent.deploy` frame's @@ -203,8 +161,14 @@ export function runGrantToWire( }; } -export type StageRunGrantsArgs = { - definition: WorkflowDefinition; +export type StageRunGrantsFromSnapshotArgs = { + /** + * The deploy-approved grant-walk snapshot frozen at approval. Its per-step + * grants drive the run's runtime `tool:`/`effect:` rows; its + * `grantRequirements` are NOT read here -- the caller passes the requirement + * slice it wants resolved through `grantRequirements` below. + */ + snapshot: GrantWalkSnapshot; tenantId: string; runPrincipalId: string; now: Date; @@ -217,9 +181,9 @@ export type StageRunGrantsArgs = { /** Declared creator grants resolved against the workflow asset's creator. */ creatorGrants: GrantRule[]; /** - * Grant requirements to resolve. The mail path pre-filters this to the - * non-invoker requirements before calling; the external route passes the - * definition's requirements unfiltered. + * Grant requirements to resolve. The mail path pre-filters the snapshot's + * requirements to the non-invoker ones before calling; the external route + * passes the snapshot's requirements unfiltered. */ grantRequirements: readonly GrantRequirement[]; }; @@ -236,46 +200,19 @@ export type StageRunGrantsResult = }; /** - * The capability walk for a workflow definition: the deploy-approved lift the - * run's runtime `tool:`/`effect:` grants project from. Isolated from - * `stageRunGrants` so a caller that walks a definition once can bind the walk - * to a stable identity and reuse it, rather than re-walking on every run (the - * mail-triggered path does exactly this). - */ -export function buildCapabilityWalk( - definition: WorkflowDefinition, -): CapabilityWalkResult { - const directorRegistry = createDefaultDirectorRegistry(); - return walkCapabilities(definition, directorRegistry); -} - -export type StageRunGrantsFromWalkArgs = Omit< - StageRunGrantsArgs, - "definition" -> & { - /** - * An already-computed capability walk. `stageRunGrants` supplies a fresh - * walk of a live definition; the mail-triggered path supplies the walk it - * froze at the deployment's approved identity, so no re-walk happens per run. - */ - walk: CapabilityWalkResult; -}; - -/** - * Stage a run's grant rows from an ALREADY-COMPUTED capability walk plus the - * resolved declared requirements. This is the walk-free tail shared by - * `stageRunGrants` (which walks a live definition then delegates here) and the - * mail-triggered materializer (which passes a walk cached at the deployment's - * approved identity). Returns the staged rows and their wire projection, or a - * rejection when a declared requirement's authority is insufficient. No - * database write happens here -- `commitRunGrants` performs it once the caller - * has accepted delivery. + * Stage a run's grant rows from the deploy-approved grant-walk snapshot plus + * the resolved declared requirements. The snapshot's per-step grants project + * the run's runtime `tool:`/`effect:` rows; the mail-triggered materializer and + * the external trigger route both drive this one tail. Returns the staged rows + * and their wire projection, or a rejection when a declared requirement's + * authority is insufficient. No database write happens here -- + * `commitRunGrants` performs it once the caller has accepted delivery. */ -export async function stageRunGrantsFromWalk( - args: StageRunGrantsFromWalkArgs, +export async function stageRunGrantsFromSnapshot( + args: StageRunGrantsFromSnapshotArgs, ): Promise { const runtimeGrantRows = deriveRunRuntimeGrantRows( - args.walk, + args.snapshot, args.tenantId, args.runPrincipalId, args.now, @@ -299,46 +236,6 @@ export async function stageRunGrantsFromWalk( return { ok: true, grantRows, stepGrants }; } -/** - * Derive and stage a run's grant rows from its definition: the walk's - * runtime `tool:`/`effect:` grants plus the resolved declared - * requirements. Walks the live definition and delegates to - * `stageRunGrantsFromWalk`. No database write happens here -- - * `commitRunGrants` performs it once the caller has accepted delivery. - */ -export async function stageRunGrants( - args: StageRunGrantsArgs, -): Promise { - return stageRunGrantsFromWalk({ - walk: buildCapabilityWalk(args.definition), - tenantId: args.tenantId, - runPrincipalId: args.runPrincipalId, - now: args.now, - invokerGrants: args.invokerGrants, - creatorGrants: args.creatorGrants, - grantRequirements: args.grantRequirements, - }); -} - -/** - * Validate a definition's `grantRequirements` at the boundary. Returns the - * validated array or a rejection carrying the validator summary. - */ -export function parseGrantRequirements( - definition: WorkflowDefinition, -): - | { ok: true; requirements: GrantRequirement[] } - | { ok: false; message: string } { - const validated = GrantRequirements(definition.grantRequirements ?? []); - if (validated instanceof type.errors) { - return { - ok: false, - message: `Invalid grant requirements: ${validated.summary}`, - }; - } - return { ok: true, requirements: validated }; -} - /** * Load a workflow asset's `creatorPrincipalId` -- the creator whose * authority creator-sourced grant requirements resolve against. Returns @@ -590,20 +487,19 @@ export async function commitRunGrants( export type MailTriggeredRunGrantsDeps = { db: DB["db"]; - assetService: AssetService; grantStore: GrantStore; }; /** - * A deployment's deploy-approved grant basis: the frozen capability walk the - * run's runtime `tool:`/`effect:` grants project from, and the creator-sourced - * grant requirements resolved against it. Both are pure functions of the - * approved definition content, so they are computed once per deployment and - * cached; nothing here depends on a live re-read of the asset blob. + * A deployment's deploy-approved grant basis: the grant-walk snapshot frozen at + * approval, from which the run's runtime `tool:`/`effect:` grants and its + * declared requirements both derive. The snapshot is a pure function of the + * approved definition content, keyed by the definition id, so it is read once + * per deployment and cached; nothing here depends on a live re-read or re-walk + * of the workflow's `workflow.json`. */ type FrozenRunGrantBasis = { - readonly walk: CapabilityWalkResult; - readonly creatorRequirements: readonly GrantRequirement[]; + readonly snapshot: GrantWalkSnapshot; }; /** @@ -611,13 +507,14 @@ type FrozenRunGrantBasis = { * `mail.outbound` handler invokes for each workflow-deployment recipient. * * A mail-triggered run derives its grants from the RECEIVING deployment's - * definition: the walk's `tool:`/`effect:` runtime grants plus the + * frozen snapshot: the snapshot's `tool:`/`effect:` runtime grants plus the * CREATOR-resolved declared requirements. Invoker-sourced requirements are * NOT materialized -- no invoker is on the wire -- and the run still * launches: a step that needs an invoker grant fails closed at its own - * authz check. The requirements are pre-filtered to `source !== "invoker"` - * before staging, so `resolveGrantMaterialization` keeps its - * reject-on-insufficient-invoker contract intact for the external route. + * authz check. The snapshot's requirements are pre-filtered to + * `source !== "invoker"` before staging, so `resolveGrantMaterialization` + * keeps its reject-on-insufficient-invoker contract intact for the external + * route. * * The materializer reserves the stable run and its immutable grants before * delivery. A delivery failure can therefore leave a grants-only run, which @@ -630,16 +527,16 @@ export function createMailTriggeredRunGrantsMaterializer( agentAddress: string; runId: string; }) => Promise { - // Closure-level cache of each deployment's deploy-approved grant basis, keyed - // by the workflow definition's identity. A definition id is content-addressed - // -- keyed by `(assetId, wireHash)`, frozen at approval -- and the anchor run + // Closure-level cache of each deployment's deploy-approved snapshot, keyed by + // the workflow definition's identity. A definition id is content-addressed -- + // keyed by `(assetId, wireHash)`, frozen at approval -- and the anchor run // carries that id, so the key names the APPROVED definition content, not the - // mutable asset blob behind it. The first trigger of a deployment hydrates - // and walks the definition once and freezes the result here; every later - // trigger consumes the frozen basis WITHOUT re-reading the asset blob or - // re-walking it. This closes the mutated-asset TOCTOU: rewriting the blob - // under a stable asset id cannot change a run's grants, because runs bind to - // the frozen approved walk, never a live re-hydrate. + // mutable asset blob behind it. The first trigger of a deployment reads the + // frozen snapshot from the version row once and caches it here; every later + // trigger consumes the cached snapshot WITHOUT re-reading it. The hub never + // walks a live definition on this path: a rewritten asset blob under a stable + // asset id cannot change a run's grants, because runs bind to the frozen + // snapshot, never a live re-hydrate or re-walk. const frozenBasisByDefinition = new Map(); return async ({ agentAddress, runId }) => { @@ -711,38 +608,35 @@ export function createMailTriggeredRunGrantsMaterializer( let basis = frozenBasisByDefinition.get(definitionId); if (basis === undefined) { - // First trigger of this deployment: read and walk the approved - // definition exactly once, then freeze the result. Neither the read nor - // the walk runs again for this definition id. - const definition = await hydrateDefinition( - deps.assetService, - definitionAssetId, - ); - - const parsedRequirements = parseGrantRequirements(definition); - if (!parsedRequirements.ok) { + // First trigger of this deployment: read the frozen snapshot from the + // version row once, then cache it. The read never runs again for this + // definition id, and no live definition is ever walked here. + const snapshot = await loadFrozenGrantSnapshot(deps.db, definitionId); + if (snapshot === null) { + // The definition has no approved grant snapshot -- the "not yet + // approved" state, mirroring a null `approvedWireHash`. Fail closed + // rather than substitute an empty grant set, which would launch a run + // with no runtime authority. throw new Error( - `mail-triggered run ${runId} for ${agentAddress}: ${parsedRequirements.message}`, + `mail-triggered run ${runId} for ${agentAddress}: definition ${definitionId} has no approved grant snapshot`, ); } - // Invoker-sourced requirements are not materialized on the mail path: - // filter them out BEFORE staging rather than teaching the resolver a - // skip mode, so the external route keeps resolving invoker grants. - const creatorRequirements = parsedRequirements.requirements.filter( - (r) => r.source !== "invoker", - ); - basis = { - walk: buildCapabilityWalk(definition), - creatorRequirements, - }; + basis = { snapshot }; frozenBasisByDefinition.set(definitionId, basis); } + // Invoker-sourced requirements are not materialized on the mail path: + // filter them out BEFORE staging rather than teaching the resolver a skip + // mode, so the external route keeps resolving invoker grants. + const creatorRequirements = basis.snapshot.grantRequirements.filter( + (r) => r.source !== "invoker", + ); + // Creator authority is resolved LIVE per run: the definition's grant SHAPE - // is frozen above, but which grants the creator currently holds is not part - // of that shape and can change between triggers. This reads the asset row's - // creator column and the creator's grants -- not the definition blob -- so - // it is not the re-read the frozen basis eliminates. + // is frozen in the snapshot, but which grants the creator currently holds + // is not part of that shape and can change between triggers. This reads the + // asset row's creator column and the creator's grants -- not the snapshot + // -- so it is not the read the frozen basis eliminates. const creatorPrincipalId = await loadAssetCreatorPrincipalId( deps.db, tenantId, @@ -752,7 +646,7 @@ export function createMailTriggeredRunGrantsMaterializer( deps.grantStore, tenantId, creatorPrincipalId, - basis.creatorRequirements, + creatorRequirements, ); // Derive the run principal id from `(tenantId, runId)`. The runId is the @@ -760,14 +654,14 @@ export function createMailTriggeredRunGrantsMaterializer( // principal and canonical grant snapshot. const runPrincipalId = await deriveRunPrincipalId(tenantId, runId); const now = new Date(); - const staged = await stageRunGrantsFromWalk({ - walk: basis.walk, + const staged = await stageRunGrantsFromSnapshot({ + snapshot: basis.snapshot, tenantId: tenantId, runPrincipalId, now, invokerGrants: [], creatorGrants, - grantRequirements: basis.creatorRequirements, + grantRequirements: creatorRequirements, }); if (!staged.ok) { return { diff --git a/vendor/intx/hub-api/src/workflow-run-trigger.ts b/vendor/intx/hub-api/src/workflow-run-trigger.ts index ddab60429..13a06f59a 100644 --- a/vendor/intx/hub-api/src/workflow-run-trigger.ts +++ b/vendor/intx/hub-api/src/workflow-run-trigger.ts @@ -23,6 +23,7 @@ import { workflowRun, } from "@intx/db/schema"; import type { DB } from "@intx/db"; +import { loadFrozenGrantSnapshot } from "@intx/db"; import type { GrantStore } from "@intx/types/authz"; import { assembleSignedContent, @@ -40,10 +41,8 @@ import { } from "@intx/types"; import type { RunGrantsFrame } from "@intx/types/sidecar"; import type { - AssetService, RepoStore, SidecarRouter, - WorkflowDefinition, WorkflowDispatchService, } from "@intx/hub-sessions"; import { deriveRunPrincipalId, generateId } from "@intx/hub-common"; @@ -53,12 +52,10 @@ import type { PrincipalRow, TenantRow } from "./context"; import { collectCreatorGrants, commitRunGrants, - hydrateDefinition, loadCommittedRunGrants, lockDispatchableAllocation, lockWorkflowRunState, - parseGrantRequirements, - stageRunGrants, + stageRunGrantsFromSnapshot, } from "./run-grant-materialization"; import type { MaterializedGrantRow } from "./grant-materialization"; import { validateAttachments } from "./attachment-validation"; @@ -85,7 +82,6 @@ export const WorkflowRunTriggerResponse = type({ export type TriggerWorkflowRunDeps = { db: DB["db"]; - assetService: AssetService; grantStore: GrantStore; sidecarRouter: SidecarRouter; workflowDispatchService?: WorkflowDispatchService; @@ -121,14 +117,8 @@ export type TriggerWorkflowRunResult = * result the caller maps onto its route surface. */ export function createWorkflowRunTrigger(deps: TriggerWorkflowRunDeps) { - const { - db, - assetService, - grantStore, - sidecarRouter, - workflowDispatchService, - repoStore, - } = deps; + const { db, grantStore, sidecarRouter, workflowDispatchService, repoStore } = + deps; async function readRunLifecycle( anchorRunId: string, @@ -293,20 +283,18 @@ export function createWorkflowRunTrigger(deps: TriggerWorkflowRunDeps) { stagedGrantRows = []; stepGrants = committedRunGrants.stepGrants; } else { - let definition: WorkflowDefinition; - try { - definition = await hydrateDefinition(assetService, definitionAssetId); - } catch (err) { + // Read the deploy-approved grant-walk snapshot frozen at approval, keyed + // by the deployment's definition id. A null snapshot is the "not yet + // approved" state; fail closed rather than derive an empty grant set. + const snapshot = await loadFrozenGrantSnapshot(db, anchor.definitionId); + if (snapshot === null) { return { ok: false, status: 409, body: { error: { code: "invalid_workflow", - message: - err instanceof Error - ? err.message - : "Failed to hydrate workflow definition", + message: `Workflow definition ${anchor.definitionId} has no approved grant snapshot`, }, }, }; @@ -333,20 +321,11 @@ export function createWorkflowRunTrigger(deps: TriggerWorkflowRunDeps) { } runPrincipalId = await deriveRunPrincipalId(tenant.id, runId); - const parsedRequirements = parseGrantRequirements(definition); - if (!parsedRequirements.ok) { - return { - ok: false, - status: 409, - body: { - error: { - code: "invalid_workflow", - message: parsedRequirements.message, - }, - }, - }; - } - const declaredGrantRequirements = parsedRequirements.requirements; + // The external route resolves invoker grants live and passes the + // snapshot's FULL requirement list unfiltered, so + // `resolveGrantMaterialization` keeps its reject-on-insufficient-invoker + // contract. + const declaredGrantRequirements = snapshot.grantRequirements; const invokerGrants = await grantStore.collectGrants( principal.id, tenant.id, @@ -357,8 +336,8 @@ export function createWorkflowRunTrigger(deps: TriggerWorkflowRunDeps) { assetRow.creatorPrincipalId, declaredGrantRequirements, ); - const staged = await stageRunGrants({ - definition, + const staged = await stageRunGrantsFromSnapshot({ + snapshot, tenantId: tenant.id, runPrincipalId, now, diff --git a/vendor/intx/hub-common/VENDORED-FROM b/vendor/intx/hub-common/VENDORED-FROM index 3a9c413f5..6d45b09bf 100644 --- a/vendor/intx/hub-common/VENDORED-FROM +++ b/vendor/intx/hub-common/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/hub-common) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/hub-sessions/VENDORED-FROM b/vendor/intx/hub-sessions/VENDORED-FROM index 81738fe8a..302fe0334 100644 --- a/vendor/intx/hub-sessions/VENDORED-FROM +++ b/vendor/intx/hub-sessions/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/hub-sessions) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. CL-5879: event-collector.ts's inference.usage case (previously falling into the "not persisted" default) now forwards {turnId, provider, model, usage} to an optional `onUsage` callback, threaded through event-collector-registry.ts's EventCollectorRegistryConfig as `onUsage(agentAddress, tenantId, sessionId, usage)` — the collector's own turn/tenant state is the only place these identifiers meet an inference.usage event. No persistence added upstream; the app wires the callback to @corbits/insights' usage sink. Terminal-anchor pack acceptance: hub-session-lookups.ts's receiveWorkflowRunPack no longer gates the anchor lookup on liveWorkflowRunStatuses — the ownership gate is the exported pure helper ownsWorkflowRunRepo (self-anchored row with a routable address), so a terminal run can still land the inbox-enqueue and markConsumed-rejection packs that retire mail which arrived in its teardown window. Upstream's live-status gate made that pair unresolvable: pack rejected as path_violation -> ack withheld -> hub redelivers, forever. diff --git a/vendor/intx/hub-sessions/src/index.ts b/vendor/intx/hub-sessions/src/index.ts index 0e7989d0b..b15df60f3 100644 --- a/vendor/intx/hub-sessions/src/index.ts +++ b/vendor/intx/hub-sessions/src/index.ts @@ -9,9 +9,10 @@ export { bridgeOrchestratorDeployContent, deployCodeSourcedWorkflow, type SessionService, - type DeployWorkflowDefinitionParams, type DeployWorkflowDefinitionResult, - type DeployPreparedWorkflowDefinitionParams, + type DeployWorkflowFromSourceParams, + type DeployPreparedCodeSourcedWorkflowParams, + type InstallAndApproveWorkflowSourceParams, type PreparedWorkflowDeployer, type DeployCodeSourcedWorkflowArgs, } from "./session-service"; @@ -21,6 +22,8 @@ export { type InstallAndApproveArgs, type InstallAndApproveResult, type ProbeGateResult, + type ProbeApprovalPolicy, + type ApproveProbedGrants, } from "./workflow-probe-gate"; export { committedReadsToSourceTree } from "./committed-source-tree"; export type { SourceTreeReads } from "./workflow-source-closure"; @@ -141,7 +144,6 @@ export { workflowDefinitionEnvelopeSchema, WORKFLOW_JSON_PATH, CAPABILITY_DECLARATIONS_JSON_PATH, - WORKFLOW_GITIGNORE_PATH, type WorkflowPrincipal, type WorkflowHubPrincipal, type WorkflowSidecarPrincipal, diff --git a/vendor/intx/hub-sessions/src/session-service.ts b/vendor/intx/hub-sessions/src/session-service.ts index b9a4e9ca9..975ed4fe1 100644 --- a/vendor/intx/hub-sessions/src/session-service.ts +++ b/vendor/intx/hub-sessions/src/session-service.ts @@ -1,10 +1,6 @@ import { type } from "arktype"; import { and, eq } from "drizzle-orm"; -import { - createDefaultDirectorRegistry, - type DirectorRegistry, -} from "@intx/agent"; import { getLogger } from "@intx/log"; import { assembleMessage, @@ -27,7 +23,6 @@ import { base64Encode, hexEncode } from "@intx/types"; import type { CredentialDelivery } from "@intx/types/sidecar"; import type { CredentialCipher } from "@intx/types"; import { generateId } from "@intx/hub-common"; -import { ensureWorkflowDefinitionForAsset } from "./workflow-definition-ensure"; import { sessionAsset as sessionAssetTable } from "@intx/db/schema"; import type { CryptoProvider, @@ -51,36 +46,21 @@ import { import { computeWireDefinitionHash } from "@intx/types/wire-definition-hash"; import type { SourceRefPin, - WorkflowProjectionDefinition, WorkflowProjectionWithSources, WorkflowSourceAssetMount, } from "@intx/types/sidecar"; import type { WorkflowDefinitionAssetSource, WorkflowDefinitionRegistrySource, + WorkflowDefinitionSource, } from "@intx/types/workflow-sources"; -import { computeLiveDefinitionHash } from "@intx/workflow"; -import { - defineWorkflow, - type WorkflowDefinition, -} from "@intx/workflow/definition"; import { - assertChainHeadIsDefault, - createWorkflowDeployOrchestrator, + buildInertProjectionStepSources, deriveRunAddress, enumerateInertOnTriggerBodies, pickStepInferenceSource, - walkCapabilities, - wrapHarnessAsSingleStepWorkflow, - type ApprovalSet, + WorkflowDefinitionInvalidError, type DeployContent as OrchestratorDeployContent, - type DeployWorkflowArgs, - type DeployWorkflowResult, - type DeploySingleStepFn, - type LaunchSessionFn, - type ReferencedBodyDefinition, - type SendMultiStepDeployFn, - type WorkflowRepoWriter, } from "@intx/workflow-deploy"; import type { AgentRepoStore, DeployContent } from "./agent-repo"; @@ -94,13 +74,18 @@ import type { SidecarAllocationRouter, SidecarRouter, } from "./ws/sidecar-handler"; -import type { Principal, RepoId } from "./repo-store"; +import type { Principal, RepoId, RepoKind } from "./repo-store"; import { buildSourceAssetMounts, type ResolveAssetAttachmentFn, } from "./workflow-closure-resolution"; import { restoreWorkflowRunToAllocation } from "./workflow-run-restore"; -import type { InstallAndApproveResult } from "./workflow-probe-gate"; +import { committedReadsToSourceTree } from "./committed-source-tree"; +import { + installAndApproveWorkflowDefinition, + type InstallAndApproveArgs, + type InstallAndApproveResult, +} from "./workflow-probe-gate"; const logger = getLogger(["interchange", "hub", "session-service"]); @@ -141,55 +126,27 @@ export type SessionService = { }): Promise; /** - * Deploy a single agent through the single-step-at-head path, - * wrapping the harness as a one-step workflow and routing it through the - * deploy core with the run's real identity. Replaces `launchSession` - * as the production single-agent deploy entry point: the run executes as a - * supervised workflow-process child. Records no deployment anchor run. - * Returns the head's agent-key ack (the key the head signs its - * reconnect challenges with). - */ - deployInstanceAtHead(params: { - agentAddress: string; - agentId: string; - runId: string; - config: HarnessConfig; - deployContent: DeployContent; - toolPackagePins?: readonly ToolPackagePin[]; - credentials?: CredentialDelivery; - }): Promise<{ publicKey: string }>; - - /** - * Deploy a one-step workflow once at the head through the deploy core, - * without a DB-backed deployment anchor run. Stages the - * head's deploy tree (deploy-tree write, pack, asset fan-out), fires the - * deployment `agent.deploy` frame carrying the workflow definition + - * source pin (the sidecar initializes the head repo and spawns the - * workflow-process child), then delivers the pack to the head. Returns - * the sidecar supervisor's principal public key. See `DeploySingleStepFn`. - */ - deploySingleStepAtHead: DeploySingleStepFn; - - /** - * Deploy a multi-step `WorkflowDefinition` through the workflow-deploy - * orchestrator's multi-step branch. This is the general workflow - * deploy entry point: it is not coupled to a single agent's - * credential/session model the way `launchSession` is. The - * orchestrator derives every per-step address - * from `anchorRunId` + `deploymentDomain`, provisions each step's - * agent-state repo via the shared per-agent deploy phases, writes the - * workflow repo, and fires the deployment-level `agent.deploy` frame. + * Deploy a CODE-SOURCED workflow definition end to end: install + probe + + * gate + freeze (`approve-probed`), then deploy the frozen definition by + * source-ref. This is the general workflow deploy entry point the + * `POST /deployments` route drives; it never hydrates a live definition from a + * static `workflow.json`. * - * Persists the deployment's anchor run -- the `workflow_run` whose id is - * `anchorRunId` -- carrying its routing identity and definition, so the - * deployment is listable per tenant off its runs; the RepoStore substrate - * has no by-kind listing API of its own. + * The service owns the source-read wiring (`repoStore` committed reads and + * asset pack fan-out) and the registry configuration, so the caller passes + * only the deploy intent: where the definition's bytes come from + * (`source`/`entry`/`pin`), the `workflow`-kind asset the definition projects + * over (`definitionAssetId`), and the shared harness config. The method + * dispatches on `source.kind`/`source.package.format` to build the install + * args, pins every top-level step's inference source under the frozen + * approval, and persists the deployment's anchor run. * - * Returns the supervisor's principal public key surfaced by the - * sidecar's `agent.deploy.ack`. + * Persists the deployment's anchor `workflow_run` (id = `anchorRunId`) via + * `deployCodeSourcedWorkflow`, so the deployment is listable per tenant. + * Returns the supervisor's principal public key from the sidecar deploy ack. */ - deployWorkflowDefinition( - params: DeployWorkflowDefinitionParams, + deployWorkflowFromSource( + params: DeployWorkflowFromSourceParams, ): Promise; /** @@ -205,62 +162,121 @@ export type SessionService = { endSession(agentAddress: string, reason: string): Promise; }; -export type DeployWorkflowDefinitionParams = { +export type DeployWorkflowDefinitionResult = { + /** Echoes the deployment id recorded on the projection row. */ + anchorRunId: string; + /** Deployment-level mail address the supervisor registers on the bus. */ + deploymentAddress: string; + /** Supervisor principal public key from the sidecar's deploy ack. */ + publicKey: string; +}; + +export type DeployWorkflowFromSourceParams = { /** Owning tenant; recorded on the deployment's anchor run. */ tenantId: string; /** - * Stable deployment identifier. The orchestrator concatenates it into - * every derived per-step address and the deployment-level address, and - * it is the deployment's anchor-run id. The caller owns its generation. + * Stable deployment identifier and anchor-run id. The deployment-level + * address derives from it; the caller owns its generation. */ anchorRunId: string; + /** Mail domain the deployment's derived addresses live under. */ + deploymentDomain: string; /** - * Mail domain the deployment's derived addresses live under. The - * orchestrator derives `-@` - * per step and `@` for the - * deployment-level supervisor address. + * The deployment-level mail address, derived by the caller from `anchorRunId` + * + `deploymentDomain`. Re-derived and asserted coherent inside + * `deployCodeSourcedWorkflow`. */ - deploymentDomain: string; - /** The hydrated workflow definition to deploy. */ - definition: WorkflowDefinition; + agentAddress: string; + /** Where the definition's bytes come from at apply time. */ + source: WorkflowDefinitionSource; + /** The `interchange.workflow` entry-module path the sidecar evaluates. */ + entry: string; + /** + * A `name@range` spec for the definition package. REQUIRED for the `registry` + * and asset-`tarball` variants (the pin selects the member); omitted for the + * asset-`source` variant, whose member is selected by `package.packageName`. + */ + pin?: string; /** - * The `workflow`-kind asset the definition was hydrated from. Recorded - * on the projection row so the listing surface can join back to the - * source asset. + * The `workflow`-kind asset the frozen definition projects a + * `workflow_definition` over. Distinct from a `source.kind === "asset"` + * source's `assetId`, which names where the bytes live. */ definitionAssetId: string; /** - * Harness configuration shared across every step's launch. The - * orchestrator overrides `agentAddress`, `agentId`, and `systemPrompt` - * per step. + * Harness config shared across the deployment. Its `sources`/`defaultSource` + * are the operator-supplied inference chain; the method pins each top-level + * step to one approved source from it. */ config: HarnessConfig; - /** Deploy-tree content shared across every step's launch. */ - deployContent: DeployContent; - /** Tool-package pins to ship with every step's deploy. */ - toolPackagePins?: readonly ToolPackagePin[]; }; -export type DeployPreparedWorkflowDefinitionParams = Omit< - DeployWorkflowDefinitionParams, - "definitionAssetId" -> & { - allocationTarget: AllocatedSidecarTarget; +/** + * Install/probe/gate/freeze inputs for a code-sourced workflow, DECOUPLED from + * deploy. The exclusive prepare path calls this on shared capacity at request + * time to freeze the approval, persists the frozen bundle, and deploys it to a + * dedicated allocation later with no re-probe. + */ +export type InstallAndApproveWorkflowSourceParams = { + /** Where the definition's bytes come from at probe time. */ + source: WorkflowDefinitionSource; + /** The `interchange.workflow` entry-module path the sidecar evaluates. */ + entry: string; + /** + * A `name@range` spec for the definition package. REQUIRED for the `registry` + * and asset-`tarball` variants; omitted for the asset-`source` variant. + */ + pin?: string; + /** The `workflow`-kind asset the frozen definition projects a definition over. */ + definitionAssetId: string; }; -export type DeployWorkflowDefinitionResult = { - /** Echoes the deployment id recorded on the projection row. */ +/** + * Inputs to deploy a previously-frozen code-sourced approval bundle to a + * dedicated allocation. Mirrors `DeployPreparedWorkflowDefinitionParams` for the + * source-ref lineage: the anchor `workflow_run` row already exists from prepare + * time, so the deploy UPDATES it under the allocation-ownership lock rather than + * inserting a fresh one. + */ +export type DeployPreparedCodeSourcedWorkflowParams = { + /** Owning tenant; the definition's own tenant for credential resolution. */ + tenantId: string; + /** The pre-inserted anchor run id, fixed at prepare time. */ anchorRunId: string; - /** Deployment-level mail address the supervisor registers on the bus. */ - deploymentAddress: string; - /** Supervisor principal public key from the sidecar's deploy ack. */ - publicKey: string; + /** Mail domain the deployment's derived addresses live under. */ + deploymentDomain: string; + /** The deployment-level mail address; re-derived and asserted coherent. */ + agentAddress: string; + /** Where the definition's bytes come from, rehydrated from the frozen bundle. */ + source: WorkflowDefinitionSource; + /** The frozen approval bundle rehydrated from the launch spec. */ + approved: InstallAndApproveResult; + /** Harness config carrying the re-resolved per-step inference chain. */ + config: HarnessConfig; + /** The exact allocation generation to deploy onto. */ + allocationTarget: AllocatedSidecarTarget; + /** Cipher for the definition's tenant-owned credential bindings, if any. */ + credentialCipher?: CredentialCipher; }; export type PreparedWorkflowDeployer = { - /** Deploy an anchor that was durably prepared before capacity was requested. */ - deployPreparedWorkflowDefinition( - params: DeployPreparedWorkflowDefinitionParams, + /** + * Install + probe + gate + freeze a code-sourced definition on shared + * capacity, returning the frozen bundle WITHOUT deploying it. The exclusive + * prepare path persists the bundle and deploys it later via + * `deployPreparedCodeSourcedWorkflow`. + */ + installAndApproveWorkflowSource( + params: InstallAndApproveWorkflowSourceParams, + ): Promise; + /** + * Deploy a previously-frozen code-sourced approval bundle to a dedicated + * allocation, updating the pre-existing anchor run under the + * allocation-ownership lock. No re-probe: the frozen projection/hash/closure + * ride verbatim. + */ + deployPreparedCodeSourcedWorkflow( + params: DeployPreparedCodeSourcedWorkflowParams, ): Promise; }; @@ -409,44 +425,7 @@ export function bridgeOrchestratorDeployContent( return bridged; } -/** - * Project a `WorkflowDefinition` onto the wire envelope the sidecar deploy - * router serializes verbatim into `workflow.json` and the workflow-process - * child re-validates against `workflowDefinitionEnvelopeSchema`: `id`, - * `triggers`, `steps`, `stepOrder`, optional `state`. The projection widens - * the `readonly` arrays at the boundary (the serializer never mutates them); a - * missing envelope-required field would round-trip into the child's envelope - * rejection on disk. - */ -function toWireWorkflowDefinition(definition: WorkflowDefinition): { - id: string; - triggers: unknown[]; - stepOrder: string[]; - steps: Record; - state?: Record; - grantRequirements?: unknown[]; - sidecarPlacement?: { - sharing: "exclusive"; - reuse?: "never" | "same-deployment"; - }; -} { - return { - id: definition.id, - triggers: [...definition.triggers], - stepOrder: [...definition.stepOrder], - steps: definition.steps as Record, - ...(definition.state !== undefined ? { state: definition.state } : {}), - ...(definition.grantRequirements !== undefined - ? { grantRequirements: [...definition.grantRequirements] } - : {}), - ...(definition.sidecarPlacement !== undefined - ? { sidecarPlacement: definition.sidecarPlacement } - : {}), - }; -} - -/** Fields both deploy-frame arms carry onto `sendAgentDeploy`, independent of - * whether the definition is live-authored or code-sourced. */ +/** Fields the deploy frame carries onto `sendAgentDeploy`. */ type DeployFrameCommonArgs = { sidecarRouter: SidecarRouter; sidecarAllocationRouter?: SidecarAllocationRouter; @@ -457,44 +436,21 @@ type DeployFrameCommonArgs = { }; /** - * Live-authored arm: the hub holds the live `WorkflowDefinition` and is the - * authority for the deployment's content hash. It projects the definition onto - * the wire envelope and recomputes the wire hash the frame carries. - */ -export type LiveAuthoredDeployFrameArgs = DeployFrameCommonArgs & { - lineage: "live-authored"; - definition: WorkflowDefinition; - /** - * Extracted onTrigger section bodies to carry inline so the sidecar - * materializes each as its own `assets/workflow//workflow.json` - * plus a co-located `sources.json`; a body child then resolves both the ref - * and its inference sources off disk without a hub round-trip. - */ - referencedDefinitions?: readonly ReferencedBodyDefinition[]; - credentials?: CredentialDelivery; -}; - -/** - * Source-ref arm: for a code-sourced (npm) deploy the hub never holds the live + * For a code-sourced (npm) deploy the hub never holds the live * `WorkflowDefinition` -- it lives only in the airlocked child. The gate/freeze - * layer already projected the definition to its inert `WorkflowProjectionDefinition` - * and hashed THAT; this arm carries both verbatim. The content hash is owned by - * the gate, so this arm never recomputes it -- recomputing over the live wire - * lineage would diverge from the inert projection the child re-verifies against. + * layer hashed the inert projection; the deploy frame carries that hash and the + * source-ref pin, and the sidecar re-materializes and evaluates the pinned code + * from the pin, so no inline definition rides the frame. The content hash is + * owned by the gate, so this frame never recomputes it -- recomputing over a + * live wire lineage would diverge from the inert projection the child + * re-verifies against. */ export type SourceRefDeployFrameArgs = DeployFrameCommonArgs & { lineage: "source-ref"; /** - * The inert wire projection the gate froze -- the same closed - * `WorkflowProjectionDefinition` a `workflow.probe.result` carries. Placed on - * the frame's `definition` field verbatim; it is already that field's type, - * so no coercion is needed. - */ - projection: WorkflowProjectionDefinition; - /** - * The gate-frozen wire hash of `projection` -- stamped onto the frame VERBATIM. - * This arm does not recompute it: the freeze layer owns the content hash, and - * the child re-verifies its recompute over the inert projection against this + * The gate-frozen wire hash of the approved projection -- stamped onto the + * frame VERBATIM. This arm does not recompute it: the freeze layer owns the + * content hash, and the child re-verifies its closure evaluation against this * exact value. */ approvedWireHash: string; @@ -507,20 +463,18 @@ export type SourceRefDeployFrameArgs = DeployFrameCommonArgs & { sourceRef: SourceRefPin; /** * Resolved credential material for the definition's credential bindings, - * delivered to the child on the frame (mirrors the live-authored arm). The - * hub resolves + decrypts here; the source-ref child decrypts nothing. The - * grant that AUTHORIZES a credential's use is minted per-run by run-grant - * materialization, not carried on this frame. + * delivered to the child on the frame. The hub resolves + decrypts here; the + * source-ref child decrypts nothing. The grant that AUTHORIZES a credential's + * use is minted per-run by run-grant materialization, not carried on this + * frame. */ credentials?: CredentialDelivery; /** * The projection's inline onTrigger section bodies, each already in inert wire * form with its per-step inference sources pinned and its own wire hash -- - * built by `deployCodeSourcedWorkflow` from the frozen projection. Carried - * verbatim on the SAME `referencedDefinitions` wire field the live-authored - * arm uses, so the sidecar stages each body's `sources.json` (and re-verify - * hash) with no lineage-specific handling. Absent when the projection has no - * inline onTrigger body. + * built by `deployCodeSourcedWorkflow` from the frozen projection. The sidecar + * stages each body's `sources.json` (and re-verify hash). Absent when the + * projection has no inline onTrigger body. */ referencedDefinitions?: readonly WorkflowProjectionWithSources[]; /** @@ -532,90 +486,48 @@ export type SourceRefDeployFrameArgs = DeployFrameCommonArgs & { assets?: readonly WorkflowSourceAssetMount[]; }; -export type SendMultiStepDeployFrameArgs = - | LiveAuthoredDeployFrameArgs - | SourceRefDeployFrameArgs; +export type SendMultiStepDeployFrameArgs = SourceRefDeployFrameArgs; /** - * Wire the workflow-deploy orchestrator's `sendMultiStepDeploy` - * dependency against `SidecarRouter.sendAgentDeploy`. The router - * accepts an optional `workflow` projection on the deploy frame; the - * sidecar's deploy router uses field presence to route the frame to - * the workflow deploy path. The supervisor public key returned by the - * sidecar's `agent.deploy.ack` is threaded back as the - * `MultiStepDeployResult.publicKey`. + * Emit the source-ref deploy frame onto `SidecarRouter.sendAgentDeploy`. The + * router accepts an optional `workflow` projection on the deploy frame; the + * sidecar's deploy router uses field presence to route the frame to the + * workflow deploy path, and returns the supervisor public key on the + * `agent.deploy.ack`. * - * The `lineage` discriminant selects who owns the content hash. On the - * `source-ref` arm the gate/freeze layer already hashed the inert projection, - * so the frozen hash and the inert projection ride the frame verbatim. On the - * `live-authored` arm the hub holds the live definition and recomputes the - * wire hash. - * The two arms are mutually exclusive at the type level: a source-ref deploy - * cannot pass a live definition and cannot omit its frozen hash. + * The gate/freeze layer already hashed the inert projection, so the frozen hash + * and the inert projection ride the frame verbatim -- this never recomputes the + * content hash. Recomputing over a live wire lineage would diverge from the + * inert projection the child re-verifies against. * - * Exported so the co-located caller-site test can assert that the - * closure constructed in `launchSession` reaches the wire surface via - * `sendAgentDeploy` with a `workflow` field structurally matching the - * `AgentDeployFrame.workflow` schema. + * Exported so the co-located caller-site test can assert that the constructed + * closure reaches the wire surface via `sendAgentDeploy` with a `workflow` + * field structurally matching the `AgentDeployFrame.workflow` schema. */ export async function sendMultiStepDeployFrame( args: SendMultiStepDeployFrameArgs, ): Promise<{ publicKey: string }> { - if (args.lineage === "source-ref") { - return args.sidecarRouter.sendAgentDeploy(args.agentAddress, args.config, { - // The inert projection and its gate-frozen hash ride the frame verbatim; - // neither is re-derived here. `projection` is already the frame's - // `definition` type, so it is assigned with no coercion. - definition: args.projection, - sources: args.sources, - approvedWireHash: args.approvedWireHash, - sourceRef: args.sourceRef, - ...(args.credentials !== undefined - ? { credentials: args.credentials } - : {}), - ...(args.referencedDefinitions !== undefined && - args.referencedDefinitions.length > 0 - ? { referencedDefinitions: [...args.referencedDefinitions] } - : {}), - ...(args.assets !== undefined && args.assets.length > 0 - ? { assets: [...args.assets] } - : {}), - }); - } - - const wireDefinition = toWireWorkflowDefinition(args.definition); - // The hub is the authority for the deployment's content hash: recompute the - // wire hash here so the frame carries the hub-approved value the sidecar - // feeds the child as `DEFINITION_HASH`. The freeze stored exactly this hash, - // so recomputing it at the hub reproduces the frozen approval's anchor; the - // sidecar never recomputes. - const approvedWireHash = await computeWireDefinitionHash(wireDefinition); const workflow = { - definition: wireDefinition, + // The deploy frame carries no inline definition: the sidecar evaluates the + // pinned code closure from `sourceRef` and re-verifies it against + // `approvedWireHash`. Only the gate-frozen hash and the pin ride the frame. sources: args.sources, - approvedWireHash, + approvedWireHash: args.approvedWireHash, + sourceRef: args.sourceRef, + ...(args.credentials !== undefined + ? { credentials: args.credentials } + : {}), ...(args.referencedDefinitions !== undefined && args.referencedDefinitions.length > 0 - ? { - referencedDefinitions: await Promise.all( - args.referencedDefinitions.map(async (body) => { - const bodyWire = toWireWorkflowDefinition(body.definition); - return { - definition: bodyWire, - sources: body.sources, - // Per-body freeze anchor: the hub recomputes each referenced - // body's wire hash so a body child re-verifies its recompute - // against the hub authority. - approvedWireHash: await computeWireDefinitionHash(bodyWire), - }; - }), - ), - } + ? { referencedDefinitions: [...args.referencedDefinitions] } : {}), - ...(args.credentials !== undefined - ? { credentials: args.credentials } + ...(args.assets !== undefined && args.assets.length > 0 + ? { assets: [...args.assets] } : {}), }; + // A prepared exclusive deploy routes its frame to the dedicated allocation; a + // shared deploy sends it on the shared router. The frozen projection/hash/pin + // ride verbatim in both cases -- only the transport differs. if (args.allocationTarget !== undefined) { if (args.sidecarAllocationRouter === undefined) { throw new Error("Exclusive deployment routing is not configured"); @@ -668,6 +580,14 @@ type DeployCodeSourcedCommonArgs = DeployFrameCommonArgs & { * deployment. */ credentialCipher?: CredentialCipher; + /** + * Present only for a prepared exclusive deploy: route the source-ref frame to + * this dedicated allocation instead of the shared router. `sidecarAllocationRouter` + * carries the allocation transport and is REQUIRED whenever `allocationTarget` + * is set. A shared deploy omits both. + */ + allocationTarget?: AllocatedSidecarTarget; + sidecarAllocationRouter?: SidecarAllocationRouter; }; /** Deploy a definition published to an npm registry: the sidecar fetches its @@ -714,10 +634,20 @@ function isAssetDeployArgs( * * A gate outcome that did not approve cannot deploy: an unapproved `approval` * fails closed here rather than shipping an unfrozen definition. + * + * This emits the source-ref deploy frame ONLY -- it does NOT write the anchor + * `workflow_run` row. `deployCodeSourcedWorkflow` wraps it with the shared-path + * INSERT; the prepared exclusive path wraps it with an UPDATE-under-lock of the + * anchor row that already exists from prepare time. It returns the frozen + * definition id so each wrapper writes the same content-addressed identity the + * gate persisted. */ -export async function deployCodeSourcedWorkflow( - args: DeployCodeSourcedWorkflowArgs, -): Promise<{ publicKey: string }> { +async function emitSourceRefDeployFrame( + args: DeployCodeSourcedWorkflowArgs & { + allocationTarget?: AllocatedSidecarTarget; + sidecarAllocationRouter?: SidecarAllocationRouter; + }, +): Promise<{ publicKey: string; definitionId: string }> { const { approval, projection, closure } = args.approved; if (!approval.ok) { throw new Error( @@ -793,38 +723,56 @@ export async function deployCodeSourcedWorkflow( } // Pin per-step inference sources for the projection's inline onTrigger bodies. - // The live-authored path pins these off the live AgentDefinition; the - // source-ref hub holds only the frozen inert projection, so it enumerates the - // inline bodies from the wire form and resolves each body step's source - // through the SAME resolver + operator-approval gate the live path uses + // The hub holds only the frozen inert projection, so it enumerates the inline + // bodies from the wire form and resolves each body step's source through the + // same resolver + operator-approval gate the top-level steps use // (`pickStepInferenceSource` against `approval.approvedGrants`). Each body's // wire hash is recomputed from the inert body verbatim, so a body child's // re-verify over the re-evaluated closure clears the same barrier a top-level - // re-verify does. The pinned sources ride OUTSIDE the hash (as on the live - // path); their trust comes from being resolved here under the approval gate, - // which is why the pin stays hub-side and is never caller-supplied. + // re-verify does. The pinned sources ride OUTSIDE the hash; their trust comes + // from being resolved here under the approval gate, which is why the pin stays + // hub-side and is never caller-supplied. // - // These entries reuse the live-authored `referencedDefinitions` wire field, so - // the sidecar stages them through its one lineage-agnostic loop with no - // source-ref-specific handling. Each entry's `definition` is the approved - // inert body def straight from the frozen, hash-covered projection (id set to - // the ref). On source-ref the sidecar stages that as a body workflow.json that - // is written REDUNDANTLY and NEVER read: the run child resolves bodies - // in-memory from the re-verified closure and hard-fails rather than reading a - // body workflow.json off disk (see the staging loop in workflow-host-wiring.ts - // and the anti-fallback guard in workflow-host run-child.ts). Only the - // co-staged sources.json is read on this path. Reuse is chosen over a - // dedicated sources-only field so the two lineages share one staging path and - // cannot drift; the redundant file is inert and approval-covered, not - // authoritative. + // These entries ride the `referencedDefinitions` wire field. Each entry's + // `definition` is the approved inert body def straight from the frozen, + // hash-covered projection (id set to the ref); the sidecar reads that id to + // key the per-body approved hash and to stage the body's `sources.json`, which + // the body child reads to pin its steps. The body child resolves the body + // DEFINITION itself in-memory from the re-verified closure and hard-fails + // rather than reading it off disk, so no body workflow.json is staged (see the + // staging loop in workflow-host-wiring.ts and the anti-fallback guard in + // workflow-host run-child.ts). const referencedDefinitions: WorkflowProjectionWithSources[] = await Promise.all( enumerateInertOnTriggerBodies(projection).map(async (body) => { const sources: Record = {}; for (const bodyStepId of body.definition.stepOrder) { + // Agent-bearing body steps run inference and need a source pinned + // through the approval gate. A non-agent body step (sleep, + // awaitSignal) declares no preference and runs no inference, so it + // advertises no `inference.source` grant the gate could approve -- + // but the deploy frame's coverage contract still requires a source + // entry for EVERY body step. Pin the deploy's default source as an + // inert placeholder for such a step: the body child resolves a + // step's source only when that step invokes inference, so this entry + // is never read, which is why it needs no operator approval. + const preferred = body.preferredByStep[bodyStepId] ?? null; + if (preferred === null) { + const placeholder = args.config.sources.find( + (s) => s.id === args.config.defaultSource, + ); + if (placeholder === undefined) { + throw new WorkflowDefinitionInvalidError( + body.ref, + `non-agent body step ${bodyStepId} needs an inert placeholder source, but the deploy config carries no defaultSource entry to pin`, + ); + } + sources[bodyStepId] = [placeholder]; + continue; + } sources[bodyStepId] = [ pickStepInferenceSource({ - preferred: body.preferredByStep[bodyStepId] ?? null, + preferred, stepId: bodyStepId, workflowId: body.ref, config: args.config, @@ -851,10 +799,15 @@ export async function deployCodeSourcedWorkflow( const result = await sendMultiStepDeployFrame({ lineage: "source-ref", sidecarRouter: args.sidecarRouter, + ...(args.sidecarAllocationRouter !== undefined + ? { sidecarAllocationRouter: args.sidecarAllocationRouter } + : {}), + ...(args.allocationTarget !== undefined + ? { allocationTarget: args.allocationTarget } + : {}), agentAddress: args.agentAddress, config: args.config, sources: args.sources, - projection, approvedWireHash: approval.approvedWireHash, sourceRef: { source: args.source, closure }, ...(credentials !== undefined ? { credentials } : {}), @@ -862,58 +815,44 @@ export async function deployCodeSourcedWorkflow( ...(assets.length > 0 ? { assets } : {}), }); - // Write the deployment's anchor `workflow_run` row -- the deployment's - // first-class record that owns its routing address and public key, mirroring - // the live-authored `deployWorkflowDefinition`. Run-grant materialization keys - // off this row (address + live status), so WITHOUT it no per-run grants (tool, - // capability, OR credential) ever materialize for a source-ref deployment. - // Born "deployed" (live but pre-trigger): the first trigger's materialization - // flips it to "running" via `anchorWithPrincipal`'s guarded update, which a - // row born "running" would skip. Its `anchorRunId` equals its own id, so the - // anchor references itself. The deployer read grant the live-authored path - // also seeds is deferred to the production route, which carries the - // authenticated deployer principal; this stays a single insert with no grant - // row to pair atomically. + return { publicKey: result.publicKey, definitionId: approval.definitionId }; +} + +/** + * The single public composition entrypoint for a SHARED code-sourced (npm) + * deploy: emit the source-ref frame, then INSERT the deployment's anchor + * `workflow_run` row -- the deployment's first-class record that owns its + * routing address and public key. Run-grant materialization keys off this row + * (address + live status), so WITHOUT it no per-run grants (tool, capability, OR + * credential) ever materialize for a source-ref deployment. Born "deployed" + * (live but pre-trigger): the first trigger's materialization flips it to + * "running" via `anchorWithPrincipal`'s guarded update, which a row born + * "running" would skip. Its `anchorRunId` equals its own id, so the anchor + * references itself. The deployer read grant is deferred to the production + * route, which carries the authenticated deployer principal; this stays a + * single insert with no grant row to pair atomically. + * + * The prepared exclusive path does NOT use this wrapper: its anchor row already + * exists from prepare time, so it wraps `emitSourceRefDeployFrame` with an + * UPDATE-under-allocation-lock instead of this INSERT. + */ +export async function deployCodeSourcedWorkflow( + args: DeployCodeSourcedWorkflowArgs, +): Promise<{ publicKey: string }> { + const { publicKey, definitionId } = await emitSourceRefDeployFrame(args); + await args.db.insert(workflowRunTable).values({ id: args.anchorRunId, tenantId: args.tenantId, anchorRunId: args.anchorRunId, - definitionId: approval.definitionId, + definitionId, address: args.agentAddress, - publicKey: result.publicKey, + publicKey, status: "deployed", createdAt: new Date(), }); - return result; -} - -/** - * `WorkflowRepoWriter` backed by the hub's repo substrate. Writes the - * orchestrator-produced workflow tree (`workflow.json`, - * `capability-declarations.json`, `.gitignore`) into a `workflow`-kind - * repo keyed by the workflow definition id, committing on the published - * asset ref. The hub principal is the only writer of the workflow repo, - * matching `workflowAuthorize`'s hub-writes / sidecar-reads split. - */ -function createHubWorkflowRepoWriter( - agentRepoStore: AgentRepoStore, -): WorkflowRepoWriter { - return { - async writeWorkflowRepo(args) { - const repoId: RepoId = { kind: "workflow", id: args.workflowRepoId }; - const files: Record = {}; - for (const [path, contents] of args.files) { - files[path] = contents; - } - await agentRepoStore.repoStore.writeTree( - HUB_PRINCIPAL, - repoId, - DEFAULT_ASSET_REF, - { files, message: "Write workflow deploy tree" }, - ); - }, - }; + return { publicKey }; } export function createSessionService( @@ -947,18 +886,14 @@ export function createSessionService( } /** - * Stage a deploy on the sidecar: resolve assets and tool packages, write - * the deploy tree, provision the agent, and deliver the deploy + asset - * packs (Phases 0-2b). Phase 1's provision has two shapes: - * - `workflowFrame` set: the single-step head hand-off fires the - * deployment `agent.deploy` frame that spawns the workflow-process - * child. Returns the supervisor public key. - * - `stageOnly` set: a multi-step per-step stage binds a transient route - * for the step address, fires a no-spawn provision frame (init repo + - * record hub key), and unbinds the route once the packs land. No - * child. - * A call with neither is rejected -- the legacy warm-harness path - * is gone. + * Stage one per-step deploy on the sidecar: resolve assets and tool + * packages, write the deploy tree, provision the step, and deliver the + * deploy + asset packs (Phases 0-2b). Phase 1 binds a transient route for + * the step address, fires a no-spawn provision frame (init repo + record + * hub key), and unbinds the route once the packs land -- no warm harness and + * no child. The deployment-level workflow frame, sent once after every step + * is staged, spawns the child. A call without `stageOnly` is rejected -- the + * legacy warm-harness and single-step-head paths are gone. */ async function executeLaunchPhases(params: { agentAddress: string; @@ -968,42 +903,19 @@ export function createSessionService( deployContent: DeployContent; toolPackagePins?: readonly ToolPackagePin[]; /** - * Single-step workflow deploy. When present, Phase 1 fires the - * deployment `agent.deploy` frame carrying the workflow definition + - * source pins (the sidecar initializes the head repo on receipt and - * spawns the workflow-process child) instead of the plain provision - * frame. The returned supervisor public key comes from that frame's - * ack. - * - * Mutually exclusive with `stageOnly`. - */ - workflowFrame?: { - definition: WorkflowDefinition; - sources: Record; - referencedDefinitions?: readonly ReferencedBodyDefinition[]; - credentials?: CredentialDelivery; - }; - /** - * Multi-step per-step stage. When true, Phase 1 binds a transient route - * for the step address, fires a no-spawn provision frame (the sidecar - * inits the step's agent-state repo and records the hub key), delivers - * the deploy + asset packs, and unbinds the route -- no provision of a - * warm harness and no child. The deployment-level workflow frame, sent - * once after every step is staged, spawns the child. Returns no ack. - * Mutually exclusive with `workflowFrame`. + * Per-step stage. When true, Phase 1 binds a transient route for the step + * address, fires a no-spawn provision frame (the sidecar inits the step's + * agent-state repo and records the hub key), delivers the deploy + asset + * packs, and unbinds the route -- no warm harness and no child. The + * deployment-level workflow frame, sent once after every step is staged, + * spawns the child. */ stageOnly?: boolean; allocationTarget?: AllocatedSidecarTarget; - }): Promise<{ publicKey: string } | undefined> { + }): Promise { const { agentAddress, agentId, runId, config, deployContent } = params; const toolPackagePins = params.toolPackagePins ?? []; const stageOnly = params.stageOnly ?? false; - if (params.workflowFrame !== undefined && stageOnly) { - throw new Error( - "executeLaunchPhases: workflowFrame and stageOnly are mutually exclusive", - ); - } - const workflowFrame = params.workflowFrame; let effectiveDeployContent: DeployContent = deployContent; @@ -1123,41 +1035,13 @@ export function createSessionService( } } try { - // Phase 1: Provision on sidecar. A single-step workflow deploy sends - // the deployment `agent.deploy` frame carrying the workflow definition - // + source pins: the sidecar's deploy router initializes the head repo - // on receipt (so the Phase 2 pack has a repo to apply into) and spawns - // the workflow-process child. A stage-only per-step deploy sends a + // Phase 1: Provision on sidecar. A stage-only per-step deploy sends a // no-spawn provision frame: the sidecar inits the step's agent-state // repo and records the hub key, but spawns nothing. Firing the frame // before the Phase 2 pack is the ordering barrier -- the repo must - // exist before the pack applies. A workflow frame's ack surfaces the - // supervisor public key to the caller. - let deployAckPublicKey: string | undefined; + // exist before the pack applies. try { - if (workflowFrame !== undefined) { - const ack = await sendMultiStepDeployFrame({ - lineage: "live-authored", - sidecarRouter, - ...(sidecarAllocationRouter !== undefined - ? { sidecarAllocationRouter } - : {}), - ...(params.allocationTarget !== undefined - ? { allocationTarget: params.allocationTarget } - : {}), - agentAddress, - config, - definition: workflowFrame.definition, - sources: workflowFrame.sources, - ...(workflowFrame.referencedDefinitions !== undefined - ? { referencedDefinitions: workflowFrame.referencedDefinitions } - : {}), - ...(workflowFrame.credentials !== undefined - ? { credentials: workflowFrame.credentials } - : {}), - }); - deployAckPublicKey = ack.publicKey; - } else if (stageOnly) { + if (stageOnly) { if (params.allocationTarget === undefined) { await sidecarRouter.sendProvisionStep(agentAddress, config); } else { @@ -1168,27 +1052,23 @@ export function createSessionService( ); } } else { - // Every caller supplies `workflowFrame` (single-step head) or - // `stageOnly` (multi-step per-step). A deploy with neither has no - // provisioning shape -- the legacy warm-harness path is gone -- so - // fail loud rather than ship a deploy pack the sidecar never - // provisioned a repo for. - throw new Error( - "executeLaunchPhases: a deploy requires either workflowFrame or stageOnly", - ); + // Every caller supplies `stageOnly`. A deploy without it has no + // provisioning shape -- the legacy warm-harness and single-step-head + // paths are gone -- so fail loud rather than ship a deploy pack the + // sidecar never provisioned a repo for. + throw new Error("executeLaunchPhases: a deploy requires stageOnly"); } } catch (err) { throw new SessionLaunchError("provision", err, false); } - // Phase 2: Pack delivery. On failure, the warm/workflow paths tear the - // sidecar deployment down; a stage-only step has no supervisor to - // undeploy, so it only drops its transient route (in the `finally`). - // The step's inited agent-state repo is left on the sidecar: the - // orchestrator aborts the whole deploy before the deployment frame is - // sent, so there is nothing to undeploy, and a redeploy of the same - // deployment overwrites the orphaned repo. This is an acceptable minor - // leak on the exceptional staging-failure path, not a live-path cost. + // Phase 2: Pack delivery. A stage-only step has no supervisor to + // undeploy, so on failure it only drops its transient route (in the + // `finally`). The step's inited agent-state repo is left on the sidecar: + // the deploy aborts before the deployment frame is sent, so there is + // nothing to undeploy, and a redeploy of the same deployment overwrites + // the orphaned repo. This is an acceptable minor leak on the exceptional + // staging-failure path, not a live-path cost. try { if (params.allocationTarget === undefined) { await sidecarRouter.sendPack(agentAddress, pack, ref, commitSha); @@ -1254,10 +1134,6 @@ export function createSessionService( } } } - - return deployAckPublicKey === undefined - ? undefined - : { publicKey: deployAckPublicKey }; } finally { if (stageOnly) { if (params.allocationTarget === undefined) { @@ -1272,119 +1148,6 @@ export function createSessionService( } } - /** - * Deploy a one-step workflow once at the head. Reuses the full - * launch-phase machinery (deploy-tree write, pack, asset fan-out) via - * `executeLaunchPhases`, swapping the Phase 1 provision frame for the - * workflow frame. The workflow frame makes the sidecar initialize the - * head repo and spawn the workflow-process child; the follow-up pack - * lands the head's deploy tree. Returns the supervisor's principal - * public key from the frame's ack. A workflow-frame launch always - * yields a deploy-ack key; its absence is a wiring bug, not a - * tolerable case. - */ - async function deploySingleStepAtHeadForRoute( - deployParams: Parameters[0], - allocationTarget?: AllocatedSidecarTarget, - ): Promise<{ publicKey: string }> { - const result = await executeLaunchPhases({ - agentAddress: deployParams.agentAddress, - agentId: deployParams.agentId, - runId: deployParams.runId, - config: deployParams.config, - deployContent: bridgeOrchestratorDeployContent( - deployParams.deployContent, - ), - workflowFrame: { - definition: deployParams.definition, - sources: deployParams.sources, - ...(deployParams.referencedDefinitions !== undefined - ? { referencedDefinitions: deployParams.referencedDefinitions } - : {}), - ...(deployParams.credentials !== undefined - ? { credentials: deployParams.credentials } - : {}), - }, - ...(deployParams.toolPackagePins !== undefined - ? { toolPackagePins: deployParams.toolPackagePins } - : {}), - ...(allocationTarget !== undefined ? { allocationTarget } : {}), - }); - if (result === undefined) { - throw new Error( - "single-step deploy at head: executeLaunchPhases returned no deploy-ack public key for a workflow-frame deploy", - ); - } - return result; - } - - const deploySingleStepAtHead: DeploySingleStepFn = (deployParams) => - deploySingleStepAtHeadForRoute(deployParams); - - /** - * Build the workflow-deploy orchestrator (with its launch-session and - * multi-step callbacks) and run one deploy. Shared by `launchSession` - * and `deployWorkflowDefinition`, which differ only in the workflow - * repo writer, the director registry, and the deploy args. - */ - async function runWorkflowDeploy(args: { - workflowRepo: WorkflowRepoWriter; - directorRegistry: DirectorRegistry; - deployArgs: DeployWorkflowArgs; - allocationTarget?: AllocatedSidecarTarget; - }): Promise { - // The per-step launcher: stage each step's deploy tree WITHOUT a warm - // harness (the supervised child runs the step), with the orchestrator's - // structural `DeployContent` narrowed back to the hub-sessions shape - // first. - const launchSessionCallback: LaunchSessionFn = (orchestratorParams) => - stageWorkflowStep({ - agentAddress: orchestratorParams.agentAddress, - agentId: orchestratorParams.agentId, - runId: orchestratorParams.runId, - config: orchestratorParams.config, - deployContent: bridgeOrchestratorDeployContent( - orchestratorParams.deployContent, - ), - ...(orchestratorParams.toolPackagePins !== undefined - ? { toolPackagePins: orchestratorParams.toolPackagePins } - : {}), - ...(args.allocationTarget !== undefined - ? { allocationTarget: args.allocationTarget } - : {}), - }); - - const sendMultiStepDeployCallback: SendMultiStepDeployFn = (deployParams) => - sendMultiStepDeployFrame({ - lineage: "live-authored", - sidecarRouter, - ...(sidecarAllocationRouter !== undefined - ? { sidecarAllocationRouter } - : {}), - ...(args.allocationTarget !== undefined - ? { allocationTarget: args.allocationTarget } - : {}), - agentAddress: deployParams.agentAddress, - config: deployParams.config, - definition: deployParams.definition, - sources: deployParams.sources, - ...(deployParams.referencedDefinitions !== undefined - ? { referencedDefinitions: deployParams.referencedDefinitions } - : {}), - }); - - const orchestrator = createWorkflowDeployOrchestrator({ - directorRegistry: args.directorRegistry, - workflowRepo: args.workflowRepo, - launchSession: launchSessionCallback, - sendMultiStepDeploy: sendMultiStepDeployCallback, - deploySingleStepAtHead: (deployParams) => - deploySingleStepAtHeadForRoute(deployParams, args.allocationTarget), - }); - - return orchestrator.deployWorkflow(args.deployArgs); - } - /** * Stage one step of a multi-step workflow deploy: bind a transient route * for the step address, fire a no-spawn provision frame (the sidecar inits @@ -1420,231 +1183,313 @@ export function createSessionService( }); } - /** - * Deploy a single agent through the single-step-at-head path: wrap - * the harness as a one-step workflow (the same wrap `launchSession` uses) and - * route it through `deploySingleStepAtHead` with the run's REAL identity - * -- so the head address IS the instance address and the deploy runs as a - * supervised workflow-process child. - * - * Unlike the orchestrator's `runSingleStepAtHead` (which derives its deploy - * key from the deployment), this passes the instance id as the `agentId` - * deploy key -- the id the head address encodes and every deploy-ref reader - * resolves by, so the hub-written deploy tree and the sidecar's state - * writeback share one repo. The child resolves its skills and tool-package - * pins by mailbox address, not by this key. It records no deployment anchor - * run (a plain instance has no workflow asset). Returns the head's agent-key - * ack. - */ - async function deployInstanceAtHead(params: { - agentAddress: string; - agentId: string; - runId: string; - config: HarnessConfig; - deployContent: DeployContent; - toolPackagePins?: readonly ToolPackagePin[]; - credentials?: CredentialDelivery; - }): Promise<{ publicKey: string }> { - const { agentAddress, agentId, runId, config, deployContent } = params; + // Resolve the npm registry config a code-sourced install resolves external + // deps against, by the registry name. A code-sourced deploy needs the + // registry map configured; a hub that mounts the deploy surface without it is + // mis-wired, so this fails loud rather than defaulting a registry URL. + function requireRegistryConfig(registryName: string): RegistryConfig { + if (toolPackageRegistries === undefined) { + throw new Error( + "deployWorkflowFromSource: the session service has no toolPackageRegistries configured; a code-sourced deploy cannot resolve its dependency closure", + ); + } + const config = toolPackageRegistries.httpRegistries.get(registryName); + if (config === undefined) { + throw new Error( + `deployWorkflowFromSource: no HTTP registry named ${JSON.stringify(registryName)} is configured`, + ); + } + return config; + } - const singleStepAgent = wrapHarnessAsSingleStepWorkflow({ - config, - deployContent, - }); - const workflow = defineWorkflow({ - id: `wf_${agentId}`, - agent: singleStepAgent, - trigger: { type: "mail", to: agentAddress }, - }); + // Build the git-pack resolver a source/tarball asset arm delivers inline. The + // pin names one backing asset, so the resolver binds that asset's repo (its + // kind fixed by the arm) and its default ref; a request for any OTHER asset id + // is a closure that reaches beyond its single backing asset and fails loud + // rather than silently packing the wrong repo. + function bindAssetAttachmentResolver( + assetId: string, + repoKind: RepoKind, + ): ResolveAssetAttachmentFn { + return async (requestedAssetId) => { + if (requestedAssetId !== assetId) { + throw new Error( + `deployWorkflowFromSource: closure references asset ${requestedAssetId}, but only the pinned source asset ${assetId} is deliverable`, + ); + } + const repoId: RepoId = { kind: repoKind, id: assetId }; + const commitSha = await agentRepoStore.repoStore.resolveRef( + HUB_PRINCIPAL, + repoId, + DEFAULT_ASSET_REF, + ); + if (commitSha === null) { + throw new Error( + `deployWorkflowFromSource: source asset ${assetId} has no commit on ${DEFAULT_ASSET_REF}`, + ); + } + const { pack, ref } = await agentRepoStore.repoStore.createPack( + HUB_PRINCIPAL, + repoId, + DEFAULT_ASSET_REF, + ); + return { pack, ref, commitSha }; + }; + } - // The sole step's id, read off the built definition. - const stepId = workflow.stepOrder[0]; - if (stepId === undefined) { + // Assemble the install args for the concrete source arm. Mirrors the + // `isAssetSourceInstallArgs`/`isAssetTarballInstallArgs` guards the probe gate + // narrows on: an asset-`source` arm binds committed reads at the pinned commit + // plus the npm registry for external deps; an asset-`tarball` arm binds the + // asset's blob reads and a pin; a `registry` arm carries only its registry + // config and a pin. A `pin` missing where the arm requires it fails closed. + async function buildInstallArgs( + params: InstallAndApproveWorkflowSourceParams, + resolveAttachment: ResolveAssetAttachmentFn | null, + ): Promise { + if (db === undefined) { throw new Error( - `instance deploy for ${agentAddress}: the wrapped single-step workflow has an empty stepOrder`, + "deployWorkflowFromSource requires a db handle to freeze the approval", ); } + const dbHandle = db; + const common = { + entry: params.entry, + assetId: params.definitionAssetId, + approvals: { mode: "approve-probed" } as const, + router: sidecarRouter, + db: dbHandle, + }; + const source = params.source; - // Pin the step's inference sources to the instance's FULL ordered source - // chain so the workflow-process child's reactor fails over across it at - // runtime. The route already resolved and authorized `config.sources` - // against the tenant catalog, so the chain is pinned directly with NO - // operator-approval sweep: the operator-approval gate does not apply on - // the pre-authorized instance path (unlike the workflow deploy path, - // which gates every source in the chain). Only the reactor's - // head-is-default invariant is enforced here. - assertChainHeadIsDefault({ - sources: config.sources, - defaultSource: config.defaultSource, - workflowId: workflow.id, - }); + if (source.kind === "asset") { + if (resolveAttachment === null) { + throw new Error( + "deployWorkflowFromSource: an asset-sourced deploy requires an attachment resolver", + ); + } + if (source.package.format === "source") { + const committed = + await agentRepoStore.repoStore.openCommittedReadsAtCommit( + HUB_PRINCIPAL, + { kind: "workflow", id: source.assetId }, + source.package.commitSha, + ); + if (committed === null) { + throw new Error( + `deployWorkflowFromSource: source asset ${source.assetId} has no commit ${source.package.commitSha}`, + ); + } + const registryName = requireDefaultRegistryName(); + return { + ...common, + source, + reads: committedReadsToSourceTree(committed), + registryName, + registryConfig: requireRegistryConfig(registryName), + resolveAttachment, + }; + } + if (params.pin === undefined) { + throw new Error( + "deployWorkflowFromSource: an asset-tarball deploy requires a name@range pin", + ); + } + if (assetService === undefined) { + throw new Error( + "deployWorkflowFromSource: an asset-tarball deploy requires an asset service to read the package blobs", + ); + } + const tarballAssetId = source.assetId; + const tarballService = assetService; + return { + ...common, + source, + pin: params.pin, + readBlob: (path) => + tarballService.readAssetBlob({ assetId: tarballAssetId, path }), + listBlobs: (dir) => + tarballService.listAssetBlobs({ assetId: tarballAssetId, dir }), + resolveAttachment, + }; + } + if (params.pin === undefined) { + throw new Error( + "deployWorkflowFromSource: a registry deploy requires a name@range pin", + ); + } + return { + ...common, + source, + pin: params.pin, + registryConfig: requireRegistryConfig(source.registry), + }; + } - return deploySingleStepAtHead({ - agentAddress, - agentId, - runId, - config, - deployContent, - definition: workflow, - sources: { [stepId]: config.sources }, - hubPublicKey: hexEncode(agentRepoStore.getSigningPublicKey()), - ...(params.toolPackagePins !== undefined - ? { toolPackagePins: params.toolPackagePins } - : {}), - ...(params.credentials !== undefined - ? { credentials: params.credentials } - : {}), - }); + function requireDefaultRegistryName(): string { + if (toolPackageRegistries === undefined) { + throw new Error( + "deployWorkflowFromSource: the session service has no toolPackageRegistries configured; a code-sourced deploy cannot resolve its dependency closure", + ); + } + return toolPackageRegistries.defaultRegistry; } - async function executeWorkflowDefinitionDeploy( - params: Omit & { - allocationTarget?: AllocatedSidecarTarget; - }, - ): Promise { - // The deploy is initiated by an authorized tenant operator against a - // workflow asset they authored; approve exactly the grant surface the - // definition declares. The same director registry feeds both this - // approval-set derivation and the orchestrator's gate so the walk the - // route approves and the walk the orchestrator enforces are identical. - const directorRegistry = createDefaultDirectorRegistry(); - const walk = walkCapabilities(params.definition, directorRegistry); - const operatorApprovals: ApprovalSet = new Set( - [...walk.perStep.values()].flatMap((declarations) => [ - ...declarations.grants, - ]), - ); + // Bind the pack resolver an asset arm delivers inline. An asset arm delivers + // its backing repo (its kind fixed by `package.format`); a registry arm + // fetches its tarballs over HTTP and delivers no asset, so it binds nothing. + // Both the install (probe) and the deploy rebind the SAME resolver from the + // source, so a prepared deploy reconstructs it from the frozen `source`. + function bindSourceAttachmentResolver( + source: WorkflowDefinitionSource, + ): ResolveAssetAttachmentFn | null { + return source.kind === "asset" + ? bindAssetAttachmentResolver( + source.assetId, + source.package.format === "source" ? "workflow" : "package-registry", + ) + : null; + } - const result = await runWorkflowDeploy({ - workflowRepo: createHubWorkflowRepoWriter(agentRepoStore), - directorRegistry, - deployArgs: { - workflow: params.definition, - runId: params.anchorRunId, - deploymentDomain: params.deploymentDomain, - config: params.config, - deployContent: params.deployContent, - operatorApprovals, - hubPublicKey: hexEncode(agentRepoStore.getSigningPublicKey()), - ...(params.toolPackagePins !== undefined - ? { toolPackagePins: params.toolPackagePins } - : {}), - }, - ...(params.allocationTarget !== undefined - ? { allocationTarget: params.allocationTarget } - : {}), - }); + // Install + probe + gate + freeze a code-sourced definition, returning the + // frozen bundle and the (asset-only) attachment resolver. The gate outcome is + // NOT asserted here: `deployWorkflowFromSource` and `installAndApproveWorkflowSource` + // each surface a non-approval as their own domain error. This is the shared + // freeze both the shared deploy and the exclusive prepare run. + async function prepareCodeSourcedApproval( + params: InstallAndApproveWorkflowSourceParams, + ): Promise<{ + approved: InstallAndApproveResult; + resolveAttachment: ResolveAssetAttachmentFn | null; + }> { + const resolveAttachment = bindSourceAttachmentResolver(params.source); + const installArgs = await buildInstallArgs(params, resolveAttachment); + const approved = await installAndApproveWorkflowDefinition(installArgs); + return { approved, resolveAttachment }; + } - return { - anchorRunId: params.anchorRunId, - deploymentAddress: deriveRunAddress({ - runId: params.anchorRunId, - domain: params.deploymentDomain, - }), - publicKey: result.publicKey, - }; + // Freeze a code-sourced approval on shared capacity WITHOUT deploying it. The + // exclusive prepare path persists the returned bundle and deploys it to a + // dedicated allocation later. A non-approval fails closed as an invalid + // definition. + async function installAndApproveWorkflowSource( + params: InstallAndApproveWorkflowSourceParams, + ): Promise { + const { approved } = await prepareCodeSourcedApproval(params); + if (!approved.approval.ok) { + throw new WorkflowDefinitionInvalidError( + approved.projection.id, + `code-sourced workflow install did not approve (reason: ${approved.approval.reason})`, + ); + } + return approved; } - async function deployWorkflowDefinition( - params: DeployWorkflowDefinitionParams, + async function deployWorkflowFromSource( + params: DeployWorkflowFromSourceParams, ): Promise { - const { - tenantId, - anchorRunId, - deploymentDomain, - definition, - definitionAssetId, - config, - } = params; - const result = await executeWorkflowDefinitionDeploy(params); - if (db === undefined) { throw new Error( - "deployWorkflowDefinition requires a db handle to record the deployment's anchor run", + "deployWorkflowFromSource requires a db handle to record the deployment's anchor run", + ); + } + const source = params.source; + const { approved, resolveAttachment } = + await prepareCodeSourcedApproval(params); + if (!approved.approval.ok) { + throw new WorkflowDefinitionInvalidError( + approved.projection.id, + `code-sourced workflow install did not approve (reason: ${approved.approval.reason})`, ); } - // The wire-projection hash keys the definition's selector-keyed identity: - // one asset backs many definitions, distinguished by this content handle. - const wireHash = await computeLiveDefinitionHash(definition); - const now = new Date(); - await db.transaction(async (tx) => { - // Project the workflow asset into a first-class definition (create-if- - // absent) so the anchor run can carry it. A native workflow's definition - // is otherwise born only in the one-time backfill; creating it here makes - // every deploy yield a definition, so the run's `definitionId` is - // populated at birth rather than only for the rows the backfill reached. - const { definitionId } = await ensureWorkflowDefinitionForAsset(tx, { - assetId: definitionAssetId, - wireHash, - }); - // The deployment's anchor run: the one workflow_run that carries the - // deployment's routing identity, 1:1 with the deployment (id and address - // both derived from `anchorRunId`). It is the deployment's sole - // first-class record -- the row that owns the address and public key the - // reconnect ownership challenge verifies: deploy-ack writes the key here - // and the key lookup reads it off this row. It is born "deployed" -- live - // but pre-trigger; the first trigger flips it to "running" -- carrying its - // definition. Its `anchorRunId` equals its own id, so the anchor row - // references itself. Child runs of this deployment are separate - // address-less rows. `principalId` is null -- the workflow-derived key - // path reads `publicKey` directly and never consults it, and the - // `workflow-run:` grant seeded below already covers reads. - await tx.insert(workflowRunTable).values({ - id: anchorRunId, - tenantId, - anchorRunId, - definitionId, - address: deriveRunAddress({ - runId: anchorRunId, - domain: deploymentDomain, - }), - publicKey: result.publicKey, - status: "deployed", - createdAt: now, - }); + // Pin every top-level step's inference source under the frozen approval, + // then hand the frozen bundle to the source-ref deploy. + const sources = buildInertProjectionStepSources({ + projection: approved.projection, + config: params.config, + operatorApprovals: approved.approval.approvedGrants, + }); - // Seed a read grant on the deployment's workflow-run resource for the - // deploying principal so they can observe run events out of the box, - // mirroring the per-instance agent-state read grant the agent deploy - // path seeds for the creator. Without this a non-owner deployer would - // deploy a workflow they cannot read the runs of. - await tx.insert(grantTable).values({ - id: generateId("grant"), - tenantId, - principalId: config.principalId, - resource: `workflow-run:${anchorRunId}`, - action: "read", - effect: "allow", - origin: "creator", - createdAt: now, - updatedAt: now, + const commonDeploy = { + approved, + sidecarRouter, + agentAddress: params.agentAddress, + config: params.config, + sources, + db, + tenantId: params.tenantId, + anchorRunId: params.anchorRunId, + deploymentDomain: params.deploymentDomain, + }; + // Branch on the source discriminant so the deploy args match the + // asset/registry arms of `DeployCodeSourcedWorkflowArgs`: an asset arm + // carries the attachment resolver (asserted non-null here to satisfy the + // union and fail loud on a mis-wired caller), a registry arm carries none. + let result: { publicKey: string }; + if (source.kind === "asset") { + if (resolveAttachment === null) { + throw new Error( + "deployWorkflowFromSource: asset source deploy is missing its attachment resolver", + ); + } + result = await deployCodeSourcedWorkflow({ + ...commonDeploy, + source, + resolveAttachment, }); + } else { + result = await deployCodeSourcedWorkflow({ ...commonDeploy, source }); + } + + // Seed the deploying principal's read grant on the deployment's workflow-run + // resource. `deployCodeSourcedWorkflow` wrote the anchor row but deliberately + // leaves this grant to the route, which carries the authenticated deployer + // principal. + const now = new Date(); + await db.insert(grantTable).values({ + id: generateId("grant"), + tenantId: params.tenantId, + principalId: params.config.principalId, + resource: `workflow-run:${params.anchorRunId}`, + action: "read", + effect: "allow", + origin: "creator", + createdAt: now, + updatedAt: now, }); - return result; + return { + anchorRunId: params.anchorRunId, + deploymentAddress: params.agentAddress, + publicKey: result.publicKey, + }; } - async function deployPreparedWorkflowDefinition( - params: DeployPreparedWorkflowDefinitionParams, - ): Promise { + /** + * Update a prepared anchor run's `publicKey` under the allocation-ownership + * lock. The anchor row was inserted at prepare time; this stamps the + * supervisor key returned by the deploy ack, but only while the allocation + * still names this exact accepted generation for this anchor. A lost lock (the + * allocation moved on, another worker took the generation) fails closed as a + * leaked-agent `SessionLaunchError` -- the deploy already reached the sidecar, + * so the caller must treat the sidecar agent as possibly live. Used by the + * `deployPreparedCodeSourcedWorkflow` prepared path. + */ + async function updateAnchorPublicKeyUnderAllocationLock(args: { + tenantId: string; + anchorRunId: string; + allocationTarget: AllocatedSidecarTarget; + publicKey: string; + }): Promise { if (db === undefined) { throw new Error( - "deployPreparedWorkflowDefinition requires a db handle to update the prepared anchor run", + "updateAnchorPublicKeyUnderAllocationLock requires a db handle", ); } - await restoreWorkflowRunToAllocation({ - agentRepoStore, - allocationRouter: requireAllocationRouter(), - allocationTarget: params.allocationTarget, - agentAddress: deriveRunAddress({ - runId: params.anchorRunId, - domain: params.deploymentDomain, - }), - }); - const result = await executeWorkflowDefinitionDeploy(params); + const dbHandle = db; try { - const updated = await db.transaction(async (tx) => { + const updated = await dbHandle.transaction(async (tx) => { const [allocation] = await tx .select({ id: sidecarAllocationTable.id, @@ -1656,28 +1501,28 @@ export function createSessionService( }) .from(sidecarAllocationTable) .where( - eq(sidecarAllocationTable.id, params.allocationTarget.allocationId), + eq(sidecarAllocationTable.id, args.allocationTarget.allocationId), ) .limit(1) .for("update"); if ( allocation === undefined || - allocation.anchorRunId !== params.anchorRunId || + allocation.anchorRunId !== args.anchorRunId || allocation.status !== "allocated" || - allocation.generation !== params.allocationTarget.generation || + allocation.generation !== args.allocationTarget.generation || allocation.ensureAcceptedGeneration !== - params.allocationTarget.generation + args.allocationTarget.generation ) { return null; } const [anchor] = await tx .update(workflowRunTable) - .set({ publicKey: result.publicKey }) + .set({ publicKey: args.publicKey }) .where( and( - eq(workflowRunTable.id, params.anchorRunId), - eq(workflowRunTable.anchorRunId, params.anchorRunId), - eq(workflowRunTable.tenantId, params.tenantId), + eq(workflowRunTable.id, args.anchorRunId), + eq(workflowRunTable.anchorRunId, args.anchorRunId), + eq(workflowRunTable.tenantId, args.tenantId), ), ) .returning({ id: workflowRunTable.id }); @@ -1685,13 +1530,107 @@ export function createSessionService( }); if (updated === null) { throw new Error( - `Prepared anchor run ${params.anchorRunId} lost allocation ownership before initialization completed`, + `Prepared anchor run ${args.anchorRunId} lost allocation ownership before initialization completed`, ); } } catch (error) { throw new SessionLaunchError("start", error, true); } - return result; + } + + /** + * Deploy a previously-frozen code-sourced approval bundle to a dedicated + * allocation. The anchor `workflow_run` row already exists from prepare time + * (with its `definitionId` set), so this UPDATES it under the + * allocation-ownership lock + * rather than inserting. No re-probe: the frozen projection/hash/closure ride + * verbatim from `params.approved`, and the per-step inference sources are + * re-pinned from the re-resolved chain (deliberately NOT frozen, since a + * resolved source carries a credential secret). + */ + async function deployPreparedCodeSourcedWorkflow( + params: DeployPreparedCodeSourcedWorkflowParams, + ): Promise { + if (db === undefined) { + throw new Error( + "deployPreparedCodeSourcedWorkflow requires a db handle to update the prepared anchor run", + ); + } + const dbHandle = db; + const approval = params.approved.approval; + if (!approval.ok) { + throw new Error( + "deployPreparedCodeSourcedWorkflow: refusing to deploy an unapproved workflow bundle", + ); + } + const allocationRouter = requireAllocationRouter(); + const source = params.source; + const resolveAttachment = bindSourceAttachmentResolver(source); + + // Re-pin every top-level step's inference source from the re-resolved chain + // under the frozen approval -- the same pin the shared deploy computes. + const sources = buildInertProjectionStepSources({ + projection: params.approved.projection, + config: params.config, + operatorApprovals: approval.approvedGrants, + }); + + // Restore the Hub-authoritative run ref onto the exact allocation generation + // before its address is routed. + await restoreWorkflowRunToAllocation({ + agentRepoStore, + allocationRouter, + allocationTarget: params.allocationTarget, + agentAddress: params.agentAddress, + }); + + const commonEmit = { + approved: params.approved, + sidecarRouter, + sidecarAllocationRouter: allocationRouter, + allocationTarget: params.allocationTarget, + agentAddress: params.agentAddress, + config: params.config, + sources, + db: dbHandle, + tenantId: params.tenantId, + anchorRunId: params.anchorRunId, + deploymentDomain: params.deploymentDomain, + ...(params.credentialCipher !== undefined + ? { credentialCipher: params.credentialCipher } + : {}), + }; + // Branch on the source discriminant so the emit args match the asset/registry + // arms: an asset arm carries the rebuilt attachment resolver (asserted + // non-null to satisfy the union), a registry arm carries none. + let result: { publicKey: string; definitionId: string }; + if (source.kind === "asset") { + if (resolveAttachment === null) { + throw new Error( + "deployPreparedCodeSourcedWorkflow: asset source deploy is missing its attachment resolver", + ); + } + result = await emitSourceRefDeployFrame({ + ...commonEmit, + source, + resolveAttachment, + }); + } else { + result = await emitSourceRefDeployFrame({ ...commonEmit, source }); + } + + await updateAnchorPublicKeyUnderAllocationLock({ + tenantId: params.tenantId, + anchorRunId: params.anchorRunId, + allocationTarget: params.allocationTarget, + publicKey: result.publicKey, + }); + + return { + anchorRunId: params.anchorRunId, + deploymentAddress: params.agentAddress, + publicKey: result.publicKey, + }; } async function rollbackCommittedAttachments( @@ -2065,10 +2004,9 @@ export function createSessionService( return { stageWorkflowStep, - deployInstanceAtHead, - deploySingleStepAtHead, - deployWorkflowDefinition, - deployPreparedWorkflowDefinition, + deployWorkflowFromSource, + installAndApproveWorkflowSource, + deployPreparedCodeSourcedWorkflow, sendUserMessage, endSession, }; diff --git a/vendor/intx/hub-sessions/src/sidecar-allocation/placement-policy.ts b/vendor/intx/hub-sessions/src/sidecar-allocation/placement-policy.ts index 4109dac3f..4e7ecf1ce 100644 --- a/vendor/intx/hub-sessions/src/sidecar-allocation/placement-policy.ts +++ b/vendor/intx/hub-sessions/src/sidecar-allocation/placement-policy.ts @@ -6,18 +6,16 @@ const EXCLUSIVE_PLACEMENT: SidecarPlacementRequirement = Object.freeze({ }); export type ResolveEffectiveSidecarPlacementOpts = { - readonly workflowPlacement?: SidecarPlacementRequirement; /** Tenant configs ordered from the workflow tenant through its ancestors. */ readonly tenantConfigs: readonly TenantConfig[]; }; /** * Resolves the placement fixed onto a new workflow run. An exclusive - * requirement at either the workflow or any tenant ancestor can only - * strengthen placement; no tenant configuration can weaken it. + * requirement at any tenant ancestor forces exclusive placement; no tenant + * configuration can weaken it. */ export function resolveEffectiveSidecarPlacement({ - workflowPlacement, tenantConfigs, }: ResolveEffectiveSidecarPlacementOpts): SidecarPlacementRequirement | null { const tenantPlacements = tenantConfigs.flatMap((config) => @@ -25,21 +23,13 @@ export function resolveEffectiveSidecarPlacement({ ? [config.sidecarPlacement] : [], ); + if (tenantPlacements.length === 0) { + return null; + } const tenantRequiresFreshCapacity = tenantPlacements.some( (placement) => placement.reuse !== "same-deployment", ); - if (workflowPlacement?.sharing === "exclusive") { - return tenantRequiresFreshCapacity - ? EXCLUSIVE_PLACEMENT - : { - sharing: "exclusive", - reuse: workflowPlacement.reuse ?? "never", - }; - } - if (tenantPlacements.length > 0) { - return tenantRequiresFreshCapacity - ? EXCLUSIVE_PLACEMENT - : { sharing: "exclusive", reuse: "same-deployment" }; - } - return null; + return tenantRequiresFreshCapacity + ? EXCLUSIVE_PLACEMENT + : { sharing: "exclusive", reuse: "same-deployment" }; } diff --git a/vendor/intx/hub-sessions/src/workflow-allocation-service.ts b/vendor/intx/hub-sessions/src/workflow-allocation-service.ts index 73fae623d..4fb46f90a 100644 --- a/vendor/intx/hub-sessions/src/workflow-allocation-service.ts +++ b/vendor/intx/hub-sessions/src/workflow-allocation-service.ts @@ -1,5 +1,3 @@ -import { type } from "arktype"; - import { createSidecarAllocationStore, createWorkflowRunLaunchSpecStore, @@ -17,13 +15,10 @@ import { type CredentialCipher, type SidecarPlacementRequirement, } from "@intx/types"; +import type { FrozenApprovalBundle } from "@intx/types/sidecar"; import type { HarnessConfig } from "@intx/types/runtime"; import type { ToolPackagePin } from "@intx/types/tool-packages"; -import { computeLiveDefinitionHash } from "@intx/workflow"; -import { - hashDefinition, - type WorkflowDefinition, -} from "@intx/workflow/definition"; +import type { WorkflowDefinitionSource } from "@intx/types/workflow-sources"; import { deriveRunAddress, deriveRunAgentId } from "@intx/workflow-deploy"; import type { DeployContent } from "./agent-repo"; @@ -37,8 +32,7 @@ import { type PreparedWorkflowDeployer, } from "./session-service"; import type { SidecarAllocationRouter } from "./ws/sidecar-handler"; -import { ensureWorkflowDefinitionForAsset } from "./workflow-definition-ensure"; -import { workflowDefinitionEnvelopeSchema } from "./workflow-kind"; +import type { InstallAndApproveResult } from "./workflow-probe-gate"; export class ExclusiveWorkflowPlacementError extends Error { readonly code: string; @@ -54,7 +48,15 @@ export type PrepareExclusiveWorkflowDeploymentArgs = { readonly tenantId: string; readonly anchorRunId: string; readonly deploymentDomain: string; - readonly definition: WorkflowDefinition; + /** Where the definition's bytes come from at probe time. */ + readonly source: WorkflowDefinitionSource; + /** The `interchange.workflow` entry-module path the sidecar evaluates. */ + readonly entry: string; + /** + * A `name@range` spec for the definition package. REQUIRED for the `registry` + * and asset-`tarball` variants; omitted for the asset-`source` variant. + */ + readonly pin?: string; readonly definitionAssetId: string; readonly placement: SidecarPlacementRequirement & { readonly sharing: "exclusive"; @@ -100,52 +102,10 @@ function randomAllocationId(): string { return `sal_${hexEncode(crypto.getRandomValues(new Uint8Array(16)))}`; } -function parseDefinitionSnapshot(snapshot: Record) { - const validated = workflowDefinitionEnvelopeSchema(snapshot); - if (validated instanceof type.errors) { - throw new Error( - `Persisted workflow definition failed validation: ${validated.summary}`, - ); - } - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- the persisted snapshot was written from a WorkflowDefinition and the same deployment-envelope schema validates it again at this DB-to-runtime boundary - return validated as unknown as WorkflowDefinition; -} - -export function resolveDeclaredWorkflowSidecarPlacement( - definition: WorkflowDefinition, -): SidecarPlacementRequirement | undefined { - const placements: SidecarPlacementRequirement[] = []; - - function visit(current: WorkflowDefinition): void { - if (current.sidecarPlacement !== undefined) { - placements.push(current.sidecarPlacement); - } - for (const primitive of Object.values(current.steps)) { - if (primitive.kind === "loop") { - visit(primitive.body); - } else if (primitive.kind === "onTrigger" && "inline" in primitive.body) { - visit(primitive.body.inline); - } - } - } - - visit(definition); - if (placements.length === 0) return undefined; - return { - sharing: "exclusive", - reuse: placements.every( - (placement) => placement.reuse === "same-deployment", - ) - ? "same-deployment" - : "never", - }; -} - -/** Resolve workflow and inherited tenant placement into one launch decision. */ +/** Resolve the tenant-inherited sidecar placement into one launch decision. */ export async function resolveWorkflowSidecarPlacement( db: DB["db"], tenantId: string, - definition: WorkflowDefinition, ): Promise { const tenantIds = await getAncestorChain(db, tenantId); const rows = await db.query.tenant.findMany({ @@ -169,11 +129,7 @@ export async function resolveWorkflowSidecarPlacement( } return config; }); - const workflowPlacement = resolveDeclaredWorkflowSidecarPlacement(definition); - return resolveEffectiveSidecarPlacement({ - tenantConfigs, - ...(workflowPlacement !== undefined ? { workflowPlacement } : {}), - }); + return resolveEffectiveSidecarPlacement({ tenantConfigs }); } export function createWorkflowAllocationService({ @@ -191,6 +147,10 @@ export function createWorkflowAllocationService({ async function prepareExclusiveDeployment( args: PrepareExclusiveWorkflowDeploymentArgs, ): Promise { + // Fail closed before any probe: exclusive placement has no meaning without a + // provisioner to stand up its dedicated sidecar, and the shared-capacity + // probe below is wasted work if no provisioner exists. (In-tree this is + // always null -- exclusive is dormant -- so a live prepare never runs here.) const provisioner = plugins.getDefaultProvisioner(); if (provisioner === null) { throw new ExclusiveWorkflowPlacementError( @@ -224,6 +184,37 @@ export function createWorkflowAllocationService({ ); } + // Probe + gate + freeze the code-sourced definition ONCE, on shared + // capacity, at request time. The freeze is sidecar-agnostic (a wire hash + // over the inert projection plus the resolved closure), so the frozen bundle + // deploys later to the dedicated allocation with no re-probe. A non-approval + // surfaces as a `WorkflowDefinitionInvalidError` from the deployer, which the + // route maps to a 409. + const approved = await preparedDeployer.installAndApproveWorkflowSource({ + source: args.source, + entry: args.entry, + ...(args.pin !== undefined ? { pin: args.pin } : {}), + definitionAssetId: args.definitionAssetId, + }); + if (!approved.approval.ok) { + // `installAndApproveWorkflowSource` already fails closed on a non-approval; + // restate the narrowing so the frozen bundle below reads the ok arm. + throw new Error( + "prepareExclusiveDeployment: install did not yield an approved definition", + ); + } + // Capture the narrowed values before the transaction: TS drops the + // `approval.ok` narrowing inside the async callback below. + const definitionId = approved.approval.definitionId; + const frozenApprovalBundle: FrozenApprovalBundle = { + source: args.source, + entry: args.entry, + projection: approved.projection, + closure: approved.closure, + approvedWireHash: approved.approval.approvedWireHash, + approvedGrants: [...approved.approval.approvedGrants], + }; + const allocationId = createAllocationId(); const createdAt = now(); const deploymentAddress = deriveRunAddress({ @@ -231,10 +222,9 @@ export function createWorkflowAllocationService({ domain: args.deploymentDomain, }); await db.transaction(async (tx) => { - const { definitionId } = await ensureWorkflowDefinitionForAsset(tx, { - assetId: args.definitionAssetId, - wireHash: await computeLiveDefinitionHash(args.definition), - }); + // The freeze already ensured (create-if-absent) the definition row keyed by + // the approved wire hash and returned its id; anchor to THAT row rather + // than re-ensuring, so the anchor's definition is exactly the one approved. await tx.insert(workflowRun).values({ id: args.anchorRunId, tenantId: args.tenantId, @@ -263,8 +253,7 @@ export function createWorkflowAllocationService({ sessionId: args.sessionId, deploymentDomain: args.deploymentDomain, sourceAuthorityPrincipalId: args.sourceAuthorityPrincipalId, - definitionSnapshot: args.definition, - definitionHash: hexEncode(hashDefinition(args.definition)), + frozenApprovalBundle, sourceOfferingIds: [...args.sourceOfferingIds], defaultSourceOfferingId: args.defaultSourceOfferingId, deployContent: args.deployContent, @@ -315,7 +304,7 @@ export function createWorkflowAllocationService({ }; const anchor = await db.query.workflowRun.findFirst({ where: eq(workflowRun.id, allocation.anchorRunId), - columns: { publicKey: true }, + columns: { publicKey: true, definitionId: true }, }); if (anchor === undefined) { throw new Error(`Allocation ${allocation.id} has no workflow anchor run`); @@ -330,18 +319,35 @@ export function createWorkflowAllocationService({ true, ); } - const spec = await launchSpecStore.get(allocation.anchorRunId); - if (spec === null) { + if (anchor.definitionId === null) { throw new Error( - `Allocation ${allocation.id} has no workflow launch specification`, + `Allocation ${allocation.id} anchor run has no frozen workflow definition`, ); } - const definition = parseDefinitionSnapshot(spec.definitionSnapshot); - if (hexEncode(hashDefinition(definition)) !== spec.definitionHash) { + const spec = await launchSpecStore.get(allocation.anchorRunId); + if (spec === null) { throw new Error( - `Allocation ${allocation.id} workflow definition hash does not match its launch specification`, + `Allocation ${allocation.id} has no workflow launch specification`, ); } + // The frozen bundle deploys verbatim -- no re-probe. Rehydrate the approval + // hand-off from it: the approved grant set becomes a `Set`, and the frozen + // definition id is the anchor's own (set at prepare time from this freeze). + const bundle = spec.frozenApprovalBundle; + const approved: InstallAndApproveResult = { + approval: { + ok: true, + definitionId: anchor.definitionId, + approvedWireHash: bundle.approvedWireHash, + approvedGrants: new Set(bundle.approvedGrants), + projection: bundle.projection, + }, + projection: bundle.projection, + closure: bundle.closure, + }; + // Re-resolve the inference chain from the catalog at launch time -- the + // launch spec stores offering ids, never resolved sources, so a rotated + // credential is picked up here and no secret was ever persisted. const resolved = await resolveSourcesByOfferingIds( db, allocation.tenantId, @@ -377,20 +383,17 @@ export function createWorkflowAllocationService({ sources: resolved.sources, defaultSource: defaultSource.id, }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- parseWorkflowRunLaunchSpecRow validates the persisted value is a JSON object; DeployContent's optional fields are validated again by the deploy-tree writers that consume them - const deployContent = spec.deployContent as DeployContent; - return preparedDeployer.deployPreparedWorkflowDefinition({ + return preparedDeployer.deployPreparedCodeSourcedWorkflow({ tenantId: allocation.tenantId, anchorRunId: allocation.anchorRunId, deploymentDomain: spec.deploymentDomain, - definition, + agentAddress: deploymentAddress, + source: bundle.source, + approved, config, - deployContent, allocationTarget, - ...(spec.toolPackagePins !== null - ? { toolPackagePins: spec.toolPackagePins } - : {}), + ...(credentialCipher !== undefined ? { credentialCipher } : {}), }); } diff --git a/vendor/intx/hub-sessions/src/workflow-kind.ts b/vendor/intx/hub-sessions/src/workflow-kind.ts index 5b202e854..8e3978f09 100644 --- a/vendor/intx/hub-sessions/src/workflow-kind.ts +++ b/vendor/intx/hub-sessions/src/workflow-kind.ts @@ -1,26 +1,17 @@ // KindHandler for the `workflow` asset kind. // -// A workflow asset is a git repo that holds a workflow definition in one of -// two shapes. `validatePush` accepts either, keyed on which manifest the tree -// carries at its top level: +// A workflow asset is a codebase: a top-level `package.json` declaring an +// `interchange.workflow` entry module plus arbitrary source files. The sidecar +// materializes the codebase into a closure and evaluates the pinned entry to the +// definition. `validatePush` requires the `package.json`; a tree that lacks one +// is rejected. The legacy `workflow.json` envelope form is no longer accepted at +// the push boundary. // -// - Envelope shape: a `workflow.json` serialized `WorkflowDefinition` (plus -// an optional `capability-declarations.json` and `.gitignore`). The deploy -// orchestrator writes this shape on every deploy, and the run/trigger -// layer reads it back to materialize grants. The content is parsed and -// structurally validated at push time; deeper primitive-shape and DAG -// validation belongs to the runtime layer that instantiates the definition -// (`defineWorkflow`). Any top-level entry outside the three-file set fails. -// - Codebase shape: a `package.json` declaring an `interchange.workflow` -// entry module plus arbitrary source files. The sidecar materializes the -// codebase into a closure and evaluates the pinned entry to the definition. -// Source files are unconstrained; the push validates the manifest's shape -// and the entry-path's containment, and refuses envelope-only artifacts and -// a committed `node_modules` so the two shapes stay disjoint. -// -// A tree with neither manifest, or one that carries a `package.json` alongside -// an envelope-valid `workflow.json`, is rejected: one asset must resolve to -// exactly one definition. The codebase shape accepts both a single package and +// Source files are unconstrained, but the push validates the manifest's shape +// and the entry-path's containment, and refuses an envelope-only +// `capability-declarations.json`, a committed `node_modules`, and an ambiguous +// tree that also carries an envelope-valid `workflow.json`, so one asset resolves +// to exactly one definition. The codebase shape accepts both a single package and // a `workspaces` monorepo; for a monorepo the push validates only the root's // well-formedness and leaves per-member validation to the resolver. // @@ -58,17 +49,10 @@ export type WorkflowPrincipal = WorkflowHubPrincipal | WorkflowSidecarPrincipal; export const WORKFLOW_JSON_PATH = "workflow.json"; export const CAPABILITY_DECLARATIONS_JSON_PATH = "capability-declarations.json"; -export const WORKFLOW_GITIGNORE_PATH = ".gitignore"; export const PACKAGE_JSON_PATH = "package.json"; export const NODE_MODULES_PATH = "node_modules"; export const PNPM_WORKSPACE_PATH = "pnpm-workspace.yaml"; -const ALLOWED_TOP_LEVEL = new Set([ - WORKFLOW_JSON_PATH, - CAPABILITY_DECLARATIONS_JSON_PATH, - WORKFLOW_GITIGNORE_PATH, -]); - /** * Structural arktype validator for the `workflow.json` envelope. The * substrate checks the cross-cutting shape of `WorkflowDefinition` @@ -76,10 +60,10 @@ const ALLOWED_TOP_LEVEL = new Set([ * `stepOrder`) but does not re-derive `defineWorkflow`'s DAG-level * validation here — primitive-level shape, default-input application, * and `after`-ref resolution belong to the runtime layer that hydrates - * the definition. Push-time validation rejects the obvious wrongs - * (missing top-level fields, wrong primitive types) so a tree that - * could not possibly hydrate into a `WorkflowDefinition` never reaches - * the deploy ref. + * the definition. The codebase push uses this validator to detect an + * ambiguous tree that also carries an envelope-valid `workflow.json`, + * and the hydrate-time definition loaders reuse it to validate a + * materialized definition before instantiation. */ const StepsObject = type("Record").narrow((value, ctx) => { if (Array.isArray(value)) { @@ -95,18 +79,12 @@ const StateObject = type("Record").narrow((value, ctx) => { return true; }); -const SidecarPlacement = type({ - sharing: "'exclusive'", - "reuse?": "'never' | 'same-deployment'", -}); - export const workflowDefinitionEnvelopeSchema = type({ id: "string > 0", triggers: "unknown[]", steps: StepsObject, stepOrder: "string[]", "state?": StateObject, - "sidecarPlacement?": SidecarPlacement, // `grantRequirements` passes through the envelope whether or not it is // declared here: arktype's `.onUndeclaredKey("ignore")` below is // passthrough, not stripping (only `"delete"` strips), so the hydrate read @@ -124,22 +102,6 @@ export const workflowDefinitionEnvelopeSchema = type({ "credentialBindings?": CredentialBinding.array(), }).onUndeclaredKey("ignore"); -/** - * Capability-declarations.json is held to "is a JSON object" at this - * commit; the per-step structure is owned by the capability-walk - * module that authors the file. `Record` on its own - * accepts arrays under arktype's structural-object semantics, so the - * push validator pairs it with an array-rejection narrow. - */ -const CapabilityDeclarationsObject = type("Record").narrow( - (value, ctx) => { - if (Array.isArray(value)) { - return ctx.mustBe("a JSON object, not an array"); - } - return true; - }, -); - const SidecarPrincipal = type({ kind: "'sidecar'", agentId: "string", @@ -188,69 +150,6 @@ function rejectPush( return { ok: false, reason }; } -/** - * Validate the envelope shape: a `workflow.json` `WorkflowDefinition` plus an - * optional `capability-declarations.json` and `.gitignore`, and nothing else at - * the top level. Entered only when the tree carries no `package.json`. - */ -async function validateWorkflowEnvelopePush( - repoId: RepoId, - ref: string, - topLevelTreePaths: string[], - readBlob: (path: string) => Promise, -): Promise { - for (const entry of topLevelTreePaths) { - if (!ALLOWED_TOP_LEVEL.has(entry)) { - return rejectPush( - repoId, - ref, - `unexpected top-level entry ${JSON.stringify(entry)}; allowed: "${WORKFLOW_JSON_PATH}", "${CAPABILITY_DECLARATIONS_JSON_PATH}", "${WORKFLOW_GITIGNORE_PATH}"`, - ); - } - } - - if (!topLevelTreePaths.includes(WORKFLOW_JSON_PATH)) { - return rejectPush( - repoId, - ref, - `tree has neither a ${PACKAGE_JSON_PATH} (codebase) nor a ${WORKFLOW_JSON_PATH} (envelope)`, - ); - } - - const workflowOutcome = await readJSONBlob(WORKFLOW_JSON_PATH, readBlob); - if (!workflowOutcome.ok) { - return rejectPush(repoId, ref, workflowOutcome.reason); - } - const validated = workflowDefinitionEnvelopeSchema(workflowOutcome.value); - if (validated instanceof type.errors) { - return rejectPush( - repoId, - ref, - `${WORKFLOW_JSON_PATH} failed validation: ${validated.summary}`, - ); - } - - if (topLevelTreePaths.includes(CAPABILITY_DECLARATIONS_JSON_PATH)) { - const capOutcome = await readJSONBlob( - CAPABILITY_DECLARATIONS_JSON_PATH, - readBlob, - ); - if (!capOutcome.ok) { - return rejectPush(repoId, ref, capOutcome.reason); - } - const capValidated = CapabilityDeclarationsObject(capOutcome.value); - if (capValidated instanceof type.errors) { - return rejectPush( - repoId, - ref, - `${CAPABILITY_DECLARATIONS_JSON_PATH} must be a JSON object: ${capValidated.summary}`, - ); - } - } - - return { ok: true }; -} - /** * Validate the codebase shape: a top-level `package.json` declaring a contained * `interchange.workflow` entry (single package), or a `workspaces` monorepo @@ -393,11 +292,10 @@ export const workflowKindHandler: KindHandler = { readBlob, ); } - return validateWorkflowEnvelopePush( + return rejectPush( repoId, ref, - topLevelTreePaths, - readBlob, + `a workflow asset must be a codebase declaring a ${PACKAGE_JSON_PATH} with an "interchange.workflow" entry; the ${WORKFLOW_JSON_PATH} envelope form is no longer supported`, ); }, onRefUpdated() { diff --git a/vendor/intx/hub-sessions/src/workflow-probe-gate.ts b/vendor/intx/hub-sessions/src/workflow-probe-gate.ts index b261594dd..46d6ce451 100644 --- a/vendor/intx/hub-sessions/src/workflow-probe-gate.ts +++ b/vendor/intx/hub-sessions/src/workflow-probe-gate.ts @@ -12,8 +12,10 @@ // 3. RECOMPUTE the wire hash over the RECEIVED projection as tamper-evidence: // a shipped hash that differs from the hub recompute is rejected, fail // closed, no coercion. -// 4. Gate the advisory grant set against the operator's `ApprovalSet`: every -// grant the probe surfaced must be operator-approved or the gate fails. +// 4. Gate the advisory grant set against the approval policy: an operator +// `ApprovalSet` requires every grant the probe surfaced to be approved or +// the gate fails, while `approve-probed` approves exactly what the probe +// surfaced. // 5. Freeze the approved wire hash onto the definition version row, keyed by // the definition's selector, and return the frozen approved grant set. // @@ -28,6 +30,7 @@ import { and, eq } from "drizzle-orm"; import type { DBExecutor } from "@intx/db"; import { workflowDefinitionVersion } from "@intx/db/schema"; +import type { GrantWalkSnapshot } from "@intx/types"; import type { PackumentFetcher, RegistryConfig } from "@intx/tool-packaging"; import type { WorkflowSourceAssetMount, @@ -58,14 +61,18 @@ const FROZEN_VERSION = "1"; /** * The frozen record an approval writes: the definition's asset selector, the - * approved wire hash (the freeze anchor), and the approved grant set. The grant - * set is a deterministic projection of the content the hash addresses; it rides - * the deploy hand-off in memory rather than a version-row column. + * approved wire hash (the freeze anchor), the approved grant set, and the + * grant-walk snapshot the run path materializes grants from. The grant set is a + * deterministic projection of the content the hash addresses and rides the + * deploy hand-off in memory; the snapshot is persisted onto the version row so a + * run derives its grants from the frozen walk without re-reading and re-walking + * the workflow's `workflow.json`. */ export type FrozenApproval = { readonly assetId: string; readonly approvedWireHash: string; readonly approvedGrants: readonly string[]; + readonly grantSnapshot: GrantWalkSnapshot; }; /** @@ -111,15 +118,16 @@ export type ProbeGateResult = /** * Build the production persistence step of the freeze. Records identity through * the selector-keyed ensure helper (a definition keyed by `(assetId, - * wireHash)`) and writes the approved wire hash onto that definition's version - * row. The grant set is not written to a version-row column -- none exists, and - * the approved wire hash already pins the exact content the grants project - * from -- so it travels with the returned frozen approval, not the row. + * wireHash)`) and writes the approved wire hash and the grant-walk snapshot onto + * that definition's version row in one transaction. The grant SET is not written + * to a version-row column -- the approved wire hash already pins the content the + * grants project from -- so it travels with the returned frozen approval; the + * snapshot is written because the run path reads it back to materialize grants. */ export function createDbFrozenApprovalWriter( db: DBExecutor, ): PersistFrozenApprovalFn { - return async ({ assetId, approvedWireHash }) => { + return async ({ assetId, approvedWireHash, grantSnapshot }) => { // Ensure-then-stamp is one freeze: a crash between the two would persist a // version row with a NULL `approvedWireHash`, which the schema treats as // the legitimate "not yet approved" state -- indistinguishable from an @@ -136,7 +144,7 @@ export function createDbFrozenApprovalWriter( // fails loud instead of open. const stamped = await tx .update(workflowDefinitionVersion) - .set({ approvedWireHash }) + .set({ approvedWireHash, grantSnapshot }) .where( and( eq(workflowDefinitionVersion.definitionId, definitionId), @@ -154,13 +162,43 @@ export function createDbFrozenApprovalWriter( }; } +/** + * Approve exactly the grant surface the probe reports, without a pre-walked + * operator `ApprovalSet` to gate against. Under this mode the gate skips the + * per-grant membership check and freezes exactly what the probe advertised. + * + * This is the code-sourced analogue of the live-authored self-approve: the hub + * has no live definition to pre-walk, so the probe's advertised grants ARE the + * declared surface. It does NOT relax tamper-evidence -- the wire-hash check + * still runs and can still fail closed. + */ +export type ApproveProbedGrants = { readonly mode: "approve-probed" }; + +/** + * How the gate turns the probe's advisory grant set into an approved set. + * Either an explicit operator `ApprovalSet` -- every advertised grant must + * appear in it or the gate fails closed -- or `approve-probed`, which approves + * exactly the surface the probe reported. + */ +export type ProbeApprovalPolicy = ApprovalSet | ApproveProbedGrants; + +function isApproveProbed( + policy: ProbeApprovalPolicy, +): policy is ApproveProbedGrants { + return "mode" in policy; +} + export type GateAndFreezeArgs = { /** The `workflow`-kind asset the frozen definition projects over. */ readonly assetId: string; /** The sidecar's inert probe answer: projection, advisory grants, shipped hash. */ readonly probeResult: WorkflowProbeResult; - /** The operator-approved grant-shape strings. The advisory set is gated against this. */ - readonly approvals: ApprovalSet; + /** + * The approval policy. An `ApprovalSet` gates the advisory set against the + * operator-approved grant-shape strings; `approve-probed` approves exactly + * the surface the probe reported. + */ + readonly approvals: ProbeApprovalPolicy; /** Persistence step for the freeze; `createDbFrozenApprovalWriter` in production. */ readonly persist: PersistFrozenApprovalFn; }; @@ -197,11 +235,13 @@ export async function gateAndFreezeProbeResult( }; } - // Gate the advisory grant set: every grant the probe surfaced must appear in - // the operator's approved set. Any miss fails the gate closed. - const unapprovedGrants = probeResult.grants.filter( - (grant) => !approvals.has(grant), - ); + // Gate the advisory grant set. Under an `ApprovalSet` every grant the probe + // surfaced must appear in the operator's approved set; any miss fails the + // gate closed. Under `approve-probed` there is no set to gate against -- the + // probe's surface IS the approved set -- so nothing is ever unapproved. + const unapprovedGrants = isApproveProbed(approvals) + ? [] + : probeResult.grants.filter((grant) => !approvals.has(grant)); if (unapprovedGrants.length > 0) { return { ok: false, reason: "grants_not_approved", unapprovedGrants }; } @@ -214,6 +254,7 @@ export async function gateAndFreezeProbeResult( assetId, approvedWireHash: recomputedWireHash, approvedGrants, + grantSnapshot: probeResult.grantWalkSnapshot, }); return { @@ -230,8 +271,12 @@ type InstallAndApproveCommonArgs = { readonly entry: string; /** The `workflow`-kind asset the frozen definition projects over. */ readonly assetId: string; - /** The operator-approved grant-shape strings. */ - readonly approvals: ApprovalSet; + /** + * The approval policy threaded to the gate: an operator `ApprovalSet` to gate + * the advisory set against, or `approve-probed` to approve exactly the + * surface the probe reports. + */ + readonly approvals: ProbeApprovalPolicy; /** The sidecar router carrying the probe transport. */ readonly router: Pick; /** Executor the freeze writes through. */ @@ -337,7 +382,9 @@ export type InstallAndApproveResult = { * result. This is production glue, not test-only wiring. * * The operator-approval decision is an input (`approvals`): the caller supplies - * the set the operator approved, and the gate holds the advisory set to it. + * either the `ApprovalSet` the operator approved, which the gate holds the + * advisory set to, or `approve-probed` to approve exactly the surface the probe + * reports. * * Returns the gate outcome alongside the inert projection and the frozen * closure so the deploy hand-off consumes them verbatim rather than re-probing diff --git a/vendor/intx/hub-sessions/src/workflow-run-kind.ts b/vendor/intx/hub-sessions/src/workflow-run-kind.ts index 45e146d82..09ad7e011 100644 --- a/vendor/intx/hub-sessions/src/workflow-run-kind.ts +++ b/vendor/intx/hub-sessions/src/workflow-run-kind.ts @@ -3013,7 +3013,7 @@ export type ReadProcessingEntryResult = { * committed -- which is exactly when the supervisor forwards * `trigger.fired` -- observes the processing entry. Reading the working tree (rather than walking the * committed git tree) matches the workflow-process child's sibling - * reads of `workflow.json` and `runs//events/`. Because the + * read of `runs//events/`. Because the * read issues no commit it cannot race the supervisor's `markConsumed` * write; it returns a point-in-time snapshot of the directory. */ diff --git a/vendor/intx/hub-sessions/src/ws/sidecar-handler.ts b/vendor/intx/hub-sessions/src/ws/sidecar-handler.ts index 013983b85..2ac2eabbd 100644 --- a/vendor/intx/hub-sessions/src/ws/sidecar-handler.ts +++ b/vendor/intx/hub-sessions/src/ws/sidecar-handler.ts @@ -15,6 +15,7 @@ import { hexEncode, isRunAddress, } from "@intx/types"; +import type { GrantWalkSnapshot } from "@intx/types"; import { deriveWorkflowRunRepoId } from "@intx/workflow-deploy"; import { type } from "arktype"; import { @@ -156,12 +157,13 @@ export type SendProbeArgs = { /** * The payload a `sendProbe` promise resolves with, lifted off the sidecar's * `workflow.probe.result` frame: the inert needs-surface projection of the - * probed workflow, the inert grant set derived from it, and the projection's - * content hash. + * probed workflow, the inert grant set derived from it, the un-flattened grant + * walk snapshot the set is derived from, and the projection's content hash. */ export type WorkflowProbeResult = { projection: WorkflowProjectionDefinition; grants: string[]; + grantWalkSnapshot: GrantWalkSnapshot; wireHash: string; }; @@ -1172,6 +1174,7 @@ export function createSidecarRouter( resolveProbe(frame.requestId, { projection: frame.projection, grants: frame.grants, + grantWalkSnapshot: frame.grantWalkSnapshot, wireHash: frame.wireHash, }); return; diff --git a/vendor/intx/inference-catalog/VENDORED-FROM b/vendor/intx/inference-catalog/VENDORED-FROM index 751e75198..df65115d9 100644 --- a/vendor/intx/inference-catalog/VENDORED-FROM +++ b/vendor/intx/inference-catalog/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/inference-catalog) -Commit: 5d2aa94a1894b13664b4c985a5acc6bcc4807f30 +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...) for both the "." and "./models" subpaths; dist references removed; src/catalog.test.ts excluded (source only, matching every other vendored package); devDependencies on the unvendored @intx/inference, @intx/inference-discovery, and @intx/types dropped since nothing in the copied src imports them. diff --git a/vendor/intx/inference/VENDORED-FROM b/vendor/intx/inference/VENDORED-FROM index 77c995e84..6bc345fc1 100644 --- a/vendor/intx/inference/VENDORED-FROM +++ b/vendor/intx/inference/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/inference) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/log/VENDORED-FROM b/vendor/intx/log/VENDORED-FROM index 4e3938956..281ce4019 100644 --- a/vendor/intx/log/VENDORED-FROM +++ b/vendor/intx/log/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/log) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/mail-memory/VENDORED-FROM b/vendor/intx/mail-memory/VENDORED-FROM index 938b81cb1..35c5d5491 100644 --- a/vendor/intx/mail-memory/VENDORED-FROM +++ b/vendor/intx/mail-memory/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/mail-memory) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/mime/VENDORED-FROM b/vendor/intx/mime/VENDORED-FROM index 99913edfd..c009b54e7 100644 --- a/vendor/intx/mime/VENDORED-FROM +++ b/vendor/intx/mime/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/mime) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/pack-transport/VENDORED-FROM b/vendor/intx/pack-transport/VENDORED-FROM index a56843ffe..6e04ac0fc 100644 --- a/vendor/intx/pack-transport/VENDORED-FROM +++ b/vendor/intx/pack-transport/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/pack-transport) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/storage-isogit/VENDORED-FROM b/vendor/intx/storage-isogit/VENDORED-FROM index 9e73d9c23..6c6d64e1f 100644 --- a/vendor/intx/storage-isogit/VENDORED-FROM +++ b/vendor/intx/storage-isogit/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/storage-isogit) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/tool-packaging/VENDORED-FROM b/vendor/intx/tool-packaging/VENDORED-FROM index 7c1a54dc9..4edb16406 100644 --- a/vendor/intx/tool-packaging/VENDORED-FROM +++ b/vendor/intx/tool-packaging/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/tool-packaging) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/types/VENDORED-FROM b/vendor/intx/types/VENDORED-FROM index 0e245db4f..41942ad12 100644 --- a/vendor/intx/types/VENDORED-FROM +++ b/vendor/intx/types/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/types) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/types/src/grant-snapshot.ts b/vendor/intx/types/src/grant-snapshot.ts new file mode 100644 index 000000000..d8b304ff6 --- /dev/null +++ b/vendor/intx/types/src/grant-snapshot.ts @@ -0,0 +1,37 @@ +// Serializable projection of the deploy-time capability walk. +// +// The capability walk produces per-step grant declarations keyed by two +// `Map`s (grant strings plus a tool-grant-to-effect map) alongside the +// definition's grant requirements. Persisting that walk so a run can +// materialize grants without re-reading and re-walking a `workflow.json` +// blob needs a plain-data shape: the `Map`s flatten to arrays and records +// so the whole thing survives a JSON round-trip. +// +// `perStep[i].grantEffects` covers TOOL grants only, mirroring the walk's +// `GrantDeclarations.grantEffects`; director/capability/inference.source/ +// mail.* grants live in `grants` and carry no effect entry. +// +// `grantRequirements` is the full, unfiltered requirement list (both +// creator- and invoker-sourced). Consumers filter it by source themselves; +// the snapshot does not filter here. + +import { type } from "arktype"; + +import { grantEffects, GrantRequirement } from "./grants"; + +const Effect = type.enumerated(...grantEffects); + +const GrantWalkStepSnapshot = type({ + stepId: "string", + grants: "string[]", + grantEffects: { + "[string]": Effect, + }, +}); + +export const GrantWalkSnapshot = type({ + perStep: GrantWalkStepSnapshot.array(), + grantRequirements: GrantRequirement.array(), +}); + +export type GrantWalkSnapshot = typeof GrantWalkSnapshot.infer; diff --git a/vendor/intx/types/src/index.ts b/vendor/intx/types/src/index.ts index 206530e4f..d851f51cd 100644 --- a/vendor/intx/types/src/index.ts +++ b/vendor/intx/types/src/index.ts @@ -4,6 +4,7 @@ export * from "./tenants"; export * from "./principals"; export * from "./roles"; export * from "./grants"; +export * from "./grant-snapshot"; export * from "./signals"; export * from "./instances"; export * from "./workflows"; diff --git a/vendor/intx/types/src/sidecar.ts b/vendor/intx/types/src/sidecar.ts index 6f44ca840..4d19375f3 100644 --- a/vendor/intx/types/src/sidecar.ts +++ b/vendor/intx/types/src/sidecar.ts @@ -8,6 +8,7 @@ // efficient but JSON is simpler to debug and inspect. import { type } from "arktype"; +import { GrantWalkSnapshot } from "./grant-snapshot"; import { WireGrantRule } from "./grant-wire"; import { BoundedApprovalSnapshot, @@ -165,8 +166,8 @@ export const SessionErrorFrame = type({ export type SessionErrorFrame = typeof SessionErrorFrame.infer; /** - * Acknowledges that an agent has been fully undeployed: harness stopped, - * state pushed (best-effort), and directory deleted. + * Acknowledges that an agent has been fully undeployed: the deployment's + * workflow child stopped, state pushed (best-effort), and directory deleted. */ export const AgentUndeployAckFrame = type({ type: "'agent.undeploy.ack'", @@ -395,11 +396,11 @@ export type CredentialDelivery = typeof CredentialDelivery.infer; * that pin (`closure`, concrete versions + integrity SRIs). The two ALWAYS * travel together -- the sidecar re-materializes the exact `closure` from * `source` and re-evaluates the pinned code -- so they are one co-required - * object rather than two independently-optional fields (which would let a - * "source without closure" state exist and be silently read as live-authored, - * downgrading the source-ref evaluate-the-pinned-code guarantee to trusting the - * inline projection). This is the same shape `WorkflowProbeRequestFrame` - * co-requires. A live-authored deploy carries no pin. + * object rather than two independently-optional fields (a "source without + * closure" state could not be re-materialized and re-evaluated, and evaluating + * the pinned code from the closure is the only channel the sidecar has to the + * runnable definition). This is the same shape `WorkflowProbeRequestFrame` + * co-requires. */ export const SourceRefPin = type({ source: WorkflowDefinitionSource, @@ -407,6 +408,32 @@ export const SourceRefPin = type({ }); export type SourceRefPin = typeof SourceRefPin.infer; +/** + * The frozen, fully-serializable record of a code-sourced workflow approval, + * persisted at prepare time and rehydrated to deploy the exact same definition + * later. It is the recovery input for an exclusively-placed workflow: the probe + * runs once on shared capacity at request time, its result is frozen here, and a + * ready allocation deploys THIS bundle verbatim with no re-probe. + * + * Every field is inert, secret-free data. `source`/`entry` name where the + * definition's bytes come from and the entry module the probe evaluated; + * `projection` is the inert wire projection the freeze hashed; `closure` is the + * frozen dependency closure the pin resolved to; `approvedWireHash` is the freeze + * anchor; `approvedGrants` is the approved grant set (rehydrated to a `Set` on + * the deploy hand-off). Per-step inference sources are deliberately NOT frozen + * here -- they carry credential secrets and are re-resolved from the launch + * spec's offering ids at deploy time. + */ +export const FrozenApprovalBundle = type({ + source: WorkflowDefinitionSource, + entry: "string > 0", + projection: WorkflowProjectionDefinition, + closure: ToolPackageManifest, + approvedWireHash: "string > 0", + approvedGrants: "string[]", +}); +export type FrozenApprovalBundle = typeof FrozenApprovalBundle.infer; + /** * A hub asset delivered inline in a source-ref frame so the sidecar can * materialize a closure entry whose bytes live in that asset. `pack` is the @@ -426,45 +453,56 @@ export const WorkflowSourceAssetMount = type({ export type WorkflowSourceAssetMount = typeof WorkflowSourceAssetMount.infer; /** - * A full workflow deploy frame: the shared `WorkflowProjectionWithSources` base - * (definition + per-step sources + approved hash, carrying the - * stepOrder-covered-by-sources narrow) intersected with the top-level-only - * extras. Sharing the base via `.and()` means the field set and the coverage - * narrow are defined once, not restated here. - */ -export const AgentDeployWorkflow = WorkflowProjectionWithSources.and( - type({ - // Extracted onTrigger section bodies, materialized to their own workflow - // assets on the sidecar so a body child's spawn-child resolves the body by - // ref without a hub round-trip (the body id IS the asset ref). Optional: - // only an onTrigger deploy carries it, and every existing non-onTrigger - // deploy omits it and still validates. Each entry carries the body - // definition AND the body's own per-step inference-source pins, materialized - // beside the body on disk (`sources.json`) so a body child -- in-process, - // its env lost across a restart -- resolves inference durably without a hub - // round-trip. - "referencedDefinitions?": WorkflowProjectionWithSources.array(), - // Initial credential material for the deployment's tools, decrypted hub-side - // and delivered on the deploy frame so it is resident before any step runs - // (closing the race where a tool resolves a credential before a push lands). - // Run-global: a credential's secret is stored once, keyed by credentialId. - // Optional -- a deploy whose definition binds no credentials omits it. - "credentials?": CredentialDelivery, - // The source-ref pin (`source` + frozen `closure`) the sidecar - // re-materializes and re-evaluates the pinned code from, instead of trusting - // the inline projection. The two co-travel (see `SourceRefPin`), so presence - // of the pin is the single signal that this is a code-sourced deploy. - // Optional: only a code-sourced (npm) deploy carries it; a live-authored - // deploy has no pin. - "sourceRef?": SourceRefPin, - // Source assets a `kind:"asset"` closure entry reads from, delivered inline - // (as on the probe) so the sidecar checks them out into its durable - // per-deployment source store before materializing the pin. Optional: only - // an asset-sourced deploy carries it; a registry-sourced pin fetches its - // tarballs over HTTP and delivers none. - "assets?": WorkflowSourceAssetMount.array(), - }), -); + * A full workflow deploy frame. The deploy lineage is source-ref only: the + * runnable definition is the pinned code closure the sidecar re-materializes and + * evaluates from `sourceRef`, so the frame carries NO inline `definition`. It + * pins each step's inference sources and the hub-approved wire hash the child + * re-verifies its closure evaluation against, plus the source-ref-specific + * extras. The sources-cover-stepOrder coverage narrow that a projection carries + * runs on the sidecar against the closure-derived definition + * (`validateWorkflowProjection`), since the frame holds no definition to cover. + * + * This is deliberately NOT built on `WorkflowProjectionWithSources`: that shape + * (definition + sources + approved hash) is the approval/probe projection and + * stays intact for the probe surface and for each `referencedDefinitions` body, + * which still carry their own inert definition. + */ +export const AgentDeployWorkflow = type({ + // Per-step inference-source failover chains, one per step in the closure's + // `stepOrder`. Threaded to the workflow-process child so it resolves inference + // at step invocation without a hub round-trip. + sources: { "[string]": InferenceSource.array().atLeastLength(1) }, + // The hub-approved wire hash of the frozen projection -- the freeze anchor the + // hub gate wrote. The sidecar feeds it to the child as `DEFINITION_HASH`, which + // the child re-verifies its closure evaluation against. Optional on the wire + // because the frame schema does not force it; enforcement lives at runtime + // instead -- the production hub builder always stamps it and the sidecar fails + // closed if it is absent. + "approvedWireHash?": "string > 0", + // Extracted onTrigger section bodies. Each entry carries the body's inert + // definition, its own per-step inference-source pins, and its approved wire + // hash. The sidecar stages each body's `sources.json` so a body child -- + // in-process, its env lost across a restart -- resolves inference durably; the + // body definition itself is resolved in-memory from the parent's re-verified + // closure. Optional: only an onTrigger deploy carries it. + "referencedDefinitions?": WorkflowProjectionWithSources.array(), + // Initial credential material for the deployment's tools, decrypted hub-side + // and delivered on the deploy frame so it is resident before any step runs + // (closing the race where a tool resolves a credential before a push lands). + // Run-global: a credential's secret is stored once, keyed by credentialId. + // Optional -- a deploy whose definition binds no credentials omits it. + "credentials?": CredentialDelivery, + // The source-ref pin (`source` + frozen `closure`) the sidecar re-materializes + // and evaluates the pinned code from. Required: source-ref is the only deploy + // lineage, and without the pin the sidecar has no definition to run. + sourceRef: SourceRefPin, + // Source assets a `kind:"asset"` closure entry reads from, delivered inline + // (as on the probe) so the sidecar checks them out into its durable + // per-deployment source store before materializing the pin. Optional: only + // an asset-sourced deploy carries it; a registry-sourced pin fetches its + // tarballs over HTTP and delivers none. + "assets?": WorkflowSourceAssetMount.array(), +}); export type AgentDeployWorkflow = typeof AgentDeployWorkflow.infer; /** @@ -495,9 +533,9 @@ export const AgentDeployFrame = type({ export type AgentDeployFrame = typeof AgentDeployFrame.infer; /** - * Remove an agent from this sidecar. The sidecar tears down the harness, - * pushes state to the hub (best-effort), deletes the agent directory, and - * responds with agent.undeploy.ack. + * Remove an agent from this sidecar. The sidecar shuts the deployment's + * supervisor down, pushes state to the hub (best-effort), deletes the agent + * directory, and responds with agent.undeploy.ack. */ export const AgentUndeployFrame = type({ type: "'agent.undeploy'", @@ -768,13 +806,13 @@ export type PackRejectFrame = typeof PackRejectFrame.infer; * cannot see `bundle.definitions` without * invoking the factory, and the `BaseEnv` * the factory needs is constructed by the - * sidecar harness AFTER the commit. Both - * paths carry the same category so the - * operator-facing failure shape is - * uniform regardless of which check - * fired; only the channel (apply.error - * frame vs runtime construct failure) - * differs. + * workflow child's step build env AFTER + * the commit. Both paths carry the same + * category so the operator-facing failure + * shape is uniform regardless of which + * check fired; only the channel + * (apply.error frame vs runtime construct + * failure) differs. * apply.swap.failed — DEPRECATED, no longer emitted. The apply * protocol stages each deploy into a stable * per-deploy-id directory and commits via a @@ -883,17 +921,26 @@ export type WorkflowProbeRequestFrame = typeof WorkflowProbeRequestFrame.infer; * by `requestId`. * * `projection` is the same closed `WorkflowProjectionDefinition` a deploy frame - * carries. `grants` is the deployment-wide inert grant surface -- the set of - * capability-grant strings the workflow requires -- for pre-deploy operator + * carries. `grants` is the deployment-wide inert grant surface -- the deduped, + * sorted union of every step's grant strings -- for pre-deploy operator * inspection. `wireHash` is the hex SHA-256 of the projection's canonical JSON * (`computeWireDefinitionHash` in `@intx/types/wire-definition-hash`), the * deployment's content-addressed handle. + * + * `grantWalkSnapshot` is the UN-flattened capability walk the flattened + * `grants` is derived from: the per-step grant declarations (each step's grant + * strings plus its tool-grant `grantEffects` map) and the definition's full, + * unfiltered `grantRequirements`. It carries the per-step grouping and the + * effect data that `grants` discards, so a later persist step can record the + * complete grant walk rather than only its flattened union. The flattened + * `grants` stays alongside it because the operator-approval gate consumes it. */ export const WorkflowProbeResultFrame = type({ type: "'workflow.probe.result'", requestId: "string", projection: WorkflowProjectionDefinition, grants: "string[]", + grantWalkSnapshot: GrantWalkSnapshot, wireHash: "string", }); export type WorkflowProbeResultFrame = typeof WorkflowProbeResultFrame.infer; diff --git a/vendor/intx/types/src/wire-workflow.ts b/vendor/intx/types/src/wire-workflow.ts index 369b48d58..694da37ba 100644 --- a/vendor/intx/types/src/wire-workflow.ts +++ b/vendor/intx/types/src/wire-workflow.ts @@ -80,11 +80,11 @@ const WorkflowSteps = type({ "[string]": "unknown" }).narrow((steps, ctx) => { * materialization (`packages/hub-sessions/src/workflow-kind.ts`'s * `workflowDefinitionEnvelopeSchema`): `id`, `triggers`, `steps`, * `stepOrder`, optional `state`. The wire validator MUST require every - * field the envelope requires — the sidecar's deploy router serializes - * `projection.definition` verbatim into `workflow.json` and the child - * rejects a tree missing any envelope-required field. Deeper validation - * of authoring-time primitive shape lives on the workflow definition - * surface in `@intx/workflow`, not on the wire. + * field the envelope requires — this projection is the approved surface + * the source-ref child re-verifies its closure-evaluated definition + * against, and the child rejects a tree missing any envelope-required + * field. Deeper validation of authoring-time primitive shape lives on the + * workflow definition surface in `@intx/workflow`, not on the wire. * * `sources` pins an ordered, non-empty inference-source list per step in * `definition.stepOrder` so the workflow-process child can resolve inference @@ -149,9 +149,9 @@ export const WorkflowProjectionWithSources = type({ // the child as the `DEFINITION_HASH` it re-verifies its own recompute // against, rather than trusting a sidecar-computed hash. At the top level it // pins the deployment's content handle; per body it pins the body's - // projection. Optional on the wire so a frame built before the source-ref - // hand-off (raw-frame paths) still validates; the production hub builder - // always stamps it. + // projection, which is re-verified in-memory as part of the parent's + // already-re-verified closure. Optional on the wire because the frame schema + // does not force it; the production hub builder always stamps it. "approvedWireHash?": "string > 0", }).narrow((value, ctx) => { for (const stepId of value.definition.stepOrder) { diff --git a/vendor/intx/workflow-deploy/README.md b/vendor/intx/workflow-deploy/README.md index 48a225b5f..200abee53 100644 --- a/vendor/intx/workflow-deploy/README.md +++ b/vendor/intx/workflow-deploy/README.md @@ -1,32 +1,44 @@ # @intx/workflow-deploy Deploy-time validation, capability walk, operator-approval gating, -and the workflow deploy orchestrator. +address derivation, and per-step source pinning for the code-sourced +deploy. This package is the deploy-side counterpart to `@intx/workflow`. It -takes a `WorkflowDefinition`, computes the per-step grant -declarations the workflow will require, gates them against an -operator-supplied `ApprovalSet`, and routes the deployment by step -count: +takes a `WorkflowDefinition`, computes the per-step grant declarations +the workflow will require, gates them against an operator-supplied +`ApprovalSet`, and derives the deployment addresses the run occupies. + +Address derivation is a pure function of `(runId, stepId, domain)`: - **Single-step workflow**: the lone step has no distinct address -- - it IS the deployment head. Deploy once at the head - (`@`) through the single-step - hand-off, staging the head's deploy tree and firing the - `agent.deploy` frame in one call. -- **Multi-step workflow**: derive per-step run addresses as - `-@`, instantiate one - `agent-state` repo per step, and write per-step deploy trees. + it IS the deployment head (`deriveRunAddress`, `@`). +- **Multi-step workflow**: each step derives a per-step run address of + the form `-@` (`deriveStepAddress`). + +`resolveStepAddress` owns the head/step collapse decision. Because the +derivation carries no per-deploy state, the supervisor reconstructs the +same addresses at spawn time from the host-sourced step count alone. Public surface: -- `createWorkflowDeployOrchestrator(opts)` — the orchestrator. -- `walkCapabilities(workflow)` — the pure capability walk; reused - to populate per-step `capability-declarations.json` and as the - input to the approval gate. +- `walkCapabilities(workflow, registry, pluginDefs)` — the pure + capability walk; reused to populate per-step capability declarations + and as the input to the approval gate. - `createApprovalSetGate(approvals)` / `createApprovalSourceGate(source)` - — operator-approval gating against a flat `ApprovalSet` or an - async source. + — operator-approval gating against a flat `ApprovalSet` or an async + source. +- `pickStepInferenceSource(...)` / `buildInertProjectionStepSources(...)` + — resolve each step's inference source against the operator-approved + grant set, so an unapproved source fails the deploy closed. +- `enumerateInertOnTriggerBodies(...)` — lift each inline onTrigger body + out of a frozen inert projection and surface its declared + `(provider, model)` preference for per-body source pinning. +- `deriveRunAddress` / `deriveStepAddress` / `resolveStepAddress` / + `deriveRunAgentId` / `deriveStepAgentId` / `deriveWorkflowRunRepoId` + — the pure address and id derivation helpers. +- `extractFoldedBody(definition)` — read the launch-relevant fields back + out of a folded single-step definition. The capability walk emits the v1 grant-shape vocabulary: `tool:`, `director:`, `capability:`, `inference.source:`, `mail.address:`, diff --git a/vendor/intx/workflow-deploy/VENDORED-FROM b/vendor/intx/workflow-deploy/VENDORED-FROM index 61b94611d..7e01a5f9f 100644 --- a/vendor/intx/workflow-deploy/VENDORED-FROM +++ b/vendor/intx/workflow-deploy/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/workflow-deploy) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. diff --git a/vendor/intx/workflow-deploy/src/capability-approval.ts b/vendor/intx/workflow-deploy/src/capability-approval.ts index 64c0d26a0..1a543d46d 100644 --- a/vendor/intx/workflow-deploy/src/capability-approval.ts +++ b/vendor/intx/workflow-deploy/src/capability-approval.ts @@ -1,6 +1,6 @@ // Operator-approval gating for the deploy-time capability walk. // -// The orchestrator hands the walk's `CapabilityWalkResult` to a gate; the +// The deploy flow hands the walk's `CapabilityWalkResult` to a gate; the // gate compares the walk-derived per-step grants against an operator- // supplied `ApprovalSet` and decides whether the deploy may proceed. // @@ -11,13 +11,13 @@ // fails the gate. // - A non-empty `unresolvedDirectors` field on the walk result is // itself a deploy-time failure; the gate surfaces it through -// `ApprovalDecision` and the orchestrator aborts. +// `ApprovalDecision` and the caller aborts the deploy. import type { CapabilityWalkResult } from "./capability-walk"; /** * A flat set of grant-shape strings the operator has approved for this - * deployment. The orchestrator-side wiring synthesizes the set from the + * deployment. The deploy flow's wiring synthesizes the set from the * deployment context (admin UI cache, legacy grant-store mirror, * scripted policy). Order does not matter; membership is the only thing * the gate consults. @@ -34,10 +34,10 @@ export interface ApprovalSource { } /** - * The decision the approval gate hands back to the orchestrator. + * The decision the approval gate hands back to the caller. * * `ok: true` -- every grant the walk surfaced is approved and the - * orchestrator may continue with deploy. + * caller may continue with deploy. * `ok: false` -- one or more grants are missing approval or the walk * surfaced unresolvable directors. `pending` carries the per-step delta * the operator must approve; `unresolvedDirectors` mirrors the walk's @@ -52,7 +52,7 @@ export type ApprovalDecision = }; /** - * The approval gate the orchestrator calls. The single method consumes + * The approval gate the deploy flow calls. The single method consumes * the walk output and yields a decision. */ export interface CapabilityApprovalGate { @@ -75,7 +75,7 @@ export interface CapabilityApprovalGate { * approve. * - `unresolvedDirectors` is mirrored verbatim. A non-empty value * forces `ok: false` regardless of whether every per-step grant - * happens to be approved -- the orchestrator must not let a deploy + * happens to be approved -- the caller must not let a deploy * proceed against a walk that could not resolve every director ref. */ export function createApprovalSetGate( diff --git a/vendor/intx/workflow-deploy/src/capability-walk.ts b/vendor/intx/workflow-deploy/src/capability-walk.ts index f1d4e56cf..d62ad5f17 100644 --- a/vendor/intx/workflow-deploy/src/capability-walk.ts +++ b/vendor/intx/workflow-deploy/src/capability-walk.ts @@ -46,11 +46,16 @@ // the caller; this module does not synthesize that registry itself // because the loader is the layer that owns package materialization. // An unresolvable director surfaces on `unresolvedDirectors` rather -// than raising -- the orchestrator translates that into a deploy-time +// than raising -- the deploy flow translates that into a deploy-time // `"unresolvable director"` failure when it wires this output into // approval flow. -import type { AgentDefinition, BaseEnv, DirectorRegistry } from "@intx/agent"; +import type { + AgentDefinition, + BaseEnv, + DirectorRegistry, + ToolDeclaration, +} from "@intx/agent"; import { effectiveDirectorRef, toolApprovalEffect, @@ -80,7 +85,7 @@ export interface GrantDeclarations { /** * The capability walk's result. `perStep` keys are workflow step ids; * `unresolvedDirectors` lists every director id the supplied registry - * could not resolve across the whole walk, so the orchestrator can + * could not resolve across the whole walk, so the deploy flow can * surface a single deploy-time failure rather than tearing down per * step. * @@ -93,6 +98,22 @@ export interface CapabilityWalkResult { readonly unresolvedDirectors: readonly string[]; } +/** + * Static tool declarations a plugin package contributes, keyed by the + * plugin-package name an agent names in `AgentDefinition.plugins`. A + * plugin package contributes NO agent-visible tool factory (its tools reach + * the agent through `env.plugins` at run time), so the walk cannot read the + * plugin's tool grant surface off the definition alone. The caller (the + * probe, over the materialized closure) loads each declared plugin's static + * `definitions` and threads them here so the walk emits `tool:` grants + * for plugin-contributed tools alongside factory-contributed ones. Empty + * when the walked closure declares no plugin package. + */ +export type PluginToolDefinitions = ReadonlyMap< + string, + readonly ToolDeclaration[] +>; + /** * Mutable accumulator threaded through the collectors while a single * step is walked. `grants` is the deduplicated grant-string set; @@ -135,6 +156,7 @@ function freezeDeclarations( export function walkCapabilities( workflow: WorkflowDefinition, registry: DirectorRegistry, + pluginDefs: PluginToolDefinitions = new Map(), ): CapabilityWalkResult { const triggerGrants = collectTriggerGrants(workflow); const unresolved = new Set(); @@ -147,30 +169,20 @@ export function walkCapabilities( `capability walk: step ${stepId} listed in stepOrder is missing from steps`, ); } - const agent = extractAgent(primitive); - if (agent === null) { - // Non-agent primitives carry no agent grants. An `action` - // additionally contributes its declared `effect:` grants, and - // a `loop` contributes the union of its body's grants (so the - // approval gate sees every agent/action the loop can run); every - // other non-agent primitive gets only the trigger-derived grants. - const collected: GrantSet = { - grants: new Set(), - effects: new Map(), - }; - for (const grant of collectActionGrants(primitive)) { - collected.grants.add(grant); - } - collectLoopBodyGrants(primitive, registry, unresolved, collected); - collectOnTriggerBodyGrants(primitive, registry, unresolved, collected); - perStep.set(stepId, freezeDeclarations(collected, triggerGrants)); - continue; - } + // Every top-level step gets a fresh grant set; `collectPrimitiveGrants` + // routes the step and any nested bodies it carries through one dispatch, + // so an approval covers every agent, action, and effect the step can run. const collected: GrantSet = { grants: new Set(), effects: new Map(), }; - collectAgentGrants(agent, registry, unresolved, collected); + collectPrimitiveGrants( + primitive, + registry, + pluginDefs, + unresolved, + collected, + ); perStep.set(stepId, freezeDeclarations(collected, triggerGrants)); } @@ -216,86 +228,129 @@ function collectActionGrants( } /** - * Collect the union of a loop body's grants (agent grants for its step / - * map steps, effect grants for its action steps) into `collected` so the - * loop node's approval covers every agent and effect the loop can run. - * The body-ban forbids a nested loop, so this does not recurse further. + * Union a single primitive's grants into `collected`: its agent grants (when + * it carries an agent), its action `effect:` grants, and -- for a + * body-bearing primitive -- the grants of every step of its nested body. A + * loop, an inline onTrigger section, and an inline childWorkflow each run their + * body per the deployment, so the operator must approve everything the body can + * run. The walk descends into the authored `{ inline }` form; a `{ ref }` body + * is a separately-declared asset whose grants were folded in from its own + * inline form, so it is skipped here. * - * Duplicate-name handling is scoped per body step: `collectAgentGrants` - * throws on a duplicate within a single agent, but two DIFFERENT body - * steps that each mint the same `tool:` are distinct runtime - * agents (the runtime builds one agent per step), so the union across - * body steps is not a duplicate-name error. + * The nesting switch is EXHAUSTIVE: a newly-added primitive kind fails the + * `never` assignment below at compile time, forcing the walk to decide how to + * treat it rather than silently dropping a nested closure's grants. A miss here + * is a silent, fail-open authorization gap because `director:` is not re-gated + * at runtime -- so the compiler, not a remembered call site, owns coverage. */ -function collectLoopBodyGrants( +function collectPrimitiveGrants( primitive: WorkflowDefinition["steps"][string], registry: DirectorRegistry, + pluginDefs: PluginToolDefinitions, unresolved: Set, collected: GrantSet, ): void { - if (primitive.kind !== "loop") { - return; + const agent = extractAgent(primitive); + if (agent !== null) { + collectAgentGrants(agent, registry, pluginDefs, unresolved, collected); } - for (const bodyStepId of primitive.body.stepOrder) { - const bodyPrimitive = primitive.body.steps[bodyStepId]; - if (bodyPrimitive === undefined) { + for (const grant of collectActionGrants(primitive)) { + collected.grants.add(grant); + } + switch (primitive.kind) { + case "loop": + collectBodyGrants( + primitive.body, + registry, + pluginDefs, + unresolved, + collected, + ); + return; + case "onTrigger": + if ("inline" in primitive.body) { + collectBodyGrants( + primitive.body.inline, + registry, + pluginDefs, + unresolved, + collected, + ); + } + return; + case "childWorkflow": + if ("inline" in primitive.definition) { + collectBodyGrants( + primitive.definition.inline, + registry, + pluginDefs, + unresolved, + collected, + ); + } + return; + case "step": + case "map": + case "action": + case "gate": + case "escalation": + case "awaitSignal": + case "sleep": + // Leaf primitives: no nested body to descend into. + return; + default: { + const exhaustive: never = primitive; throw new Error( - `capability walk: loop body step ${bodyStepId} listed in stepOrder is missing from steps`, + `capability walk: unhandled primitive kind ${JSON.stringify( + (exhaustive as { kind: string }).kind, + )}`, ); } - const bodyAgent = extractAgent(bodyPrimitive); - if (bodyAgent !== null) { - collectAgentGrants(bodyAgent, registry, unresolved, collected); - } - for (const grant of collectActionGrants(bodyPrimitive)) { - collected.grants.add(grant); - } } } /** - * Union an onTrigger section body's agent/action grants into the section's - * declaration set, so the approval gate sees every agent and action the - * section can run per event. The walk runs before the deploy step extracts - * the body into its own asset, so it sees the authored `{ inline }` form; a - * deployed `{ ref }` body is an independent asset with its own declarations - * and is skipped here. A section body may itself contain a loop, whose body - * grants are collected too; a nested onTrigger is forbidden at definition - * time, so there is no section-within-section recursion to handle. + * Walk every step of a nested body (a loop body, an inline onTrigger section + * body, or an inline childWorkflow definition) and union its grants into + * `collected`. Each step routes through `collectPrimitiveGrants`, so a body + * that itself nests another body is covered by the same single dispatch and + * the loop-body ban (a validator-owned invariant) simply means the recursion + * never encounters a nesting primitive inside a loop body. + * + * Duplicate-name handling is scoped per body step: `collectAgentGrants` throws + * on a duplicate within a single agent, but two DIFFERENT body steps that each + * mint the same `tool:` are distinct runtime agents (the runtime builds + * one agent per step), so the union across body steps is not a duplicate-name + * error. */ -function collectOnTriggerBodyGrants( - primitive: WorkflowDefinition["steps"][string], +function collectBodyGrants( + body: WorkflowDefinition, registry: DirectorRegistry, + pluginDefs: PluginToolDefinitions, unresolved: Set, collected: GrantSet, ): void { - if (primitive.kind !== "onTrigger") { - return; - } - if (!("inline" in primitive.body)) { - return; - } - for (const bodyStepId of primitive.body.inline.stepOrder) { - const bodyPrimitive = primitive.body.inline.steps[bodyStepId]; + for (const bodyStepId of body.stepOrder) { + const bodyPrimitive = body.steps[bodyStepId]; if (bodyPrimitive === undefined) { throw new Error( - `capability walk: onTrigger body step ${bodyStepId} listed in stepOrder is missing from steps`, + `capability walk: body step ${bodyStepId} listed in stepOrder is missing from steps`, ); } - const bodyAgent = extractAgent(bodyPrimitive); - if (bodyAgent !== null) { - collectAgentGrants(bodyAgent, registry, unresolved, collected); - } - for (const grant of collectActionGrants(bodyPrimitive)) { - collected.grants.add(grant); - } - collectLoopBodyGrants(bodyPrimitive, registry, unresolved, collected); + collectPrimitiveGrants( + bodyPrimitive, + registry, + pluginDefs, + unresolved, + collected, + ); } } function collectAgentGrants( agent: AgentDefinition, registry: DirectorRegistry, + pluginDefs: PluginToolDefinitions, unresolved: Set, collected: GrantSet, ): void { @@ -319,22 +374,30 @@ function collectAgentGrants( throw new DuplicateWalkToolError(definition.name, factory.id); } seenToolNames.add(definition.name); - const grant = `tool:${definition.name}`; - collected.grants.add(grant); - // Ask-wins merge. `collected` is one GrantSet shared across every - // body step of a loop, so two body steps declaring the same bare - // tool name write the same `tool:` key here. A plain overwrite - // would let a later unmarked declaration downgrade an earlier `ask` - // to `allow`; keep `ask` if either the existing or the incoming - // effect asks, so a same-named sibling can never silently drop the - // approval gate. - const incoming = toolApprovalEffect(definition); - const existing = collected.effects.get(grant); - collected.effects.set( - grant, - existing === "ask" || incoming === "ask" ? "ask" : incoming, + emitToolGrant(definition, collected); + } + } + // Plugin-contributed tools. A plugin package (`agent.plugins`) exposes + // its tools through `env.plugins` at run time, so they never appear in + // `agent.toolFactories`; the loaded static `definitions` supplied by the + // caller carry the tool names to authorize. A plugin tool sharing a name + // with a factory tool (or another plugin's tool) is a real collision -- + // both dispatch under the same bare runtime name -- so it flows through + // the SAME `seenToolNames` guard. + for (const pluginName of agent.plugins ?? []) { + const definitions = pluginDefs.get(pluginName); + if (definitions === undefined) { + throw new Error( + `capability walk: agent ${JSON.stringify(agent.id)} declares plugin ${JSON.stringify(pluginName)} but no static tool definitions were loaded for it; a declared plugin whose grant surface cannot be resolved must fail closed`, ); } + for (const definition of definitions) { + if (seenToolNames.has(definition.name)) { + throw new DuplicateWalkToolError(definition.name, pluginName); + } + seenToolNames.add(definition.name); + emitToolGrant(definition, collected); + } } for (const capability of agent.capabilities) { collected.grants.add(`capability:${capability}`); @@ -352,6 +415,27 @@ function collectAgentGrants( } } +/** + * Add a tool's `tool:` grant and its authorization effect to the + * collected set, applying the ask-wins merge. `collected` is one GrantSet + * shared across every body step of a loop, so two body steps declaring the + * same bare tool name write the same `tool:` key; a plain overwrite + * would let a later unmarked declaration downgrade an earlier `ask` to + * `allow`, so keep `ask` if either the existing or incoming effect asks. + * Shared by the factory-declared and plugin-contributed tool paths so both + * derive the effect through the one canonical `toolApprovalEffect` mapping. + */ +function emitToolGrant(definition: ToolDeclaration, collected: GrantSet): void { + const grant = `tool:${definition.name}`; + collected.grants.add(grant); + const incoming = toolApprovalEffect(definition); + const existing = collected.effects.get(grant); + collected.effects.set( + grant, + existing === "ask" || incoming === "ask" ? "ask" : incoming, + ); +} + function collectTriggerGrants(workflow: WorkflowDefinition): string[] { const grants = new Set(); for (const trigger of workflow.triggers) { diff --git a/vendor/intx/workflow-deploy/src/fold-synthesis.ts b/vendor/intx/workflow-deploy/src/fold-synthesis.ts index 2ce39db4a..6bd3beb9f 100644 --- a/vendor/intx/workflow-deploy/src/fold-synthesis.ts +++ b/vendor/intx/workflow-deploy/src/fold-synthesis.ts @@ -1,7 +1,7 @@ // Agent-to-workflow fold: reading a folded definition's launch body. // // `extractFoldedBody` reads the launch-relevant fields back out of a folded -// single-step `workflow.json`. The inverse builder, `synthesizeFoldedWorkflow`, +// single-step workflow definition. The inverse builder, `synthesizeFoldedWorkflow`, // is test-only and lives in `@intx/workflow-deploy/testing`. import type { CredentialBinding, GrantRequirement } from "@intx/types"; @@ -32,7 +32,7 @@ export interface FoldedBody { * of `synthesizeFoldedWorkflow`. A folded definition is a single `step`-kind * primitive carrying the agent; the system prompt and tool-package pins live on * that agent, the grant requirements on the envelope. Raises if the definition - * is not that single-step shape, so a malformed `workflow.json` surfaces here + * is not that single-step shape, so a malformed folded definition surfaces here * rather than launching a broken instance. */ export function extractFoldedBody(definition: WorkflowDefinition): FoldedBody { diff --git a/vendor/intx/workflow-deploy/src/index.ts b/vendor/intx/workflow-deploy/src/index.ts index c796faa0d..dc53298b8 100644 --- a/vendor/intx/workflow-deploy/src/index.ts +++ b/vendor/intx/workflow-deploy/src/index.ts @@ -7,14 +7,17 @@ // consumes. // - approval gate: consumes the walk's output plus an operator- // supplied `ApprovalSet` and yields a per-step pending delta. -// - orchestrator: validates the workflow, runs the walk + approval -// gate, writes the workflow repo, and branches on the single-step- -// vs-multi-step dichotomy for per-agent launches. +// - deploy derivation + source pinning: pure address derivation +// (`deriveRunAddress`, `deriveStepAddress`, `resolveStepAddress`, ...) +// and per-step inference-source resolution against the operator- +// approved grant set (`pickStepInferenceSource`, +// `buildInertProjectionStepSources`). export { walkCapabilities, type CapabilityWalkResult, type GrantDeclarations, + type PluginToolDefinitions, } from "./capability-walk"; export { createApprovalSetGate, @@ -31,32 +34,15 @@ export { type InertBodyStepPreference, } from "./inert-ontrigger-bodies"; export { - assertChainHeadIsDefault, - isSourceApproved, pickStepInferenceSource, + buildInertProjectionStepSources, buildSingleStepAgentDefinition, - createWorkflowDeployOrchestrator, deriveRunAddress, deriveRunAgentId, deriveStepAddress, resolveStepAddress, deriveStepAgentId, deriveWorkflowRunRepoId, - wrapHarnessAsSingleStepWorkflow, - CapabilityApprovalDeniedError, - MultiStepDeployHandoffMissingError, - MultiStepDeploymentArgsMissingError, - SingleStepDeployHandoffMissingError, WorkflowDefinitionInvalidError, type DeployContent, - type DeploySingleStepFn, - type DeployWorkflowArgs, - type DeployWorkflowResult, - type LaunchSessionFn, - type MultiStepDeployResult, - type ReferencedBodyDefinition, - type SendMultiStepDeployFn, - type WorkflowDeployOrchestrator, - type WorkflowDeployOrchestratorDeps, - type WorkflowRepoWriter, } from "./orchestrator"; diff --git a/vendor/intx/workflow-deploy/src/inert-ontrigger-bodies.ts b/vendor/intx/workflow-deploy/src/inert-ontrigger-bodies.ts index dd2dfdbd3..177acfb8c 100644 --- a/vendor/intx/workflow-deploy/src/inert-ontrigger-bodies.ts +++ b/vendor/intx/workflow-deploy/src/inert-ontrigger-bodies.ts @@ -1,15 +1,11 @@ // Enumerate the inline onTrigger section bodies of a FROZEN inert projection. // -// The live-authored deploy path holds the live `WorkflowDefinition` and lifts -// its inline onTrigger bodies with `extractOnTriggerBodies` (see -// `orchestrator.ts`), reading each body agent's declared inference preference -// straight off the live `AgentDefinition`. The source-ref (code-sourced) deploy -// path never holds the live definition -- the hub has only the inert -// `WorkflowProjectionDefinition` the gate froze and hashed. This module is the -// source-ref counterpart: it walks that frozen projection, lifts each inline -// onTrigger body, and surfaces each body step's declared `(provider, model)` -// preference from the projection's `modelSources` so the hub can pin per-body -// inference sources through the SAME resolver + approval gate the live path uses +// On the source-ref (code-sourced) deploy the hub never holds a live +// `WorkflowDefinition` -- it has only the inert `WorkflowProjectionDefinition` +// the gate froze and hashed. This module walks that frozen projection, lifts +// each inline onTrigger body, and surfaces each body step's declared +// `(provider, model)` preference from the projection's `modelSources` so the +// hub can pin per-body inference sources through the resolver + approval gate // (`pickStepInferenceSource`). // // It reads NOTHING off an unvalidated `unknown`: the wire projection types its @@ -39,9 +35,9 @@ export interface InertBodyStepPreference { export interface EnumeratedInertOnTriggerBody { /** * The body's ref -- `onTriggerBodyRef(projection.id, stepId)`. This is also - * `definition.id`, the id the sidecar stages the body's `workflow.json` and - * `sources.json` under, and the id the source-ref run child re-derives when it - * rewrites the re-evaluated closure -- so the three agree byte-for-byte. + * `definition.id`, the id the sidecar stages the body's `sources.json` under, + * and the id the source-ref run child re-derives when it rewrites the + * re-evaluated closure -- so the three agree byte-for-byte. */ readonly ref: string; /** @@ -105,15 +101,21 @@ function firstPreference( } /** - * Read a body step's declared preference. Mirrors `extractAgent`: a `step` - * carries the agent directly, a `map` carries it on its inner step, and any - * other primitive declares none (`null`). A `step`/`map` that fails the agent - * shape is a malformed projection and throws. + * Read an inert projection step's declared inference preference. Mirrors + * `extractAgent`: a `step` carries the agent directly, a `map` carries it on its + * inner step, and any other primitive declares none (`null`). A `step`/`map` + * that fails the agent shape is a malformed projection and throws. + * + * `context` is a caller label the throw prefixes with, so a malformed step is + * traceable to whoever read it (an inline onTrigger body enumeration, or the + * top-level projection step-source pinning). Exported so both the body + * enumeration here and the top-level source pin in `orchestrator.ts` read a + * step's preference through one validator. */ -function readBodyStepPreference( +export function readInertStepPreference( stepValue: unknown, - bodyRef: string, - bodyStepId: string, + context: string, + stepId: string, ): InertBodyStepPreference | null { const asStep = StepWithAgent(stepValue); if (!(asStep instanceof type.errors)) { @@ -129,7 +131,7 @@ function readBodyStepPreference( (kind.kind === "step" || kind.kind === "map") ) { throw new Error( - `enumerateInertOnTriggerBodies: body ${bodyRef} step ${bodyStepId} is a ${kind.kind} primitive but carries no valid agent.modelSources`, + `${context}step ${stepId} is a ${kind.kind} primitive but carries no valid agent.modelSources`, ); } return null; @@ -164,9 +166,9 @@ export function enumerateInertOnTriggerBodies( const preferredByStep: Record = {}; for (const bodyStepId of definition.stepOrder) { - preferredByStep[bodyStepId] = readBodyStepPreference( + preferredByStep[bodyStepId] = readInertStepPreference( definition.steps[bodyStepId], - ref, + `enumerateInertOnTriggerBodies: body ${ref} `, bodyStepId, ); } diff --git a/vendor/intx/workflow-deploy/src/orchestrator.ts b/vendor/intx/workflow-deploy/src/orchestrator.ts index b991fd9af..bef676d03 100644 --- a/vendor/intx/workflow-deploy/src/orchestrator.ts +++ b/vendor/intx/workflow-deploy/src/orchestrator.ts @@ -1,63 +1,42 @@ -// Workflow-deploy orchestrator. +// Workflow-deploy derivation and source-pinning utilities. // -// A deploy validates the workflow, runs the capability walk, and gates on -// operator approval, then routes by step count. -// -// A one-step workflow has no distinct step address: the lone step IS the -// deployment head. It deploys once at the head (`deriveRunAddress`) -// through the single-step hand-off -- the tree staging and the -// `agent.deploy` frame collapse onto one head deploy, with no per-step -// provisioning loop. -// -// A workflow with more than one step derives per-step run addresses of -// the form `-@`, instantiates -// one agent-state repo per step keyed by the derived address, and writes -// each step's deploy tree onto its own repo. The derivation is a pure -// function of `(runId, stepId, domain)`, so the +// The deployment address model is a pure function of `(runId, stepId, +// domain)`: a one-step workflow has no distinct step address -- the lone +// step IS the deployment head (`deriveRunAddress`) -- while a workflow with +// more than one step derives per-step addresses of the form +// `-@`. Because the derivation is pure, the // supervisor reconstructs the same addresses at spawn time without any -// per-deploy state. +// per-deploy state, and `resolveStepAddress` is the single owner of the +// head/step collapse decision for a consumer that must choose an address +// from the host-sourced step count alone. // -// The workflow definition envelope plus the walk's per-step grant -// declarations land on a `workflow` repo before any agent-state write -// happens; if the workflow repo write fails, no agent-state repo is -// created. +// The source-pinning utilities (`pickStepInferenceSource`, +// `buildInertProjectionStepSources`, `isSourceApproved`) resolve each step's +// inference source against the operator-approved grant set, so an unapproved +// source fails the deploy closed rather than slipping past the capability-walk +// gate. import type { AgentDefinition, AnnotatedToolFactory, BaseEnv, - DirectorRegistry, InferencePreference, } from "@intx/agent"; -import type { - HarnessConfig, - InferenceSource, - ToolDefinition, -} from "@intx/types/runtime"; +import type { HarnessConfig, InferenceSource } from "@intx/types/runtime"; import type { ToolPackagePin } from "@intx/types/tool-packages"; -import type { CredentialDelivery } from "@intx/types/sidecar"; +import type { WorkflowProjectionDefinition } from "@intx/types/sidecar"; import { formatRunAddress } from "@intx/types"; -import { - STEP_ID_PATTERN, - type Primitive, - type WorkflowDefinition, -} from "@intx/workflow/definition"; -import { rewriteInlineOnTriggerBodies } from "@intx/workflow"; -import { - createApprovalSetGate, - type ApprovalDecision, - type ApprovalSet, - type CapabilityApprovalGate, -} from "./capability-approval"; -import { walkCapabilities, type CapabilityWalkResult } from "./capability-walk"; +import { type ApprovalSet } from "./capability-approval"; +import { readInertStepPreference } from "./inert-ontrigger-bodies"; /** - * Minimal `DeployContent` shape the orchestrator passes through to - * `launchSession`. Carried as a structural type so this package does - * not need a runtime dependency on `@intx/hub-sessions` to name the - * type. Mirrors the public fields of - * `packages/hub-sessions/src/agent-repo.ts`'s `DeployContent`. + * Minimal structural `DeployContent` shape. Carried as a structural type so + * this package does not need a runtime dependency on `@intx/hub-sessions` to + * name the type. Mirrors the public fields of + * `packages/hub-sessions/src/agent-repo.ts`'s `DeployContent`; the hub's + * `bridgeOrchestratorDeployContent` narrows this widened shape back to the + * canonical one at the deploy boundary. */ export interface DeployContent { readonly systemPrompt: string; @@ -66,237 +45,10 @@ export interface DeployContent { } /** - * The launch-session surface the orchestrator depends on. Matches - * `SessionService.launchSession` so that method can collapse to a - * thin caller of `deployWorkflow` without any signature juggling. - */ -export type LaunchSessionFn = (params: { - agentAddress: string; - agentId: string; - runId: string; - config: HarnessConfig; - deployContent: DeployContent; - toolPackagePins?: readonly ToolPackagePin[]; -}) => Promise; - -/** - * An extracted onTrigger section body carried inline in the deploy frame: - * the rewritten `{ ref }`-target definition plus the body's own per-step - * inference-source pins. The sidecar materializes both alongside each other - * (`assets/workflow//workflow.json` + `sources.json`) so a body - * child resolves its definition AND its inference sources off disk -- the - * body child runs in-process (no process env) and its env is lost across a - * restart, so the sources must be durable and co-located with the body - * definition, not passed through an ephemeral channel. - * - * `sources` is keyed by the body's step ids (matching - * `definition.stepOrder`), each an ordered non-empty failover chain, exactly - * as the top-level deploy pins its own steps. Every body step id must have a - * matching entry, per the wire validator's per-body narrow. - */ -export interface ReferencedBodyDefinition { - readonly definition: WorkflowDefinition; - readonly sources: Record; -} - -/** - * Multi-step deploy hand-off. Called once after the per-step - * provisioning loop has completed; mirrors the wire shape the deploy - * router consumes (the `agent.deploy` frame's `workflow?` field). The - * caller-site closure constructs the frame and waits on the sidecar's - * `agent.deploy.ack`, surfacing the supervisor's principal public key - * back through the result. - * - * The orchestrator does not synthesize the deployment-level address; - * the caller passes the bus-registered address the sidecar's - * supervisor will accept on the frame's `agentAddress` field. The - * orchestrator computes `agentAddress` via `deriveRunAddress` - * and `agentId` via `deriveRunAgentId`. - * - * `sources` is keyed by step id (matching `definition.stepOrder`); - * every step id must have a matching entry, per the wire validator's - * narrow. - */ -export type SendMultiStepDeployFn = (params: { - agentAddress: string; - agentId: string; - config: HarnessConfig; - definition: WorkflowDefinition; - sources: Record; - hubPublicKey: string; - /** - * Extracted onTrigger section bodies to materialize on the sidecar so a - * body child resolves by ref. Empty/absent for a workflow with no section. - */ - referencedDefinitions?: readonly ReferencedBodyDefinition[]; -}) => Promise; - -/** - * Single-step deploy hand-off. A one-step workflow has no distinct steps - * (the lone step IS the head), so it does NOT take the per-step - * provisioning loop: it deploys once at the head, staging the head's - * deploy tree AND firing the deployment `agent.deploy` frame that carries - * the workflow definition and the sole step's source pin. The caller-site - * closure produces the deploy pack, sends the workflow frame (the sidecar - * initializes the head repo on receipt), then delivers the pack to the - * head; it waits on the `agent.deploy.ack` and surfaces the supervisor's - * principal public key back through the result. - * - * This carries the head deploy content and tool pins (which the head-tree - * staging needs) alongside the definition + sources (which the frame - * needs) -- the union of what `LaunchSessionFn` and `SendMultiStepDeployFn` - * carry, because for one step the tree staging and the frame collapse onto - * a single head deploy. - */ -export type DeploySingleStepFn = (params: { - agentAddress: string; - agentId: string; - runId: string; - config: HarnessConfig; - deployContent: DeployContent; - definition: WorkflowDefinition; - sources: Record; - hubPublicKey: string; - toolPackagePins?: readonly ToolPackagePin[]; - /** - * Extracted onTrigger section bodies to materialize on the sidecar so a - * body child resolves by ref. Empty/absent for a workflow with no section. - */ - referencedDefinitions?: readonly ReferencedBodyDefinition[]; - /** - * Decrypted credential material for the deployment's tools, delivered on the - * deploy frame so it is resident before any step runs. Absent when the - * definition binds no credentials. - */ - credentials?: CredentialDelivery; -}) => Promise; - -/** - * Result returned by `sendMultiStepDeploy`. Surfaces the sidecar - * supervisor's principal public key (hex-encoded Ed25519) from the - * `agent.deploy.ack` frame back through `deployWorkflow` so the - * orchestrator's caller can persist or verify the deployment's - * cryptographic identity. - */ -export interface MultiStepDeployResult { - readonly publicKey: string; -} - -/** - * Result returned by `deployWorkflow`. Surfaces the supervisor public key - * collected from the sidecar's `agent.deploy.ack` so the caller can stash - * it alongside the deployment record. - */ -export type DeployWorkflowResult = { - readonly publicKey: string; -}; - -/** - * Minimal interface for writing the workflow repo. The orchestrator - * writes a single tree containing `workflow.json`, - * `capability-declarations.json`, and `.gitignore`. The structural type - * keeps `@intx/workflow-deploy` independent of `@intx/hub-sessions`'s - * substrate. - */ -export interface WorkflowRepoWriter { - writeWorkflowRepo(args: { - workflowRepoId: string; - files: ReadonlyMap; - }): Promise; -} - -export interface WorkflowDeployOrchestratorDeps { - /** - * Director registry the capability walk consults. The orchestrator - * does not synthesize a registry itself; the host wiring (hub) folds - * in `interchange.directors`-loaded factories before constructing the - * orchestrator. - */ - readonly directorRegistry: DirectorRegistry; - /** Writes the workflow repo's deploy tree. Every deploy calls this once. */ - readonly workflowRepo: WorkflowRepoWriter; - /** - * Performs the per-agent deploy + session start. The multi-step branch - * calls this once per step. In production this is - * `SessionService.launchSession`; tests pass a tracking stub. - */ - readonly launchSession: LaunchSessionFn; - /** - * Fires the deployment-level `agent.deploy` frame that carries the - * workflow definition and per-step source pins to the sidecar. The - * multi-step branch calls this exactly once, after every per-step - * `agent-state` repo has been provisioned via `launchSession`. - * - * Optional so a caller that only exercises the single-step branch does - * not have to wire a stub. The multi-step branch fails fast with - * `MultiStepDeployHandoffMissingError` if the dep is absent. - */ - readonly sendMultiStepDeploy?: SendMultiStepDeployFn; - /** - * Deploys a single-step workflow once at the head: stages the head's - * deploy tree and fires the deployment `agent.deploy` frame in one - * hand-off (see `DeploySingleStepFn`). The single-step branch calls - * this exactly once and never runs the per-step `launchSession` loop. - * - * Optional for the same reason as `sendMultiStepDeploy`; the single-step - * branch fails fast with `SingleStepDeployHandoffMissingError` if the - * dep is absent. - */ - readonly deploySingleStepAtHead?: DeploySingleStepFn; -} - -export interface DeployWorkflowArgs { - /** The workflow definition the orchestrator validates and deploys. */ - readonly workflow: WorkflowDefinition; - /** - * Stable identifier the branch concatenates into derived agent - * addresses. Required. - */ - readonly runId?: string; - /** - * Mail-domain for the deployment. Required. The multi-step branch - * derives per-step addresses as - * `-@`; the single-step - * branch deploys the lone step at `@`. - */ - readonly deploymentDomain?: string; - /** - * Harness configuration shared across every step's launch. The - * orchestrator overrides `agentAddress`, `agentId`, and `systemPrompt` - * per step in the multi-step branch. - */ - readonly config: HarnessConfig; - /** - * Deploy-tree content shared across every step's launch. The - * orchestrator overrides `systemPrompt` per step in the multi-step - * branch from the step's agent definition. - */ - readonly deployContent: DeployContent; - /** Tool-package pins to ship with every step's deploy. */ - readonly toolPackagePins?: readonly ToolPackagePin[]; - /** - * Flat set of grant-shape strings the operator has approved for this - * deployment. Every grant the capability walk surfaces must be in - * this set; an unapproved grant fails the deploy with the offending - * step and missing source. - */ - readonly operatorApprovals: ApprovalSet; - /** - * Hex-encoded hub Ed25519 public key threaded onto the `agent.deploy` - * frame so the sidecar can verify the deploy-tree commit signatures. - * Required for both deploy paths (single-step head and multi-step). - */ - readonly hubPublicKey?: string; -} - -export interface WorkflowDeployOrchestrator { - deployWorkflow(args: DeployWorkflowArgs): Promise; -} - -/** - * Error thrown by `deployWorkflow` when a workflow definition fails the - * orchestrator's pre-deploy validation. Carries the offending workflow - * id so the caller's logs name the deployment that was rejected. + * Error thrown when a workflow definition fails deploy-time validation -- + * an inverted or unapproved inference chain, or a step whose source the + * operator never approved. Carries the offending workflow id so the caller's + * logs name the deployment that was rejected. */ export class WorkflowDefinitionInvalidError extends Error { readonly workflowId: string; @@ -309,443 +61,6 @@ export class WorkflowDefinitionInvalidError extends Error { } } -/** - * Error thrown when the orchestrator must derive a per-step address but - * the caller did not supply both `runId` and `deploymentDomain`. - */ -export class MultiStepDeploymentArgsMissingError extends Error { - constructor(missing: string) { - super(`deploy requires ${missing}; supply both runId and deploymentDomain`); - this.name = "MultiStepDeploymentArgsMissingError"; - } -} - -/** - * Error thrown when the multi-step branch is reached but the - * `sendMultiStepDeploy` dependency was not wired. The single-step branch - * does not consult this dep, so the dep is optional on the deps record; - * callers that may take the multi-step branch must wire it. - */ -export class MultiStepDeployHandoffMissingError extends Error { - constructor() { - super( - "multi-step deploy requires sendMultiStepDeploy dep; wire it on the orchestrator's WorkflowDeployOrchestratorDeps record", - ); - this.name = "MultiStepDeployHandoffMissingError"; - } -} - -/** - * Error thrown when the single-step branch is reached but the - * `deploySingleStepAtHead` dependency was not wired. Parallel to - * `MultiStepDeployHandoffMissingError`; the multi-step branch does not - * consult this dep, so it is optional on the deps record. - */ -export class SingleStepDeployHandoffMissingError extends Error { - constructor() { - super( - "single-step deploy requires deploySingleStepAtHead dep; wire it on the orchestrator's WorkflowDeployOrchestratorDeps record", - ); - this.name = "SingleStepDeployHandoffMissingError"; - } -} - -/** - * Error thrown when the capability-approval gate rejects the deploy. - * Carries the per-step `pending` delta and the unresolvable director - * ids so the caller can surface the exact remediation surface to the - * operator. - */ -export class CapabilityApprovalDeniedError extends Error { - readonly pending: ReadonlyMap; - readonly unresolvedDirectors: readonly string[]; - constructor(decision: Extract) { - super(formatApprovalDeniedMessage(decision)); - this.name = "CapabilityApprovalDeniedError"; - this.pending = decision.pending; - this.unresolvedDirectors = decision.unresolvedDirectors; - } -} - -function formatApprovalDeniedMessage( - decision: Extract, -): string { - if (decision.unresolvedDirectors.length > 0) { - const first = decision.unresolvedDirectors[0]; - return `unresolvable director: ${String(first)}`; - } - const firstPending = [...decision.pending.entries()][0]; - if (firstPending === undefined) { - return "capability approval denied"; - } - const [stepId, missing] = firstPending; - const firstGrant = missing[0]; - if (firstGrant === undefined) { - return `step ${stepId} has zero approved sources`; - } - return `step ${stepId} missing approval for ${firstGrant}`; -} - -/** - * Build a `WorkflowDeployOrchestrator`. The orchestrator owns the - * step-count routing (single-step head vs multi-step derived); its deps - * own everything else. - */ -export function createWorkflowDeployOrchestrator( - deps: WorkflowDeployOrchestratorDeps, -): WorkflowDeployOrchestrator { - const { - directorRegistry, - workflowRepo, - launchSession, - sendMultiStepDeploy, - deploySingleStepAtHead, - } = deps; - - return { - async deployWorkflow( - args: DeployWorkflowArgs, - ): Promise { - validateWorkflowDefinition(args.workflow); - - const walk = walkCapabilities(args.workflow, directorRegistry); - const gate: CapabilityApprovalGate = createApprovalSetGate( - args.operatorApprovals, - ); - const decision = await gate.evaluate(walk); - if (!decision.ok) { - throw new CapabilityApprovalDeniedError(decision); - } - - // Materialize each onTrigger section's authored inline body into its - // own workflow asset and rewrite the primitive to a ref, so the runtime - // spawns the body as a child run resolved by ref. The walk above ran on - // the inline form so the operator approved the body agents' caps; the - // stored definition carries `{ ref }` bodies from here on. - const { workflow: deployed, referencedDefinitions } = - await extractOnTriggerBodies({ - workflow: args.workflow, - registry: directorRegistry, - workflowRepo, - config: args.config, - operatorApprovals: args.operatorApprovals, - }); - - await writeWorkflowRepoTree({ - workflow: deployed, - walk, - workflowRepo, - }); - - // The deploy hand-off ships the EXTRACTED definition: the runtime runs - // the `definition` frame carried in the deploy, so it must be the one - // whose onTrigger bodies are `{ ref }` -- the inline form throws at the - // runtime. Extraction preserves `stepOrder` and every non-onTrigger - // step, so branch selection and per-step derivation are unaffected. The - // extracted body definitions ride the frame too (referencedDefinitions) - // so the sidecar materializes them on disk for the body child to resolve. - const deployArgs: DeployWorkflowArgs = { ...args, workflow: deployed }; - - // A one-step workflow has no distinct steps: the lone step IS the - // head. It deploys once at the head (no per-step provisioning loop), - // so it routes through the dedicated single-step hand-off rather - // than `runMultiStepBranch`. The multi-step branch is reached only - // for `stepOrder.length >= 2`. - if (deployArgs.workflow.stepOrder.length === 1) { - const result = await runSingleStepAtHead({ - args: deployArgs, - deploySingleStepAtHead, - referencedDefinitions, - }); - return { publicKey: result.publicKey }; - } - - const result = await runMultiStepBranch({ - args: deployArgs, - launchSession, - sendMultiStepDeploy, - referencedDefinitions, - }); - return { publicKey: result.publicKey }; - }, - }; -} - -/** - * Deploy a one-step workflow once at the head. The lone step has no - * distinct per-step address -- it IS the head (`deriveRunAddress`) - * -- so this pins the sole step's inference source, builds the head - * config + deploy content, and hands the whole thing to - * `deploySingleStepAtHead` in a single call. There is no per-step - * `launchSession` loop and no separate deployment frame: the tree staging - * and the `agent.deploy` frame collapse onto one head deploy. The result - * surfaces the sidecar supervisor's principal public key, same as the - * multi-step branch. - */ -async function runSingleStepAtHead(args: { - args: DeployWorkflowArgs; - deploySingleStepAtHead: DeploySingleStepFn | undefined; - referencedDefinitions: readonly ReferencedBodyDefinition[]; -}): Promise { - const { args: deploy, deploySingleStepAtHead, referencedDefinitions } = args; - const runId = deploy.runId; - const deploymentDomain = deploy.deploymentDomain; - if (runId === undefined) { - throw new MultiStepDeploymentArgsMissingError("runId"); - } - if (deploymentDomain === undefined) { - throw new MultiStepDeploymentArgsMissingError("deploymentDomain"); - } - if (deploySingleStepAtHead === undefined) { - throw new SingleStepDeployHandoffMissingError(); - } - if (deploy.hubPublicKey === undefined) { - throw new MultiStepDeploymentArgsMissingError("hubPublicKey"); - } - - // The sole step. `validateWorkflowDefinition` already guaranteed - // `stepOrder` is non-empty and every entry has a matching `steps` - // primitive; the index access is re-narrowed here for the compiler. - const stepId = deploy.workflow.stepOrder[0]; - if (stepId === undefined) { - throw new WorkflowDefinitionInvalidError( - deploy.workflow.id, - "single-step deploy requires a non-empty stepOrder", - ); - } - const primitive = deploy.workflow.steps[stepId]; - if (primitive === undefined) { - throw new WorkflowDefinitionInvalidError( - deploy.workflow.id, - `step ${stepId} listed in stepOrder is missing from steps`, - ); - } - const stepAgent = extractAgent(primitive); - // The lone step's chain IS the deploy-wide source chain: a one-step - // workflow pins its FULL ordered chain so the reactor fails over across it - // -- whole-workflow failover, identical to the instance path. (The - // multi-step branch keeps the per-step single-source collapse; failover - // across distinct steps is not a thing.) Unlike the pre-authorized instance - // path, the workflow deploy is gated: every source in the chain must be in - // the operator-approved set, and an unapproved source is a loud rejection - // rather than a silent skip that would reshape the reviewed chain. - assertChainHeadIsDefault({ - sources: deploy.config.sources, - defaultSource: deploy.config.defaultSource, - workflowId: deploy.workflow.id, - }); - for (const candidate of deploy.config.sources) { - if (!isSourceApproved(candidate, deploy.operatorApprovals)) { - throw new WorkflowDefinitionInvalidError( - deploy.workflow.id, - `step ${stepId} inference chain includes ${candidate.provider}:${candidate.model}, which is not in the operator-approved grant set`, - ); - } - } - - // The lone step IS the head: one deploy at the deployment address, no - // per-step derivation. The head's agentId and runId are the same - // `` (the minted run id) identity. - const headAddress = deriveRunAddress({ - runId, - domain: deploymentDomain, - }); - const headId = deriveRunAgentId({ runId }); - const headConfig: HarnessConfig = { - ...deploy.config, - agentAddress: headAddress, - agentId: headId, - ...(stepAgent !== null ? { systemPrompt: stepAgent.systemPrompt } : {}), - }; - const headDeployContent: DeployContent = - stepAgent !== null - ? { ...deploy.deployContent, systemPrompt: stepAgent.systemPrompt } - : deploy.deployContent; - - // Tool pins for the child's tool materialization: prefer the pins carried on - // the folded step agent (the definition is the self-contained home for tools - // under the workflow model), falling back to the deploy-supplied pins for the - // live-authored instance path. Per-step pins for genuine multi-step workflows - // are a separate, deferred concern; this path is single-step by construction. - const headToolPackagePins = - stepAgent?.toolPackagePins ?? deploy.toolPackagePins; - - return deploySingleStepAtHead({ - agentAddress: headAddress, - agentId: headId, - runId: headId, - config: headConfig, - deployContent: headDeployContent, - definition: deploy.workflow, - // Pin the full ordered chain gated above; the reactor fails over forward - // across it, matching the instance deploy path. - sources: { [stepId]: [...deploy.config.sources] }, - hubPublicKey: deploy.hubPublicKey, - ...(headToolPackagePins !== undefined - ? { toolPackagePins: headToolPackagePins } - : {}), - ...(referencedDefinitions.length > 0 ? { referencedDefinitions } : {}), - }); -} - -async function runMultiStepBranch(args: { - args: DeployWorkflowArgs; - launchSession: LaunchSessionFn; - sendMultiStepDeploy: SendMultiStepDeployFn | undefined; - referencedDefinitions: readonly ReferencedBodyDefinition[]; -}): Promise { - const { - args: deploy, - launchSession, - sendMultiStepDeploy, - referencedDefinitions, - } = args; - const runId = deploy.runId; - const deploymentDomain = deploy.deploymentDomain; - if (runId === undefined) { - throw new MultiStepDeploymentArgsMissingError("runId"); - } - if (deploymentDomain === undefined) { - throw new MultiStepDeploymentArgsMissingError("deploymentDomain"); - } - if (sendMultiStepDeploy === undefined) { - throw new MultiStepDeployHandoffMissingError(); - } - if (deploy.hubPublicKey === undefined) { - throw new MultiStepDeploymentArgsMissingError("hubPublicKey"); - } - // Pin every step's inference source before launching any session. - // Threading the pin pass ahead of the launch pass means a step whose - // source the operator never approved (or whose preferred provider+model - // is missing from HarnessConfig.sources) rejects the whole deploy - // before `launchSession` provisions an agent-state repo at the sidecar - // with no rollback. The pin is a pure function of the workflow + config - // so the up-front pass is safe to run before any side-effecting work. - type PreparedStep = { - stepId: string; - agentAddress: string; - agentId: string; - stepRunId: string; - config: HarnessConfig; - deployContent: DeployContent; - }; - const sources: Record = {}; - const prepared: PreparedStep[] = []; - for (const stepId of deploy.workflow.stepOrder) { - const primitive = deploy.workflow.steps[stepId]; - if (primitive === undefined) { - throw new WorkflowDefinitionInvalidError( - deploy.workflow.id, - `step ${stepId} listed in stepOrder is missing from steps`, - ); - } - const stepAgent = extractAgent(primitive); - // A workflow step pins a single source (no per-step failover), wrapped in - // a one-element list. Per-step failover chains are an instance-only - // concern; this preserves prior workflow-step behavior. - sources[stepId] = [ - pickStepInferenceSource({ - preferred: stepAgent?.inference.sources[0] ?? null, - stepId, - workflowId: deploy.workflow.id, - config: deploy.config, - operatorApprovals: deploy.operatorApprovals, - }), - ]; - const agentAddress = deriveStepAddress({ - runId, - stepId, - domain: deploymentDomain, - }); - const agentId = deriveStepAgentId({ runId, stepId }); - const stepRunId = deriveStepRunId({ runId, stepId }); - const stepConfig: HarnessConfig = { - ...deploy.config, - agentAddress, - agentId, - ...(stepAgent !== null ? { systemPrompt: stepAgent.systemPrompt } : {}), - }; - const stepDeployContent: DeployContent = - stepAgent !== null - ? { ...deploy.deployContent, systemPrompt: stepAgent.systemPrompt } - : deploy.deployContent; - prepared.push({ - stepId, - agentAddress, - agentId, - stepRunId, - config: stepConfig, - deployContent: stepDeployContent, - }); - } - for (const step of prepared) { - await launchSession({ - agentAddress: step.agentAddress, - agentId: step.agentId, - runId: step.stepRunId, - config: step.config, - deployContent: step.deployContent, - ...(deploy.toolPackagePins !== undefined - ? { toolPackagePins: deploy.toolPackagePins } - : {}), - }); - } - - const deploymentAddress = deriveRunAddress({ - runId, - domain: deploymentDomain, - }); - const deploymentAgentId = deriveRunAgentId({ runId }); - const deploymentConfig: HarnessConfig = { - ...deploy.config, - agentAddress: deploymentAddress, - agentId: deploymentAgentId, - }; - return sendMultiStepDeploy({ - agentAddress: deploymentAddress, - agentId: deploymentAgentId, - config: deploymentConfig, - definition: deploy.workflow, - sources, - hubPublicKey: deploy.hubPublicKey, - ...(referencedDefinitions.length > 0 ? { referencedDefinitions } : {}), - }); -} - -/** - * Assert the reactor's forward-only failover invariant on a single-step - * source chain: the chain is non-empty and its head is the default source. - * The reactor activates the chain's element 0 and fails over forward with no - * wrap, so the default must be element 0; a default placed elsewhere would - * silently no-op failover. Shared by the instance and workflow single-step - * deploy paths, which both pin a full ordered chain. - * - * Throws `WorkflowDefinitionInvalidError` (a client/definition error) so the - * deploy route can classify an inverted request as a 409 rather than a 502. - */ -export function assertChainHeadIsDefault(args: { - sources: readonly InferenceSource[]; - defaultSource: string; - workflowId: string; -}): void { - if (args.sources.length === 0) { - throw new WorkflowDefinitionInvalidError( - args.workflowId, - "config.sources is empty; at least the default source is required as the chain head", - ); - } - if (args.sources[0]?.id !== args.defaultSource) { - throw new WorkflowDefinitionInvalidError( - args.workflowId, - `config.sources[0] (${JSON.stringify( - args.sources[0]?.id, - )}) must be the default source ${JSON.stringify( - args.defaultSource, - )}; a single-step deploy pins the full ordered chain and the reactor activates the head, so the default must be element 0`, - ); - } -} - /** * Whether an inference source is in the operator-approved grant set, keyed * by provider and model. The single definition of "approved source," shared @@ -768,22 +83,21 @@ export function isSourceApproved( * The caller passes the step's preferred `(provider, model)` -- the step * agent's first declared source, or `null` for a step that declares none * (a non-agent step such as sleep/gate/awaitSignal, or an agent with no - * declared source). The identity is all this needs, so both the live path - * (from an `AgentDefinition`) and the source-ref hub path (from the frozen - * inert projection's `modelSources`) feed the same resolver. + * declared source). The identity is all this needs: the source-ref hub deploy + * reads it off the frozen inert projection's `modelSources` and feeds it here. * * The capability walk emits `inference.source::` * grants only for the (provider, model) pairs the agent declared. The * pinning pass here can otherwise resolve a source the walk never * surfaced -- the `HarnessConfig.defaultSource` fallback path for a step * whose preference is unresolvable, or the same fallback for a step that - * carries no preference at all. In both cases the orchestrator must + * carries no preference at all. In both cases the source-pinning pass must * refuse to pin a `(provider, model)` the operator never approved; * silently shipping an unapproved source would defeat the capability- * walk gate the deploy just passed. * * Exported so the source-ref hub deploy can pin its inert onTrigger body - * steps through the exact same resolver the live-authored path uses. + * steps through the same resolver its top-level steps use. */ export function pickStepInferenceSource(args: { preferred: { provider: string; model: string } | null; @@ -822,11 +136,50 @@ export function pickStepInferenceSource(args: { ); } +/** + * Pin every TOP-LEVEL step of a frozen inert projection to a single approved + * inference source, producing the `sources` map the source-ref deploy frame + * carries. The hub holds no live definition, so each step's declared + * `(provider, model)` preference is read off the inert projection's + * `modelSources` and resolved through the `pickStepInferenceSource` resolver + + * operator-approval gate. A step whose preferred source the operator never + * approved (or that resolves to no approved source at all) throws, failing the + * whole deploy closed before any frame is sent. + * + * Every step in `stepOrder` gets one entry (a non-agent step falls back to the + * approved default), so the sidecar child finds a pinned source for each + * staged step. + */ +export function buildInertProjectionStepSources(args: { + projection: WorkflowProjectionDefinition; + config: HarnessConfig; + operatorApprovals: ApprovalSet; +}): Record { + const sources: Record = {}; + for (const stepId of args.projection.stepOrder) { + const preferred = readInertStepPreference( + args.projection.steps[stepId], + "buildInertProjectionStepSources: ", + stepId, + ); + sources[stepId] = [ + pickStepInferenceSource({ + preferred, + stepId, + workflowId: args.projection.id, + config: args.config, + operatorApprovals: args.operatorApprovals, + }), + ]; + } + return sources; +} + /** * Pure function: derive a step's run address from * `(runId, stepId, domain)`. Exported so the supervisor can reconstruct * the same addresses at spawn time without sharing storage with the - * orchestrator. + * deploy flow. * * The local part IS the run id with the step suffix appended; the runId is * already a minted `run_` carrying the `run_` marker `parseRunAddress` @@ -853,16 +206,6 @@ export function deriveStepAgentId(args: { return `${args.runId}-${args.stepId}`; } -/** - * Derive the per-step run id. Pure function of `(runId, stepId)`. - */ -export function deriveStepRunId(args: { - runId: string; - stepId: string; -}): string { - return `${args.runId}-${args.stepId}`; -} - /** * Derive the deployment-level mail address the supervisor registers on * the bus. It is the run id `@` the domain; pure function of `(runId, domain)`. @@ -943,264 +286,13 @@ export function deriveWorkflowRunRepoId(agentAddress: string): string { return agentAddress.replaceAll(/[^a-zA-Z0-9_-]/g, "-"); } -/** - * Run the in-orchestrator validation pass against a `WorkflowDefinition` - * before any deploy-side work happens. `defineWorkflow` already - * structurally validates definitions at authoring time; this pass - * defensively re-asserts the deploy-relevant constraints in case the - * caller hands in a definition synthesized through a different path. - */ -function validateWorkflowDefinition(workflow: WorkflowDefinition): void { - if (workflow.stepOrder.length === 0) { - throw new WorkflowDefinitionInvalidError( - workflow.id, - "stepOrder must be non-empty", - ); - } - for (const stepId of workflow.stepOrder) { - if (!STEP_ID_PATTERN.test(stepId)) { - throw new WorkflowDefinitionInvalidError( - workflow.id, - `step id ${JSON.stringify(stepId)} must match ${STEP_ID_PATTERN.source}`, - ); - } - if (workflow.steps[stepId] === undefined) { - throw new WorkflowDefinitionInvalidError( - workflow.id, - `step ${stepId} listed in stepOrder is missing from steps`, - ); - } - } -} - -/** - * Project a primitive to its agent definition when it carries one. - * Mirrors the same projection the capability walk uses; the multi-step - * branch consumes the agent's `systemPrompt` to override the launch's - * deploy-tree prompt per step. Primitives without an agent (sleep, - * gate, awaitSignal, ...) reuse the deploy-shared prompt. - */ -function extractAgent(primitive: Primitive): AgentDefinition | null { - if (primitive.kind === "step") return primitive.agent; - if (primitive.kind === "map") return primitive.step.agent; - return null; -} - -/** - * Deploy each onTrigger section's authored inline body as its own workflow - * asset and rewrite the primitive to reference it. A section runs its body - * as a child run resolved by ref -- the same production path childWorkflow - * uses -- so the deployed definition carries `{ ref }` bodies while the - * author writes `{ inline }`. The ref is derived deterministically from the - * parent workflow id and the section's step id, so a redeploy of the same - * definition produces the same ref. A workflow with no inline section body - * is returned unchanged with no referenced bodies. Exported for a focused - * unit test. - * - * Returns the rewritten workflow AND each extracted body as a - * `ReferencedBodyDefinition` ({@link ReferencedBodyDefinition}) -- the body - * definition plus its own per-step inference-source pins -- so the deploy can - * both store the body at the hub (via `writeWorkflowRepoTree`) and carry it - * inline in the deploy frame for the sidecar to materialize (the hub-stored - * copy is not on the sidecar's disk, so a body child's spawn-child would - * otherwise fail to resolve the ref, and the body child -- in-process, env - * lost across a restart -- needs its sources durable on disk beside it). Each - * body's sources are pinned against the operator-approved set exactly as the - * top-level steps are, and a tool-bearing body agent is rejected here (see - * `pinBodySources`). - */ -export async function extractOnTriggerBodies(args: { - workflow: WorkflowDefinition; - registry: DirectorRegistry; - workflowRepo: WorkflowRepoWriter; - config: HarnessConfig; - operatorApprovals: ApprovalSet; -}): Promise<{ - workflow: WorkflowDefinition; - referencedDefinitions: readonly ReferencedBodyDefinition[]; -}> { - // Structural rewrite (shared with the source-ref run child and sidecar - // deploy router). The orchestrator then layers the live-authored deploy - // machinery -- capability walk, hub write, and source-pinning -- onto each - // extracted body. - const { workflow, bodies } = rewriteInlineOnTriggerBodies(args.workflow); - if (bodies.length === 0) { - return { workflow: args.workflow, referencedDefinitions: [] }; - } - const referencedDefinitions: ReferencedBodyDefinition[] = []; - for (const { definition } of bodies) { - const bodyWalk = walkCapabilities(definition, args.registry); - await writeWorkflowRepoTree({ - workflow: definition, - walk: bodyWalk, - workflowRepo: args.workflowRepo, - }); - // Pin the body's own per-step inference sources (gated against the same - // operator-approved set) and reject any tool-bearing body agent -- both in - // `pinBodySources`. The pins ride inline so the body child resolves - // inference off disk, durably across a restart. - const bodySources = pinBodySources({ - body: definition, - config: args.config, - operatorApprovals: args.operatorApprovals, - }); - referencedDefinitions.push({ definition, sources: bodySources }); - } - return { workflow, referencedDefinitions }; -} - -/** - * Pin every step of an extracted onTrigger body to an operator-approved - * inference source, mirroring the top-level multi-step per-step pin: a single - * source wrapped in a one-element failover chain, agent-preferred when - * approved and available, else the gated `defaultSource`. Non-agent body - * steps (sleep, awaitSignal, childWorkflow) pin the fallback exactly like the - * top-level non-agent steps, so the body's `sources` covers every `stepOrder` - * entry -- the coverage the wire validator's per-body narrow requires. - * - * A body agent that declares any tool surface is rejected here - * (`assertBodyAgentToolless`): INTR-310 wires body agent-step execution but - * DEFERS staging body tool trees, while the section already unions a body - * agent's tool grants into its own authorized set -- so a tool-bearing body - * agent would be authorized for tools whose deploy tree is never staged and - * would materialize an empty tool set at invoke. Reject at deploy rather than - * ship that silent-correctness trap. - */ -function pinBodySources(args: { - body: WorkflowDefinition; - config: HarnessConfig; - operatorApprovals: ApprovalSet; -}): Record { - const sources: Record = {}; - for (const stepId of args.body.stepOrder) { - const primitive = args.body.steps[stepId]; - if (primitive === undefined) { - throw new WorkflowDefinitionInvalidError( - args.body.id, - `body step ${stepId} listed in stepOrder is missing from steps`, - ); - } - const stepAgent = extractAgent(primitive); - assertBodyAgentToolless(stepAgent, args.body.id, stepId); - sources[stepId] = [ - pickStepInferenceSource({ - preferred: stepAgent?.inference.sources[0] ?? null, - stepId, - workflowId: args.body.id, - config: args.config, - operatorApprovals: args.operatorApprovals, - }), - ]; - } - return sources; -} - -/** - * Reject a body agent that declares any tool surface (`toolFactories` or - * `toolPackagePins`). Body agent tool trees are not yet staged (INTR-310 - * follow-up); the section unions a body agent's tool grants into its own - * authorized set, so a tool-bearing body agent would be authorized for tools - * whose deploy tree never landed on the sidecar and would materialize an - * empty tool set at invoke -- a silent-correctness trap. Fail loud at deploy - * until body tool trees ship. A toolless body agent, or a non-agent step - * (`agent === null`), is accepted. - */ -function assertBodyAgentToolless( - agent: AgentDefinition | null, - bodyId: string, - stepId: string, -): void { - if (agent === null) return; - const toolFactoryCount = agent.toolFactories.length; - const toolPinCount = agent.toolPackagePins?.length ?? 0; - if (toolFactoryCount === 0 && toolPinCount === 0) return; - throw new WorkflowDefinitionInvalidError( - bodyId, - `onTrigger body step ${stepId} declares a tool-bearing agent (${String( - toolFactoryCount, - )} tool factories, ${String( - toolPinCount, - )} tool-package pins); body agent tools are not yet supported (INTR-310 follow-up), and shipping one would authorize the body agent for tools whose deploy tree is never staged`, - ); -} - -async function writeWorkflowRepoTree(args: { - workflow: WorkflowDefinition; - walk: CapabilityWalkResult; - workflowRepo: WorkflowRepoWriter; -}): Promise { - const files = new Map(); - files.set("workflow.json", JSON.stringify(args.workflow, null, 2)); - files.set( - "capability-declarations.json", - JSON.stringify(serializeWalk(args.walk), null, 2), - ); - files.set(".gitignore", ""); - await args.workflowRepo.writeWorkflowRepo({ - workflowRepoId: args.workflow.id, - files, - }); -} - -function serializeWalk(walk: CapabilityWalkResult): unknown { - // `GrantDeclarations.grantEffects` is a `Map`, which `JSON.stringify` - // would silently emit as `{}` -- corrupting capability-declarations.json - // into effect-less noise. Convert each Map to a plain object explicitly - // so the audited declaration carries real per-tool effect data. - const perStep: Record = {}; - for (const [stepId, declarations] of walk.perStep) { - perStep[stepId] = { - grants: declarations.grants, - grantEffects: Object.fromEntries(declarations.grantEffects), - }; - } - return { - perStep, - unresolvedDirectors: walk.unresolvedDirectors, - }; -} - -/** - * Build an `AgentDefinition` from a `HarnessConfig` and a - * `DeployContent`. `SessionService.deployInstanceAtHead` uses it to wrap - * a single agent's harness as a one-step workflow and deploy it - * at the head. The deploy tree itself (`deployContent.systemPrompt`, the - * harness's `tools` and `grants` arrays) is the source of truth for - * runtime behaviour; the wrap synthesizes only the surfaces the - * capability walk needs to gate the deploy against the operator-approval - * set. - * - * The walk inspects `agent.toolFactories[i].id` to emit `tool:` - * grants. The wrap projects each `HarnessConfig.tools[i].name` onto a - * synthesized `AnnotatedToolFactory` whose `id` matches; the factory - * function itself is never invoked on the walk path. Skipping this - * projection would let the gate admit every deploy regardless of what - * `HarnessConfig.tools` named, weakening the approval gate. - */ -export function wrapHarnessAsSingleStepWorkflow(args: { - config: HarnessConfig; - deployContent: DeployContent; -}): AgentDefinition { - return buildSingleStepAgentDefinition({ - id: args.config.agentId, - systemPrompt: args.deployContent.systemPrompt, - inferencePreferences: args.config.sources.map((source) => ({ - provider: source.provider, - model: source.model, - })), - toolFactories: args.config.tools.map(synthesizeWalkToolFactory), - }); -} - /** * Assemble a single-step `AgentDefinition` from already-resolved fields. This - * is the single place the single-step agent shape is constructed, shared by - * the live-config wrap (`wrapHarnessAsSingleStepWorkflow`) and the offline - * agent-to-workflow fold synthesis, so the two cannot drift on which fields a - * wrapped or folded agent carries. Callers pass resolved inputs: the wrap - * passes walk-only synthesized tool factories and no pins; the fold passes - * empty tool factories (its tools ride as `toolPackagePins`), the agent's own - * pins, and its catalog-resolved inference preferences. + * is the single place the single-step agent shape is constructed, so the + * offline agent-to-workflow fold synthesis cannot drift on which fields a + * folded agent carries. Callers pass resolved inputs: the fold passes empty + * tool factories (its tools ride as `toolPackagePins`), the agent's own pins, + * and its catalog-resolved inference preferences. */ export function buildSingleStepAgentDefinition(args: { id: string; @@ -1225,35 +317,3 @@ export function buildSingleStepAgentDefinition(args: { : {}), }; } - -/** - * Synthesize an `AnnotatedToolFactory` from a wire-shaped - * `ToolDefinition`. The factory's `id` mirrors the tool's `name` so the - * capability walk emits a `tool:` grant the operator-approval - * gate can deny. The factory function itself is never invoked on the - * walk path; the wrap never participates in agent instantiation. If a - * future caller mistakes this synthesized factory for a real one and - * invokes it, the throw surfaces the misuse loudly rather than silently - * fabricating a tool bundle. - * - * `validateNamespacedId` (the constructor `defineTool` runs) is - * deliberately skipped: `HarnessConfig.tools[i].name` is the existing - * wire shape downstream consumers gate against, and re-validating it - * here would diverge the single-step wrap's surface from what the - * harness actually loads. The walk and the gate only consult `.id`, so a bare - * name still produces a stable grant string. - */ -function synthesizeWalkToolFactory( - tool: ToolDefinition, -): AnnotatedToolFactory { - const factory = (_env: BaseEnv): never => { - throw new Error( - `wrapHarnessAsSingleStepWorkflow synthesized tool factory for ${JSON.stringify(tool.name)} is walk-only; do not instantiate the single-step wrap agent`, - ); - }; - return Object.assign(factory, { - id: tool.name, - requires: Object.freeze([]) as readonly string[], - definitions: [{ name: tool.name }], - }); -} diff --git a/vendor/intx/workflow-host/VENDORED-FROM b/vendor/intx/workflow-host/VENDORED-FROM index 75a258c9c..3ac970027 100644 --- a/vendor/intx/workflow-host/VENDORED-FROM +++ b/vendor/intx/workflow-host/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/workflow-host) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. CL-6164: the supervisor's signal.deliver branch drops mail whose extracted conversation body is empty (the new hasConversationText gate in conversation-text.ts), recording an empty_conversation_content rejection, instead of delivering "" -- which throws in agent.send and kills the run with StepFailed/retriesExhausted. Attachments-only conversation.message mail (e.g. @corbits/chat's workbench.agent-joined event send) is exactly that shape. CL-6325: adds the action-primitive adapters (adapters/action-invoker.ts, adapters/effect-ledger.ts, adapters/run-blobs.ts and their tests), the child-run action-handler seam in child/run-child.ts, and their index.ts exports -- copied from gtm-workbench's packages/workflow-host workspace fork, not from upstream, which has no action-primitive adapters at the pinned commit (see VENDORED.md). diff --git a/vendor/intx/workflow-host/src/adapters/spawn-child.ts b/vendor/intx/workflow-host/src/adapters/spawn-child.ts index 9da4b448b..131095885 100644 --- a/vendor/intx/workflow-host/src/adapters/spawn-child.ts +++ b/vendor/intx/workflow-host/src/adapters/spawn-child.ts @@ -1,13 +1,12 @@ // Production `WorkflowRuntimeEnv.SpawnChildWorkflow` adapter. // -// The runtime body sees the spawn callback shape: given a -// `definitionRef` (a workflow asset's repo id), a parent-allocated -// `childRunId`, the materialized child input, and parent attribution, -// settle once the child run reaches a terminal phase. The adapter -// itself does not execute the child workflow -- it resolves the -// `definitionRef` into a concrete `WorkflowDefinition` from the -// workflow repo's deploy ref, then delegates the spawn to a -// runtime-supplied `runChild` callback. The supervisor wires the +// The runtime body sees the spawn callback shape: given a `definitionRef` +// (the internal ref the deploy step assigned when it lifted the authored +// inline child), a parent-allocated `childRunId`, the materialized child +// input, and parent attribution, settle once the child run reaches a terminal +// phase. The adapter itself does not execute the child workflow -- it resolves +// the `definitionRef` into a concrete `WorkflowDefinition` and delegates the +// spawn to a runtime-supplied `runChild` callback. The supervisor wires the // callback against a child `WorkflowRuntimeEnv` and `runtimeRun`. // // Two spawn types with DIFFERENT trust structures resolve here, so they @@ -15,31 +14,21 @@ // they are the same: // // - onTrigger BODY (the suspendable adapter): a body is a section -// extracted from the PARENT's own approved definition, so the parent's -// approval already carries the body's `approvedWireHash` on the signed -// deploy frame (surfaced here as `referencedDefinitionHashes[bodyId]`). -// That hash arrives OUT-OF-BAND from the on-disk bytes, so the body path -// routes through the `loadVerifiedWorkflowDefinition` re-verify barrier -// and fails closed on mismatch (or on a body with no frame-carried hash, -// which is a misconfigured deploy). This is where re-verify is -// load-bearing. +// extracted from the PARENT's own approved definition. Source-ref is the +// only deploy lineage, so the body is resolved in-memory from the parent's +// re-evaluated closure (`createInMemorySpawnSuspendableChild`), already +// covered by the parent's re-verify -- no separate on-disk read and no +// separate per-body re-verify. // -// - childWorkflow (the terminal adapter): a `childWorkflow{definitionRef}` -// references a SEPARATELY-approved workflow asset by id. The parent's -// approval has no authority over that asset and carries no hash for it, -// so there is no out-of-band pin to verify against -- a gate here could -// only fail-closed-always. This path reads + envelope-validates the -// asset directly (`readWorkflowDefinitionEnvelope`). Its integrity rests -// on the workflow-kind repo's hub-writes / sidecar-reads authorization -// plus push-time envelope validation; the child asset's own content hash -// is re-verified when the child is itself deployed, not from a parent it -// is merely referenced by. -// -// Both paths read `workflow.json` from the deploy working tree at -// `getRepoDir(repoId)` (the deploy-time `writeTree` materializes the file -// there, so a flat `fs.readFile` gives the envelope without a git -// object-database read) and share that read+validate step; only the terminal -// re-verify gate differs. +// - childWorkflow (the terminal adapter): an owned import embedded inline in +// the parent's definition. It is lifted to an internal `{ ref }` at child +// boot and resolved in-memory from the parent's closure map +// (`createInMemorySpawnChild`) -- exactly like a source-ref onTrigger +// body, with NO on-disk asset and NO separate per-child re-verify (the +// parent's re-verify already covers it, since the inline child rides the +// parent's hashed projection). The terminal-only drive (await the child's +// terminal, no park) is the only thing that distinguishes it from the +// suspendable body adapter. // // Drain coordination is handled by the supervisor's drain primitive // (`packages/workflow-host/src/supervisor`), not by this adapter. The @@ -71,7 +60,6 @@ // childRunId, ... }`) is the seam that makes the scoping unambiguous // at the boundary. -import type { Principal, RepoStore } from "@intx/hub-sessions/substrate"; import type { InferenceEvent } from "@intx/types/runtime"; import type { SpawnChildWorkflow, @@ -81,13 +69,6 @@ import type { WorkflowEvent, } from "@intx/workflow"; -import { - loadVerifiedWorkflowDefinition, - readWorkflowDefinitionEnvelope, -} from "../child/verified-definition-loader"; - -const WORKFLOW_JSON_PATH = "workflow.json"; - /** * The terminal-status shape the runtime body expects back from a * spawn. Mirrored from `SpawnChildWorkflow`'s return type so the @@ -117,52 +98,24 @@ export type RunChildWorkflow = (input: { signal: AbortSignal; }) => Promise<{ terminalStatus: ChildTerminalStatus }>; -export interface WorkflowSpawnChildOpts { - /** - * Substrate the deploy orchestrator wrote the workflow asset into. - * The adapter reads the workflow envelope through - * `substrate.getRepoDir` -- the deploy-time `writeTree` already - * materialized the file under the returned directory and a flat - * `fs.readFile` does not need to walk the git object database. - */ - substrate: RepoStore; - /** - * Principal the adapter presents to the substrate for any future - * authorize-gated read path. The current implementation does not - * gate `getRepoDir` (the substrate documents it as a pure path - * computation), but holding the principal in closure keeps the - * adapter symmetric with the sibling production adapters and ready - * for a future API that surfaces an authorize gate on the same - * read path. - */ - principal: Principal; - /** - * Ref under the workflow asset's repo whose tree holds the - * deployed `workflow.json`. Callers typically supply - * `"refs/heads/main"` -- the workflow-kind handler enforces the - * envelope's structural shape at push time so a deploy ref read - * here either yields a valid envelope or surfaces a targeted - * parse/validation error. - */ - deployRef: string; - /** - * Runtime-supplied child execution callback. The adapter delegates - * here once the `WorkflowDefinition` is resolved; the supervisor - * owns the child `WorkflowRuntimeEnv` and the `runtimeRun` - * invocation. - */ - runChild: RunChildWorkflow; -} - /** - * Construct the production `WorkflowRuntimeEnv.SpawnChildWorkflow` - * adapter. The substrate handle, the principal, the deploy ref, and - * the runtime-supplied child callback live in closure; the returned - * callable satisfies the runtime-env interface. + * Construct the terminal `WorkflowRuntimeEnv.SpawnChildWorkflow` adapter for an + * owned childWorkflow import. The child re-evaluated the whole pinned closure + * and lifted every inline child to an internal `{ ref }`, so the child + * definitions are in hand and already covered by the parent's re-verify. + * Resolve each `definitionRef` + * from the in-memory `bodies` map and delegate to the runtime-supplied + * `runChild`, with NO on-disk round-trip and NO separate per-child re-verify: + * materializing the child back out and re-fingerprinting it would round-trip + * trusted-in-hand data for no gain, and the closure re-eval on restart + * re-derives the same bodies durably. Mirrors + * {@link createInMemorySpawnSuspendableChild} but drives the child terminal-only + * (await its terminal status) rather than across approval parks. */ -export function createWorkflowSpawnChild( - opts: WorkflowSpawnChildOpts, -): SpawnChildWorkflow { +export function createInMemorySpawnChild(opts: { + bodies: ReadonlyMap; + runChild: RunChildWorkflow; +}): SpawnChildWorkflow { return async ({ definitionRef, childRunId, @@ -175,37 +128,14 @@ export function createWorkflowSpawnChild( throw abortError(signal); } - // childWorkflow spawn: resolve a SEPARATELY-approved workflow asset by - // id. The parent's approval carries no hash for it (there is no - // out-of-band pin), so this reads + envelope-validates the asset without - // a re-verify gate -- gating here could only fail-closed-always. The - // asset's integrity rests on the workflow-kind repo's hub-writes / - // sidecar-reads authorization plus push-time envelope validation; the - // asset re-verifies against its OWN approved hash when it is deployed, - // not from a parent that merely references it. - // - // KNOWN GAP (deliberately not closed here): a re-verify gate would only add - // value against an attacker who can overwrite this loose working-tree file - // but not the hub-authored committed asset it was checked out from -- i.e. - // local-disk tamper of SIDECAR_DATA_DIR. That is out of the sidecar's - // threat model (it is host/process-isolation's job): the same write also - // reaches sibling loose reads that are worse targets -- `sources.json` - // (routes inference, carries API keys) and a run's `grants.json` (its - // capability ceiling). Hardening one of many equivalent loose reads is - // theater; the hub-writes/sidecar-reads authorization plus push-time - // envelope validation above is the real boundary. Shipping a per-ref - // approved hash on the parent frame instead would not merely be redundant - // -- it would convert this late-bound reference into an early-bound one and - // fail closed against a child's legitimate independent redeploy. Whether a - // childWorkflow should instead be an OWNED, parent-namespaced sub-workflow - // (whose hash would then be intrinsic to the parent's approval, like an - // onTrigger body) is a product decision tracked separately, not a barrier - // to bolt on here. - const definition = await readWorkflowDefinitionEnvelope({ - substrate: opts.substrate, - repoId: { kind: "workflow", id: definitionRef }, - workflowPath: WORKFLOW_JSON_PATH, - }); + const definition = opts.bodies.get(definitionRef); + if (definition === undefined) { + throw new Error( + `workflow-runtime: spawn-child has no in-memory childWorkflow ` + + `definition for ${JSON.stringify(definitionRef)}; the parent's ` + + `closure should have lifted every inline child`, + ); + } // Re-check the abort signal after the resolution await. The // caller can fire `signal.abort()` between the entry-time check @@ -274,121 +204,18 @@ export type HostSpawnSuspendableChild = ( onEvent: (event: InferenceEvent) => void, ) => ReturnType; -export interface WorkflowSpawnSuspendableChildOpts { - /** - * Substrate the deploy orchestrator wrote the workflow asset into. - * Read through `substrate.getRepoDir` exactly as the terminal-only - * adapter resolves its definition. - */ - substrate: RepoStore; - /** - * Principal held in closure for symmetry with the terminal-only adapter - * and a future authorize-gated read path; `getRepoDir` resolution does - * not gate on it today. - */ - principal: Principal; - /** - * Ref under the workflow asset's repo whose tree holds the deployed - * `workflow.json`. - */ - deployRef: string; - /** - * Runtime-supplied suspendable child execution callback. The adapter - * delegates here once the `WorkflowDefinition` is resolved; the - * supervisor owns the child `WorkflowRuntimeEnv`, the `runtimeRun` - * invocation, and the returned handle. - */ - runSuspendableChild: RunSuspendableChild; - /** - * Hub-approved wire hash per referenced onTrigger body id, sourced from - * `SpawnTimeEnv.referencedDefinitionHashes` (the parent's signed deploy - * frame). REQUIRED, not optional: a body is part of the parent's approval, - * so its hash is an out-of-band pin the body path re-verifies against. A - * `definitionRef` with no entry here is a misconfigured deploy and fails - * closed at resolution. The map may be empty (a deployment with no bodies), - * but the host must pass it explicitly rather than defaulting it away. - */ - referencedDefinitionHashes: Record; -} - -/** - * Construct the production `WorkflowRuntimeEnv.SpawnSuspendableChild` - * adapter. Mirrors {@link createWorkflowSpawnChild} in shape -- resolve the - * `definitionRef` to a concrete `WorkflowDefinition` and delegate to the - * runtime-supplied `runSuspendableChild`, which returns the live handle - * `runOnTrigger` drives across the body's approval parks -- but a body is - * part of the parent's approval, so this path RE-VERIFIES the resolved - * definition against the parent's frame-carried body hash - * (`resolveVerifiedBody`), where the terminal childWorkflow adapter reads a - * separately-approved asset with no such pin. - */ -export function createWorkflowSpawnSuspendableChild( - opts: WorkflowSpawnSuspendableChildOpts, -): HostSpawnSuspendableChild { - return async ( - { - definitionRef, - childRunId, - input, - parentRunId, - parentStepId, - signal, - resumeFromEvents, - }, - onEvent, - ) => { - if (signal.aborted) { - throw abortError(signal); - } - - // onTrigger body spawn: the body's approved hash is intrinsic to the - // parent's approval and rides the signed frame, so this path re-verifies - // against that out-of-band pin. - const definition = await resolveVerifiedBody( - { - substrate: opts.substrate, - deployRef: opts.deployRef, - referencedDefinitionHashes: opts.referencedDefinitionHashes, - }, - definitionRef, - ); - - // Re-check the abort signal after the resolution await, mirroring the - // terminal-only adapter: a caller can fire `signal.abort()` between the - // entry-time check and here, and the child callback must not spin up a - // run against an already-aborted signal. - if (signal.aborted) { - throw abortError(signal); - } - - return opts.runSuspendableChild( - { - definition, - definitionRef, - childRunId, - input, - parentRunId, - parentStepId, - signal, - ...(resumeFromEvents !== undefined ? { resumeFromEvents } : {}), - }, - onEvent, - ); - }; -} - /** - * In-memory variant of {@link createWorkflowSpawnSuspendableChild} for the - * source-ref (code-sourced) path. The parent child re-evaluated the whole - * pinned closure in one sandbox and re-verified it against the approved hash -- - * which already covers every inline onTrigger body -- so the body definitions - * are in hand and already proven. Resolve each `definitionRef` from that - * in-memory `bodies` map and run it in-process, with NO disk round-trip and NO - * separate per-body re-verify: materializing the body back out and - * re-fingerprinting it would round-trip trusted-in-hand data for no gain, and - * the closure re-eval on restart re-derives the same bodies durably. The body - * still runs in the parent's sandbox (in-process today; a stricter per-body - * boundary is the deferred, opt-in SandboxBoundary case). + * Construct the `WorkflowRuntimeEnv.SpawnSuspendableChild` adapter for the + * source-ref (code-sourced) path -- the only deploy lineage. The parent child + * re-evaluated the whole pinned closure in one sandbox and re-verified it + * against the approved hash -- which already covers every inline onTrigger body + * -- so the body definitions are in hand and already proven. Resolve each + * `definitionRef` from that in-memory `bodies` map and run it in-process, with + * NO disk round-trip and NO separate per-body re-verify: materializing the body + * back out and re-fingerprinting it would round-trip trusted-in-hand data for no + * gain, and the closure re-eval on restart re-derives the same bodies durably. + * The body still runs in the parent's sandbox (in-process today; a stricter + * per-body boundary is the deferred, opt-in SandboxBoundary case). */ export function createInMemorySpawnSuspendableChild(opts: { bodies: ReadonlyMap; @@ -439,39 +266,6 @@ export function createInMemorySpawnSuspendableChild(opts: { }; } -/** - * Resolve a referenced onTrigger body's `definitionRef` to a re-verified - * `WorkflowDefinition`. The body's `approvedWireHash` is intrinsic to the - * parent's approval and rides the signed deploy frame, so the load routes - * through the `loadVerifiedWorkflowDefinition` re-verify barrier: read - * `workflow.json`, validate the envelope, recompute the wire hash, and fail - * closed if it differs from the frame-carried hash for this body. A - * `definitionRef` with no frame-carried hash is a misconfigured deploy (the - * parent's approval should have carried every body's hash), so the resolver - * refuses to load it rather than resolving an unverified body. - */ -async function resolveVerifiedBody( - opts: { - substrate: RepoStore; - deployRef: string; - referencedDefinitionHashes: Record; - }, - definitionRef: string, -): Promise { - const approvedHash = opts.referencedDefinitionHashes[definitionRef]; - if (approvedHash === undefined) { - throw new Error( - `workflow-runtime: spawn-child has no hub-approved wire hash for onTrigger body ${JSON.stringify(definitionRef)} on ${opts.deployRef}; the parent's approval should carry every body's hash -- refusing to load an unverified body`, - ); - } - return loadVerifiedWorkflowDefinition({ - substrate: opts.substrate, - repoId: { kind: "workflow", id: definitionRef }, - workflowPath: WORKFLOW_JSON_PATH, - approvedHash, - }); -} - /** * Construct the rejection used when `signal.aborted` short-circuits. * Mirrors the abort-error shape the sibling step-invoker adapter diff --git a/vendor/intx/workflow-host/src/child/env-bootstrap.ts b/vendor/intx/workflow-host/src/child/env-bootstrap.ts index 894dee70b..5c13db279 100644 --- a/vendor/intx/workflow-host/src/child/env-bootstrap.ts +++ b/vendor/intx/workflow-host/src/child/env-bootstrap.ts @@ -69,39 +69,23 @@ const SpawnTimeEnvShape = type({ // so the warm-keep decision is deterministic and a multi-step agent is // never warm-kept by a silent default. "WARM_KEEP?": "string", - // JSON object mapping each referenced onTrigger body id to the hub-approved - // wire hash of that body's projection. The sidecar's deploy router injects - // it (via the substrate env) from the deploy frame's per-body approved - // hashes so a body child can re-verify its own recompute against the hub - // authority. Optional: only an onTrigger deploy that carried referenced - // bodies with approved hashes sets it; absent otherwise. Parsed to a record - // below; malformed JSON or a non-string value throws. - "REFERENCED_DEFINITION_HASHES?": "string", - // Deployment lineage marker selecting the child's definition load path. - // `"source-ref"` for a deployment whose definition was sourced from a pinned - // code closure the sidecar materialized; the child evaluates that closure to - // a LIVE definition and re-verifies it by project-then-hash. Absent (or - // `"live-authored"`) means the child reads the inert `workflow.json` off the - // deploy tree. Optional so a live-authored deployment, which ships no marker, - // still parses; parsed to the `lineage` field below and cross-checked against - // CLOSURE_PACKAGE_DIR. - "WORKFLOW_LINEAGE?": "string > 0", - // Sidecar-local directory of the materialized workflow-definition closure a - // source-ref deployment evaluates. The sidecar computes it when it applies - // the frozen closure and threads it here; it never travels on the hub deploy - // frame. Present iff the lineage is `"source-ref"`; a mismatch between the - // two throws below. - "CLOSURE_PACKAGE_DIR?": "string > 0", + // Sidecar-local directory of the materialized workflow-definition closure the + // deployment evaluates. Source-ref is the only deploy lineage, so the child + // always evaluates a pinned code closure to a LIVE definition and re-verifies + // it by project-then-hash; there is nothing to evaluate without this dir, so + // it is required. The sidecar computes it when it applies the frozen closure + // and threads it here; it never travels on the hub deploy frame. + CLOSURE_PACKAGE_DIR: "string > 0", }).onUndeclaredKey("ignore"); /** - * The lineage-independent fields of the parsed spawn-time env. The hex-encoded - * trust anchors decode to their raw byte representations so the IPC channel - * constructors can consume them without re-validating the hex shape. The - * lineage-correlated pair (`lineage` + `closurePackageDir`) rides the - * `SpawnTimeEnv` union below. + * The parsed spawn-time env. The hex-encoded trust anchors decode to their raw + * byte representations so the IPC channel constructors can consume them without + * re-validating the hex shape. Source-ref is the only deploy lineage, so every + * child evaluates the pinned code closure at `closurePackageDir`; the field is + * always present. */ -export interface SpawnTimeEnvBase { +export interface SpawnTimeEnv { /** Channel identifier minted by the supervisor for this spawn. */ channelId: string; /** 32-byte shared HMAC key for the event channel. */ @@ -118,13 +102,6 @@ export interface SpawnTimeEnvBase { * this value. */ definitionHash: string; - /** - * Hub-approved wire hash per referenced onTrigger body id. Empty when the - * deployment carried no referenced bodies (or none with an approved hash). - * A body child re-verifies its body projection recompute against the entry - * keyed by the body id. - */ - referencedDefinitionHashes: Record; /** Mail address the deployment registered on the bus. */ mailboxAddress: string; /** @@ -141,37 +118,14 @@ export interface SpawnTimeEnvBase { * cache when set and keeps cold instantiate-send-teardown otherwise. */ warmKeep: boolean; + /** + * Sidecar-local dir of the materialized workflow-definition closure the child + * evaluates to a live definition and re-verifies by project-then-hash. + * Source-ref is the only deploy lineage, so it is always present. + */ + closurePackageDir: string; } -/** - * Parsed and validated spawn-time env. `lineage` and `closurePackageDir` form a - * discriminated pair rather than two independent fields: a source-ref - * deployment always carries the closure dir it evaluates, and a live-authored - * one never does. `parseLineage` enforces that correlation at the boundary, and - * modeling it here lets a consumer that narrows on `lineage` read - * `closurePackageDir` with the right type and no redundant presence check. - */ -export type SpawnTimeEnv = SpawnTimeEnvBase & - ( - | { - /** - * Evaluate the pinned code closure at `closurePackageDir` to a live - * definition and re-verify it by project-then-hash. - */ - lineage: "source-ref"; - /** Sidecar-local dir of the materialized workflow-definition closure. */ - closurePackageDir: string; - } - | { - /** - * Read the inert `workflow.json` off the deploy tree. An absent lineage - * marker parses as this arm. - */ - lineage: "live-authored"; - closurePackageDir?: undefined; - } - ); - /** * Parse and validate `process.env`-shaped input into the typed * `SpawnTimeEnv` struct. Any missing key, malformed hex, or off-size @@ -222,102 +176,18 @@ export function parseSpawnTimeEnv( `workflow-child STEP_COUNT must be a positive integer; got ${JSON.stringify(validated.STEP_COUNT)}`, ); } - const referencedDefinitionHashes = parseReferencedDefinitionHashes( - validated.REFERENCED_DEFINITION_HASHES, - ); - // Spread the correlated lineage pair verbatim so the discriminated union is - // preserved -- destructuring into separate fields would erase the - // source-ref-implies-closurePackageDir correlation the union encodes. - const lineageEnv = parseLineage( - validated.WORKFLOW_LINEAGE, - validated.CLOSURE_PACKAGE_DIR, - ); return { channelId: validated.IPC_CHANNEL_ID, hmacKey, hostPublicKey, anchorRunId: validated.DEPLOYMENT_ID, definitionHash: validated.DEFINITION_HASH, - referencedDefinitionHashes, mailboxAddress: validated.MAILBOX_ADDRESS, stepCount, // Strict `=== "true"` so any other value (including the key's // absence) reads false. Warm-keep is opt-in and deterministic; a // typo'd or partial value must not silently enable it. warmKeep: validated.WARM_KEEP === "true", - ...lineageEnv, + closurePackageDir: validated.CLOSURE_PACKAGE_DIR, }; } - -/** - * Resolve the deployment lineage and its closure package directory from the - * two optional spawn-env keys, cross-checking them so an inconsistent pair - * fails closed rather than loading the wrong definition path. - * - * An absent `WORKFLOW_LINEAGE` marker is the live-authored common case. A - * source-ref lineage MUST carry `CLOSURE_PACKAGE_DIR` (there is nothing to - * evaluate without it); a live-authored lineage must NOT carry one (only a - * source-ref deployment materializes a closure). Any other pairing is a - * boundary wiring bug and throws. - */ -function parseLineage( - rawLineage: string | undefined, - rawClosurePackageDir: string | undefined, -): - | { lineage: "source-ref"; closurePackageDir: string } - | { lineage: "live-authored"; closurePackageDir?: undefined } { - if (rawLineage === undefined || rawLineage === "live-authored") { - if (rawClosurePackageDir !== undefined) { - throw new Error( - "workflow-child live-authored deployment must not carry CLOSURE_PACKAGE_DIR; only a source-ref deployment evaluates a closure", - ); - } - return { lineage: "live-authored" }; - } - if (rawLineage === "source-ref") { - if (rawClosurePackageDir === undefined) { - throw new Error( - "workflow-child source-ref deployment requires CLOSURE_PACKAGE_DIR; the sidecar must thread the materialized closure package directory", - ); - } - return { lineage: "source-ref", closurePackageDir: rawClosurePackageDir }; - } - throw new Error( - `workflow-child WORKFLOW_LINEAGE must be "source-ref" or "live-authored"; got ${JSON.stringify(rawLineage)}`, - ); -} - -/** A JSON object of `bodyId -> approved wire hash`, each a non-empty string. */ -const ReferencedDefinitionHashesShape = type({ - "[string]": "string > 0", -}); - -/** - * Parse the `REFERENCED_DEFINITION_HASHES` env value into a validated - * `bodyId -> approvedWireHash` record. An absent value is the common case (a - * deployment with no referenced onTrigger bodies) and yields an empty record. - * A present value must be a JSON object whose every value is a non-empty - * string; malformed JSON or an off-shape object throws so the child aborts - * before it trusts an unparseable per-body hash map. - */ -function parseReferencedDefinitionHashes( - raw: string | undefined, -): Record { - if (raw === undefined) return {}; - let parsed: unknown; - try { - parsed = JSON.parse(raw); - } catch (cause) { - throw new Error( - "workflow-child REFERENCED_DEFINITION_HASHES must be valid JSON", - { cause }, - ); - } - const validated = ReferencedDefinitionHashesShape(parsed); - if (validated instanceof type.errors) { - throw new Error( - `workflow-child REFERENCED_DEFINITION_HASHES failed validation: ${validated.summary}`, - ); - } - return validated; -} diff --git a/vendor/intx/workflow-host/src/child/index.ts b/vendor/intx/workflow-host/src/child/index.ts index 390aa8bb4..a04aff079 100644 --- a/vendor/intx/workflow-host/src/child/index.ts +++ b/vendor/intx/workflow-host/src/child/index.ts @@ -36,11 +36,6 @@ export { export { parseSpawnTimeEnv, type SpawnTimeEnv } from "./env-bootstrap"; -export { - loadVerifiedWorkflowDefinition, - type LoadVerifiedWorkflowDefinitionOpts, -} from "./verified-definition-loader"; - export { discoverInFlightRuns, type DiscoverRunsOpts, diff --git a/vendor/intx/workflow-host/src/child/run-child.ts b/vendor/intx/workflow-host/src/child/run-child.ts index 82c023f31..16bfe38d6 100644 --- a/vendor/intx/workflow-host/src/child/run-child.ts +++ b/vendor/intx/workflow-host/src/child/run-child.ts @@ -60,8 +60,10 @@ import type { } from "@intx/hub-sessions/substrate"; import { readProcessingEntry } from "@intx/hub-sessions/substrate"; import type { DirectorRegistry } from "@intx/agent"; -import { createDefaultDirectorRegistry } from "@intx/agent"; -import { rewriteInlineOnTriggerBodies } from "@intx/workflow"; +import { + rewriteInlineOnTriggerBodies, + rewriteInlineChildWorkflowBodies, +} from "@intx/workflow"; import type { AuthzCallResult } from "@intx/inference"; import type { @@ -97,8 +99,12 @@ import { createWorkflowRunBlobSubstrate } from "../adapters/blob-substrate"; import type { HostSpawnSuspendableChild, RunSuspendableChild, + RunChildWorkflow, +} from "../adapters/spawn-child"; +import { + createInMemorySpawnSuspendableChild, + createInMemorySpawnChild, } from "../adapters/spawn-child"; -import { createInMemorySpawnSuspendableChild } from "../adapters/spawn-child"; import { createControlChannelSender, createEventChannelSender, @@ -116,10 +122,7 @@ import type { CredentialsSnapshot } from "../supervisor/credentials"; import { hashGrants } from "../supervisor/credentials"; import type { SpawnTimeEnv } from "./env-bootstrap"; -import { - loadVerifiedWorkflowDefinition, - loadVerifiedWorkflowDefinitionFromClosure, -} from "./verified-definition-loader"; +import { loadVerifiedWorkflowDefinitionFromClosure } from "./verified-definition-loader"; import { loadWorkflowDirectorRegistryFromClosure } from "../workflow-definition-loader"; import { discoverInFlightRuns } from "./self-discovery"; import { @@ -131,8 +134,6 @@ import { createWarmAgentCache, type WarmAgentCache } from "./warm-agent-cache"; const logger = getLogger(["workflow-host", "child"]); -const WORKFLOW_JSON_PATH = "workflow.json"; - /** * `WorkflowAuthorize` closure factory shape. The child's authorize * evaluates a `(resource, action)` request against the active @@ -320,10 +321,6 @@ export interface RunWorkflowChildBindings { * the host's substrate accepts for `runs//` writes. */ principal: Principal; - /** Workflow-asset repo identity (used to load `workflow.json`). */ - workflowDefinitionRepoId: RepoId; - /** Workflow-asset ref the deploy orchestrator wrote to. */ - workflowDefinitionRef: string; /** * Step-invoker callback the runtime body invokes per step. The * shape is the workflow-runtime `StepInvoker` widened with an @@ -334,32 +331,30 @@ export interface RunWorkflowChildBindings { */ invokeStep: ChildStepInvoker; /** - * Child-spawn callback the runtime body invokes for `childWorkflow` - * primitives. The production binary wires this against - * `createWorkflowSpawnChild`; tests inject a stub. + * Terminal child-spawn callback the runtime body invokes for a + * `childWorkflow` primitive when the deployment embeds NO inline child + * import (the map `run-child` lifts is empty). Optional and, in practice, + * only a test seam: a production deployment that carries a childWorkflow + * always has a non-empty lifted-body map and routes through the in-memory + * resolver built from `runChild` below, and one that carries none never + * invokes this. A workflow that reaches a childWorkflow with neither this + * nor `runChild` wired fails loud at spawn. */ - spawnChild: SpawnChildWorkflow; + spawnChild?: SpawnChildWorkflow; /** - * Suspendable child-spawn callback the runtime body invokes for an - * `onTrigger` section's per-event body: a child run driven across approval - * parks via a live handle (see `SpawnSuspendableChild`). The production - * binary wires this against `createWorkflowSpawnSuspendableChild`. Optional - * because it is only needed to service `onTrigger` sections -- a child - * process that never runs one omits it, and `runOnTrigger` fails loud if a - * workflow uses a section the env did not wire. - * - * Host-widened with an `onEvent` sink (`HostSpawnSuspendableChild`): the - * runtime env exposes the narrow `SpawnSuspendableChild`, and `buildRuntimeEnv` - * injects the run's event-channel funnel into this binding so a body's live - * inference events reach the hub stream. The runtime contract stays narrow. + * Raw in-process terminal child executor. `run-child` builds the in-memory + * childWorkflow resolver from this executor plus the lifted-body map it + * extracts after loading the definition -- the parent's own re-verified + * closure -- so an owned inline child resolves with NO on-disk read. Parallel + * to `runSuspendableChild` for onTrigger bodies. Optional for the same + * reason: a child that embeds no childWorkflow import omits it. */ - spawnSuspendableChild?: HostSpawnSuspendableChild; + runChild?: RunChildWorkflow; /** - * Raw in-process suspendable-child executor. On the source-ref lineage, - * `run-child` builds the in-memory body resolver from this executor plus the - * bodies map it extracts AFTER re-evaluating the closure -- the substrate - * factory cannot build that resolver because the bodies map does not exist - * pre-eval. Optional for the same reason as `spawnSuspendableChild`: a child + * Raw in-process suspendable-child executor. `run-child` builds the in-memory + * onTrigger-body resolver from this executor plus the bodies map it extracts + * AFTER re-evaluating the closure -- the substrate factory cannot build that + * resolver because the bodies map does not exist pre-eval. Optional: a child * that runs no onTrigger section omits it. */ runSuspendableChild?: RunSuspendableChild; @@ -408,8 +403,6 @@ export interface RunWorkflowChildBindings { * invocation step settles as a terminal failure, the pre-recovery behavior. */ readParkedApprovalOps?: ReadParkedApprovalOps; - /** Optional director registry; defaults to the canonical built-ins. */ - directors?: DirectorRegistry; /** Optional clock override; production wires `() => new Date()`. */ clock?: () => Date; /** Optional id generator override; production wires a monotonic one. */ @@ -632,73 +625,65 @@ export async function runWorkflowChild( writer: opts.eventWriter, }); - // Re-verify barrier at the load boundary, branching on deployment lineage. - // `opts.env.definitionHash` is the hub-approved wire hash in both arms; the + // Re-verify barrier at the load boundary. Source-ref is the only deploy + // lineage: the inert projection is a non-executable approval surface (agents + // carry `modelSources`/no `inference`, tool factories are plain data), so the + // child EVALUATES the pinned code closure to a live definition and re-verifies + // by projecting it back to inert and hashing (`computeLiveDefinitionHash`) + // against `opts.env.definitionHash`; a divergent closure fails closed. The // load happens once before both the resume loop and the trigger loop, so the // same verified definition serves every fresh trigger AND every resume. // - // - source-ref: the inert `workflow.json` is a non-executable approval - // surface (agents carry `modelSources`/no `inference`, tool factories are - // plain data), so the child EVALUATES the pinned code closure to a live - // definition and re-verifies by projecting it back to inert and hashing - // (`computeLiveDefinitionHash`); a divergent closure fails closed. - // - live-authored: read the inert `workflow.json` off the deploy tree and - // re-verify the on-disk bytes' hash, unchanged. - // Extracted source-ref onTrigger bodies, keyed by ref, for the in-memory - // suspendable-child resolver below (empty on the live-authored arm, which - // resolves bodies from disk instead). - let bodiesMap = new Map(); - let definition: WorkflowDefinition; - if (opts.env.lineage === "source-ref") { - // The `SpawnTimeEnv` union guarantees a source-ref env carries - // `closurePackageDir`; no presence check is needed here. - definition = await loadVerifiedWorkflowDefinitionFromClosure({ - packageDir: opts.env.closurePackageDir, - approvedHash: opts.env.definitionHash, - }); - // Post-verify structural rewrite: the re-verify above hashed the closure's - // INLINE onTrigger bodies (matching the frozen approval); now lift each to - // a `{ ref }` so the runtime dispatches to the body child, and keep the - // extracted body definitions in an in-memory map. The source-ref - // suspendable-child resolver runs each body from THIS map -- the parent's - // already-re-verified closure -- with no disk read and no separate per-body - // re-verify. The rewrite MUST follow the re-verify: rewriting first would - // diverge from the frozen inline-body hash. - const { workflow, bodies } = rewriteInlineOnTriggerBodies(definition); - definition = workflow; - bodiesMap = new Map(bodies.map((b) => [b.ref, b.definition])); - } else { - definition = await loadVerifiedWorkflowDefinition({ - substrate: opts.bindings.substrate, - repoId: opts.bindings.workflowDefinitionRepoId, - workflowPath: WORKFLOW_JSON_PATH, - approvedHash: opts.env.definitionHash, - }); - } + // Post-verify structural rewrite: the re-verify above hashed the closure's + // INLINE onTrigger bodies (matching the frozen approval); now lift each to a + // `{ ref }` so the runtime dispatches to the body child, and keep the + // extracted body definitions in an in-memory map. The suspendable-child + // resolver runs each body from THIS map -- the parent's already-re-verified + // closure -- with no disk read and no separate per-body re-verify. The rewrite + // MUST follow the re-verify: rewriting first would diverge from the frozen + // inline-body hash. + const verifiedDefinition = await loadVerifiedWorkflowDefinitionFromClosure({ + packageDir: opts.env.closurePackageDir, + approvedHash: opts.env.definitionHash, + }); + const { workflow, bodies } = rewriteInlineOnTriggerBodies(verifiedDefinition); + let definition: WorkflowDefinition = workflow; + const bodiesMap = new Map( + bodies.map((b) => [b.ref, b.definition]), + ); - // Directors resolve from the pinned closure on the source-ref arm so a - // custom director authored in the workflow's own package runs; the - // live-authored arm keeps the injected-or-default registry. Loading - // directors OUTSIDE the definition-hash re-verify is safe: the approved - // hash pins each director's id + config (which director runs cannot change - // post-approval) and the closure's SRI pins its module bytes. Folding - // directors into the hash would be redundant, so it is deliberately not - // done -- see `loadWorkflowDirectorRegistryFromClosure`. - const directors = - opts.env.lineage === "source-ref" - ? await loadWorkflowDirectorRegistryFromClosure({ - packageDir: opts.env.closurePackageDir, - }) - : (opts.bindings.directors ?? createDefaultDirectorRegistry()); + // An owned `childWorkflow` import embeds its child inline in the parent's + // definition (folded into the parent's hash and approval), so it is already + // covered by the re-verify above. Lift each inline child to an internal + // `{ ref }` -- the form the runtime dispatches -- and keep the lifted + // definitions in an in-memory map. The terminal childWorkflow resolver below + // runs each child from THIS map, with no on-disk asset read and no separate + // per-child re-verify. + const childRewrite = rewriteInlineChildWorkflowBodies(definition); + definition = childRewrite.workflow; + const childBodiesMap = new Map( + childRewrite.bodies.map((b) => [b.ref, b.definition]), + ); + + // Directors resolve from the pinned closure so a custom director authored in + // the workflow's own package runs. Loading directors OUTSIDE the + // definition-hash re-verify is safe: the approved hash pins each director's + // id + config (which director runs cannot change post-approval) and the + // closure's SRI pins its module bytes. Folding directors into the hash would + // be redundant, so it is deliberately not done -- see + // `loadWorkflowDirectorRegistryFromClosure`. + const directors = await loadWorkflowDirectorRegistryFromClosure({ + packageDir: opts.env.closurePackageDir, + }); // Suspendable-child (onTrigger body) resolver, selected ONCE per deployment: // the bodies map is immutable and the per-run `onEvent` is injected later in - // `buildRuntimeEnv`. On source-ref, resolve each body from the parent's - // in-memory closure (already re-verified above) via the raw executor binding; - // the live-authored arm keeps the injected disk-backed binding. A source-ref - // deployment that carries bodies but whose host wired no executor is a - // misconfiguration -- fail loud at startup rather than silently falling back - // to a disk read (the exact behaviour this arm exists to avoid). + // `buildRuntimeEnv`. Resolve each body from the parent's in-memory closure + // (already re-verified above) via the raw executor binding. A deployment that + // carries bodies but whose host wired no executor is a misconfiguration -- + // fail loud at startup rather than silently falling back to a disk read (the + // exact behaviour this arm exists to avoid). A deployment with no onTrigger + // body leaves the host undefined; its suspendable-child slot is never invoked. let suspendableChildHost: HostSpawnSuspendableChild | undefined; if (bodiesMap.size > 0) { const executor = opts.bindings.runSuspendableChild; @@ -713,8 +698,41 @@ export async function runWorkflowChild( bodies: bodiesMap, runSuspendableChild: executor, }); + } + + // Terminal childWorkflow resolver, selected ONCE per deployment. When the + // definition embeds any inline child (the lifted map is non-empty), resolve + // each from that in-memory map via the raw terminal executor -- the parent's + // own re-verified closure -- so an owned child spawns with no disk read. A + // deployment that embeds a childWorkflow but whose host wired no executor is + // a misconfiguration and fails loud at startup rather than falling back to a + // disk read. A definition with no inline child keeps the injected binding (a + // test seam); its childWorkflow slot is never invoked. + let spawnChild: SpawnChildWorkflow; + if (childBodiesMap.size > 0) { + const executor = opts.bindings.runChild; + if (executor === undefined) { + throw new Error( + "workflow-child: deployment embeds childWorkflow imports but the " + + "host wired no runChild executor; cannot resolve children in-memory", + ); + } + spawnChild = createInMemorySpawnChild({ + bodies: childBodiesMap, + runChild: executor, + }); + } else if (opts.bindings.spawnChild !== undefined) { + spawnChild = opts.bindings.spawnChild; } else { - suspendableChildHost = opts.bindings.spawnSuspendableChild; + // No inline child and no injected binding: a workflow that nonetheless + // reaches a childWorkflow spawn fails loud here rather than silently + // completing against a child that never ran. + spawnChild = async ({ definitionRef }) => { + throw new Error( + `workflow-child: childWorkflow ${definitionRef} reached the runtime ` + + `but no child executor is wired`, + ); + }; } const authorize = createCredentialsBackedAuthorize( @@ -779,6 +797,7 @@ export async function runWorkflowChild( authorize, directors, suspendableChildHost, + spawnChild, clock, newId, drainController, @@ -873,6 +892,7 @@ export async function runWorkflowChild( authorize, directors, suspendableChildHost, + spawnChild, clock, newId, eventSender, @@ -950,6 +970,7 @@ async function handleControlPayload( authorize: WorkflowAuthorizeFn; directors: DirectorRegistry; suspendableChildHost: HostSpawnSuspendableChild | undefined; + spawnChild: SpawnChildWorkflow; clock: () => Date; newId: (prefix: string) => string; eventSender: ReturnType; @@ -1010,6 +1031,7 @@ async function handleControlPayload( authorize: ctx.authorize, directors: ctx.directors, suspendableChildHost: ctx.suspendableChildHost, + spawnChild: ctx.spawnChild, clock: ctx.clock, newId: ctx.newId, drainController: ctx.drainController, @@ -1370,6 +1392,7 @@ function buildRuntimeEnv(args: { authorize: WorkflowAuthorizeFn; directors: DirectorRegistry; suspendableChildHost: HostSpawnSuspendableChild | undefined; + spawnChild: SpawnChildWorkflow; clock: () => Date; newId: (prefix: string) => string; drainController: DrainController; @@ -1460,7 +1483,7 @@ function buildRuntimeEnv(args: { directors: args.directors, authorize: args.authorize, invokeStep, - spawnChild: args.bindings.spawnChild, + spawnChild: args.spawnChild, // Wire the suspendable-child seam only when the host supplied it; a child // that never runs an onTrigger section omits the binding, and the runtime // body fails loud if a workflow reaches a section the env did not wire. diff --git a/vendor/intx/workflow-host/src/child/verified-definition-loader.ts b/vendor/intx/workflow-host/src/child/verified-definition-loader.ts index ea2a8ab7c..a6e9fd73b 100644 --- a/vendor/intx/workflow-host/src/child/verified-definition-loader.ts +++ b/vendor/intx/workflow-host/src/child/verified-definition-loader.ts @@ -1,160 +1,28 @@ -// Shared definition read + optional re-verify for the workflow-process child. +// Source-ref definition load + re-verify barrier for the workflow-process child. // -// The child reaches a workflow definition through structurally identical -// read paths that read `workflow.json` from the deploy working tree, parse -// it, and validate the envelope. `readWorkflowDefinitionEnvelope` owns that -// read+validate step in one layer. `loadVerifiedWorkflowDefinition` wraps it -// with the re-verify barrier: it recomputes the wire hash over the validated -// projection and refuses to return a definition whose recompute does not -// match the hub-approved hash. A mismatch throws -- fail closed, no fallback -// and no coercion. +// A source-ref deployment's runnable definition is the evaluated pinned code +// closure, not an on-disk `workflow.json`. `loadVerifiedWorkflowDefinitionFromClosure` +// evaluates that closure to a live `WorkflowDefinition` and re-verifies it by +// project-then-hash: it projects the live definition back to its inert form, +// hashes it (`computeLiveDefinitionHash`), and refuses to return a definition +// whose recompute does not match the hub-approved hash. A mismatch throws -- +// fail closed, no fallback and no coercion. // -// The gate is a THIN WRAPPER, not baked into the read, precisely because the -// re-verify barrier is load-bearing only where the caller holds an approved -// hash that arrived OUT-OF-BAND from the bytes being checked (a signed spawn -// env / deploy frame the file-writer cannot forge). Callers with such a hash -// -- the top-level run child (`run-child.ts`, `SpawnTimeEnv.definitionHash`) -// and the onTrigger-body spawn path (`adapters/spawn-child.ts`, the parent's -// frame-carried `referencedDefinitionHashes[bodyId]`) -- use the gated -// wrapper. A caller with no out-of-band pin -- a `childWorkflow` spawn, which -// resolves a SEPARATELY-approved, hub-authored, sidecar-read-only asset the -// parent's frame has no authority over -- uses `readWorkflowDefinitionEnvelope` -// directly. Gating that path could only fail-closed-always, since there is no -// approved hash to check against; its integrity is the asset repo's -// hub-writes/sidecar-reads authorization plus push-time envelope validation. -// -// The barrier lives at the LOAD boundary, not at run start. A resumed run -// reuses the definition this loader returned at child boot, so gating the -// load covers fresh runs, resumed runs, and referenced onTrigger bodies -// with a single check. `RunStarted.definitionHash` is deliberately NOT the -// barrier: it hashes a different projection and never fires when the run -// log already carries a `RunStarted`, so it is skipped on resume. - -import { type } from "arktype"; +// The re-verify barrier is load-bearing because the approved hash arrives +// OUT-OF-BAND from the bytes being checked (a signed spawn env the closure +// materializer cannot forge). It lives at the LOAD boundary, not at run start: +// a resumed run reuses the definition this loader returned at child boot, so +// gating the load covers fresh runs, resumed runs, and referenced onTrigger +// bodies (which the child extracts from the same re-verified closure) with a +// single check. `RunStarted.definitionHash` is deliberately NOT the barrier: it +// hashes a different projection and never fires when the run log already carries +// a `RunStarted`, so it is skipped on resume. -import type { RepoId, RepoStore } from "@intx/hub-sessions/substrate"; -import { workflowDefinitionEnvelopeSchema } from "@intx/hub-sessions/substrate"; -import { computeWireDefinitionHash } from "@intx/types/wire-definition-hash"; import { computeLiveDefinitionHash } from "@intx/workflow"; import type { WorkflowDefinition } from "@intx/workflow"; import { loadWorkflowDefinitionFromClosure } from "../workflow-definition-loader"; -export interface ReadWorkflowDefinitionEnvelopeOpts { - /** Substrate the deploy orchestrator wrote the workflow asset into. */ - substrate: RepoStore; - /** - * Workflow-asset repo whose deploy working tree holds the definition. - * The read composes `substrate.getRepoDir(repoId)` with `workflowPath`. - */ - repoId: RepoId; - /** - * Repo-relative path to the workflow JSON within the deploy working - * tree. Production callers pass `workflow.json`. - */ - workflowPath: string; -} - -export interface LoadVerifiedWorkflowDefinitionOpts - extends ReadWorkflowDefinitionEnvelopeOpts { - /** - * Hub-approved wire hash the recompute must match. Sourced from the hub - * authority: `SpawnTimeEnv.definitionHash` for the top-level run, or the - * per-body entry of `SpawnTimeEnv.referencedDefinitionHashes` for a - * referenced onTrigger body. A recompute that differs throws. - */ - approvedHash: string; -} - -/** - * Read and envelope-validate a workflow definition from the deploy working - * tree. Returns the validated `WorkflowDefinition` WITHOUT a re-verify gate: - * this is the read step callers that hold no out-of-band approved hash use - * directly (a `childWorkflow` spawn resolving a separately-approved, - * hub-authored, sidecar-read-only asset). Callers that DO hold an out-of-band - * pin wrap this with `loadVerifiedWorkflowDefinition`. - */ -export async function readWorkflowDefinitionEnvelope( - opts: ReadWorkflowDefinitionEnvelopeOpts, -): Promise { - const fs = await import("node:fs/promises"); - const path = await import("node:path"); - const dir = opts.substrate.getRepoDir(opts.repoId); - const filePath = path.join(dir, opts.workflowPath); - const label = `${opts.repoId.kind}/${opts.repoId.id}`; - - // Neutral "definition read" prefix, NOT "verified": this envelope read is - // shared by the gated loaders below AND the deliberately-ungated childWorkflow - // spawn, so labeling its errors "verified" would misdescribe the ungated path. - // The re-verify errors that DO gate stay labeled "verified" in the loaders. - let raw: string; - try { - raw = await fs.readFile(filePath, "utf8"); - } catch (cause) { - if (isErrnoNotFound(cause)) { - throw new Error( - `workflow-host definition read: ${opts.workflowPath} not present under ${label}`, - { cause }, - ); - } - throw new Error( - `workflow-host definition read: cannot read ${opts.workflowPath} for ${label}`, - { cause }, - ); - } - - let parsed: unknown; - try { - parsed = JSON.parse(raw); - } catch (cause) { - throw new Error( - `workflow-host definition read: ${opts.workflowPath} for ${label} is not valid JSON`, - { cause }, - ); - } - - const validated = workflowDefinitionEnvelopeSchema(parsed); - if (validated instanceof type.errors) { - throw new Error( - `workflow-host definition read: ${opts.workflowPath} for ${label} failed envelope validation: ${validated.summary}`, - ); - } - // The envelope schema enforces the structural shape the runtime body and - // state machine consume; the discriminated narrow over every primitive - // variant lives downstream in the runtime body. `.onUndeclaredKey("ignore")` - // is passthrough, not stripping, so the validated object carries the same - // fields the on-disk bytes did -- a hash recompute over it therefore hashes - // a faithful projection of exactly what was read. - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- envelope schema enforces structural shape; primitive narrows live downstream in the runtime body - return validated as unknown as WorkflowDefinition; -} - -/** - * Read, envelope-validate, and re-verify a workflow definition from the - * deploy working tree. Returns the validated `WorkflowDefinition` only - * when its recomputed wire hash matches `approvedHash`; otherwise throws. - * The read+validate is delegated to `readWorkflowDefinitionEnvelope`; this - * function adds only the out-of-band-pin re-verify barrier. - */ -export async function loadVerifiedWorkflowDefinition( - opts: LoadVerifiedWorkflowDefinitionOpts, -): Promise { - const definition = await readWorkflowDefinitionEnvelope({ - substrate: opts.substrate, - repoId: opts.repoId, - workflowPath: opts.workflowPath, - }); - const label = `${opts.repoId.kind}/${opts.repoId.id}`; - - const recomputed = await computeWireDefinitionHash(definition); - if (recomputed !== opts.approvedHash) { - throw new Error( - `workflow-host verified-definition loader: recomputed wire hash ${recomputed} for ${label} does not match the approved hash ${opts.approvedHash}; refusing to load a definition tampered after approval`, - ); - } - return definition; -} - export interface LoadVerifiedWorkflowDefinitionFromClosureOpts { /** * Sidecar-local directory of the materialized workflow-definition closure: @@ -181,8 +49,7 @@ export interface LoadVerifiedWorkflowDefinitionFromClosureOpts { /** * Evaluate a source-ref deployment's pinned code closure to a live * `WorkflowDefinition` and re-verify it by project-then-hash before returning - * it. This is the source-ref counterpart to `loadVerifiedWorkflowDefinition`: - * the inert projection is a non-executable approval surface, so the runtime + * it. The inert projection is a non-executable approval surface, so the runtime * needs the live definition the closure evaluates to. The re-verify projects * that live definition back to its inert form and hashes it * (`computeLiveDefinitionHash`), matching the hub-approved wire hash by byte @@ -205,12 +72,3 @@ export async function loadVerifiedWorkflowDefinitionFromClosure( } return definition; } - -function isErrnoNotFound(cause: unknown): boolean { - return ( - cause !== null && - typeof cause === "object" && - "code" in cause && - cause.code === "ENOENT" - ); -} diff --git a/vendor/intx/workflow-host/src/index.ts b/vendor/intx/workflow-host/src/index.ts index 775736250..85c4083c7 100644 --- a/vendor/intx/workflow-host/src/index.ts +++ b/vendor/intx/workflow-host/src/index.ts @@ -1,8 +1,11 @@ export { loadWorkflowDefinitionFromClosure, loadWorkflowDirectorRegistryFromClosure, + loadWorkflowPluginFactoriesFromClosure, + loadWorkflowPluginToolDefinitionsFromClosure, type LoadWorkflowDefinitionFromClosureArgs, type LoadWorkflowDirectorRegistryFromClosureArgs, + type LoadWorkflowPluginsFromClosureArgs, } from "./workflow-definition-loader"; export { createWorkflowRunRepoStore, @@ -29,14 +32,11 @@ export { type WorkflowRunEffectLedgerOpts, } from "./adapters/effect-ledger"; export { - createWorkflowSpawnChild, - createWorkflowSpawnSuspendableChild, + createInMemorySpawnChild, createInMemorySpawnSuspendableChild, type ChildTerminalStatus, type RunChildWorkflow, type RunSuspendableChild, - type WorkflowSpawnChildOpts, - type WorkflowSpawnSuspendableChildOpts, } from "./adapters/spawn-child"; export { createWorkflowSupervisor, @@ -154,7 +154,6 @@ export { createSupervisorBackedTransport, createWarmAgentCache, discoverInFlightRuns, - loadVerifiedWorkflowDefinition, parseSpawnTimeEnv, runWorkflowChild, runWorkflowChildFromProcessEnv, @@ -171,7 +170,6 @@ export { type DrainController, type GrantEvaluator, type LoadParkedApproval, - type LoadVerifiedWorkflowDefinitionOpts, type RunWorkflowChildBindings, type RunWorkflowChildFromProcessEnvOpts, type RunWorkflowChildOpts, diff --git a/vendor/intx/workflow-host/src/supervisor/recycle.ts b/vendor/intx/workflow-host/src/supervisor/recycle.ts index 118ce2b55..f23f11b5e 100644 --- a/vendor/intx/workflow-host/src/supervisor/recycle.ts +++ b/vendor/intx/workflow-host/src/supervisor/recycle.ts @@ -4,8 +4,9 @@ // // Recycle is the supervisor's "same deploy tree, fresh process" path. // It tears the existing workflow-process child down and stands a new -// one up against the SAME deploy tree (same `workflow.json`, same -// per-step credential repos). It is STRICTLY ORTHOGONAL TO REDEPLOY: +// one up against the SAME deploy tree (same materialized source +// closure, same per-step credential repos). It is STRICTLY ORTHOGONAL +// TO REDEPLOY: // // - Recycle = same deploy tree, fresh process. // - Redeploy = new deploy tree. @@ -322,8 +323,8 @@ export async function triggerRecycle( // Step 3: respawn. Fresh channelId, fresh HMAC key, fresh Ed25519 // IPC keypair. Per-step credentials are re-read so a grants update // that landed since the original spawn is reflected in the new - // child's snapshot. The deploy tree (`workflow.json`, agents, - // workflow-asset repo) is UNCHANGED. + // child's snapshot. The deploy tree (the materialized source closure, + // the workflow-asset repo, the agent-state repos) is UNCHANGED. const channelId = generateChannelId(); const hmacKey = generateHmacKey(); const ipcKeypair = await ( @@ -388,7 +389,8 @@ export async function triggerRecycle( // the previous child's lifetime is picked up here -- the recycle // doubles as the supervisor's grant-refresh path. The deploy tree // is not consulted; this read is against the `agent-state` repos - // alone, whose contents are independent of `workflow.json`. + // alone, whose contents are independent of the materialized source + // closure. // // This is a substrate read that can reject -- a grants file that // became malformed is precisely the recycle's grant-refresh path. The diff --git a/vendor/intx/workflow-host/src/workflow-definition-loader.ts b/vendor/intx/workflow-host/src/workflow-definition-loader.ts index a8096c4b2..572ea1aac 100644 --- a/vendor/intx/workflow-host/src/workflow-definition-loader.ts +++ b/vendor/intx/workflow-host/src/workflow-definition-loader.ts @@ -28,7 +28,10 @@ import { createDefaultDirectorRegistry, createWorkflowDirectorRegistry, isAnnotatedDirectorFactory, + isAnnotatedPluginFactory, + type AnnotatedPluginFactory, type DirectorRegistry, + type ToolDeclaration, } from "@intx/agent"; import { PackageJSON, isContainedEntryPath } from "@intx/types/package-json"; import { workflowDefinitionEnvelopeSchema } from "@intx/hub-sessions/substrate"; @@ -205,6 +208,174 @@ export async function loadWorkflowDirectorRegistryFromClosure( return createWorkflowDirectorRegistry(loaded); } +export interface LoadWorkflowPluginsFromClosureArgs { + /** + * Directory of the materialized workflow package within the closure -- + * the same directory `loadWorkflowDefinitionFromClosure` reads. Each + * declared plugin package is resolved from this package's laid-out + * `node_modules/`, exactly as the workflow entry's own bare-specifier + * imports resolve. + */ + readonly packageDir: string; + /** + * Plugin-package names the workflow's agents declare via + * `AgentDefinition.plugins` (`["@intx/tools-lsp"]`). Each MUST be a + * direct dependency of the workflow package so it is laid out under the + * workflow package's `node_modules/`. Empty is valid (no plugins). + */ + readonly plugins: readonly string[]; + /** See `LoadWorkflowDefinitionFromClosureArgs.importCacheKey`. */ + readonly importCacheKey?: string; + /** Test seam for dynamic import; see the definition loader's variant. */ + readonly importModule?: (importUrl: string) => Promise; +} + +/** + * Import each declared plugin package's `interchange.tools` module from the + * materialized workflow closure and collect the `AnnotatedPluginFactory` + * values it exports. This is the run-child counterpart to the tool-package + * loader's plugin channel: a source-ref workflow contributes no plugin factory + * through its agent definition (a plugin has no agent slot), so the child + * materializes the declared plugins straight from the already-laid-out closure + * -- no re-download, no manifest -- and feeds them into the existing per-step + * plugin chain. The closure bytes were SRI-verified when the deploy applied the + * frozen closure, and resolution walks the same `node_modules/` graph the + * workflow entry's imports use. + * + * @throws if a declared plugin package cannot be resolved, declares no + * `interchange.tools` entry, the entry escapes the package, cannot be + * imported, or exports no `AnnotatedPluginFactory` value + */ +export async function loadWorkflowPluginFactoriesFromClosure( + args: LoadWorkflowPluginsFromClosureArgs, +): Promise { + const importModule = + args.importModule ?? ((url: string) => import(url) as Promise); + const out: AnnotatedPluginFactory[] = []; + for (const pluginName of args.plugins) { + const factories = await loadPluginPackageFactories({ + workflowPackageDir: args.packageDir, + pluginName, + importModule, + ...(args.importCacheKey !== undefined + ? { importCacheKey: args.importCacheKey } + : {}), + }); + out.push(...factories); + } + return out; +} + +/** + * Read the static tool `definitions` each declared plugin package + * contributes, keyed by plugin-package name, WITHOUT retaining the plugin + * factory (so the caller never instantiates a plugin, which for LSP would + * start a subprocess). This is the probe/capability-walk counterpart to + * `loadWorkflowPluginFactoriesFromClosure`: it loads the SAME plugin module + * from the SAME frozen closure so the tool grant surface the walk approves + * matches the plugin the run-child materializes. + * + * A plugin package that exports plugin factories but declares no tool + * definitions (a middleware-only plugin) maps to an empty array -- valid, + * it contributes no tool grant. + * + * @throws under the same conditions as `loadWorkflowPluginFactoriesFromClosure` + */ +export async function loadWorkflowPluginToolDefinitionsFromClosure( + args: LoadWorkflowPluginsFromClosureArgs, +): Promise> { + const importModule = + args.importModule ?? ((url: string) => import(url) as Promise); + const byPackage = new Map(); + for (const pluginName of args.plugins) { + const factories = await loadPluginPackageFactories({ + workflowPackageDir: args.packageDir, + pluginName, + importModule, + ...(args.importCacheKey !== undefined + ? { importCacheKey: args.importCacheKey } + : {}), + }); + const definitions: ToolDeclaration[] = []; + for (const factory of factories) { + definitions.push(...factory.definitions); + } + byPackage.set(pluginName, definitions); + } + return byPackage; +} + +async function loadPluginPackageFactories(args: { + workflowPackageDir: string; + pluginName: string; + importCacheKey?: string; + importModule: (importUrl: string) => Promise; +}): Promise { + // Resolve the plugin package from the workflow package's laid-out + // `node_modules/`. The closure materializer symlinks each direct + // dependency into the requirer's `node_modules/`, so a declared plugin + // package (which must be a workflow dependency) sits here. Realpath it so + // a plugin whose entry-path containment is checked below compares + // realpath-vs-realpath. + const linkedDir = path.join( + args.workflowPackageDir, + "node_modules", + args.pluginName, + ); + let pluginPkgDir: string; + try { + pluginPkgDir = await fs.realpath(linkedDir); + } catch (cause) { + throw new Error( + `plugin package ${JSON.stringify(args.pluginName)} could not be resolved from the workflow closure at ${args.workflowPackageDir}; it must be a direct dependency of the workflow package`, + { cause }, + ); + } + + const pkgJson = await readPackageJSON(pluginPkgDir); + const entryRel = pkgJson.interchange?.tools; + if (entryRel === undefined) { + throw new Error( + `plugin package ${JSON.stringify(args.pluginName)} at ${pluginPkgDir} declares no "interchange.tools" entry; it is not a tool package`, + ); + } + + const entryAbs = await resolveContainedEntry( + pluginPkgDir, + entryRel, + "interchange.tools", + ); + + const importUrl = + args.importCacheKey === undefined + ? pathToFileURL(entryAbs).href + : `${pathToFileURL(entryAbs).href}?importCacheKey=${encodeURIComponent(args.importCacheKey)}`; + + let mod: unknown; + try { + mod = await args.importModule(importUrl); + } catch (cause) { + throw new Error( + `failed to import interchange.tools entry ${JSON.stringify(entryRel)} for plugin package ${JSON.stringify(args.pluginName)} at ${pluginPkgDir}`, + { cause }, + ); + } + if (mod === null || typeof mod !== "object") { + throw new Error( + `interchange.tools entry ${JSON.stringify(entryRel)} for plugin package ${JSON.stringify(args.pluginName)} at ${pluginPkgDir} did not evaluate to a module object`, + ); + } + + const factories = Object.values(mod).filter(isAnnotatedPluginFactory); + if (factories.length === 0) { + throw new Error( + `interchange.tools entry ${JSON.stringify(entryRel)} for plugin package ${JSON.stringify(args.pluginName)} at ${pluginPkgDir} exported no AnnotatedPluginFactory values; a package named in an agent's plugins list must export a definePlugin factory`, + ); + } + logger.debug`loaded ${String(factories.length)} plugin factory(ies) from ${args.pluginName} at ${pluginPkgDir}`; + return factories; +} + async function readPackageJSON(packageDir: string): Promise { const pkgJsonPath = path.join(packageDir, "package.json"); let raw: string; diff --git a/vendor/intx/workflow/VENDORED-FROM b/vendor/intx/workflow/VENDORED-FROM index 838cb3b84..d0005a113 100644 --- a/vendor/intx/workflow/VENDORED-FROM +++ b/vendor/intx/workflow/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/workflow) -Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec +Commit: 4ed8baf4789d4b51fcff7f03e1f6146ab45b9f2b License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. CL-6326: `onTrigger` gains an `onBodyFailure?: "end" | "continue"` policy field (definition/primitives.ts); `runtime/run.ts`'s steady-state drive loop and `planOnTriggerResume` read it live to let a `"continue"`-policy section re-arm past a `failed` body occurrence instead of ending the whole run (`cancelled` is unaffected, always terminal-is-final). See VENDORED.md and docs/revendor-inventory.md. diff --git a/vendor/intx/workflow/src/declared-plugins.ts b/vendor/intx/workflow/src/declared-plugins.ts new file mode 100644 index 000000000..652af3241 --- /dev/null +++ b/vendor/intx/workflow/src/declared-plugins.ts @@ -0,0 +1,79 @@ +// Enumerate the plugin-package names a workflow's agents declare. +// +// A plugin package contributes no agent-visible tool factory: its +// `definePlugin` factory reaches an agent only through `env.plugins`, so +// the only record of which plugin packages a workflow uses is the +// per-agent `AgentDefinition.plugins` list. The deploy-time probe needs +// that union up front -- before the capability walk runs -- so it can load +// each declared plugin's static tool `definitions` from the materialized +// closure and surface the plugin-contributed tool grants into the walk. +// +// The traversal mirrors the capability walk's agent extraction (step and +// map carry an agent; loop, onTrigger, and inline childWorkflow bodies are +// nested definitions whose own agents are collected recursively). A +// by-`ref` body is an independent asset with its own approval surface and +// is not descended into here, matching the walk. + +import type { Primitive, WorkflowDefinition } from "./definition/index"; + +/** + * Collect the deduplicated union of every plugin-package name declared by + * any agent reachable in the definition, including agents nested in loop, + * inline onTrigger, and inline childWorkflow bodies. Order is deterministic + * (first-seen) so a caller building a load plan is reproducible. + */ +export function collectDeclaredPluginNames( + definition: WorkflowDefinition, +): string[] { + const names = new Set(); + collectFromDefinition(definition, names); + return [...names]; +} + +function collectFromDefinition( + definition: WorkflowDefinition, + names: Set, +): void { + for (const stepId of definition.stepOrder) { + const primitive = definition.steps[stepId]; + if (primitive === undefined) continue; + collectFromPrimitive(primitive, names); + } +} + +function collectFromPrimitive(primitive: Primitive, names: Set): void { + switch (primitive.kind) { + case "step": + addAgentPlugins(primitive.agent.plugins, names); + return; + case "map": + addAgentPlugins(primitive.step.agent.plugins, names); + return; + case "loop": + collectFromDefinition(primitive.body, names); + return; + case "onTrigger": + if ("inline" in primitive.body) { + collectFromDefinition(primitive.body.inline, names); + } + return; + case "childWorkflow": + if ("inline" in primitive.definition) { + collectFromDefinition(primitive.definition.inline, names); + } + return; + default: + // Non-agent, non-nesting primitives (gate, awaitSignal, sleep, + // escalation, action) declare no plugins. + return; + } +} + +function addAgentPlugins( + plugins: readonly string[] | undefined, + names: Set, +): void { + for (const name of plugins ?? []) { + names.add(name); + } +} diff --git a/vendor/intx/workflow/src/definition/index.ts b/vendor/intx/workflow/src/definition/index.ts index 6c8c5b432..02f4535b5 100644 --- a/vendor/intx/workflow/src/definition/index.ts +++ b/vendor/intx/workflow/src/definition/index.ts @@ -28,6 +28,7 @@ export { type ActionPrimitive, type AwaitSignalOpts, type AwaitSignalPrimitive, + type ChildWorkflowBody, type ChildWorkflowOpts, type ChildWorkflowPrimitive, type DrainBehavior, @@ -64,7 +65,6 @@ export { defineWorkflow, hashDefinition, STEP_ID_PATTERN, - type SidecarPlacementRequirement, type SingularWorkflowConfig, type WorkflowConfig, type WorkflowDefinition, diff --git a/vendor/intx/workflow/src/definition/primitives.ts b/vendor/intx/workflow/src/definition/primitives.ts index 948e697bc..ca87381e1 100644 --- a/vendor/intx/workflow/src/definition/primitives.ts +++ b/vendor/intx/workflow/src/definition/primitives.ts @@ -134,13 +134,35 @@ export interface SleepPrimitive extends PrimitiveBase { drainBehavior?: DrainBehavior; } +/** + * Spawns an OWNED child workflow. The child is an import: its full + * `WorkflowDefinition` is embedded inline (mirroring an inline `onTrigger` + * body and a `loop` body), so the child's grants fold into the parent's + * approved surface and no separately-deployed asset is read. `input` + * selects the child run's launch payload. + */ export interface ChildWorkflowPrimitive extends PrimitiveBase { kind: "childWorkflow"; - definitionRef: string; + definition: ChildWorkflowBody; input?: Selector; drainBehavior?: DrainBehavior; } +/** + * The embedded child definition, in one of its two lifecycle forms. + * Authored inline (the constructor wraps the author's `WorkflowDefinition` + * as `{ inline }`); the deploy step materializes that inline child into its + * own workflow asset and rewrites it to `{ ref }`, so the runtime spawns the + * child as a run resolved by ref. The `{ ref }` arm is only the internal + * extracted-child handle -- never an author-facing separate-deployment id. + * Exactly one arm is present -- a discriminated union, not two optionals, so + * neither "both" nor "neither" is representable and consumers switch + * exhaustively. Mirrors `OnTriggerBody`. + */ +export type ChildWorkflowBody = + | { inline: WorkflowDefinition } + | { ref: string }; + export interface EscalationPrimitive extends PrimitiveBase { kind: "escalation"; to: string; @@ -297,11 +319,10 @@ export interface StepOpts { * `"unbounded"` is the long-lived interactive agent that never self-completes. * * Validates the declared value on every read: `step()` rejects a bad value at - * authoring time, but a definition hydrated from `workflow.json` never passes - * through `step()` (the envelope schema checks structure only), so this read - * point is where a persisted `triggers: 0`/`-1`/`1.5` fails loud instead of - * silently coercing (a non-positive budget would behave as `1`; a fractional - * one would service an extra trigger). + * authoring time, but this read point re-checks rather than trust that every + * definition reached it through `step()`, so a `triggers: 0`/`-1`/`1.5` fails + * loud instead of silently coercing (a non-positive budget would behave as + * `1`; a fractional one would service an extra trigger). */ export function stepTriggerBudget(step: StepPrimitive): number | "unbounded" { if (step.triggers === undefined) return 1; @@ -331,8 +352,8 @@ function validateTriggers(triggers: number | "unbounded"): void { * would re-service the launch trigger and never re-service the * already-consumed one -- a wrong conversation reported as success. * `step()` enforces this at authoring time; the runtime re-applies it at - * `runStep` entry because a definition hydrated from `workflow.json` never - * passes through `step()`. + * `runStep` entry as a defensive re-check, rather than trust that every + * definition reached it through `step()`. */ export function validateRetryTriggerCombination(step: StepPrimitive): void { const retry = step.retry; @@ -477,7 +498,7 @@ export function sleep(opts: SleepOpts): SleepPrimitive { } export interface ChildWorkflowOpts { - definitionRef: string; + definition: WorkflowDefinition; input?: Selector; drainBehavior?: DrainBehavior; after?: readonly string[]; @@ -488,7 +509,8 @@ export function childWorkflow(opts: ChildWorkflowOpts): ChildWorkflowPrimitive { return { kind: "childWorkflow", id: "", - definitionRef: opts.definitionRef, + // Authored inline; the deploy step rewrites this to `{ ref }`. + definition: { inline: opts.definition }, drainBehavior, ...(opts.input !== undefined ? { input: opts.input } : {}), ...(opts.after !== undefined ? { after: opts.after } : {}), diff --git a/vendor/intx/workflow/src/definition/shorthand.ts b/vendor/intx/workflow/src/definition/shorthand.ts index cbaf38367..4c2ffdf60 100644 --- a/vendor/intx/workflow/src/definition/shorthand.ts +++ b/vendor/intx/workflow/src/definition/shorthand.ts @@ -23,11 +23,7 @@ // compare definitions) see no spurious differences. import type { BaseEnv } from "@intx/agent"; -import type { - CredentialBinding, - GrantRequirement, - SidecarPlacementRequirement, -} from "@intx/types"; +import type { CredentialBinding, GrantRequirement } from "@intx/types"; import { step } from "./primitives"; import type { Primitive } from "./primitives"; @@ -40,7 +36,6 @@ export interface SingularShorthand { trigger?: Trigger; triggers?: readonly Trigger[]; state?: { schema?: StateSchema }; - sidecarPlacement?: SidecarPlacementRequirement; grantRequirements?: readonly GrantRequirement[]; credentialBindings?: readonly CredentialBinding[]; } @@ -51,7 +46,6 @@ export interface PluralShape { triggers?: readonly Trigger[]; steps: Record; state?: { schema?: StateSchema }; - sidecarPlacement?: SidecarPlacementRequirement; grantRequirements?: readonly GrantRequirement[]; credentialBindings?: readonly CredentialBinding[]; } @@ -65,9 +59,6 @@ export function normalizeSingularShorthand( ...(config.triggers !== undefined ? { triggers: config.triggers } : {}), steps: { default: step({ agent: config.agent }) }, ...(config.state !== undefined ? { state: config.state } : {}), - ...(config.sidecarPlacement !== undefined - ? { sidecarPlacement: config.sidecarPlacement } - : {}), ...(config.grantRequirements !== undefined ? { grantRequirements: config.grantRequirements } : {}), diff --git a/vendor/intx/workflow/src/definition/workflow.ts b/vendor/intx/workflow/src/definition/workflow.ts index 95d4f04cd..c46197ce9 100644 --- a/vendor/intx/workflow/src/definition/workflow.ts +++ b/vendor/intx/workflow/src/definition/workflow.ts @@ -9,11 +9,7 @@ import { canonicalizeForHash } from "@intx/agent"; import type { AgentDefinition, BaseEnv } from "@intx/agent"; -import { - SidecarPlacementRequirement, - type CredentialBinding, - type GrantRequirement, -} from "@intx/types"; +import type { CredentialBinding, GrantRequirement } from "@intx/types"; import { normalizeSingularShorthand } from "./shorthand"; import { @@ -25,8 +21,6 @@ import { } from "./primitives"; import type { Trigger } from "./triggers"; -export type { SidecarPlacementRequirement } from "@intx/types"; - export interface WorkflowDefinition { id: string; triggers: readonly Trigger[]; @@ -38,11 +32,6 @@ export interface WorkflowDefinition { */ stepOrder: readonly string[]; state?: { schema?: StateSchema }; - /** - * Requires an exclusive sidecar for this workflow. This is a placement - * guarantee, not a process, filesystem, network, or host boundary. - */ - sidecarPlacement?: SidecarPlacementRequirement; /** * The grant requirements a run resolves against the creator's and * invoker's authority at trigger time. Each entry declares a resource, @@ -68,7 +57,6 @@ export interface WorkflowConfig { triggers?: readonly Trigger[]; steps: Record; state?: { schema?: StateSchema }; - sidecarPlacement?: SidecarPlacementRequirement; grantRequirements?: readonly GrantRequirement[]; credentialBindings?: readonly CredentialBinding[]; } @@ -79,7 +67,6 @@ export interface SingularWorkflowConfig { trigger?: Trigger; triggers?: readonly Trigger[]; state?: { schema?: StateSchema }; - sidecarPlacement?: SidecarPlacementRequirement; grantRequirements?: readonly GrantRequirement[]; credentialBindings?: readonly CredentialBinding[]; } @@ -157,12 +144,7 @@ function normalize(config: WorkflowConfig): WorkflowDefinition { stepOrder.push(stepId); } - validateAfterRefs(steps); - // Runs after validateAfterRefs so every after/then/else endpoint is - // already known to name a real step; this pass only rejects cycles. - validateAcyclic(steps); - validateLoopBody(steps); - validateOnTriggerBody(steps); + validateSteps(steps); // An onTrigger section's `on` is the first-class binding between a // trigger and the section it drives, so each section contributes its @@ -175,9 +157,6 @@ function normalize(config: WorkflowConfig): WorkflowDefinition { steps, stepOrder, ...(config.state !== undefined ? { state: config.state } : {}), - ...(config.sidecarPlacement !== undefined - ? { sidecarPlacement: normalizeSidecarPlacement(config.sidecarPlacement) } - : {}), ...(config.grantRequirements !== undefined ? { grantRequirements: config.grantRequirements } : {}), @@ -188,16 +167,6 @@ function normalize(config: WorkflowConfig): WorkflowDefinition { return definition; } -function normalizeSidecarPlacement( - placement: SidecarPlacementRequirement, -): SidecarPlacementRequirement { - const validated = SidecarPlacementRequirement.assert(placement); - return { - sharing: "exclusive", - reuse: validated.reuse ?? "never", - }; -} - function resolveTriggers( config: WorkflowConfig, sectionTriggers: readonly Trigger[], @@ -306,6 +275,24 @@ function applyDefaultInputStep( return primitive; } +/** + * Run every step-record validation pass in the order their dependencies + * require. `validateChildWorkflowBody` re-enters this same suite on an + * inline child body, so factoring the passes here keeps the top-level and + * embedded-child validations identical -- a malformed child (dangling + * `after`, cycle, forbidden loop body, nested section) fails at the parent's + * authoring time exactly as it would at its own. + */ +function validateSteps(steps: Record): void { + validateAfterRefs(steps); + // Runs after validateAfterRefs so every after/then/else endpoint is + // already known to name a real step; this pass only rejects cycles. + validateAcyclic(steps); + validateLoopBody(steps); + validateOnTriggerBody(steps); + validateChildWorkflowBody(steps); +} + function validateAfterRefs(steps: Record): void { const ids = new Set(Object.keys(steps)); for (const [stepId, primitive] of Object.entries(steps)) { @@ -467,6 +454,25 @@ function validateOnTriggerBody(steps: Record): void { } } +/** + * Recursively validate every inline `childWorkflow` body. A child is an + * owned import embedded inline, so its full definition must be as valid as a + * top-level one; this pass re-enters `validateSteps` on the inline body so a + * malformed embedded child is rejected at the parent's authoring time. A + * deployed `{ ref }` body was validated at its own deploy and is skipped. + * + * A separate pass from `validateAcyclic`, which does not recurse into the + * child's own (already-normalized) `WorkflowDefinition`. The recursion is + * bounded by the authored nesting depth. + */ +function validateChildWorkflowBody(steps: Record): void { + for (const primitive of Object.values(steps)) { + if (primitive.kind !== "childWorkflow") continue; + if (!("inline" in primitive.definition)) continue; + validateSteps(primitive.definition.inline.steps); + } +} + /** * Reject any dependency cycle in the definition. The graph is the union * of two edge kinds: an `after: [X]` on step S contributes X -> S (X @@ -579,9 +585,6 @@ function projectForHash(definition: WorkflowDefinition): unknown { id: definition.id, triggers: definition.triggers, ...(definition.state !== undefined ? { state: definition.state } : {}), - ...(definition.sidecarPlacement !== undefined - ? { sidecarPlacement: definition.sidecarPlacement } - : {}), ...(definition.grantRequirements !== undefined ? { grantRequirements: definition.grantRequirements } : {}), diff --git a/vendor/intx/workflow/src/index.ts b/vendor/intx/workflow/src/index.ts index 197961e86..0968d47e0 100644 --- a/vendor/intx/workflow/src/index.ts +++ b/vendor/intx/workflow/src/index.ts @@ -5,16 +5,22 @@ export type { export * from "./state-machine/index"; export * from "./definition/index"; +export { collectDeclaredPluginNames } from "./declared-plugins"; export { onTriggerBodyRef, rewriteInlineOnTriggerBodies, + rewriteInlineChildWorkflowBodies, type ExtractedOnTriggerBody, type OnTriggerBodyRewrite, + type ExtractedChildWorkflowBody, + type ChildWorkflowBodyRewrite, } from "./ontrigger-bodies"; export { projectLiveToInert, computeLiveDefinitionHash, type InertAgent, + type InertChildWorkflow, + type InertChildWorkflowBody, type InertLoop, type InertMap, type InertModelSource, diff --git a/vendor/intx/workflow/src/live-inert-projector.ts b/vendor/intx/workflow/src/live-inert-projector.ts index 30b56fc78..461d6e1e3 100644 --- a/vendor/intx/workflow/src/live-inert-projector.ts +++ b/vendor/intx/workflow/src/live-inert-projector.ts @@ -96,6 +96,13 @@ export interface InertAgent { readonly director?: DirectorRef; readonly capabilities: readonly string[]; readonly toolFactories: readonly InertToolFactory[]; + /** + * Plugin-package names the agent declares (`AgentDefinition.plugins`). + * Part of the hashed grant surface: a plugin package contributes tool + * grants (via its static `definitions`) that the operator approves, so a + * tampered plugin set must move the wire hash and fail re-verify. + */ + readonly plugins?: readonly string[]; readonly modelSources: readonly InertModelSource[]; readonly tags?: Readonly>; readonly toolPackagePins?: readonly ToolPackagePin[]; @@ -150,11 +157,30 @@ export interface InertOnTrigger { readonly after?: readonly string[]; } -// The gate/awaitSignal/sleep/childWorkflow/escalation/action primitives -// carry no functions or arktype `Type` values -- they are already pure -// plain data -- so their inert form is structurally identical to the live -// primitive. They are reconstructed field by field below rather than -// aliased so the projection is a self-contained tree. +/** Plain-data mirror of `ChildWorkflowBody`: an inline child definition + * projects recursively to an `InertWorkflowDefinition`, and the internal + * extracted-body `{ ref }` handle passes through. Mirrors + * {@link InertOnTriggerBody}. */ +export type InertChildWorkflowBody = + | { readonly inline: InertWorkflowDefinition } + | { readonly ref: string }; + +export interface InertChildWorkflow { + readonly kind: "childWorkflow"; + readonly id: string; + readonly definition: InertChildWorkflowBody; + readonly input?: Selector; + readonly drainBehavior?: DrainBehavior; + readonly after?: readonly string[]; +} + +// The gate/awaitSignal/sleep/escalation/action primitives carry no functions +// or arktype `Type` values -- they are already pure plain data -- so their +// inert form is structurally identical to the live primitive. They are +// reconstructed field by field below rather than aliased so the projection is +// a self-contained tree. `childWorkflow` carries an inline child definition +// (a live `WorkflowDefinition`), so it projects recursively like `onTrigger` +// rather than aliasing the live primitive. export type InertStep = | InertStepStep | InertMap @@ -164,7 +190,7 @@ export type InertStep = | GatePrimitive | AwaitSignalPrimitive | SleepPrimitive - | ChildWorkflowPrimitive + | InertChildWorkflow | EscalationPrimitive; export interface InertWorkflowDefinition { @@ -421,11 +447,18 @@ function projectSleep(primitive: SleepPrimitive): SleepPrimitive { function projectChildWorkflow( primitive: ChildWorkflowPrimitive, -): ChildWorkflowPrimitive { +): InertChildWorkflow { + // Mirror `projectOnTrigger`: an inline child definition projects recursively + // (its grant surface must survive the child->hub boundary just like the + // parent's own steps), the internal `{ ref }` handle passes through. + const definition: InertChildWorkflowBody = + "inline" in primitive.definition + ? { inline: projectDefinition(primitive.definition.inline) } + : { ref: primitive.definition.ref }; return { kind: "childWorkflow", id: primitive.id, - definitionRef: primitive.definitionRef, + definition, ...(primitive.input !== undefined ? { input: primitive.input } : {}), ...(primitive.drainBehavior !== undefined ? { drainBehavior: primitive.drainBehavior } @@ -465,6 +498,7 @@ function projectAgent(agent: AgentDefinition): InertAgent { ...(agent.director !== undefined ? { director: agent.director } : {}), capabilities: [...agent.capabilities], toolFactories: agent.toolFactories.map(projectToolFactory), + ...(agent.plugins !== undefined ? { plugins: [...agent.plugins] } : {}), modelSources: agent.inference.sources.map(projectModelSource), ...(agent.tags !== undefined ? { tags: { ...agent.tags } } : {}), ...(agent.toolPackagePins !== undefined diff --git a/vendor/intx/workflow/src/ontrigger-bodies.ts b/vendor/intx/workflow/src/ontrigger-bodies.ts index 5ff622805..3a63aa658 100644 --- a/vendor/intx/workflow/src/ontrigger-bodies.ts +++ b/vendor/intx/workflow/src/ontrigger-bodies.ts @@ -10,9 +10,9 @@ // It carries NO deploy machinery (no capability walk, no source-pinning, no hub // write), so the callers that run it over a RE-EVALUATED closure -- the // source-ref run child and the sidecar deploy router, which have neither a -// director registry nor the operator approval set -- share the exact rewrite -// the live-authored orchestrator (`extractOnTriggerBodies`) layers its -// walk/pin/write onto. +// director registry nor the operator approval set -- share one exact structural +// rewrite, kept separate from the capability walk and source-pinning the deploy +// layers on elsewhere. import type { Primitive, WorkflowDefinition } from "./definition/index"; @@ -67,3 +67,51 @@ export function rewriteInlineOnTriggerBodies( } return { workflow: { ...workflow, steps }, bodies }; } + +export interface ExtractedChildWorkflowBody { + /** The body's ref -- `__` -- and the id of `definition`. */ + readonly ref: string; + /** The inline child lifted to a standalone definition (its id is `ref`). */ + readonly definition: WorkflowDefinition; +} + +export interface ChildWorkflowBodyRewrite { + /** The workflow with every inline childWorkflow definition replaced by a `{ ref }`. */ + readonly workflow: WorkflowDefinition; + /** The extracted child definitions, one per rewritten inline child. */ + readonly bodies: readonly ExtractedChildWorkflowBody[]; +} + +/** + * Replace each inline `childWorkflow` definition with a `{ ref }` and return the + * extracted child definitions (each child's id is its ref). The childWorkflow + * counterpart to {@link rewriteInlineOnTriggerBodies}: pure and + * side-effect-free (no walk, no pin, no write), and it mints refs through the + * same {@link onTriggerBodyRef} `__` scheme -- a step + * carries at most one of an onTrigger section or a childWorkflow, so the two + * rewriters never collide on a ref. The runtime dispatches a `{ ref }` child by + * resolving the extracted definition from an in-memory map keyed by the ref, so + * the host lifts these bodies at child boot and never reads a separate on-disk + * asset. When the workflow has no inline childWorkflow the original object is + * returned unchanged with an empty `bodies`. + */ +export function rewriteInlineChildWorkflowBodies( + workflow: WorkflowDefinition, +): ChildWorkflowBodyRewrite { + const steps: Record = { ...workflow.steps }; + const bodies: ExtractedChildWorkflowBody[] = []; + for (const [stepId, primitive] of Object.entries(steps)) { + if (primitive.kind !== "childWorkflow") continue; + if (!("inline" in primitive.definition)) continue; + const ref = onTriggerBodyRef(workflow.id, stepId); + bodies.push({ + ref, + definition: { ...primitive.definition.inline, id: ref }, + }); + steps[stepId] = { ...primitive, definition: { ref } }; + } + if (bodies.length === 0) { + return { workflow, bodies: [] }; + } + return { workflow: { ...workflow, steps }, bodies }; +} diff --git a/vendor/intx/workflow/src/runlocal/run-local.ts b/vendor/intx/workflow/src/runlocal/run-local.ts index ee3e5ce0c..d141038c0 100644 --- a/vendor/intx/workflow/src/runlocal/run-local.ts +++ b/vendor/intx/workflow/src/runlocal/run-local.ts @@ -18,6 +18,7 @@ import type { WorkflowAuthorizeFn, } from "../authorize-context"; import type { WorkflowDefinition } from "../definition/index"; +import { rewriteInlineChildWorkflowBodies } from "../ontrigger-bodies"; import { runtimeRun, type RuntimeRunOptions } from "../runtime/run"; import { createNoopDrainController } from "../runtime/drain"; import { createEffectContext } from "../runtime/effect-context"; @@ -67,8 +68,6 @@ export interface RunLocalOptions extends RuntimeRunOptions { * from `@intx/agent` (the same surface production uses). */ directors?: DirectorRegistry; - /** Resolve a `definitionRef` for `childWorkflow` spawns. */ - childResolver?: (ref: string) => WorkflowDefinition; /** Inject a deterministic clock for tests. */ clock?: () => Date; /** Inject a deterministic id generator for tests. */ @@ -102,6 +101,16 @@ export function runLocal( const clock = options.clock ?? defaultClock; const newId = options.newId ?? defaultNewId; + // A `childWorkflow` primitive carries its child definition inline. Lift each + // inline child to a standalone definition keyed by an internal ref and run + // the rewritten workflow whose children are `{ ref }` -- the shape the + // runtime dispatches. The in-memory spawn callback resolves each ref from the + // lifted map, so no separate child resolver is needed. A recursive child that + // embeds its own child is rewritten again when its run reaches this function. + const { workflow: rewritten, bodies } = + rewriteInlineChildWorkflowBodies(definition); + const childBodies = new Map(bodies.map((b) => [b.ref, b.definition])); + const repoStore = createInMemoryRepoStore(); const env: WorkflowRuntimeEnv = { repoStore, @@ -113,10 +122,10 @@ export function runLocal( invokeStep, invokeAction, effects, - spawnChild: createNoopSpawnChild(options.childResolver), + spawnChild: createInMemorySpawnChild(childBodies), clock, newId, - drain: createNoopDrainController(definition), + drain: createNoopDrainController(rewritten), }; // Wired after construction because the loop-iteration runner closes // over the env it belongs to, so that each iteration's child run @@ -126,7 +135,7 @@ export function runLocal( env.loopFns = options.loopFns; } - return runtimeRun(definition, env, extractRuntimeOptions(options)); + return runtimeRun(rewritten, env, extractRuntimeOptions(options)); } function extractRuntimeOptions(options: RunLocalOptions): RuntimeRunOptions { @@ -216,20 +225,20 @@ function createInMemoryEffectLedger(): EffectLedger { }; } -function createNoopSpawnChild( - resolver: ((ref: string) => WorkflowDefinition) | undefined, +function createInMemorySpawnChild( + bodies: ReadonlyMap, ): SpawnChildWorkflow { return async ({ definitionRef, childRunId, input, signal }) => { - if (!resolver) { - // The author wired a `childWorkflow` primitive into their - // workflow but did not supply a resolver. Failing loudly is the - // right call -- a silent stub-completion would let workflows - // pass tests against a child that was never executed. + const resolved = bodies.get(definitionRef); + if (resolved === undefined) { + // The runtime dispatched a childWorkflow ref with no lifted definition. + // Every inline child is lifted into `bodies` before the run starts, so a + // miss is a rewrite/dispatch bug -- fail loud rather than silently + // completing against a child that was never executed. throw new Error( - `childWorkflow ${definitionRef} requires a childResolver; pass one to runLocal({ childResolver })`, + `childWorkflow ${definitionRef} has no lifted definition; the inline child should have been extracted before the run started`, ); } - const resolved = resolver(definitionRef); // Recursively invoke runLocal for the resolved child against the // parent-allocated childRunId so the parent's audit log and the // child's own log agree on identity. diff --git a/vendor/intx/workflow/src/runtime/env.ts b/vendor/intx/workflow/src/runtime/env.ts index 5ffb60ab3..8b8f73424 100644 --- a/vendor/intx/workflow/src/runtime/env.ts +++ b/vendor/intx/workflow/src/runtime/env.ts @@ -299,11 +299,12 @@ export interface BlobSubstrate { * Spawn callback for `childWorkflow`. The parent runtime allocates the * `childRunId` and commits `ChildSpawned` *before* invoking the * callback so the parent's audit log records the spawn before any - * work begins on the child side. The callback resolves - * `definitionRef` to a concrete `WorkflowDefinition` using whatever - * lookup the runtime supplies (a `childResolver` function in - * `runLocal`, a deploy-time resolver in production), constructs the - * child run against the supplied id, and returns the terminal status. + * work begins on the child side. The callback resolves `definitionRef` + * -- the internal ref the deploy step assigned when it lifted the authored + * inline child -- to a concrete `WorkflowDefinition` using whatever in-memory + * lookup the runtime supplies (a lifted-body map in `runLocal`, the parent's + * re-evaluated closure map in production), constructs the child run against + * the supplied id, and returns the terminal status. * * The runtime body does not carry a definition lookup of its own. */ diff --git a/vendor/intx/workflow/src/runtime/run.ts b/vendor/intx/workflow/src/runtime/run.ts index 7a247ea9b..81931d51e 100644 --- a/vendor/intx/workflow/src/runtime/run.ts +++ b/vendor/intx/workflow/src/runtime/run.ts @@ -1132,9 +1132,9 @@ async function runStep( selectorCtx: SelectorContext, abort: AbortSignal, ): Promise { - // A definition hydrated from workflow.json never passed through - // `step()`, so its retry/budget cross-field guard is re-applied here, - // at the runtime's single read point for both fields. + // Re-apply the retry/budget cross-field guard here as a defensive + // re-check, at the runtime's single read point for both fields, rather + // than trust that every definition reached it through `step()`. validateRetryTriggerCombination(step); let attempt = 1; const maxAttempts = step.retry?.maxAttempts ?? 1; @@ -3935,6 +3935,18 @@ async function runChildWorkflow( abort: AbortSignal, ): Promise { void parent; + // Post-extraction the child definition is the internal `{ ref }` handle: the + // deploy step lifts the authored inline child to a standalone definition and + // the host resolves it from an in-memory closure map keyed by this ref. An + // inline child reaching the runtime is a deploy-step bug -- the same + // contract `runOnTrigger` enforces on its body. + if (!("ref" in primitive.definition)) { + throw new Error( + `childWorkflow ${primitive.id} reached the runtime with an inline ` + + `definition; the deploy step must lift the child to an internal ref`, + ); + } + const definitionRef = primitive.definition.ref; const childInput = primitive.input !== undefined ? evaluate(primitive.input, selectorCtx) @@ -3949,7 +3961,7 @@ async function runChildWorkflow( // ChildCancelRequested against. const childRunId = env.newId("run"); await emitStepStartedWithValue(env, parentRunId, primitive.id, { - definitionRef: primitive.definitionRef, + definitionRef, input: childInput, ...(primitive.drainBehavior !== undefined ? { drainBehavior: primitive.drainBehavior } @@ -3962,7 +3974,7 @@ async function runChildWorkflow( at: env.clock().toISOString(), stepId: primitive.id, childRunId, - childDefinitionRef: primitive.definitionRef, + childDefinitionRef: definitionRef, }; state = await commit(env, parentRunId, spawned); // Segment boundary: the parent is about to hand off to and AWAIT a @@ -3988,7 +4000,7 @@ async function runChildWorkflow( let child: { terminalStatus: "completed" | "failed" | "cancelled" }; try { child = await env.spawnChild({ - definitionRef: primitive.definitionRef, + definitionRef, childRunId, input: childInput, parentRunId, @@ -4026,7 +4038,7 @@ async function runChildWorkflow( // and silent-if-forgotten. runPrimitiveSafe's catch lands the // StepFailed when the throw bubbles out of this runner. throw new ChildWorkflowFailedError( - `child run ${childRunId} (${primitive.definitionRef}) ended ${child.terminalStatus}`, + `child run ${childRunId} (${definitionRef}) ended ${child.terminalStatus}`, child.terminalStatus, ); }