# Schema Required Parameters

## Problem

The enriched schema exposed via `plexus_activation_schema` RPC doesn't include the `required` array at the params level, even though `schemars` generates it correctly.

### What schemars Generates

```json
{
  "oneOf": [
    {
      "properties": {
        "method": { "enum": ["node_create_text"] },
        "params": {
          "type": "object",
          "required": ["tree_id", "content"],  // ✅ Generated by schemars
          "properties": {
            "tree_id": { "type": "string" },
            "content": { "type": "string" },
            "parent": { "type": ["string", "null"] },
            "metadata": true
          }
        }
      }
    }
  ]
}
```

### What Clients Receive

```json
{
  "oneOf": [
    {
      "properties": {
        "method": { "type": "string" },
        "params": {
          "type": "object",
          // ❌ NO required array!
          "properties": {
            "tree_id": { "type": "string", "format": "uuid" },
            "content": { "type": "string" }
          }
        }
      }
    }
  ]
}
```

## Root Cause

The `SchemaProperty` struct in `src/plexus/schema.rs` is missing the `required` field:

```rust
pub struct SchemaProperty {
    #[serde(rename = "type")]
    pub property_type: Option<Value>,
    pub description: Option<String>,
    pub format: Option<String>,
    pub properties: Option<HashMap<String, SchemaProperty>>,
    // ❌ required field is missing!

    #[serde(flatten)]
    pub additional: HashMap<String, Value>,  // It ends up here instead
}
```

When substrate deserializes the `schemars` output:

```rust
// In arbor/activation.rs
let schema_json = ArborMethod::schema();  // Has required: ["tree_id", "content"]

let mut schema: Schema = serde_json::from_value(schema_json)
    .expect("Failed to parse schema");
// ← required array goes into SchemaProperty.additional, not exposed
```

Later when serializing to send to clients, `additional` fields aren't properly propagated.

## Impact on Frontends

Without the `required` array, CLI frontends cannot:

1. **Validate required params client-side** - all params treated as optional
2. **Show helpful errors** - users get server errors instead:
   ```
   Subscription error: RpcErrorObj {errCode = -32602,
     errMessage = "Invalid params",
     errData = "missing field `tree_id`"}
   ```
3. **Generate correct help text** - can't distinguish required vs optional flags:
   ```
   # Current (all optional):
   Usage: symbols-dyn arbor node-create-text [--tree-id UUID] [--content TEXT]

   # Should be (required shown):
   Usage: symbols-dyn arbor node-create-text --tree-id UUID --content TEXT
                                             [--parent UUID] [--metadata JSON]
   ```

## Solution

Add `required` field to `SchemaProperty`:

```rust
pub struct SchemaProperty {
    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
    pub property_type: Option<Value>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub format: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub properties: Option<HashMap<String, SchemaProperty>>,

    // ✅ Add this:
    #[serde(skip_serializing_if = "Option::is_none")]
    pub required: Option<Vec<String>>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub default: Option<Value>,

    // ...
}
```

### Why This Works

1. `schemars` generates `required` at the params level
2. `serde_json::from_value` deserializes it into `SchemaProperty.required`
3. When serializing to send to clients, `required` is included
4. Clients can now validate required params

### Implementation Notes

- The `skip_serializing_if = "Option::is_none"` ensures empty `required` arrays aren't sent
- This is a **pure additive change** - existing code continues to work
- The field was always present in the JSON, just not captured in the type

## Testing

```rust
#[test]
fn test_schema_preserves_required() {
    let schema_json = ArborMethod::schema();
    let schema: Schema = serde_json::from_value(schema_json).unwrap();

    // Check node_create_text variant
    let variant = &schema.one_of.unwrap()[7];  // NodeCreateText
    let params = variant.properties.as_ref().unwrap().get("params").unwrap();
    let required = params.required.as_ref().unwrap();

    assert!(required.contains(&"tree_id".to_string()));
    assert!(required.contains(&"content".to_string()));
    assert!(!required.contains(&"parent".to_string()));  // Optional
}
```

## Expected Client Behavior After Fix

```bash
$ symbols-dyn arbor node-create-text
Missing: --tree-id UUID --content TEXT

Usage: symbols-dyn arbor node-create-text --tree-id UUID --content TEXT
                                          [--parent UUID] [--metadata JSON]

Required options:
  --tree-id UUID           UUID of the tree
  --content TEXT           Text content for the node

Optional:
  --parent UUID            UUID of the parent node (optional)
  --metadata JSON          Optional node metadata
  -h,--help                Show this help text
```

## Related

- Frontend issue tracking this: `symbols/docs/architecture/16680915202275612671_required-params-validation.md`
- Demonstrates schemars generates `required` correctly
- Documents workarounds frontends currently use (infer from nullability)
