OSS-first docs

These docs teach the open system first: contracts, generated surfaces, runtimes, governance, and incremental adoption. Studio shows up as the operating layer on top, not as the source of truth.

Spec-driven validation + typing

Define a single operation with SchemaModel, generate validation, and keep your existing handler logic.

What you'll build

  • One command spec with explicit input/output models.
  • Validation + typing without rewriting your service layer.
  • Clear acceptance scenarios for regression safety.

1) Define the spec

Create src/contracts/contact-create.operation.ts:

src/contracts/contact-create.operation.ts
import { defineCommand } from "@contractspec/lib.contracts-spec/operations";
import { SchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";

const ContactInput = new SchemaModel({
  name: "ContactInput",
  fields: {
    email: { type: ScalarTypeEnum.Email(), isOptional: false },
    firstName: { type: ScalarTypeEnum.NonEmptyString(), isOptional: false },
    lastName: { type: ScalarTypeEnum.NonEmptyString(), isOptional: false },
  },
});

const ContactOutput = new SchemaModel({
  name: "ContactOutput",
  fields: {
    id: { type: ScalarTypeEnum.String(), isOptional: false },
    email: { type: ScalarTypeEnum.Email(), isOptional: false },
    createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
  },
});

export const ContactCreateCommand = defineCommand({
  meta: {
    key: "contact.create",
    version: "1.0.0",
    description: "Create a CRM contact",
    owners: ["@sales"],
    tags: ["crm", "contacts"],
  },
  io: {
    input: ContactInput,
    output: ContactOutput,
  },
  policy: {
    auth: "user",
  },
  acceptance: {
    scenarios: [
      {
        key: "create-contact",
        given: ["User is authenticated"],
        when: ["ContactCreateCommand executes"],
        then: ["Contact is persisted", "Email is validated"],
      },
    ],
  },
});

2) Wire the handler

Keep your existing code. Just ensure the handler returns the output shape defined above.

src/handlers/contact-create.ts
import { ContactCreateCommand } from "@/contracts/contact-create.operation";
 
 export async function handleContactCreate(
   input: (typeof ContactCreateCommand)["io"]["input"],
   ctx: { userId: string }
 ) {
   // Your existing persistence logic
   return {
     id: "contact_123",
     email: input.email,
     createdAt: new Date().toISOString(),
   };
 }

3) Validate

spec-validation
contractspec validate src/contracts/contact-create.operation.ts

Expected output: Validation passed.

Example package

The CRM Pipeline example includes real specs, handlers, and presentations for contact + deal flows.

crm-example
# Build + validate the CRM pipeline example
cd packages/examples/crm-pipeline
bun install
bun run build
bun run validate

Need validation tied to real outcomes?

Studio links checks to evidence, focus decisions, and post-release verification so specs evolve with product truth.

See what Studio adds
OSS docsbuildStart with OSS. Adopt Studio when you want the operating layer.

Why ContractSpec

Keep educational and comparison content reachable without letting it define the primary OSS learning path.