Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ unauthorized external action after completing independent, authorized work.
`-application`, `-contracts`, `-domain`, `-store`, `-hooks`, `-policy`,
`-tool-catalog`, rusqlite parity/runtime crates).
- `dashboard/`, the single embedded dashboard (React + rsbuild + vitest).
`dashboard/src/contracts/` (from Rust schemas via schemars) and the
TypeScript SDK sources in `sdks/typescript/src/` (from the canonical
operation registry) are generated. Never hand-edit them; regenerate both
with the dashboard `contracts:generate` script and verify with
`contracts:check`.
`dashboard/src/contracts/` (from Rust schemas via schemars), and the
TypeScript SDK sources in `sdks/typescript/src/` plus the Rust descriptors in
`crates/tracedecay-sdk/src/operations.rs` (from the canonical operation
registry) are generated. Never hand-edit them; regenerate all of them with
the dashboard `contracts:generate` script and verify with `contracts:check`.
- `plugin/`, host bundles (Claude, Codex, Cursor, Kimi, opencode).
- `tests/`, shared fixtures, distribution suites, and shell/Python gates that
no single crate owns; crate-level integration suites and criterion benches
Expand Down
8 changes: 0 additions & 8 deletions crates/tracedecay-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ tracedecay-domain = { path = "../tracedecay-domain" }
tracedecay-tool-catalog = { path = "../tracedecay-tool-catalog" }
tracing = "0.1"

# `build.rs` renders `src/operations.rs` from the canonical registry through
# `src/codegen.rs`. These mirror the runtime dependencies the descriptors are
# projected from; no crate here depends on this one, so there is no cycle.
[build-dependencies]
serde_json = "1"
tracedecay-contracts = { path = "../tracedecay-contracts" }
tracedecay-tool-catalog = { path = "../tracedecay-tool-catalog" }

[dev-dependencies]
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
tempfile = "3"
Expand Down
20 changes: 0 additions & 20 deletions crates/tracedecay-sdk/build.rs

This file was deleted.

11 changes: 3 additions & 8 deletions crates/tracedecay-sdk/src/bin/generate.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
//! Writes the checked-in TypeScript SDK sources from the canonical registry.
//!
//! Rust descriptors are generated into `OUT_DIR` by the SDK crate's
//! `build.rs`, so only the published npm package's sources are written into
//! the checkout here. Both entry points share `src/codegen.rs`.
//! Writes the checked-in Rust operation descriptors and TypeScript SDK sources
//! from the canonical registry through the shared `src/codegen.rs`.

// The Rust renderer in the shared generator has no caller on this path.
#[allow(dead_code)]
#[path = "../codegen.rs"]
mod codegen;

Expand All @@ -17,5 +12,5 @@ fn main() -> Result<(), Box<dyn Error>> {
.nth(1)
.map(PathBuf::from)
.ok_or("usage: generate <repository-root>")?;
codegen::write_typescript_sdk(&root)
codegen::write_sdk_sources(&root)
}
43 changes: 29 additions & 14 deletions crates/tracedecay-sdk/src/codegen.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Canonical SDK generator, shared by the crate's `build.rs` (Rust operation
//! descriptors into `OUT_DIR`) and `src/bin/generate.rs` (the checked-in
//! TypeScript SDK sources). It is the single authority for both surfaces.
//! Canonical SDK generator behind `src/bin/generate.rs`, which writes the
//! checked-in Rust operation descriptors and TypeScript SDK sources. It is the
//! single authority for both surfaces.

use std::collections::{BTreeMap, BTreeSet};
use std::error::Error;
Expand Down Expand Up @@ -79,27 +79,24 @@ struct UnavailableOperation {
disposition: ExecutableUnavailableDispositionV1,
}

/// Render the typed Rust operation descriptors for `include!` from
/// `src/operations.rs`. No module header: the including file owns it.
pub fn render_rust_operations_source() -> Result<String, Box<dyn Error>> {
let registry = canonical_application_registry()?;
let operations = canonical_operations(&registry)?;
let unavailable = canonical_unavailable_operations(&registry);
render_rust_operations(&operations, &unavailable)
}
/// Repository-relative path of the checked-in Rust operation descriptors.
const RUST_OPERATIONS_PATH: &str = "crates/tracedecay-sdk/src/operations.rs";

/// Write the checked-in TypeScript SDK sources under `<root>/sdks/typescript`.
pub fn write_typescript_sdk(root: &Path) -> Result<(), Box<dyn Error>> {
/// Write the checked-in Rust descriptors and the TypeScript SDK sources under
/// `<root>`.
pub fn write_sdk_sources(root: &Path) -> Result<(), Box<dyn Error>> {
let registry = canonical_application_registry()?;
let operations = canonical_operations(&registry)?;
let unavailable = canonical_unavailable_operations(&registry);
let destination = root.join("sdks/typescript/src");
// Render every artifact before changing the checkout, so a rendering
// failure cannot leave a half-written package behind.
let rust_operations = render_rust_operations(&operations, &unavailable)?;
let types = render_types();
let typescript_operations = render_operations(&operations, &unavailable)?;
let index = render_index();

write(&root.join(RUST_OPERATIONS_PATH), &rust_operations)?;
write(&destination.join("types.ts"), &types)?;
write(&destination.join("operations.ts"), &typescript_operations)?;
write(&destination.join("index.ts"), &index)?;
Expand Down Expand Up @@ -890,7 +887,12 @@ fn render_rust_operations(
unavailable: &[UnavailableOperation],
) -> Result<String, Box<dyn Error>> {
let mut out = String::from(
"use serde::Serialize;\n\
"//! Typed public operation descriptors.\n\
//!\n\
//! Generated by `pnpm --dir dashboard run contracts:generate` from the\n\
//! canonical application registry. DO NOT EDIT; change `src/codegen.rs`.\n\n\
#![cfg_attr(rustfmt, rustfmt::skip)]\n\n\
use serde::Serialize;\n\
use serde::de::DeserializeOwned;\n\
use tracedecay_contracts::retained_surfaces::{SdkRequestIdControlV1, SdkResultSemanticsV1};\n\
use tracedecay_tool_catalog::{CancellationPoint, DeadlineBehavior, EffectClass, ExecutableUnavailableDispositionV1, IdempotencyContract, ReceiptContract, ReconciliationContract, TerminalState};\n\n\
Expand Down Expand Up @@ -1634,4 +1636,17 @@ mod tests {
assert!(generated_rust.contains("ReceiptContract::DurableEffect"));
assert!(generated_rust.contains("TerminalState::EffectUnknown"));
}

#[test]
fn checked_in_rust_operations_match_canonical_registry() {
let registry = canonical_application_registry().unwrap();
let operations = canonical_operations(&registry).unwrap();
let unavailable = canonical_unavailable_operations(&registry);
let rendered = render_rust_operations(&operations, &unavailable).unwrap();
assert!(
include_str!("operations.rs") == rendered,
"{} is stale; run `pnpm --dir dashboard run contracts:generate` and commit the result",
super::RUST_OPERATIONS_PATH,
);
}
}
Loading
Loading