Contracts
retrospect-cli.contract.md
CLI Contract: spec-kitty retrospect *
Mission: retrospective-default-policy-01KS049J Phase: 1 — Contracts
This contract specifies the user-facing surfaces this mission adds to or changes on the spec-kitty retrospect and spec-kitty agent retrospect namespaces. JSON output shapes are stable contracts.
spec-kitty retrospect create
Author a retrospective for one completed mission.
Usage
spec-kitty retrospect create --mission <handle> [--overwrite | --update] [--json]
| Flag | Type | Description |
|---|---|---|
--mission <handle> | required | mission_id (ULID), mid8, or mission_slug. Resolver disambiguates by mission_id; ambiguous handles return MISSION_AMBIGUOUS_SELECTOR. |
--overwrite | optional | Replace an existing record. Mutually exclusive with --update. |
--update | optional | Merge into an existing record per the merge semantics in data-model.md. Mutually exclusive with --overwrite. |
--json | optional | Emit a JSON contract (below). When absent, emit a Rich human-readable summary. |
Exit codes
0— record authored (or merged) successfully.1— error (record already exists without--overwrite/--update, mission not completed, missing artifacts, validation failure, etc.).2— invalid invocation (bad flags, ambiguous mission handle).
JSON output (success)
{
"result": "success",
"mission_id": "01J6XW9KQT7M0YB3N4R5CQZ2EX",
"mission_slug": "my-feature-01J6XW9K",
"record_path": "/abs/path/.kittify/missions/<mission_id>/retrospective.yaml",
"findings_status": "has_findings",
"counts": {"helped": 4, "not_helpful": 2, "gaps": 1, "proposals": 3, "evidence_refs": 10},
"provenance_kind": "explicit_create",
"policy_source": {"enabled": "<default>", "timing": "<default>", "failure_policy": "<default>"},
"next_step": "Run `spec-kitty agent retrospect synthesize --mission <handle> --preview` to review proposals."
}
JSON output (error: record already exists)
{
"result": "blocked",
"code": "RETROSPECTIVE_RECORD_EXISTS",
"mission_id": "01J6XW9KQT7M0YB3N4R5CQZ2EX",
"mission_slug": "my-feature-01J6XW9K",
"record_path": "/abs/path/.kittify/missions/<mission_id>/retrospective.yaml",
"blocked_reason": "A retrospective record already exists for this mission. Pass --overwrite to replace it or --update to merge.",
"exit_code": 1
}
JSON output (error: mission not completed)
{
"result": "blocked",
"code": "MISSION_NOT_COMPLETED",
"mission_id": "01J6XW9KQT7M0YB3N4R5CQZ2EX",
"mission_slug": "my-feature-01J6XW9K",
"blocked_reason": "Mission has WPs in non-terminal lanes: WP02 (in_progress), WP04 (for_review). Complete the mission before authoring a retrospective.",
"open_wps": [{"wp_id": "WP02", "lane": "in_progress"}, {"wp_id": "WP04", "lane": "for_review"}],
"exit_code": 1
}
Side effects
- On success: writes the YAML record at
record_path; appends aRetrospectiveCapturedevent tokitty-specs/<mission_slug>/status.events.jsonl(or the canonical event log path the runtime uses). - On
--update: appends toprovenance_history[]in the record, replaces top-levelprovenanceandpolicy_source. - Auto-commit policy: if
.kittify/config.yaml#agents.auto_commit: true(the project default), the record + event log change are committed with a structured message. Otherwise the operator commits.
spec-kitty retrospect backfill
Author records for historical missions.
Usage
spec-kitty retrospect backfill [--since <ISO-date>] [--until <ISO-date>] [--mission <handle>]
[--dry-run] [--emit-skipped] [--emit-failures] [--json]
| Flag | Type | Description |
|---|---|---|
--since <ISO-date> | optional | Only consider missions whose completion timestamp ≥ this date. Default: 30 days ago. |
--until <ISO-date> | optional | Only consider missions whose completion timestamp ≤ this date. Default: now. |
--mission <handle> | optional | Restrict to a single mission. Skips other missions in scope. |
--dry-run | optional | Report what would be authored without writing. |
--emit-skipped | optional | Append a RetrospectiveCaptured event with provenance noting the skip. Default: skips are CLI-output only. |
--emit-failures | optional | Append RetrospectiveCaptureFailed events for failed candidates. Default: failures are CLI-output only. |
--json | optional | Emit JSON contract (below). |
Exit codes
0— all candidates processed cleanly (some may have been skipped or failed; aggregate report in stdout).1— fatal error (e.g. malformed.kittify/config.yaml, missing critical infrastructure). Per-mission failures are NOT fatal.2— invalid invocation.
JSON output
{
"result": "success",
"window": {"since": "2026-01-01", "until": "2026-05-19"},
"scanned": 47,
"created": 12,
"skipped": [
{"mission_id": "...", "mission_slug": "...", "reason": "already_exists", "record_path": "..."},
{"mission_id": "...", "mission_slug": "...", "reason": "not_completed"},
{"mission_id": "...", "mission_slug": "...", "reason": "out_of_window"}
],
"failed": [
{"mission_id": "...", "mission_slug": "...", "failure_category": "missing_artifacts", "missing": ["status.events.jsonl"], "remediation_hint": "Mission lacks an event log; rebuild via `spec-kitty migrate normalize-lifecycle --mission <handle>`."}
],
"next_actions": [
"Run `spec-kitty agent retrospect synthesize --mission <handle> --preview` on newly authored records.",
"Inspect the 1 failed mission listed above."
]
}
Side effects
- Per successful candidate: writes record; emits
RetrospectiveCapturedevent. - Per skipped/failed candidate: CLI output only unless
--emit-skipped/--emit-failuresare passed. - Auto-commit policy: identical to
createif--dry-runis absent.
spec-kitty retrospect summary
No semantic change in this mission. This contract clarifies the read-only invariant.
Contract
summaryMUST NOT author, mutate, or write any retrospective record.summaryMUST distinguish four record states in its output:has_findings,ran_no_findings,missing(no record on disk),failed(most recentRetrospectiveCaptureFailednot followed by aRetrospectiveCaptured).- JSON output shape per the existing
retrospect summarycontract, with two added fields:policy_source(snapshot from the most recent attribution event) andfindings_status(per record).
spec-kitty agent retrospect synthesize
Tighten the fabrication fallback.
Default behavior (changed)
When invoked on a mission with no retrospective.yaml:
{
"result": "blocked",
"code": "RETROSPECTIVE_RECORD_MISSING",
"mission_id": "...",
"mission_slug": "...",
"blocked_reason": "No retrospective record found for this mission. Author one with: spec-kitty retrospect create --mission <handle>",
"exit_code": 1
}
Compatibility flag (preserved)
When invoked with --fabricate-empty on a mission with no retrospective.yaml, the legacy fabrication path runs and authors a findings_status: ran_no_findings record with provenance.kind = "synthesize_fabricate". The action is logged in the event log with actor attribution.
All other modes (unchanged)
--preview, --apply <proposal_id>, default proposal listing — identical wire contract to today. The only change is the default-path error and the new --fabricate-empty flag.
Env-var deprecation contract (FR-015, NFR-006)
SPEC_KITTY_RETROSPECTIVEandSPEC_KITTY_MODEcontinue to be honored as test/dev overrides.- First use per process emits a single
DeprecationWarningAND a single Rich stderr notice (controlled bySPEC_KITTY_NO_DEPRECATION_WARNINGS=1). - Durable policy always wins when both env and config/charter are present. The deprecation warning still emits in that case.
- The warning text MUST cite the durable replacement key path (
retrospective.enabledorretrospective.timing+retrospective.failure_policy) AND link todocs/how-to/use-retrospective-learning.md.
Cross-references
- Schemas: retrospective-policy.schema.json, retrospective-record.schema.json
- Event contracts: retrospective-events.contract.md
- Operator quickstart: ../quickstart.md
retrospective-events.contract.md
Event Contract: Retrospective Lifecycle Events
Mission: retrospective-default-policy-01KS049J Phase: 1 — Contracts
This contract specifies the new (or extended) retrospective lifecycle event payloads that join the canonical event log. Wire shape is additive; existing reducers MUST continue to reduce historical events byte-identically (FR-021).
Reuse vs new
Per research.md R-3, implementation MUST first check whether spec_kitty_events (consumed via the FR-024 frozen public surface) already exposes the retrospective lifecycle events. If yes, reuse with additive policy_source attribution. If no, the three canonical event types below (RetrospectiveCaptured, RetrospectiveCaptureFailed, RetrospectiveSkipped) land in the local emit path under specify_cli.status infrastructure.
The contracts below specify the canonical payloads regardless of whether the event-type name is new or reused; the reduction shape is identical. All three types are canonical at the time this mission ships — there is no "provisional" event in this contract.
Common envelope
All retrospective lifecycle events share the canonical event envelope used by other kitty-specs/<mission_slug>/status.events.jsonl entries:
{
"type": <event_type>,
"schema_version": 1,
"event_id": <ULID>,
"lamport": <int>,
"at": <RFC 3339 timestamp>,
"actor": {"kind": "human"|"agent"|"runtime", "id": <str>, "display": <str?>},
"mission_id": <ULID>,
"mission_slug": <str>,
"wp_id": null, # always null for mission-level retrospective events
"force": false, # retrospective events are never lane transitions
"execution_mode": "worktree" | "main",
...<event-specific fields>
}
from_lane, to_lane, review_ref, reason, feature_slug are NOT present on retrospective events — they are state-transition envelope fields only.
RetrospectiveCaptured
Fired when generation succeeds and a record is on disk.
Payload (event-specific fields)
| Field | Type | Description |
|---|---|---|
findings_status | enum{has_findings, ran_no_findings} | Outcome category. Never missing or failed here — see RetrospectiveCaptureFailed. |
record_path | string | Absolute (or repo-relative) path to the written record. |
generator_version | string | Generator version that produced the record. |
policy_source | dict[str,str] | Resolver source-map snapshot. Per retrospective-policy.schema.json leaf-key conventions. |
provenance_kind | enum{runtime_post_completion, runtime_strict_gate, explicit_create, backfill} | How the action was invoked. synthesize_fabricate provenance fires a separate provenance attribution but reuses this event type for the captured record. |
proposal_count | int | Number of proposals in the captured record. |
evidence_ref_count | int | Number of evidence references. |
Example
{
"type": "RetrospectiveCaptured",
"schema_version": 1,
"event_id": "01KS06EXAMPLECAPTUREDXYZAB",
"lamport": 142,
"at": "2026-05-19T13:00:00+00:00",
"actor": {"kind": "runtime", "id": "spec-kitty-cli@3.2.0", "display": "spec-kitty runtime"},
"mission_id": "01J6XW9KQT7M0YB3N4R5CQZ2EX",
"mission_slug": "my-feature-01J6XW9K",
"wp_id": null,
"force": false,
"execution_mode": "main",
"findings_status": "has_findings",
"record_path": "/abs/.kittify/missions/01J6XW9KQT7M0YB3N4R5CQZ2EX/retrospective.yaml",
"generator_version": "1.0",
"policy_source": {
"enabled": ".kittify/config.yaml#retrospective.enabled",
"timing": "<default>",
"failure_policy": "<default>"
},
"provenance_kind": "runtime_post_completion",
"proposal_count": 3,
"evidence_ref_count": 10
}
Invariants
- A single mission can have ≥ 0
RetrospectiveCapturedevents. The most recent is authoritative. - The reducer in
specify_cli.status.reducerMUST NOT classify this as a lane transition. Existing materializations are unaffected. findings_statusMUST match the value persisted in the record atrecord_path.
RetrospectiveCaptureFailed
Fired when generation is attempted under failure_policy: warn and fails. Does NOT fire under failure_policy: block (the completion-block event itself carries the failure reason) or enabled: false (no attempt is made).
Payload (event-specific fields)
| Field | Type | Description |
|---|---|---|
failure_category | enum | One of: missing_artifacts, generator_exception, schema_validation_error, io_error, other. |
failure_message | string | Plain-text human-readable description. Stripped of stack traces — those go to logs. |
remediation_hint | `string \ | null` |
policy_source | dict[str,str] | Resolver source-map snapshot at the time of attempt. |
attempted_provenance_kind | enum{runtime_post_completion, runtime_strict_gate, explicit_create, backfill} | The provenance the attempt would have carried on success. |
missing_artifacts | `list[string] \ | null` |
Example
{
"type": "RetrospectiveCaptureFailed",
"schema_version": 1,
"event_id": "01KS06EXAMPLEFAILEDXYZABCD",
"lamport": 143,
"at": "2026-05-19T13:01:00+00:00",
"actor": {"kind": "runtime", "id": "spec-kitty-cli@3.2.0", "display": "spec-kitty runtime"},
"mission_id": "01J6XW9KQT7M0YB3N4R5CQZ2EX",
"mission_slug": "my-feature-01J6XW9K",
"wp_id": null,
"force": false,
"execution_mode": "main",
"failure_category": "missing_artifacts",
"failure_message": "Cannot author retrospective: required artifacts not found.",
"remediation_hint": "Mission lacks status.events.jsonl. Rebuild via `spec-kitty migrate normalize-lifecycle --mission <handle>` and retry with `spec-kitty retrospect create --mission <handle>`.",
"policy_source": {
"enabled": ".kittify/config.yaml#retrospective.enabled",
"timing": "<default>",
"failure_policy": "<default>"
},
"attempted_provenance_kind": "runtime_post_completion",
"missing_artifacts": ["kitty-specs/my-feature-01J6XW9K/status.events.jsonl"]
}
Invariants
- A
RetrospectiveCaptureFailedevent MUST NOT block mission completion underfailure_policy: warn. - Subsequent
RetrospectiveCapturedfor the same mission supersedes priorRetrospectiveCaptureFailedfor read-purposes (summary'sfailedlane is "most recent Failed not followed by a Captured"). - Reducer impact identical to
RetrospectiveCaptured— no lane transition.
RetrospectiveSkipped
Fired when the strict gate (timing: before_completion + failure_policy: block) is bypassed via --skip-retrospective on the completing command. Records actor, reason, and resolved policy source so the bypass is auditable. Does NOT fire under default policy (no gate; nothing to skip) or under enabled: false (no policy; nothing to skip).
Payload (event-specific fields)
| Field | Type | Description |
|---|---|---|
skip_reason | string | Free-form operator reason. MUST be non-empty; CLI rejects --skip-retrospective without a reason. |
skip_reason_source | enum{cli_flag, config_flag, ci_environment} | Where the reason came from. cli_flag for --skip-retrospective="<reason>"; config_flag for a .kittify/config.yaml operator override (not in scope this release); ci_environment reserved for future CI annotations. |
policy_source | dict[str,str] | Resolver source-map snapshot. Identifies which charter or config keys made the gate strict (and therefore worth skipping). |
bypassed_provenance_kind | enum{runtime_strict_gate} | The provenance kind the attempt would have carried had it not been skipped. Today this is always runtime_strict_gate (the only path that can be skipped). |
would_have_attempted | bool | true when the runtime had loaded the policy and was about to dispatch the generator. Always true today; reserved for future "skip before resolve" paths. |
Example
{
"type": "RetrospectiveSkipped",
"schema_version": 1,
"event_id": "01KS06EXAMPLESKIPPEDXYZABC",
"lamport": 144,
"at": "2026-05-19T13:02:00+00:00",
"actor": {"kind": "human", "id": "robert@spec-kitty.ai", "display": "Robert Douglass"},
"mission_id": "01J6XW9KQT7M0YB3N4R5CQZ2EX",
"mission_slug": "my-feature-01J6XW9K",
"wp_id": null,
"force": false,
"execution_mode": "main",
"skip_reason": "Strict gate blocking on missing mission-review-report.md; merge needed for release window. Will author retrospective post-merge via `spec-kitty retrospect create`.",
"skip_reason_source": "cli_flag",
"policy_source": {
"enabled": ".kittify/charter/charter.md:retrospective.enabled",
"timing": ".kittify/charter/charter.md:retrospective.timing",
"failure_policy": ".kittify/charter/charter.md:retrospective.failure_policy"
},
"bypassed_provenance_kind": "runtime_strict_gate",
"would_have_attempted": true
}
Invariants
RetrospectiveSkippedMUST be emitted ONLY under strict policy paths (timing: before_completion + failure_policy: blockANDenabled: true). Emission under any other policy combination is a runtime bug.skip_reasonis non-empty. CLI flag accepts--skip-retrospective="<reason>"form (or interactive prompt if invoked without a reason in TTY mode); rejects empty.- Followed eventually by either:
- a successful
RetrospectiveCapturedevent from a laterspec-kitty retrospect create --mission <handle>invocation, OR - operator-recorded acceptance that no retrospective will be authored for this mission (out of band).
- Reducer impact identical to the other two event types — no lane transition, additive top-level snapshot key only (
retrospective.last_skipped_at). - The emitted
MissionCompletedevent MUST appear AFTERRetrospectiveSkippedin the event log (by lamport). Verified by WP04 integration tests.
Reduction guarantees (FR-021)
For every mission's status.events.jsonl file:
- The lane-state reduction (current lane of each WP, mission lifecycle phase) MUST be byte-identical before and after this mission, when the file contains only pre-existing event types.
- Adding the three new retrospective event types is additive; the reducer MUST treat unknown-to-it event types as no-ops for lane reduction and pass them through for read-purposes.
- Materialized snapshots (
status.json, lifecycle views) MAY gain new top-level keys reflecting retrospective state (e.g.retrospective.last_captured_at,retrospective.last_failed_at,retrospective.last_skipped_at), but MUST NOT mutate keys that pre-date this mission.
Schema test obligations
- Round-trip test: a
RetrospectiveCapturedevent serialized to JSONL and read back via the canonical event reader produces the same payload (byte-equal after sort-keys normalization). - Schema test: validating each example payload against the JSON envelope used by
spec-kitty validate events(if such a validator exists in the canonical event log infrastructure) passes. - Backward-compat test: a historical mission's
status.events.jsonlwith no retrospective events still produces the samestatus.jsonsnapshot before and after this mission merges.
retrospective-policy.schema.json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://spec-kitty.example/contracts/retrospective-default-policy-01KS049J/retrospective-policy.schema.json", "title": "RetrospectivePolicy", "description": "Durable policy controlling retrospective generation behavior. Resolved from charter frontmatter and .kittify/config.yaml at mission boundaries.", "type": "object", "additionalProperties": false, "required": [ "enabled", "timing", "failure_policy", "write_record", "generate_proposals", "apply_proposals", "permissions" ], "properties": { "enabled": { "type": "boolean", "default": true, "description": "Whether retrospective generation runs at mission boundaries." }, "timing": { "type": "string", "enum": ["post_completion", "before_completion"], "default": "post_completion", "description": "When generation runs relative to mission completion." }, "failure_policy": { "type": "string", "enum": ["warn", "block"], "default": "warn", "description": "What to do when generation fails." }, "write_record": { "type": "boolean", "default": true, "description": "Whether the generator persists the record to disk." }, "generate_proposals": { "type": "boolean", "default": true, "description": "Whether the generator emits proposals[]." }, "apply_proposals": { "type": "string", "enum": ["require_human", "low_risk_auto"], "default": "require_human", "description": "Whether the runtime may auto-apply low-risk proposals." }, "permissions": { "type": "object", "additionalProperties": false, "required": [ "write_record", "inspect_mission_artifacts", "propose_glossary_changes", "propose_drg_changes", "propose_doctrine_changes", "apply_low_risk_changes", "apply_structural_changes" ], "properties": { "write_record": {"type": "boolean", "default": true}, "inspect_mission_artifacts": {"type": "boolean", "default": true}, "propose_glossary_changes": {"type": "boolean", "default": true}, "propose_drg_changes": {"type": "boolean", "default": true}, "propose_doctrine_changes": {"type": "boolean", "default": true}, "apply_low_risk_changes": { "type": "boolean", "default": false, "description": "If true, the runtime may auto-apply proposals whose risk_class == 'low'. Currently only flag_not_helpful qualifies." }, "apply_structural_changes": { "type": "boolean", "default": false, "description": "MUST stay false unless the operator explicitly opts in. Built-in defaults never set this true." } } }, "precedence": { "type": "string", "enum": ["charter", "config"], "description": "Only meaningful in charter frontmatter. Default behavior: charter wins. Set 'config' to delegate authority to .kittify/config.yaml." }, "generator": { "type": "string", "enum": ["python"], "default": "python", "description": "Forward-compatible. Currently the runtime supports only 'python' (pure-Python module). Profile invocation may land in a future release." } } }
retrospective-record.schema.json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://spec-kitty.example/contracts/retrospective-default-policy-01KS049J/retrospective-record.schema.json", "title": "RetrospectiveRecord", "description": "The on-disk retrospective.yaml record for a single mission. Authored by the runtime, by spec-kitty retrospect create, or by spec-kitty retrospect backfill.", "type": "object", "additionalProperties": false, "required": [ "schema_version", "mission_id", "mission_slug", "mission_number", "friendly_name", "mission_type", "target_branch", "created_at", "created_by", "provenance", "policy_source", "findings_status", "helped", "not_helpful", "gaps", "proposals", "evidence_refs", "generator_version" ], "properties": { "schema_version": {"const": 1}, "mission_id": {"type": "string", "pattern": "^[0-9A-HJKMNP-TV-Z]{26}$"}, "mission_slug": {"type": "string"}, "mission_number": {"type": ["integer", "null"]}, "friendly_name": {"type": "string"}, "mission_type": {"type": "string"}, "target_branch": {"type": "string"}, "created_at": {"type": "string", "format": "date-time"}, "created_by": {"$ref": "#/$defs/Actor"}, "provenance": {"$ref": "#/$defs/Provenance"}, "policy_source": { "type": "object", "additionalProperties": {"type": "string"}, "description": "Snapshot of the resolver's source_map at authoring time." }, "findings_status": { "type": "string", "enum": ["has_findings", "ran_no_findings"], "description": "Persisted records may only be has_findings or ran_no_findings. missing/failed are event-payload-only states." }, "helped": {"type": "array", "items": {"$ref": "#/$defs/Finding"}}, "not_helpful": {"type": "array", "items": {"$ref": "#/$defs/Finding"}}, "gaps": {"type": "array", "items": {"$ref": "#/$defs/Finding"}}, "proposals": {"type": "array", "items": {"$ref": "#/$defs/Proposal"}}, "evidence_refs": {"type": "array", "items": {"$ref": "#/$defs/EvidenceRef"}}, "generator_version": {"type": "string"}, "provenance_history": { "type": "array", "items": {"$ref": "#/$defs/Provenance"}, "description": "Present only when the record has been --update'd. Most-recent-first." } }, "$defs": { "Actor": { "type": "object", "additionalProperties": false, "required": ["kind", "id"], "properties": { "kind": {"type": "string", "enum": ["human", "agent", "runtime"]}, "id": {"type": "string"}, "display": {"type": "string"} } }, "Provenance": { "type": "object", "additionalProperties": false, "required": ["kind", "invoked_at", "policy_resolved_from"], "properties": { "kind": { "type": "string", "enum": ["runtime_post_completion", "runtime_strict_gate", "explicit_create", "backfill", "synthesize_fabricate"] }, "command": {"type": ["string", "null"]}, "invoked_at": {"type": "string", "format": "date-time"}, "policy_resolved_from": {"type": "object", "additionalProperties": {"type": "string"}} } }, "Finding": { "type": "object", "additionalProperties": false, "required": ["id", "category", "summary", "evidence_refs"], "properties": { "id": {"type": "string", "pattern": "^[a-z]-[0-9]{3,}$"}, "category": { "type": "string", "enum": ["process", "tooling", "spec_quality", "review_loop", "design", "implementation", "doc", "other"] }, "summary": {"type": "string", "minLength": 1, "maxLength": 280}, "details": {"type": ["string", "null"]}, "evidence_refs": {"type": "array", "items": {"type": "string"}} } }, "Proposal": { "type": "object", "additionalProperties": false, "required": ["id", "category", "risk_class", "summary", "evidence_refs", "suggested_action", "auto_applicable"], "properties": { "id": {"type": "string", "pattern": "^p-[0-9]{3,}$"}, "category": { "type": "string", "enum": ["glossary", "drg", "doctrine", "tooling", "process", "other"] }, "risk_class": {"type": "string", "enum": ["low", "structural"]}, "summary": {"type": "string", "minLength": 1, "maxLength": 280}, "details": {"type": ["string", "null"]}, "evidence_refs": {"type": "array", "items": {"type": "string"}}, "suggested_action": {"type": "string"}, "auto_applicable": {"type": "boolean"} } }, "EvidenceRef": { "type": "object", "additionalProperties": false, "required": ["id", "kind"], "properties": { "id": {"type": "string", "pattern": "^e-[0-9]{3,}$"}, "kind": {"type": "string", "enum": ["file", "event_range", "external"]}, "path": {"type": ["string", "null"]}, "range": {"type": ["string", "null"]}, "url": {"type": ["string", "null"]} } } } }