From e7146fb7968aec33539cd94cdfde07fb67dcb2f3 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 28 Aug 2026 08:54:59 -0700 Subject: [PATCH] Vendoring ledger: state the a8bc06ae pin as done; retire the inventory items upstream absorbed Every vendor/intx row and the sidecar fork now carry the 2026-10-26 kill date, VENDORED.md describes the current pin in one paragraph instead of the v0.3.0 release notes plus a re-pin-in-progress note, and docs/revendor-inventory.md marks CL-6164 (empty-mail drop) and CL-6326 (onBodyFailure) retired with the upstream and workbench commits that closed them. The sidecar's signing-keypair.ts is byte-identical to upstream again and atomic-write.ts differs only by the repo-wide logger namespace; the ledger row says so. --- VENDORED.md | 78 ++++++++++++----------------- apps/sidecar/src/atomic-write.ts | 50 ++++++++++++------ apps/sidecar/src/signing-keypair.ts | 44 +++++++--------- docs/revendor-inventory.md | 21 +++++++- scripts/checks/kill-dates.txt | 10 ++-- 5 files changed, 110 insertions(+), 93 deletions(-) diff --git a/VENDORED.md b/VENDORED.md index 214de92c4..d88930f66 100644 --- a/VENDORED.md +++ b/VENDORED.md @@ -22,54 +22,38 @@ never a convenience. ## Ledger -| 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`: five modules byte-identical (`default-harness.ts`, `source-asset-delivery.ts`, `workflow-closure-apply.ts`, `workflow-probe-handler.ts`, `workflow-run-pack-restore.ts`), four near-verbatim, the rest (`index.ts`, `config.ts`, `tool-materialization.ts`, `step-agent-tools.ts`, `workflow-host-wiring/`, `workflow-substrate-factory/`, …) 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) @ `a8bc06ae` (origin/main, 2026-08-27) | 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-10-26 | `check:killdates` | -| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the operator-configurable doom-loop threshold (`afd0c82b`, `c421c092`) the re-vendored `workflow-host` configures; no local delta; retired by the next `@intx/agent` publish | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 covers the base package but not the `wire_projection` column/loader delta (CL-6324) or the `workflow_definition.origin` column separating a definition from the per-run record of one folded run's deploy (CL-6452), shipped as migrations `0086`/`0087` behind upstream's `0085_add_approval_run_idx`, plus `0088` rewriting the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` in stored wire projections; retired when upstream absorbs the deltas | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the connector reply drain (`driveConnectorReplies`, `ConnectorReplyDrain`, `AgentEventStream`; `11590e66`) the sidecar's warm mail loop drives; no local delta; retired by the next `@intx/harness` publish | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the `agentDir` path export (`927556de`) the sidecar's deploy-tree lookup uses, and its own `@intx/mail-memory`/`@intx/harness` pins must resolve the vendored copies; no local delta; retired by the next publish | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 covers the base package but not the null-principal `resolveApproval` for policy-resolved decisions (CL-6345) or the bearer-authenticated workflow-deploy mirror (`middleware/workflow-run-deploy-auth.ts`, CL-workflow-deploy-bearer); retired when upstream absorbs the deltas | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 covers the base package but not the pack-acceptance fixes (`ownsWorkflowRunRepo`, `anchorAddressForPackSource`, `decideTerminalRunFlip`), the adopted deploy front + `sourceRef` (CL-6324), the wire-projection writer (CL-6324), malformed tool-call-name sanitization (CL-6478) or the sealed-run terminal-status backfill (CL-6595); retired when upstream absorbs the deltas | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates doom-loop detection (`8da4c827`, `afd0c82b`, `c421c092`); one local delta: `providers/google-genai-files.ts` builds its upload body as `new Uint8Array(bytes)` because TS 6's lib.dom `BodyInit` rejects `Uint8Array` (upstream compiles ESNext-only under TS 5.9); retired by the next publish | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the `@intx/mailbox` extraction (`af03bb90`), on-demand body reads (`54f7c239`) and `expunge` returning the swept uids (`bcabb1f8`) that the re-vendored `workflow-host` binds against; no local delta; retired by the next publish | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/mailbox` | `@intx/mailbox` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | Never published: a new package at the target pin (`af03bb90`) that `workflow-host`'s substrate mailbox store and supervisor-backed transport import; no local delta; retired by its first publish | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the non-RFC message-id guard `isMessageId` (`d97e1832`), the full `References` chain (`65c6fe70`) and the lossless `decodeMail` decoder (`3b6d06b2`) that `mailbox`/`mail-memory` at the same pin import; no local delta; retired by the next publish | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the type surface the re-vendored trees compile against: `expunge` returning `expungedUids` (`bcabb1f8`), plain-string `PackRejectReason` (`7b42f405`), the run authorization/approvals REST types (`71ad6c08`), the decoded-mail `Mail`/`MailPartReader` model (`3b6d06b2`) and the `interchange.actions`/`loops` package-json refs (`3bd5b837`, `1ea2f39b`); no local delta; retired by the next publish | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | No local delta: npm 0.3.0 predates the `onBodyFailure: "tolerate"` section policy (`b977ade6`) that `@corbits/agent-runtime` authors and the action/loop primitives (`3bd5b837`, `1ea2f39b`) the re-vendored `workflow-host` runs; retired by the next `@intx/workflow` publish | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | No local delta: npm 0.3.0 predates `inertLoopBody` and the loop-body source pin (`1ea2f39b`) that the re-vendored `hub-sessions` imports; retired by the next `@intx/workflow-deploy` publish | sawyer | 2026-10-26 | `check:killdates` | -| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 covers the base package but not the body-spawn authorize/credential/mail-part-reader threading and grants head-collapse (CL-6448) that let the fork run tool-bearing onTrigger bodies; retired when upstream absorbs the delta | sawyer | 2026-10-26 | `check:killdates` | +| 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`: six modules byte-identical (`default-harness.ts`, `signing-keypair.ts`, `source-asset-delivery.ts`, `workflow-closure-apply.ts`, `workflow-probe-handler.ts`, `workflow-run-pack-restore.ts`), three near-verbatim (`atomic-write.ts` differs only by the repo-wide logger namespace), the rest (`index.ts`, `config.ts`, `tool-materialization.ts`, `step-agent-tools.ts`, `workflow-host-wiring/`, `workflow-substrate-factory/`, …) 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) @ `a8bc06ae` (origin/main, 2026-08-27) | 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-10-26 | `check:killdates` | +| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the operator-configurable doom-loop threshold (`afd0c82b`, `c421c092`) the re-vendored `workflow-host` configures; no local delta; retired by the next `@intx/agent` publish | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 covers the base package but not the `wire_projection` column/loader delta (CL-6324) or the `workflow_definition.origin` column separating a definition from the per-run record of one folded run's deploy (CL-6452), shipped as migrations `0086`/`0087` behind upstream's `0085_add_approval_run_idx`, plus `0088` rewriting the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` in stored wire projections; retired when upstream absorbs the deltas | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the connector reply drain (`driveConnectorReplies`, `ConnectorReplyDrain`, `AgentEventStream`; `11590e66`) the sidecar's warm mail loop drives; no local delta; retired by the next `@intx/harness` publish | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the `agentDir` path export (`927556de`) the sidecar's deploy-tree lookup uses, and its own `@intx/mail-memory`/`@intx/harness` pins must resolve the vendored copies; no local delta; retired by the next publish | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 covers the base package but not the null-principal `resolveApproval` for policy-resolved decisions (CL-6345) or the bearer-authenticated workflow-deploy mirror (`middleware/workflow-run-deploy-auth.ts`, CL-workflow-deploy-bearer); retired when upstream absorbs the deltas | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 covers the base package but not the pack-acceptance fixes (`ownsWorkflowRunRepo`, `anchorAddressForPackSource`, `decideTerminalRunFlip`), the adopted deploy front + `sourceRef` (CL-6324), the wire-projection writer (CL-6324), malformed tool-call-name sanitization (CL-6478) or the sealed-run terminal-status backfill (CL-6595); retired when upstream absorbs the deltas | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates doom-loop detection (`8da4c827`, `afd0c82b`, `c421c092`); one local delta: `providers/google-genai-files.ts` builds its upload body as `new Uint8Array(bytes)` because TS 6's lib.dom `BodyInit` rejects `Uint8Array` (upstream compiles ESNext-only under TS 5.9); retired by the next publish | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the `@intx/mailbox` extraction (`af03bb90`), on-demand body reads (`54f7c239`) and `expunge` returning the swept uids (`bcabb1f8`) that the re-vendored `workflow-host` binds against; no local delta; retired by the next publish | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/mailbox` | `@intx/mailbox` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | Never published: a new package at the target pin (`af03bb90`) that `workflow-host`'s substrate mailbox store and supervisor-backed transport import; no local delta; retired by its first publish | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the non-RFC message-id guard `isMessageId` (`d97e1832`), the full `References` chain (`65c6fe70`) and the lossless `decodeMail` decoder (`3b6d06b2`) that `mailbox`/`mail-memory` at the same pin import; no local delta; retired by the next publish | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 predates the type surface the re-vendored trees compile against: `expunge` returning `expungedUids` (`bcabb1f8`), plain-string `PackRejectReason` (`7b42f405`), the run authorization/approvals REST types (`71ad6c08`), the decoded-mail `Mail`/`MailPartReader` model (`3b6d06b2`) and the `interchange.actions`/`loops` package-json refs (`3bd5b837`, `1ea2f39b`); no local delta; retired by the next publish | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | No local delta: npm 0.3.0 predates the `onBodyFailure: "tolerate"` section policy (`b977ade6`) that `@corbits/agent-runtime` authors and the action/loop primitives (`3bd5b837`, `1ea2f39b`) the re-vendored `workflow-host` runs; retired by the next `@intx/workflow` publish | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | No local delta: npm 0.3.0 predates `inertLoopBody` and the loop-body source pin (`1ea2f39b`) that the re-vendored `hub-sessions` imports; retired by the next `@intx/workflow-deploy` publish | sawyer | 2026-10-26 | `check:killdates` | +| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `a8bc06ae` (origin/main, 2026-08-27) | npm 0.3.0 covers the base package but not the body-spawn authorize/credential/mail-part-reader threading and grants head-collapse (CL-6448) that let the fork run tool-bearing onTrigger bodies; retired when upstream absorbs the delta | sawyer | 2026-10-26 | `check:killdates` | -The re-pin to `a8bc06ae` (upstream `origin/main`, 2026-08-27, 72 commits past -`v0.3.0`) is landing row by row; npm is still `0.3.0`, so every tree an -already re-pinned tree imports at a newer API is vendored too, all at the -same commit — a vendored tree never mixes pins. The root `package.json` -`overrides` therefore point each vendored name at `workspace:*` (so the -published `@intx/harness`, `@intx/hub-agent`, `@intx/tool-packaging`, -`@intx/authz`, … resolve their own `@intx/*` dependencies onto the vendored -copies instead of a second npm copy) and keep the unchanged names on `0.3.0`. - -The pinned commit `b5580a02` is upstream's `v0.3.0` release tag, 16 commits -past the previous pin `4ed8baf4`: a workflow-host supervisor -crash-respawn/backoff policy with a `RunFailed` terminal commit when the -crash-loop guard latches, credential/wallet deletion guards with -per-credential grant cleanup (and the removal of the dead `bindingGrants` -construction from `buildCredentialDelivery`), and an orphaned-grant cleanup -migration (upstream `0084`, which took the number our `wire_projection` -migration held — ours is now `0086`, behind upstream's later `0085`). - -v0.3.0 is also the first release whose `@intx/*` npm publishes cover the -folded model, so the fifteen previously vendored trees that carried no local -delta — `agent`, `authz`, `crypto`, `harness`, `hub-agent`, `hub-common`, -`inference`, `inference-catalog`, `log`, `mail-memory`, `mime`, -`pack-transport`, `storage-isogit`, `tool-packaging`, `types` — are retired: -deleted and consumed as published `@intx/*@0.3.0` packages. The rows -above survive because each carries a local delta the publish lacks, or is -imported at a newer API by a tree that does. The root `package.json` `overrides` pin every npm-consumed `@intx/*` -name to `0.3.0` and every vendored name to `workspace:*`, so external -dependencies' own `@intx/*` pins collapse onto the same resolution workbench -uses: the npm publish for retired names, the vendored workspace copy for -surviving ones. +The pin is `a8bc06ae` (upstream `origin/main`, 2026-08-27), 72 commits past +the `v0.3.0` release tag `b5580a02`. npm is still `0.3.0`, so every tree a +re-pinned tree imports at a newer API is vendored too, at the same commit — +a vendored tree never mixes pins: `agent`, `db`, `harness`, `hub-agent`, +`hub-api`, `hub-sessions`, `inference`, `mail-memory`, `mailbox`, `mime`, +`types`, `workflow`, `workflow-deploy`, and `workflow-host`, plus +`apps/sidecar`. The npm-consumed names whose source is byte-identical +between `v0.3.0` and `a8bc06ae` stay on npm `0.3.0`: `authz`, `crypto`, +`hub-common`, `log`, `pack-transport`, `storage-isogit`, `tool-packaging`, +`inference-catalog`. The root `package.json` `overrides` point every +vendored name at `workspace:*` (so the published `@intx/harness`, +`@intx/hub-agent`, `@intx/tool-packaging`, `@intx/authz`, … resolve their +own `@intx/*` dependencies onto the vendored copies instead of a second npm +copy) and every unchanged name at `0.3.0`. Local modifications (all surviving `vendor/intx/*` rows): each package's exports map is repointed from the upstream `intx-src` resolve condition to diff --git a/apps/sidecar/src/atomic-write.ts b/apps/sidecar/src/atomic-write.ts index 49510fa1c..cb609c4a1 100644 --- a/apps/sidecar/src/atomic-write.ts +++ b/apps/sidecar/src/atomic-write.ts @@ -1,16 +1,18 @@ -// Atomic, durable file replacement for the host's non-rebuildable -// on-disk records. The bytes land in a fresh per-write temp file that is -// fsynced and then renamed over the target; because rename is atomic -// within a directory, a reader only ever observes the prior complete -// file or the new complete file, never a torn one. The fsync before the -// rename extends that guarantee past process death to power loss. +// Atomic, durable file replacement for the sidecar's non-rebuildable +// on-disk records. Distinct from the cache's rebuildable temp+rename +// (no fsync, a lost write just forces a re-fetch) and from +// `fsyncWriteFile`'s in-place fsync write (no atomicity, a torn write +// leaves a half-file): this is the tier for a sole restore source that +// must survive both a process kill and a power loss without ever +// exposing a torn record. import { open, rename, unlink } from "node:fs/promises"; import { dirname } from "node:path"; + import { getLogger } from "@intx/log"; import { hexEncode } from "@intx/types"; -const log = getLogger(["sidecar", "atomic-write"]); +const logger = getLogger(["sidecar", "atomic-write"]); export interface AtomicWriteOptions { /** Permission mode applied when the temp file is created. */ @@ -18,11 +20,27 @@ export interface AtomicWriteOptions { } /** - * Replace `path` with `contents` atomically and durably. The parent - * directory is fsynced after the rename so the new link is itself - * durable; a filesystem that rejects directory fsync only degrades - * durability -- the file is already renamed and fsynced -- so that - * failure is logged, not thrown. + * Replace `path` with `contents` atomically and durably. The bytes land + * in a fresh per-write temp file that is fsynced and then `rename`d over + * `path`; because rename is atomic within a directory, a reader only + * ever observes the prior complete file or the new complete file, never + * a torn one. The fsync before the rename is what extends that + * guarantee past process death to OS crash / power loss: without it, the + * ext4 delayed-allocation window can surface the renamed path as a + * zero-length file after a power loss. + * + * The parent directory is fsynced after the rename so the new link is + * itself durable, but a filesystem that rejects directory fsync + * (FAT/exFAT, some network mounts) only degrades durability -- the file + * is already renamed and fsynced -- so that failure is logged, not + * thrown. + * + * `mode` is applied on the temp file's creation, so it takes effect on + * every write. A plain in-place overwrite of an existing file would + * silently keep the original file's mode instead. + * + * The temp file follows `createTarballCache`'s `.tmp..` + * naming convention for consistency across the sidecar's staged writes. */ export async function writeFileAtomicDurable( path: string, @@ -40,8 +58,10 @@ export async function writeFileAtomicDurable( } await rename(tmp, path); } catch (cause) { - // Best-effort temp cleanup: the temp may never have been created, - // and a second failure here must not mask the original cause. + // The write failed and is about to rethrow; unlink the temp so a + // failed write leaves no orphan. Best-effort: the temp may never + // have been created, and a second failure here must not mask the + // original cause. await unlink(tmp).catch(() => undefined); throw cause; } @@ -54,6 +74,6 @@ export async function writeFileAtomicDurable( await dirHandle.close(); } } catch (err) { - log.warn`parent-dir fsync failed for ${path}; durability is degraded but the file is renamed and fsynced -- ${err instanceof Error ? err.message : String(err)}`; + logger.warn`parent-dir fsync failed for ${path}; durability is degraded but the file is renamed and fsynced — ${err instanceof Error ? err.message : String(err)}`; } } diff --git a/apps/sidecar/src/signing-keypair.ts b/apps/sidecar/src/signing-keypair.ts index 3617f6c61..cebc309bb 100644 --- a/apps/sidecar/src/signing-keypair.ts +++ b/apps/sidecar/src/signing-keypair.ts @@ -1,15 +1,3 @@ -// The host's persisted Ed25519 identity. One key, one identity for the -// sidecar process: whatever execution body runs on this host signs as -// this keypair, so the identity survives the body being replaced. -// -// Copied near-verbatim from Interchange's own -// `apps/sidecar/src/signing-keypair.ts` (faremeter/interchange @ -// 59f5e7b9) — see the `apps/sidecar` row in VENDORED.md. This is a host -// identity, distinct from the per-agent keys `@intx/hub-agent`'s -// `agent-key-store.ts` custodies: an agent key answers "which agent is -// this" on a challenge, while this one signs the workflow-run commits -// the supervisor and each workflow-process child produce. - import fs from "node:fs/promises"; import path from "node:path"; import { derivePublicKeyBytes, generateKeyPair } from "@intx/crypto"; @@ -19,9 +7,13 @@ const PRIVATE_KEY_FILENAME = "ed25519.private"; const PUBLIC_KEY_FILENAME = "ed25519.public"; function bytesEqual(a: Uint8Array, b: Uint8Array): boolean { - if (a.length !== b.length) return false; + if (a.length !== b.length) { + return false; + } for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false; + if (a[i] !== b[i]) { + return false; + } } return true; } @@ -36,18 +28,20 @@ async function exists(filePath: string): Promise { } /** - * Load the host's persisted Ed25519 signing keypair, minting a fresh one - * on first boot. + * Load the sidecar's persisted Ed25519 signing keypair, minting a fresh + * one on first boot. * * The 32-byte seed in `ed25519.private` is the sole source of truth for - * the identity; the public key is always derived from it. The - * `ed25519.public` file is an identity anchor, not a cache: on every - * load it is cross-checked against the seed-derived public key. A - * mismatch means one of the two files was corrupted or swapped; rather - * than advertise a public key the host cannot sign with -- which would - * make every signature fail verification far from the root cause -- the - * boot halts and an operator decides whether to restore the seed or - * remove the directory to mint a fresh identity. + * the sidecar's identity; the public key is always derived from it. The + * `ed25519.public` file is an identity anchor, not a cache: on every load + * it is cross-checked against the seed-derived public key. A mismatch + * means either the seed or the public file was corrupted or swapped (a + * bad backup restore, disk bitrot, an operator fat-finger). Rather than + * trusting the file and advertising a public key the sidecar cannot sign + * with -- which would make every signature fail verification at the hub, + * far from the root cause -- we fail loudly here at boot. We cannot tell + * which of the two files rotted, so we halt and let an operator decide + * (restore the seed, or remove the directory to mint a fresh identity). */ export async function loadOrMintSidecarKeypair( signingDir: string, @@ -82,7 +76,7 @@ export async function loadOrMintSidecarKeypair( } if (!bytesEqual(derivedPublicKey, storedPublicKey)) { throw new Error( - `sidecar signing public key at ${publicKeyPath} does not match the key derived from the seed at ${privateKeyPath}; restore the matching seed, or remove ${signingDir} to mint a fresh identity`, + `sidecar signing public key at ${publicKeyPath} does not match the key derived from the seed at ${privateKeyPath}; the sidecar would advertise a public key it cannot sign with, so every signature would fail verification at the hub; restore the matching seed, or remove ${signingDir} to mint a fresh identity`, ); } return { privateKey: seed, publicKey: derivedPublicKey }; diff --git a/docs/revendor-inventory.md b/docs/revendor-inventory.md index bb22304c1..35d049ecb 100644 --- a/docs/revendor-inventory.md +++ b/docs/revendor-inventory.md @@ -188,7 +188,7 @@ numbers searched — flagged as NOT FOUND rather than guessed at. | Asset delete (CL-6040) | **NOT FOUND** | No "CL-6040", `deleteAsset`, or asset package at all in the worktree. | | Tenant-mint gating (`apps/hub/src/tenant-create-guard.ts`, CL-6041) | **STILL-OPEN** | Wraps the native `POST /api/tenants` because it's ungated; `git log 26ae23e8..origin/main -- packages/hub-api/src/routes/tenants.ts` upstream shows zero commits — route unchanged. | | IDKind task (`packages/tasks/src/launcher.ts:264-269`, CL-6056) | **RETIRED** | `@corbits/tasks` was deleted (tasks primitive removed in favor of workflows/routines) — `mintTaskId()` and its citing code no longer exist in this repo. | -| `receiveWorkflowRunPack` anchors (`packages/folded-runs/src/launch.ts:240-247`, CL-6044) | **STILL-OPEN mechanism / CHANGED context** | Upstream `vendor/intx/hub-sessions/src/hub-session-lookups.ts:369-373` still requires `anchor.anchorRunId === anchor.id` before accepting a pack — same check the workaround targets. But per `e2732d32`'s commit message, the folded-launch surface it mimics (stop, mail send/history, turns, event stream) is being retired upstream in favor of run-first surfaces — the ground workbench stands on is shifting even though the specific check hasn't. | +| `receiveWorkflowRunPack` anchors (`packages/folded-runs/src/launch.ts:240-247`, CL-6044) | **STILL-OPEN mechanism / CHANGED context** | Upstream `vendor/intx/hub-sessions/src/hub-session-lookups.ts:369-373` still requires `anchor.anchorRunId === anchor.id` before accepting a pack — same check the workaround targets. But per `e2732d32`'s commit message, the folded-launch surface it mimics (stop, mail send/history, turns, event stream) is being retired upstream in favor of run-first surfaces — the ground workbench stands on is shifting even though the specific check hasn't. **2026-08-28:** `hub-session-lookups.ts` is still untouched upstream at `a8bc06ae` (`git -C ~/abklabs/interchange log --oneline b5580a02..a8bc06ae -- packages/hub-sessions/src/hub-session-lookups.ts` is empty), so this row's verdict stands unchanged. | | Mail-trigger anchor (CL-6020) | **NOT FOUND** | No "CL-6020" or comparable mail-trigger-anchor workaround found. | | Abort race (CL-5960) | **NOT FOUND** | No "CL-5960" or abort/race workaround found. | | Self-update grants (`packages/agent-directory/src/workflow-capability-routes.ts:16-40`, `packages/capability-tools/src/client.ts:14-22`, CL-6085) | **STILL-OPEN** | A run's own `kind: "workflow"` principal is never seeded a `workflow-definition:/update` grant; upstream's `715c8be6` (removes unenforced approved-grant shipment) and `09d4cfd1` (freezes per-deployment grant walk) don't add own-definition self-update grant materialization — unrelated to this gap. | @@ -287,6 +287,15 @@ whose status flips terminal while its deployment stays mounted and its workbench stays the active chat surface is what generates the in-flight mail in the first place. +**2026-08-28: RETIRED at the `a8bc06ae` re-pin.** Upstream `81ef5ad9` +("Deliver decoded inbound mail to deployed workflows") decodes each inbound +message into a `Mail` and commits its parts before dispatch instead of +extracting `text/plain` alone, so an attachments-only, empty-text mail is no +longer collapsed to `""` on the resume path — the local `hasConversationText` +guard has no upstream gap left to patch. Re-vendored on this branch in +`84a2e6cb` ("Re-vendor @intx/workflow-host at a8bc06ae; vendor harness and +hub-agent; adopt the warm-agent mailbox in the sidecar"). + **On the 7 NOT FOUND items:** the workaround may live under a name not guessed here, may not have landed yet, or the ticket may describe a gap with no code-side mitigation to retire. Recommend re-running this check against @@ -327,6 +336,16 @@ re-pinned tree once PR #59 lands. version, per `vendor/intx/workflow`'s broader retirement condition in `VENDORED.md`). +**2026-08-28: RETIRED at the `a8bc06ae` re-pin.** Upstream `b977ade6` ("Add a +tolerate body-failure policy to onTrigger sections") ships the same edge +under `onBodyFailure: "tolerate"` (default `"end"`, a cancelled body always +terminates), so the local `"continue"` delta has no upstream gap left to +patch. The literal was renamed `"continue"` → `"tolerate"` at the authoring +site (`@corbits/agent-runtime`), with `@intx/db` migration `0088` rewriting +the retired literal inside stored wire projections. Re-vendored on this +branch in `7110d93a` ("Re-vendor @intx/workflow and @intx/workflow-deploy at +a8bc06ae with no delta"). + ## Estimated fix effort | Item | Shape | Rough size | diff --git a/scripts/checks/kill-dates.txt b/scripts/checks/kill-dates.txt index 195834f83..d27809279 100644 --- a/scripts/checks/kill-dates.txt +++ b/scripts/checks/kill-dates.txt @@ -15,19 +15,19 @@ # package's VENDORED-FROM delta line in the same change. apps/sidecar | sawyer | 2026-10-26 vendor/intx/agent | sawyer | 2026-10-26 | d0d56d9f452b78f4b541ad8f4e89f975e8069446bb98f2c8097b90de4b020243 -vendor/intx/db | sawyer | 2026-09-19 | 0a4cdb9a8a6ff19d5d4713cbc4f5cc9257aad839b1fa393b2026e6d5afd828b9 +vendor/intx/db | sawyer | 2026-10-26 | 0a4cdb9a8a6ff19d5d4713cbc4f5cc9257aad839b1fa393b2026e6d5afd828b9 vendor/intx/harness | sawyer | 2026-10-26 | 867f2b0eb4a360c68bf552d5b95a9530411e2c1a2d046d1f5ce718d4a9be36a8 vendor/intx/hub-agent | sawyer | 2026-10-26 | 30d5050511f22bc73b3c5d34728dfdf5b791de203452b83d4333a1dc762afceb -vendor/intx/hub-api | sawyer | 2026-09-19 | 42ee33e027559b236065382cb94f393bbcfee69625615894f82be778f34f7aa1 -vendor/intx/hub-sessions | sawyer | 2026-09-19 | 53addc3090ad9f54bc4bac8fb50ad8d567ccf46f30bb5403d447351cb16b4fb6 +vendor/intx/hub-api | sawyer | 2026-10-26 | 42ee33e027559b236065382cb94f393bbcfee69625615894f82be778f34f7aa1 +vendor/intx/hub-sessions | sawyer | 2026-10-26 | 53addc3090ad9f54bc4bac8fb50ad8d567ccf46f30bb5403d447351cb16b4fb6 vendor/intx/inference | sawyer | 2026-10-26 | 77fec29b078e8d03e686747c70e6b62ac1fd1434db0fb2c1e12e84b6dc71465f vendor/intx/mail-memory | sawyer | 2026-10-26 | 9f3601a7fb22e2d1c63daa976f3afccbd79af2187c155a0080c0d60c82450b92 vendor/intx/mailbox | sawyer | 2026-10-26 | d36d7ffcc32018571276e4922a8c2714b7ee0bb5deb80b01e73859245975d4c6 vendor/intx/mime | sawyer | 2026-10-26 | d02e5f8f1429eac7c27d3a37eec31111f8a1053c91fbfae77ac58a0d63c823ed vendor/intx/types | sawyer | 2026-10-26 | ec1de14b859007b4db137da1533d4ce79d11024ad69c8b36938017319d6d8e86 -vendor/intx/workflow | sawyer | 2026-09-19 | 4b51b9bd6a124cfaa0c916e2b26c04ac9170618bb092f0c8e1c312263fc84fdf +vendor/intx/workflow | sawyer | 2026-10-26 | 4b51b9bd6a124cfaa0c916e2b26c04ac9170618bb092f0c8e1c312263fc84fdf vendor/intx/workflow-deploy | sawyer | 2026-10-26 | 960a2ae408223649fe8be0e3b9d63f2b0cca25259bc0ae06761bca521bb738e5 -vendor/intx/workflow-host | sawyer | 2026-09-19 | aff0342a526387ea9ccb52827fd4f13d9dd52645b37795362ce9b2fd912a5da5 +vendor/intx/workflow-host | sawyer | 2026-10-26 | aff0342a526387ea9ccb52827fd4f13d9dd52645b37795362ce9b2fd912a5da5 packages/folded-runs | sawyer | 2026-11-01