{
  "$comment": "Source of truth for the calendar class. PROFILE.md and SKILL.md are views and must not disagree.",
  "id": "org.classprofiles.calendar",
  "state": "draft",
  "title": "Calendar",
  "summary": "Read, create, change, and cancel events, and find free time, on any calendar backend.",
  "ten_jobs": [
    "See what's on my calendar today/this week",
    "Create a simple timed event",
    "Create an all-day event",
    "Move an event to a new time",
    "Change an event's details (title, location, notes)",
    "Cancel an event",
    "Find a free slot of a given length in a window",
    "Check whether a specific time is free",
    "Look up the details of a known event",
    "Invite someone to an event I'm creating"
  ],
  "objects": {
    "Event": {
      "$comment": "The shared event shape. Unknown response fields must be preserved by intermediaries.",
      "type": "object",
      "required": [
        "id",
        "title",
        "start",
        "end",
        "state"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "description": "Opaque. Never parse."
        },
        "title": {
          "type": "string"
        },
        "start": {
          "type": "string",
          "format": "date-time",
          "description": "RFC 3339 with explicit offset."
        },
        "end": {
          "type": "string",
          "format": "date-time"
        },
        "timezone": {
          "type": "string",
          "description": "IANA name, e.g. America/New_York."
        },
        "all_day": {
          "type": "boolean",
          "default": false
        },
        "location": {
          "type": "string"
        },
        "description_md": {
          "type": "string"
        },
        "attendees": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "email"
            ],
            "properties": {
              "email": {
                "type": "string",
                "format": "email"
              },
              "name": {
                "type": "string"
              },
              "response": {
                "type": "string",
                "enum": [
                  "accepted",
                  "declined",
                  "tentative",
                  "none"
                ],
                "default": "none"
              }
            }
          }
        },
        "state": {
          "type": "string",
          "enum": [
            "confirmed",
            "tentative",
            "cancelled"
          ]
        }
      }
    },
    "FreeSlot": {
      "type": "object",
      "required": [
        "start",
        "end"
      ],
      "properties": {
        "start": {
          "type": "string",
          "format": "date-time"
        },
        "end": {
          "type": "string",
          "format": "date-time"
        }
      }
    }
  },
  "verbs": [
    {
      "id": "org.classprofiles.calendar/list_events@1",
      "name": "calendar.list_events",
      "tier": "baseline",
      "since": "org.classprofiles.calendar/core@1",
      "summary": "Returns entries overlapping a time range, optionally narrowed by text.",
      "risk": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "example": {
        "request": {
          "range_start": "2026-02-23T00:00:00-05:00",
          "range_end": "2026-02-28T00:00:00-05:00",
          "query": "dentist"
        },
        "result": {
          "structuredContent": {
            "events": [
              {
                "id": "evt_8f3k",
                "title": "Dentist",
                "start": "2026-02-25T09:00:00-05:00",
                "end": "2026-02-25T09:45:00-05:00",
                "timezone": "America/New_York",
                "state": "confirmed"
              }
            ]
          }
        }
      },
      "tool": {
        "inputSchema": {
          "type": "object",
          "required": [
            "range_start",
            "range_end"
          ],
          "properties": {
            "range_start": {
              "type": "string",
              "format": "date-time",
              "description": "Inclusive start of the half-open overlap range."
            },
            "range_end": {
              "type": "string",
              "format": "date-time",
              "description": "Exclusive end of the half-open overlap range."
            },
            "query": {
              "type": "string",
              "description": "Case-insensitive substring match on title, location, or description_md."
            },
            "limit": {
              "type": "integer",
              "minimum": 1,
              "default": 50
            },
            "cursor": {
              "type": "string",
              "description": "Opaque continuation token; reuse with the same range and query."
            }
          }
        }
      },
      "contract": {
        "structuredContent": {
          "type": "object",
          "required": [
            "events"
          ],
          "properties": {
            "events": {
              "type": "array",
              "description": "Ordered by start instant, then opaque id.",
              "items": {
                "$ref": "#/objects/Event"
              }
            },
            "next_cursor": {
              "type": "string",
              "description": "Present only when another page exists."
            }
          }
        }
      }
    },
    {
      "id": "org.classprofiles.calendar/get_event@1",
      "name": "calendar.get_event",
      "tier": "baseline",
      "since": "org.classprofiles.calendar/core@1",
      "summary": "Fetches one event by opaque identity.",
      "risk": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "example": {
        "request": {
          "event_id": "evt_8f3k"
        },
        "result": {
          "structuredContent": {
            "id": "evt_8f3k",
            "title": "Dentist",
            "start": "2026-02-25T09:00:00-05:00",
            "end": "2026-02-25T09:45:00-05:00",
            "timezone": "America/New_York",
            "state": "confirmed"
          }
        }
      },
      "tool": {
        "inputSchema": {
          "type": "object",
          "required": [
            "event_id"
          ],
          "properties": {
            "event_id": {
              "type": "string"
            }
          }
        }
      },
      "contract": {
        "structuredContent": {
          "$ref": "#/objects/Event"
        }
      }
    },
    {
      "id": "org.classprofiles.calendar/create_event@1",
      "name": "calendar.create_event",
      "tier": "baseline",
      "since": "org.classprofiles.calendar/core@1",
      "summary": "Adds a timed or all-day entry, with optional invitations.",
      "risk": {
        "destructiveHint": false
      },
      "example": {
        "request": {
          "title": "Sprint review",
          "start": "2026-02-26T15:00:00-05:00",
          "end": "2026-02-26T16:00:00-05:00",
          "timezone": "America/New_York",
          "attendees": [
            {
              "email": "priya@example.com",
              "name": "Priya"
            }
          ]
        },
        "result": {
          "structuredContent": {
            "id": "evt_a91x",
            "title": "Sprint review",
            "start": "2026-02-26T15:00:00-05:00",
            "end": "2026-02-26T16:00:00-05:00",
            "timezone": "America/New_York",
            "state": "confirmed",
            "attendees": [
              {
                "email": "priya@example.com",
                "name": "Priya"
              }
            ]
          }
        }
      },
      "socially_irreversible": true,
      "$risk_note": "The flag is conservative because supplying attendees can send invitations other people observe.",
      "tool": {
        "inputSchema": {
          "type": "object",
          "required": [
            "title",
            "start",
            "end"
          ],
          "properties": {
            "title": {
              "type": "string"
            },
            "start": {
              "type": "string",
              "format": "date-time"
            },
            "end": {
              "type": "string",
              "format": "date-time"
            },
            "timezone": {
              "type": "string"
            },
            "all_day": {
              "type": "boolean",
              "default": false
            },
            "location": {
              "type": "string"
            },
            "description_md": {
              "type": "string"
            },
            "attendees": {
              "type": "array",
              "items": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "contract": {
        "structuredContent": {
          "$ref": "#/objects/Event"
        }
      }
    },
    {
      "id": "org.classprofiles.calendar/update_event@1",
      "name": "calendar.update_event",
      "tier": "baseline",
      "since": "org.classprofiles.calendar/core@1",
      "summary": "Changes selected event details while preserving everything omitted.",
      "example": {
        "request": {
          "event_id": "evt_a91x",
          "patch": {
            "start": "2026-02-26T16:00:00-05:00",
            "end": "2026-02-26T17:00:00-05:00"
          }
        },
        "result": {
          "structuredContent": {
            "id": "evt_a91x",
            "title": "Sprint review",
            "start": "2026-02-26T16:00:00-05:00",
            "end": "2026-02-26T17:00:00-05:00",
            "timezone": "America/New_York",
            "state": "confirmed"
          }
        }
      },
      "socially_irreversible": true,
      "$risk_note": "An update may notify attendees or change a schedule that other people observe.",
      "tool": {
        "inputSchema": {
          "type": "object",
          "required": [
            "event_id",
            "patch"
          ],
          "properties": {
            "event_id": {
              "type": "string"
            },
            "patch": {
              "type": "object",
              "minProperties": 1,
              "properties": {
                "title": {
                  "type": "string"
                },
                "start": {
                  "type": "string",
                  "format": "date-time"
                },
                "end": {
                  "type": "string",
                  "format": "date-time"
                },
                "timezone": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "location": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "description_md": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "contract": {
        "structuredContent": {
          "$ref": "#/objects/Event"
        }
      }
    },
    {
      "id": "org.classprofiles.calendar/cancel_event@1",
      "name": "calendar.cancel_event",
      "tier": "baseline",
      "since": "org.classprofiles.calendar/core@1",
      "summary": "Cancels an entry while keeping it fetchable as a tombstone.",
      "example": {
        "request": {
          "event_id": "evt_a91x",
          "notify_attendees": true
        },
        "result": {
          "structuredContent": {
            "id": "evt_a91x",
            "title": "Sprint review",
            "start": "2026-02-26T16:00:00-05:00",
            "end": "2026-02-26T17:00:00-05:00",
            "timezone": "America/New_York",
            "state": "cancelled"
          }
        }
      },
      "socially_irreversible": true,
      "$risk_note": "The flag is conservative because cancellation may notify attendees unless the call disables notification.",
      "tool": {
        "inputSchema": {
          "type": "object",
          "required": [
            "event_id"
          ],
          "properties": {
            "event_id": {
              "type": "string"
            },
            "notify_attendees": {
              "type": "boolean",
              "default": true
            }
          }
        }
      },
      "contract": {
        "structuredContent": {
          "$ref": "#/objects/Event"
        }
      }
    },
    {
      "id": "org.classprofiles.calendar/find_free_time@1",
      "name": "calendar.find_free_time",
      "tier": "baseline",
      "since": "org.classprofiles.calendar/core@1",
      "summary": "Finds free intervals of the requested minimum length inside a time window.",
      "risk": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "example": {
        "request": {
          "duration_minutes": 45,
          "window_start": "2026-02-26T09:00:00-05:00",
          "window_end": "2026-02-26T18:00:00-05:00",
          "max_results": 3
        },
        "result": {
          "structuredContent": {
            "slots": [
              {
                "start": "2026-02-26T09:00:00-05:00",
                "end": "2026-02-26T10:30:00-05:00"
              },
              {
                "start": "2026-02-26T13:00:00-05:00",
                "end": "2026-02-26T15:00:00-05:00"
              }
            ]
          }
        }
      },
      "tool": {
        "inputSchema": {
          "type": "object",
          "required": [
            "duration_minutes",
            "window_start",
            "window_end"
          ],
          "properties": {
            "duration_minutes": {
              "type": "integer",
              "minimum": 1
            },
            "window_start": {
              "type": "string",
              "format": "date-time",
              "description": "Inclusive start of the half-open search window."
            },
            "window_end": {
              "type": "string",
              "format": "date-time",
              "description": "Exclusive end of the half-open search window."
            },
            "max_results": {
              "type": "integer",
              "minimum": 1,
              "default": 5
            }
          }
        }
      },
      "contract": {
        "structuredContent": {
          "type": "object",
          "required": [
            "slots"
          ],
          "properties": {
            "slots": {
              "type": "array",
              "items": {
                "$ref": "#/objects/FreeSlot"
              }
            }
          }
        }
      }
    },
    {
      "id": "org.classprofiles.calendar/respond@1",
      "name": "calendar.respond",
      "tier": "facet",
      "since": "calendar/rsvp@1",
      "summary": "Accepts, declines, or tentatively answers an invitation for the current user.",
      "risk": {
        "destructiveHint": false
      },
      "degrades_to": {
        "target": null,
        "loss": "RSVP is unavailable; the user must respond in the vendor app.",
        "mode": "invisible"
      },
      "example": {
        "request": {
          "event_id": "evt_8f3k",
          "response": "accepted"
        },
        "result": {
          "structuredContent": {
            "id": "evt_8f3k",
            "title": "Dentist",
            "start": "2026-02-25T09:00:00-05:00",
            "end": "2026-02-25T09:45:00-05:00",
            "state": "confirmed",
            "attendees": [
              {
                "email": "leo@example.com",
                "response": "accepted"
              }
            ]
          }
        }
      },
      "socially_irreversible": true,
      "$risk_note": "The response is visible to the invitation organizer and may trigger notifications.",
      "facet": "calendar/rsvp@1",
      "tool": {
        "inputSchema": {
          "type": "object",
          "required": [
            "event_id",
            "response"
          ],
          "properties": {
            "event_id": {
              "type": "string"
            },
            "response": {
              "type": "string",
              "enum": [
                "accepted",
                "declined",
                "tentative"
              ]
            }
          }
        }
      },
      "contract": {
        "structuredContent": {
          "$ref": "#/objects/Event"
        }
      }
    }
  ],
  "properties": [
    {
      "id": "org.classprofiles.calendar/recurrence@1",
      "name": "recurrence",
      "tier": "facet",
      "since": "calendar/recurrence@1",
      "attaches_to": [
        "org.classprofiles.calendar/create_event@1",
        "objects/Event"
      ],
      "summary": "Repeats daily or weekly via canonical RRULE with bounded optional controls.",
      "degrades_to": {
        "target": "org.classprofiles.calendar/create_event@1",
        "loss": "Only the first occurrence is created; the series does not repeat.",
        "mode": "graceful"
      },
      "example": {
        "result": "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR"
      },
      "facet": "calendar/recurrence@1",
      "tool": {
        "type": "string",
        "pattern": "^RRULE:FREQ=(DAILY(;INTERVAL=[1-9][0-9]*)?|WEEKLY(;INTERVAL=[1-9][0-9]*)?(;BYDAY=(MO|TU|WE|TH|FR|SA|SU)(,(MO|TU|WE|TH|FR|SA|SU))*)?)(;(UNTIL=[1-9][0-9]{3}((0[13578]|1[02])(0[1-9]|[12][0-9]|3[01])|(0[469]|11)(0[1-9]|[12][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))T([01][0-9]|2[0-3])[0-5][0-9][0-5][0-9]Z|COUNT=[1-9][0-9]*))?$"
      },
      "contract": {
        "type": "string",
        "$comment": "Prototype 0.1 accepts recurrence only at creation; whole-series mutation is outside scope. It accepts only DAILY and WEEKLY, and BYDAY applies only to WEEKLY. Canonical field order is FREQ, optional positive INTERVAL, optional BYDAY list, then at most one of inclusive UTC UNTIL or positive COUNT. COUNT includes the initial occurrence. UNTIL deliberately excludes February 29 so calendar validity is expressible without leap-year ambiguity.",
        "pattern": "^RRULE:FREQ=(DAILY(;INTERVAL=[1-9][0-9]*)?|WEEKLY(;INTERVAL=[1-9][0-9]*)?(;BYDAY=(MO|TU|WE|TH|FR|SA|SU)(,(MO|TU|WE|TH|FR|SA|SU))*)?)(;(UNTIL=[1-9][0-9]{3}((0[13578]|1[02])(0[1-9]|[12][0-9]|3[01])|(0[469]|11)(0[1-9]|[12][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))T([01][0-9]|2[0-3])[0-5][0-9][0-5][0-9]Z|COUNT=[1-9][0-9]*))?$"
      }
    },
    {
      "id": "org.classprofiles.calendar/reminders@1",
      "name": "reminders_minutes",
      "tier": "facet",
      "since": "calendar/reminders@1",
      "attaches_to": [
        "org.classprofiles.calendar/create_event@1",
        "org.classprofiles.calendar/update_event@1#/patch",
        "objects/Event"
      ],
      "summary": "Stores requested reminder offsets as minutes before start.",
      "degrades_to": {
        "target": null,
        "loss": "Requested reminder changes are not applied; creates use backend defaults and updates leave existing reminders unchanged.",
        "mode": "graceful"
      },
      "example": {
        "result": [180, 1440]
      },
      "facet": "calendar/reminders@1",
      "tool": {
        "type": "array",
        "uniqueItems": true,
        "items": {
          "type": "integer",
          "minimum": 0
        }
      },
      "contract": {
        "type": "array",
        "uniqueItems": true,
        "items": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    {
      "id": "com.acme.cal/travel_buffer@1",
      "name": "travel_buffer_minutes",
      "tier": "vendor",
      "since": "com.acme.cal/travel_buffer@1",
      "attaches_to": [
        "org.classprofiles.calendar/create_event@1",
        "objects/Event"
      ],
      "summary": "Blocks availability before and after an entry with a physical location.",
      "degrades_to": {
        "target": "org.classprofiles.calendar/create_event@1",
        "loss": "Events are created without travel buffers; adjacent bookings may be back-to-back.",
        "mode": "graceful"
      },
      "example": {
        "result": { "before": 30, "after": 30 }
      },
      "tool": {
        "type": "object",
        "required": [
          "before",
          "after"
        ],
        "properties": {
          "before": {
            "type": "integer",
            "minimum": 0
          },
          "after": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "contract": {
        "type": "object",
        "required": [
          "before",
          "after"
        ],
        "properties": {
          "before": {
            "type": "integer",
            "minimum": 0
          },
          "after": {
            "type": "integer",
            "minimum": 0
          }
        }
      }
    }
  ],
  "sandbox_contract": "Before every fixture scenario, implementations reset an otherwise-empty scratch calendar isolated from user data; fixtures run in the reference week starting Monday 2026-02-23 (America/New_York; deliberately pre-DST — a DST-straddling edge suite is deferred beyond Prototype 0.1). The only exception is a calendar/rsvp@1 scenario: reset state contains exactly one query-matchable event titled 'Class Profiles RSVP fixture invitation', organized by organizer@example.com and inviting the sandbox principal participant@example.com with semantic response 'none', represented on the sparse wire by an omitted response field.",
  "licenses": {
    "spec": "Apache-2.0"
  },
  "baseline": "org.classprofiles.calendar/core@1",
  "digest_note": "Prototype 0.1 artifacts are undigested and do not yet define canonical serialization. Publication is blocked until a later revision specifies reproducible canonical bytes.",
  "facets": [
    {
      "id": "calendar/recurrence@1",
      "summary": "Daily/weekly RRULE subset with positive intervals/counts, weekdays, and UTC cutoffs."
    },
    {
      "id": "calendar/rsvp@1",
      "summary": "Record the user's response to an invitation (accepted, declined, tentative)."
    },
    {
      "id": "calendar/reminders@1",
      "summary": "Persisted reminder configuration expressed as minutes before start; delivery is out of scope."
    }
  ]
}
