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]
FlagTypeDescription
--mission <handle>requiredmission_id (ULID), mid8, or mission_slug. Resolver disambiguates by mission_id; ambiguous handles return MISSION_AMBIGUOUS_SELECTOR.
--overwriteoptionalReplace an existing record. Mutually exclusive with --update.
--updateoptionalMerge into an existing record per the merge semantics in data-model.md. Mutually exclusive with --overwrite.
--jsonoptionalEmit 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 a RetrospectiveCaptured event to kitty-specs/<mission_slug>/status.events.jsonl (or the canonical event log path the runtime uses).
  • On --update: appends to provenance_history[] in the record, replaces top-level provenance and policy_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]
FlagTypeDescription
--since <ISO-date>optionalOnly consider missions whose completion timestamp ≥ this date. Default: 30 days ago.
--until <ISO-date>optionalOnly consider missions whose completion timestamp ≤ this date. Default: now.
--mission <handle>optionalRestrict to a single mission. Skips other missions in scope.
--dry-runoptionalReport what would be authored without writing.
--emit-skippedoptionalAppend a RetrospectiveCaptured event with provenance noting the skip. Default: skips are CLI-output only.
--emit-failuresoptionalAppend RetrospectiveCaptureFailed events for failed candidates. Default: failures are CLI-output only.
--jsonoptionalEmit 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 RetrospectiveCaptured event.
  • Per skipped/failed candidate: CLI output only unless --emit-skipped / --emit-failures are passed.
  • Auto-commit policy: identical to create if --dry-run is absent.

spec-kitty retrospect summary

No semantic change in this mission. This contract clarifies the read-only invariant.

Contract

  • summary MUST NOT author, mutate, or write any retrospective record.
  • summary MUST distinguish four record states in its output: has_findings, ran_no_findings, missing (no record on disk), failed (most recent RetrospectiveCaptureFailed not followed by a RetrospectiveCaptured).
  • JSON output shape per the existing retrospect summary contract, with two added fields: policy_source (snapshot from the most recent attribution event) and findings_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_RETROSPECTIVE and SPEC_KITTY_MODE continue to be honored as test/dev overrides.
  • First use per process emits a single DeprecationWarning AND a single Rich stderr notice (controlled by SPEC_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.enabled or retrospective.timing+retrospective.failure_policy) AND link to docs/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)

FieldTypeDescription
findings_statusenum{has_findings, ran_no_findings}Outcome category. Never missing or failed here — see RetrospectiveCaptureFailed.
record_pathstringAbsolute (or repo-relative) path to the written record.
generator_versionstringGenerator version that produced the record.
policy_sourcedict[str,str]Resolver source-map snapshot. Per retrospective-policy.schema.json leaf-key conventions.
provenance_kindenum{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_countintNumber of proposals in the captured record.
evidence_ref_countintNumber 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 RetrospectiveCaptured events. The most recent is authoritative.
  • The reducer in specify_cli.status.reducer MUST NOT classify this as a lane transition. Existing materializations are unaffected.
  • findings_status MUST match the value persisted in the record at record_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)

FieldTypeDescription
failure_categoryenumOne of: missing_artifacts, generator_exception, schema_validation_error, io_error, other.
failure_messagestringPlain-text human-readable description. Stripped of stack traces — those go to logs.
remediation_hint`string \null`
policy_sourcedict[str,str]Resolver source-map snapshot at the time of attempt.
attempted_provenance_kindenum{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 RetrospectiveCaptureFailed event MUST NOT block mission completion under failure_policy: warn.
  • Subsequent RetrospectiveCaptured for the same mission supersedes prior RetrospectiveCaptureFailed for read-purposes (summary's failed lane 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)

FieldTypeDescription
skip_reasonstringFree-form operator reason. MUST be non-empty; CLI rejects --skip-retrospective without a reason.
skip_reason_sourceenum{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_sourcedict[str,str]Resolver source-map snapshot. Identifies which charter or config keys made the gate strict (and therefore worth skipping).
bypassed_provenance_kindenum{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_attemptedbooltrue 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

  • RetrospectiveSkipped MUST be emitted ONLY under strict policy paths (timing: before_completion + failure_policy: block AND enabled: true). Emission under any other policy combination is a runtime bug.
  • skip_reason is 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 RetrospectiveCaptured event from a later spec-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 MissionCompleted event MUST appear AFTER RetrospectiveSkipped in 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 RetrospectiveCaptured event 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.jsonl with no retrospective events still produces the same status.json snapshot 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"]} } } } }