Generate SDK bindings at build time and preserve descriptor identities

Merged
VA
3d ago
+2841-27106

The three clean-checkout review threads are fixed at signed head 81d49688a90894856d6b7c463299febaab23ed24.

  • Grouped WASM builds synchronously generate the complete shared dependency closure once before launching any group. Children build only their assigned WASM target and do not rerun generation. Standalone WASM builds prepare their dependencies in project/npm order: Filesystem builds Actors first; Harness and Pi build Actors, Objects and Harness. No lock or polling serialization was added. Existing native-family, source-snapshot and compiler-receipt checks still run.
  • Filesystem and Harness check now generate protobuf/contracts and use TypeScript build mode to prepare referenced declarations. Their build commands also build references.
  • Pi build/test/check prepare their referenced WASM before TypeScript compilation. Pi now exposes check with its existing type-test coverage.

Real clean-checkout proof used a fresh detached Git worktree, with git clean -xfd and frozen dependency installation before each command. Commands ran in a Linux user/network namespace with loopback enabled, no external routes, an asserted ENETUNREACH connection to an external IP, and Cargo offline. Installed dependencies/toolchains/caches were populated before network isolation. No generated outputs were copied into the cleaned worktree. Profile dev was used throughout.

PackageClean buildClean checkClean test command
Filesystem41.36 s28.93 s40.11 s
Harness48.56 s34.22 s58.22 s
Pi51.64 s39.93 s37.98 s

These command times include generation, Cargo/TypeScript compilation and tests. Harness's actual runtime suite passed 304 tests in 3.45 s; Filesystem's S3 suite passed 12 tests in 0.05 s, and its memory composition test passed. Pi's commands include its type-test compiler. An earlier cold Filesystem build took 270.60 s and the initial Harness build 190.05 s, including dependency compilation; these are not suite runtimes.

The actual clean, offline grouped build of all nine WASM targets passed in 166.62 s, including compilation. The deterministic orchestration/property suite passes 21 tests in 1.48 s, covering all 72 group completion orders, generation failure, a broken-order negative control, arbitrary package graphs, dependency order and standalone target closures. Focused changed-script type checking passes in 0.19 s. Real empty-cache/network-denied Buf generation and its restored-registry-dependency negative control remain in that suite.

The clean runs exposed stale main fixtures: the file-tool contract still recorded the old names/revision, and two projection fixtures omitted the recorded invocation/provider metadata envelope. Refreshed the contract with the existing native generator and corrected the projection fixtures, including the negative exact-number input so it tests its intended invariant. No validation was relaxed. The focused projection suite passes 10 tests in 1.09 s against real WASM. All retained protocol descriptor baselines and pinned handshake hashes are unchanged.

Clean package/group runs used 22962b4985d157d57d58bd37661db81d5e481036; final 81d49688a90894856d6b7c463299febaab23ed24 differs only by an explicit string assertion in the graph property test. All production build/generation and package code is identical; the final orchestration and script-type suites ran at the final head. Rust build-generation code is unchanged by this delta; the previous Windows/GNU/aarch64/Git/package evidence below remains applicable. A broad scripts-only check also encountered existing MCP qualification mock typing errors and missing Filesystem dist after the clean WASM-only step; the focused check covers all changed scripts and their imports without changing unrelated tests.

Decisions: generate shared sources once before parallelism; build dependency WASM for standalone packages; use TypeScript build mode to prepare referenced declarations; preserve every native provenance/compatibility check. No new compiler or proto vendoring.


Latest review fixes at signed head 657af66fa85214bc58e7a4d3857f0e275e2f4166:

  • Removed the unused Google APIs registry dependency from buf.yaml and its buf.lock entry; no protos were vendored. All imports are SDK-owned schemas or Buf's local Google well-known types. The real TypeScript generator now succeeds with independently empty Buf/Bun/XDG caches while an inherited seccomp filter rejects network sockets (IPv4 and IPv6 denial is asserted). Every packaged protobuf JS/declaration output is checked. A negative control restores both old dependency entries and fails on the blocked network.
  • The production source collector now includes compatibility/Cargo.toml and every file under compatibility/src/**. Mutation controls enumerate the real helper inputs, edit each independently, and require a different attested identity. The old omission is a negative control. A 32-case shrinking property covers nested source paths, non-Rust include inputs and arbitrary content edits. The standalone generator lockfile records only its new test dependency closure.

Focused validation for this delta: TypeScript generation/property/offline suite 2 passed, 2.08 s; actual source-attestation mutation/property suite 2 passed, 0.04 s on bb1. Windows MSVC cargo check --manifest-path rust/crates/sdk-generation/Cargo.toml --bin sdk-generation --tests passed (10.28 s compilation, queue excluded). No protocol identity, pinned hash, descriptor bytes or Rust build-generation code changed in this delta; the prior validation below remains applicable to those unchanged paths. No wait for CI is included.

Cloud commands: Rust Git consumers of acyclic-actors, acyclic-fs, acyclic-objects, acyclic-stream and acyclic-machines generate automatically in their ordinary cargo build --locked; no standalone generation command or system protoc is needed. Published npm packages include their generated output. TypeScript source-checkout consumers run bun scripts/generate.mjs <package> from the SDK root after normal dependency installation (for example bun scripts/generate.mjs objects, or actors, filesystem, stream, machines); build/test/prepack scripts invoke it automatically.

Decisions: remove the unused registry dependency rather than vendor it; keep the offline network-denial regression in the existing generation suite; include all helper source files so future include inputs are attested as well.

Ready for re-review at 657af66fa85214bc58e7a4d3857f0e275e2f4166.


Rust and TypeScript bindings are generated during builds instead of being committed and synchronized. Rust build scripts use shared acyclic-proto-build, vendored protoc and crate-owned packaged protos. Objects includes Restore. TypeScript build/test/prepack scripts generate package-local ignored projections. Handwritten source, compatibility snapshots and their checks remain; the generated-source job, synchronizers, intermediate Rust tree and retired Rust normalizer are removed.

All three review blockers are fixed at signed head 27bf927f583b94778f4bfdb0f404b2fdaa3ab0dd:

  • Descriptor identities: all seven regenerated closures are byte-identical to the retained baselines. No retained descriptor bytes or compatibility manifest hashes changed from the reviewed head. Machines' existing public_descriptor_is_pinned passes unchanged (b865f611…). The old compiler was Buf, whose embedded Google schemas, source-location order, trailing-comment convention and ImageFile metadata differ from protoc. Pin vendored protoc to the already-locked =3.3.0, retain imports/source info/options, preserve unknown options with reflection, reproduce Buf's project-file encoding, and bind Google imports to the existing compiler-input baselines. The shared build dependency lives under compatibility/ so published packages contain these existing baselines without duplicate snapshots, new compiler vendoring, root-relative includes or build-time downloads. Actors' old private-projection fixtures now contain the retained complete public closure; all twelve descriptor/adversarial controls remain and pass, and fixture copies are checked against that baseline.
  • Native admission: regenerated Actors and Stream baselines with the actual Rust generator. Source roots are sorted and include the shared compatibility/build dependency; its parent root already covers the individual family baseline. A regression compares both committed family baselines with the actual generator, rather than a substitute implementation.
  • Compatibility checking: check-baselines.mjs regenerates every complete descriptor closure from current crate-owned roots/imports and compares exact bytes. Its controls change SessionHello.destination_node from tag 3 to 99 and mutate the imported shared handshake schema; both fail the real checker. The descriptors mode builds no SDK runtime crate. Existing digest, packaged-copy and native/WASM fixture checks remain.

Merged main, including #689's shared tracing/proptest fix and #688's aarch64 job. The retired generated-source job stays deleted. Real Actions on this head: SDK aarch64 Linux check passed (1m43s including compilation), and SDK Static passed (8s).

Focused validation, build time excluded:

SuiteResultWall time
Seven descriptor baselines + 64-case shrinking canonicalization property2 passed0.51 s Linux / 0.53 s Windows
Complete-closure checker, breaking controls, both actual native-family generators3 passed3.54 s
Generation, publication and CI controls90 passed5.43 s
Machines unchanged descriptor pin1 passed<0.01 s
Actors complete descriptor/adversarial contract12 passed0.10 s
Auth authorization / contract / operations2 / 6 / 2 passed0.19 / <0.01 / 0.02 s
Objects Restore / response4 / 1 passed<0.01 s each
GNU/Linux ASAN descriptor fuzz target, committed corpus, 256 runspassed2.25 s

Required Windows MSVC library/test checks passed for the shared helper and all seven protocol crates; Stream's test closure includes local, required by its state-replacement test. Focused proto-codegen and SDK fuzz checks passed. Shared helper and descriptor fuzz target clippy passed with -D warnings. The Harness browser library-test artifact compiles and links successfully; the former shared-tracing gap is closed. Filesystem with s3-http also passes the Linux-hosted x86_64-pc-windows-gnu cross-check (47.50s build).

Packaging and Cloud Git handoff:

  • cargo package --no-verify --list contains the owned protos in every protocol crate and includes the retained descriptor inputs in acyclic-proto-build.
  • Actual Objects cargo package verification passed from extracted archives outside the source workspace, using all six local dependency archives, network disabled, PROTOC unset and a failing system-protoc shim (11.43s compilation). An initial scratch directory nested beneath the SDK workspace was rejected by Cargo; moving the extracted archives outside the workspace fixed the test setup without changing source.
  • Published Git revision 27bf927f583b94778f4bfdb0f404b2fdaa3ab0dd: offline consumer PASSED on Windows MSVC. The scratch consumer pins Objects to the actual origin Git revision; 238 packages were vendored, PROTOC/PROTOC_INCLUDE were unset, a failing executable shadowed system protoc, and cargo build --locked --offline --lib succeeded in 18.56s. This gate is also installed in existing Linux qualification. No workspace scripts or files outside the Git source are needed by build.rs.
  • Earlier same-implementation npm pack dry runs for Objects/Actors include generated protobuf JavaScript and declarations; their TypeScript generation/prepack paths are unchanged by these review fixes.

Build cost, Windows MSVC/default grpc, dependencies warm, Objects alone cleaned between clean samples:

OperationBefore removalFinal head
Objects clean package build7.934 s6.597 s
Objects no-op build0.358 s0.317 s

Single samples, not a statistical speed claim. The no-op lower bound is Cargo freshness checking: no protoc or generator runs. Changed protos regenerate bindings/descriptor once; rerun-if-changed tracks the crate-owned protocol directory. No charges, limits or runtime synchronization are removed or changed.

Live step: a fresh macOS-host run could not start because ssh ivar requires Tailscale SSH authentication. No macOS result is claimed. All requested Linux/aarch64, Windows GNU/MSVC and offline Git/package checks above passed.

Earlier descriptor, package and platform validation above was recorded at 27bf927f583b94778f4bfdb0f404b2fdaa3ab0dd.

Generator source-root review fix (624d5ac8d49d6a19d071b0c3f199230b87b994db)

Add package-closure.mjs to the Rust-owned source roots and regenerate both Actors and Stream admission baselines with the actual generator. The new exhaustive import inventory also found check-compatibility-digests.mjs absent from both families, and Stream's selected Actors contract helpers absent from its roots; include those inputs as well.

The check parses each owned script's static module imports with Node's module parser, recursively checks local imports and literal dynamic imports, and runs the actual contract-module selector with the real package closure for computed contract imports. Unhandled computed imports fail closed. Negative controls remove package-closure from each family, insert an uncovered static import, insert an uncovered literal dynamic import, and insert an unhandled computed import. All must be rejected.

Focused checkResultWall time after build
Generation / source-root properties and controls5 passed0.17 s
Descriptor identities, breaking controls, both actual native-family generators3 passed1.49 s

Linux rebuilt only sdk-proto-codegen (0.45 s). The final Windows sdk-proto-codegen bin/tests check passed through lb.ps1 (0.41 s; the preceding dependency compilation took 5.48 s). Existing clean/offline package and grouped WASM evidence above is unchanged: this delta changes admission inventories and tests, not generation/build behavior. No protocol descriptor or pinned hash changed.

Decision: attest the actual transitive script closure and reject newly introduced uncovered imports rather than rely only on baseline equality.

Main conflict resolution (49ea2cbc7bf539c1b0cd21dc01a1949d6bd6aa73)

Merge origin/main (7b609457c) without rebasing. Keep main's sdk-generation manifest and lockfile, including its added archive/hash dependencies, fixture and lint configuration. Keep both Harness generated WASM declarations deleted; generation recreates them from current Rust. No descriptor baseline or pinned hash changes.

Focused verification used a fresh checkout of the merge commit and an initially empty Cargo target. npm dependencies were installed normally, then generation/check ran inside a network namespace with no external routes (the external-connect negative control returned ENETUNREACH), Cargo offline, and a new empty Buf cache. Both deleted Harness declarations were absent before generation and present afterward; tracked source remained clean. Harness generation plus the real package check passed, including its dependency WASM builds and project references.

Focused checkResultTime
sdk-generation clean library test buildPassed6.78 s compilation
Actual build-helper attestation / shrinking properties2 passed0.02 s
Contract source-inventory regression1 passed<0.01 s
Harness offline generation and package checkPassed343.39 s including compilation after resuming
Windows sdk-generation library/tests checkPassed0.11s compilation
Windows Harness library/tests checkPassed1m 31s compilation

The first remote job was terminated by a supervisor restart after 63 s, after the docs tests passed. Resumed the same initially empty target rather than repeat those tests or discard compilation; the required codegen executable was already built (0.24 s freshness/build command). Successful remote job: 1792163753408-67273, source content cc488dd717f7aec80cb5a8c21f20a84970dd18bdc6ad23b6e2988761aa27cfc7. Windows timings exclude slot waits. Existing broader clean/offline and platform evidence above is retained for unchanged code.

Standalone SDK WASM prerequisite (e35c1e327adfd5e5a7abe169a3ab9f338d159fb7)

SDK build/test now run bun ../../../scripts/build-wasm.mjs sdk through build:wasm before TypeScript compilation. Audited all 12 packages and found the same missing prerequisite in Actors test and Workers test / test:cloud; fix those too. Other regular build/test/check paths already prepare their WASM closure; Swarm has no WASM dependency. Existing check:types commands require built dist output and remain checks of that output.

The regression check enumerates every package's standalone build/test/check and project-compilation commands, follows local and dependency bun run prerequisites, and requires the complete production WASM closure before tsc -b. Negative controls remove SDK's prerequisite from build/test or build only Actors' incomplete closure. All are rejected.

CheckResultWall time
Orchestration / exhaustive package prerequisites / negative controls21 passed0.17 s
Clean SDK bun run buildPassed399.09 s including cold compilation
Separately cleaned SDK bun run testPassed41.13 s including generation/build/typechecking

Real proof used a fresh clone and initially empty Cargo target, with no previous WASM target. Before each SDK command, git clean -xfd removed all generated declarations, dist output and TypeScript build state, then dependencies were installed from the normal install cache. Both commands ran with a new empty Buf cache, Cargo offline, and networking blocked in an isolated namespace; an external-connect control returned ENETUNREACH. The second command reused Rust compilation artifacts outside the checkout, while starting with no generated WASM declarations. Both completed with clean tracked source. Remote job 1792165504485-125940; source content de90c14178b9c6a6350358f610188b31302d8905315a6bf319e89fda9b8f2f47.

This delta changes package commands and a Node test only. The Windows Rust compilation proof at parent 49ea2cbc7 above remains applicable to unchanged Rust source; no duplicate Cargo check was run.