Skip to content

Missing spec/ Directory and generate_spec.sh Script #9

@dcguim

Description

@dcguim

Summary

The sdk/python/generate_models.sh and generate_ts_schema_types.js scripts both expect a spec/ directory containing pre-resolved JSON schemas. However:

  1. spec/ is gitignored (line 3 of .gitignore)
  2. No script exists to generate spec/ from source/schemas/
  3. Running the generators fails with FileNotFoundError: /path/to/spec

Background

UCP schemas in source/schemas/ contain ucp_request and ucp_response annotations that control field visibility per direction and operation. These annotations must be resolved before generating SDK models.

Per CONTRIBUTING.md:

"Schemas live in source/ and are published with ucp_* annotations intact. Agents use ucp-schema to resolve annotations for specific operations at runtime."

Current State of Scripts

Script Input Output Status
main.py (MkDocs) source/schemas/ Resolves at runtime Works - calls ucp-schema resolve on-demand
generate_ts_schema_types.js spec/ generated/schema-types.ts Broken - spec/ doesn't exist
sdk/python/generate_models.sh ../../spec/ src/ucp_sdk/models/ Broken - spec/ doesn't exist

How main.py Resolves Schemas (lines 88-137)

cmd = [
    "ucp-schema",
    "resolve",
    str(schema_path),
    "--request" if direction == "request" else "--response",
    "--op",
    operation,
]
if bundle:
    cmd.append("--bundle")

result = subprocess.run(cmd, capture_output=True, text=True, check=False)

This works for documentation because it resolves at runtime. But the SDK generators need pre-resolved schemas in spec/.

Missing Models

Comparing source/schemas/shopping/ with models/schemas/shopping/:

UCP Schema Models Generated
cart.json None - completely missing

All other schemas (checkout.json, discount.json, fulfillment.json, buyer_consent.json, ap2_mandate.json, order.json, payment.json) have corresponding models.

Proposed Solution: generate_spec.sh

Create a script that populates spec/ from source/schemas/ using ucp-schema resolve. This script should:

  1. Resolve annotated schemas for all directions/operations
  2. Bundle $ref pointers for self-contained schemas
  3. Mirror the structure expected by existing generators

Suggested Implementation

#!/bin/bash
# generate_spec.sh - Generate resolved schemas into spec/ directory
#
# This script uses ucp-schema to resolve UCP annotations from source/schemas/
# and outputs self-contained JSON schemas to spec/ for SDK generators.

set -e

cd "$(dirname "$0")"

# Directories
SOURCE_DIR="source/schemas"
SPEC_DIR="spec"

# Schemas with UCP annotations
ANNOTATED_SCHEMAS=(
    "shopping/checkout.json"
    "shopping/cart.json"
    "shopping/discount.json"
    "shopping/fulfillment.json"
    "shopping/ap2_mandate.json"
    "shopping/buyer_consent.json"
)

# Schemas without UCP annotations (just bundle refs)
PLAIN_SCHEMAS=(
    "shopping/order.json"
    "shopping/payment.json"
)

# Operations for request direction (matching existing models)
REQUEST_OPS=("create" "update")

# Check for ucp-schema
if ! command -v ucp-schema &> /dev/null; then
    echo "Error: ucp-schema not found."
    echo "Install with: cargo install ucp-schema"
    exit 1
fi

# Setup directories
echo "Setting up spec/ directory..."
rm -rf "$SPEC_DIR"
mkdir -p "$SPEC_DIR/schemas/shopping"
mkdir -p "$SPEC_DIR/handlers"

# Resolve annotated schemas
echo "Resolving annotated schemas..."
for schema in "${ANNOTATED_SCHEMAS[@]}"; do
    basename=$(basename "$schema" .json)
    echo "  Processing $basename..."

    # Response schema (read operation)
    ucp-schema resolve "$SOURCE_DIR/$schema" \
        --response --op read --bundle --pretty \
        > "$SPEC_DIR/schemas/shopping/${basename}_resp.json"

    # Request schemas for each operation
    for op in "${REQUEST_OPS[@]}"; do
        ucp-schema resolve "$SOURCE_DIR/$schema" \
            --request --op "$op" --bundle --pretty \
            > "$SPEC_DIR/schemas/shopping/${basename}_${op}_req.json"
    done
done

# Bundle plain schemas
echo "Bundling plain schemas..."
for schema in "${PLAIN_SCHEMAS[@]}"; do
    basename=$(basename "$schema" .json)
    echo "  Bundling $basename..."
    ucp-schema resolve "$SOURCE_DIR/$schema" \
        --response --op read --bundle --pretty \
        > "$SPEC_DIR/schemas/shopping/${basename}.json"
done

# Copy types (no resolution needed)
echo "Copying type schemas..."
mkdir -p "$SPEC_DIR/schemas/shopping/types"
cp "$SOURCE_DIR/shopping/types/"*.json "$SPEC_DIR/schemas/shopping/types/"

echo ""
echo "Done! Resolved schemas written to $SPEC_DIR/"
find "$SPEC_DIR" -name "*.json" | wc -l | xargs echo "Total JSON files:"

Updated Workflow

After adding generate_spec.sh:

source/schemas/  --(generate_spec.sh)-->  spec/  --(generate_models.sh)-->  models/
                         |                  |
                         |                  +--(generate_ts_schema_types.js)--> generated/
                         |
                   uses ucp-schema resolve

CI Integration

Add to .github/workflows/docs.yml or create a new workflow:

- name: Generate spec/ from source/
  run: ./generate_spec.sh

- name: Generate Python models
  run: bash sdk/python/generate_models.sh

- name: Generate TypeScript types
  run: node generate_ts_schema_types.js

Previous Attempt

I attempted to create a workaround script at /Users/dguim/localwork/ucp/generate_pydantic_models.sh that:

  1. Used ucp-schema resolve to generate resolved schemas
  2. Ran datamodel-code-generator on the output

However, this produced problematic Pydantic models. For example, in the generated response/checkout.py:

class Instrument(Checkout):
    """A payment instrument with selection state."""

    model_config = ConfigDict(
        extra="allow",
    )
    selected: bool | None = None

This incorrectly has Instrument inheriting from Checkout, which doesn't make semantic sense - a payment instrument should not be a full checkout object. This appears to be an artifact of how datamodel-code-generator interprets the allOf composition in the bundled JSON Schema.

This suggests that either:

  1. The schema bundling strategy needs adjustment
  2. datamodel-code-generator options need tuning
  3. The spec/ structure should be different from what I attempted

Questions for Maintainers

  1. Is this the intended approach? Should spec/ be generated from source/ using ucp-schema?

  2. Naming convention: Should resolved schemas use {name}_resp.json / {name}_{op}_req.json format, or a different structure?

  3. Why is Cart missing? cart.json exists in source/schemas/shopping/ but has no generated models.

  4. Handler schemas: Should source/handlers/ also be resolved into spec/handlers/?

Related Files

  • .gitignore line 3: /spec/
  • main.py lines 88-137: Runtime resolution for docs
  • generate_ts_schema_types.js line 5: SOURCE_ROOT = path.resolve(__dirname, 'spec')
  • sdk/python/generate_models.sh line 11: SCHEMA_DIR="../../spec/"
  • ucp-schema CLI: https://github.com/universal-commerce-protocol/ucp-schema

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions