Skip to content

ADR-0157: metadata-generated typed module data SDK - #412

Open
nikstern wants to merge 1 commit into
nerdsane:mainfrom
nikstern:codex/typed-module-data-sdk-adr
Open

ADR-0157: metadata-generated typed module data SDK#412
nikstern wants to merge 1 commit into
nerdsane:mainfrom
nikstern:codex/typed-module-data-sdk-adr

Conversation

@nikstern

@nikstern nikstern commented Jul 24, 2026

Copy link
Copy Markdown

Problem

ADR-0099 removed the TCP hop for same-process module calls, but modules still behave like hand-written OData clients. They must:

  • build /tdata URLs for entities, actions, queries, and File content;
  • construct and parse loosely typed JSON;
  • translate between Rust names and CSDL casing;
  • copy authentication and tenant headers;
  • classify HTTP status codes and OData errors; and
  • poll after writes without stating which committed version they need.

That plumbing is repeated across modules and makes internal correctness depend on HTTP conventions.

Decision

Generate a typed SDK for each WASM module from the exact CSDL and IOA metadata that Temper verifies and deploys. The generated SDK calls a direct, versioned WASM host ABI instead of loopback OData.

OData remains the public external API. Both OData handlers and the generated SDK call the same governed application service, so Cedar authorization, transition verification, guards, relations, persistence, projections, audit events, and tenant isolation stay on one path.

Illustrative change:

Before:
POST /tdata/Tasks(task-1)/Temper.StartWork
Authorization: Bearer ...
{ AssigneeId: agent-1 }
then parse HTTP status and read the entity again

After:
tasks.start_work(task_id, StartWorkInput { assignee_id })
returns typed result plus CommitToken { entity, id, sequence }

Generated application code does not construct URLs, headers, OData envelopes, or status-code policies.

Concrete v1 contract

Build and schema binding

  1. Resolve every application dependency to an immutable bundle version and digest.
  2. Generate a per-module SDK and capability manifest before compiling WASM.
  3. Bind the compiled module to the ABI version, locked dependency digest, schema digest, capability digest, and exact symbols it uses.
  4. Recompute those values during deployment and hot reload. A stale module fails activation while the previous version remains active.

Least-privilege module grants

Each app.toml WASM module declaration lists its allowed operations, entities, actions, query fields, File operations, and budgets. Trigger declarations may select a module but cannot widen its grant.

The generator emits only the granted methods, and the host checks the same grant again at runtime.

Authorization

Tenant, principal, module identity, and capability grants live in a host-only invocation snapshot. They are never accepted from guest parameters or headers.

External callers retain their resolved Cedar principal. Internal workflows must explicitly use a named service principal. Missing authority fails closed rather than becoming system access.

Reads and writes

Successful writes return a per-entity commit token containing the committed sequence. A later keyed read can require at least that sequence.

The server either:

  1. serves a projection that is new enough;
  2. performs one bounded authoritative read; or
  3. returns a structured consistency error.

Neither the server nor generated SDK polls or sleeps waiting for projection catch-up.

Structured errors

SDK calls return typed errors such as:

  • invalid request or schema mismatch;
  • not found or already exists;
  • authorization denied;
  • guard, relation, or verification rejection;
  • conflict or unavailable consistency;
  • exhausted budget or transient failure; and
  • internal failure.

Only the OData adapter maps these errors to HTTP status codes.

Batches and files

  • Ordinary batches are bounded, preserve input order, and return one result per item. They are explicitly non-atomic.
  • Atomic behavior is available only through actions already declared as verified composite transactions.
  • File metadata uses typed entity operations.
  • File bytes use bounded host stream handles instead of File $value URLs or JSON encoding.
  • File handles are tied to one invocation, module, direction, and byte budget, and are consumed exactly once on EOF, commit, or abort.

What does not change

  • Public OData metadata, CRUD, query, paging, action, and File $value endpoints remain supported.
  • Modules still use host_http_call for external providers and webhooks.
  • No TemperPaw-specific entity or workflow primitive is added to the kernel.
  • Existing Cedar, IOA verification, event sourcing, projection, and audit semantics remain mandatory.

Rollout

  1. Add the generated SDK, direct host ABI, shared service, consistency contract, and compatibility checks to Temper. Keep the ADR-0099 wrapper temporarily.
  2. Migrate TemperPaw modules from hand-written OData calls to generated clients.
  3. Prove no packaged module still uses local /tdata calls, then remove the local wrapper and stop injecting internal Temper URL and API-key secrets.
  4. Deploy in dependency order and verify direct, routed, batch, and File workflows plus Datadog correctness and latency signals.

Important tradeoffs

  • Temper gains a stable module ABI and generated-code compatibility surface.
  • OData handlers must be separated from domain execution without changing public behavior.
  • V1 uses a bounded JSON envelope behind generated clients; modules never construct it directly, and the contract can later map to WIT.
  • The old and new internal paths coexist during migration, so parity tests and prompt removal of the old path are required.

Validation

  • Mandatory code-quality review: PASS with no findings after three review iterations.
  • git diff --check: PASS.
  • cargo fmt --check: PASS.
  • This is a documentation-only PR. Workspace tests, clippy, DST review, local end-to-end execution, deployment, and Datadog proof are required in the implementation PR.

@nikstern
nikstern marked this pull request as ready for review July 24, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant