Skip to content
Back to skills

Grain Schema

ASecurity

Define Malli schemas for commands, events, and queries using defschemas

  • 20 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 12, 2026
documentationgo

Security analysis

A100/100

Scanned September 12, 2026

npx -y skills add ObneyAI/orc --skill grain-schema --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Grain Schema?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Grain Schema
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/obneyai-grain-schema/badge)](https://www.skillsdirectory.com/skills/obneyai-grain-schema)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: grain-schema
description: Define Malli schemas for commands, events, and queries using defschemas
---

# Grain Schema Pattern

## Required Imports

```clojure
(ns my.service.interface.schemas
  (:require [ai.obney.grain.schema-util.interface :refer [defschemas]]))
```

## Schema Registration

Use `defschemas` to register named Malli schemas:

```clojure
(defschemas schema-group-name
  {:schema-key schema-definition
   :another-key another-definition})
```

## Command Schemas

Naming: `:namespace/action` (verb form)

```clojure
(defschemas commands
  {;; Create command
   :crm/create-contact
   [:map
    [:type-slug :string]
    [:field-values [:map-of :keyword :any]]
    [:contact-id {:optional true} :uuid]
    [:tags {:optional true} [:set :string]]]

   ;; Update command
   :crm/update-contact
   [:map
    [:contact-id :uuid]
    [:changes [:map-of :keyword :any]]]

   ;; Action command
   :advisor/start-meeting
   [:map
    [:student-id :uuid]
    [:meeting-type [:enum :intake-interview :freeform]]]})
```

## Event Schemas

Naming: `:namespace/entity-past-tense` (what happened)

```clojure
(defschemas events
  {;; Created event
   :crm/contact-created
   [:map
    [:contact-id :uuid]
    [:type-id :uuid]
    [:type-slug :string]
    [:display-name :string]
    [:field-values [:map-of :keyword :any]]
    [:tags [:set :string]]]

   ;; Updated event
   :crm/contact-updated
   [:map
    [:contact-id :uuid]
    [:changes [:map-of :keyword :any]]]

   ;; Action completed event
   :advisor/meeting-started
   [:map
    [:meeting-id :uuid]
    [:student-id :uuid]
    [:meeting-type [:enum :intake-interview :freeform]]
    [:started-at :string]]})
```

## Query Schemas

Naming: `:namespace/query-name` (what to get)

```clojure
(defschemas queries
  {:crm/get-contact
   [:map
    [:contact-id :uuid]]

   :crm/list-contacts
   [:map
    [:type-slug {:optional true} :string]
    [:status {:optional true} [:enum :active :inactive :archived]]
    [:limit {:optional true} :int]
    [:offset {:optional true} :int]]

   :advisor/meetings-screen
   [:map
    [:student-id :uuid]]})
```

## Common Malli Types

```clojure
:string                              ;; String
:int                                 ;; Integer
:double                              ;; Float
:boolean                             ;; Boolean
:uuid                                ;; UUID
:keyword                             ;; Keyword
:any                                 ;; Any value

[:enum :a :b :c]                     ;; Enum
[:set :string]                       ;; Set of strings
[:vector :uuid]                      ;; Vector of UUIDs
[:map-of :keyword :any]              ;; Map with keyword keys

[:map                                ;; Map with specific keys
 [:required-key :string]
 [:optional-key {:optional true} :int]]

[:re #"^[a-z]+$"]                    ;; Regex pattern
```

## Custom Type Registration

```clojure
(defschemas custom-types
  {:email [:re #"^[^@\s]+@[^@\s]+\.[^@\s]+$"]
   :phone [:re #"^\d{10,15}$"]
   :encrypted [:map
               [:ciphertext :string]
               [:encrypted-key :string]
               [:iv :string]]})
```

## Reusable Sub-Schemas

```clojure
(def attribution-source
  [:enum :application_form :intake_form :referral :manual_entry])

(def attribution
  [:map
   [:source attribution-source]
   [:source-details {:optional true} :any]
   [:referring-contact-id {:optional true} :uuid]
   [:recorded-at :string]])

(defschemas events
  {:crm/attribution-recorded
   [:map
    [:contact-id :uuid]
    [:attribution attribution]]})
```

## Conventions

- Group schemas by category: commands, events, queries
- Commands: `:namespace/verb` (create, update, delete, start, etc.)
- Events: `:namespace/entity-past-tense` (created, updated, started)
- Queries: `:namespace/get-entity` or `:namespace/list-entities`
- Use `{:optional true}` for optional fields
- Define reusable sub-schemas as defs
- Match event schemas to what `->event` produces

## Reference Files

- `components/user-service/src/ai/obney/workshop/user_service/interface/schemas.clj` - User schemas
- `components/crm-service/src/ai/obney/workshop/crm_service/interface/schemas.clj` - CRM schemas with custom types

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.