Skip to content

Conversation

@sfierro
Copy link
Contributor

@sfierro sfierro commented Nov 21, 2025

Summary by CodeRabbit

  • New Features

    • Specs now use a structured properties object with many property types (behaviour, tone, formatting, localization, safety, tooling, etc.) for richer guidance.
  • Changes

    • "Definition" renamed to "Description" across UI and API; create/edit flows now submit properties instead of a flat type field.
    • Type display and sorting now derive from properties.spec_type.
    • Some spec templates consolidated/renamed (behaviour) and others removed.
  • Tests

    • Expanded tests for properties validation, creation, update, and error cases.

✏️ Tip: You can customize this high-level summary in your review settings.

@sfierro sfierro changed the base branch from main to sfierro/specs November 21, 2025 01:19
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Walkthrough

Spec "type" and exported SpecType enum were replaced by a discriminated union properties (with a spec_type discriminator); schemas, UI payloads and renderers, Python datamodel, server API, and tests were updated to use and validate the new properties-based design. Definition fields were reworked to description where applicable.

Changes

Cohort / File(s) Summary
TypeScript API schema & types
app/web_ui/src/lib/api_schema.d.ts, app/web_ui/src/lib/types.ts
Added many new property schemas (Behaviour, Tone, Formatting, Localization, AppropriateToolUse, Toxicity, Bias, etc.). Spec/SpecUpsertRequest now use description and a discriminated properties union. SpecProperties type alias added; previous SpecType alias removed/derived from properties.
Web UI pages
app/web_ui/src/routes/(app)/specs/...
app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte, .../[spec_id]/+page.svelte, .../create_spec/+page.svelte, .../create_spec/template_select/+page.svelte
UI bindings and displays moved from type/spec.typespec.properties.spec_type. Create/update payloads send properties instead of flat type; form field names/ids and initial values updated (name/definition → name/description changes and spec_type init changed to behaviour). Some templates removed/renamed and existing_spec_types updated.
Python: spec properties model
libs/core/kiln_ai/datamodel/spec_properties.py
New SpecType enum plus many TypedDict property types and per-type validators (BehaviourProperties, ToneProperties, FormattingProperties, LocalizationProperties, AppropriateToolUseProperties, ReferenceAnswerAccuracyProperties, FactualCorrectnessProperties, HallucinationsProperties, CompletenessProperties, ToxicityProperties, BiasProperties, MaliciousnessProperties, NsfwProperties, TabooProperties, JailbreakProperties, PromptLeakageProperties). Added shared validate_string_properties and Annotated AfterValidator wrappers; exported SpecProperties union.
Python: Spec model
libs/core/kiln_ai/datamodel/spec.py
Removed type: SpecType; added properties: SpecProperties with Pydantic discriminator="spec_type". Imports adjusted to use spec_properties module; existing fields (name, definition/description, priority, status, tags, eval_id) preserved.
Server API
libs/server/kiln_server/spec_api.py
SpecUpsertRequest and endpoints now accept properties: SpecProperties = Field(discriminator="spec_type") instead of flat type/definition. Create/update logic updated to read/write properties.
Tests (server & core)
libs/core/.../test_spec.py, .../test_task.py, libs/server/.../test_spec_api.py, libs/core/.../test_spec_properties.py
Tests updated to construct specs with properties=... instead of type=...; new fixtures and parameterized tests added for many property types; validation/error-case tests added for empty/invalid nested property fields. Imports for SpecType moved to spec_properties.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WebUI
    participant API
    participant Validator
    participant DB

    Note over WebUI,API: Create / Update spec (properties-based)
    User->>WebUI: submit form (name, description, properties:{ spec_type, ... })
    WebUI->>API: POST/PATCH /spec { name, description, properties, eval_id, tags }
    API->>Validator: parse SpecUpsertRequest (Field discriminator="spec_type")
    Validator->>Validator: validate properties shape for spec_type
    alt valid
        Validator-->>API: validated Spec object
        API->>DB: save Spec (properties stored with spec_type)
        DB-->>API: 201/200 OK
        API-->>WebUI: Spec response (includes properties)
        WebUI-->>User: success UI
    else invalid
        Validator-->>API: 422 with field-specific errors
        API-->>WebUI: 422 response
        WebUI-->>User: show validation errors
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Multi-layer changes across TypeScript schemas, Svelte UI, Python datamodel, server API, and many tests.
  • Areas to pay extra attention:
    • libs/core/kiln_ai/datamodel/spec_properties.py — correctness of TypedDict shapes, required vs optional fields, and AfterValidator wrappers.
    • libs/core/kiln_ai/datamodel/spec.py — Pydantic discriminator config and compatibility with existing serialized data.
    • libs/server/kiln_server/spec_api.py and WebUI pages — request/response shapes, nullability (eval_id), and runtime guards for unimplemented spec_types.
    • Test updates — ensure they correctly exercise validation messages and serialization round-trips.

Possibly related PRs

Suggested reviewers

  • scosman
  • chiang-daniel
  • tawnymanticore

Poem

🐇 I nudged the spec_type into properties' nest,
fields validated and neatly dressed.
From UI to API the union sings,
behaviours, tones, and many things.
A little rabbit cheers — clean specs, well blessed!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is empty; no description was provided by the author despite the template requiring one with sections for what the PR does, related issues, CLA confirmation, and checklists. Add a comprehensive PR description following the template, including: what the PR does, related issue links, CLA confirmation, and completion of the provided checklists.
Docstring Coverage ⚠️ Warning Docstring coverage is 43.69% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: adding a spec properties property and modifying the definition field to description.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sfierro/spec_properties

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Nov 21, 2025

📊 Coverage Report

Overall Coverage: 92%

Diff: origin/sfierro/specs...HEAD

  • libs/core/kiln_ai/datamodel/spec.py (100%)
  • libs/core/kiln_ai/datamodel/spec_properties.py (100%)
  • libs/server/kiln_server/spec_api.py (100%)

Summary

  • Total: 167 lines
  • Missing: 0 lines
  • Coverage: 100%

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte (1)

92-95: Consider simplifying the title construction.

The conditional logic is correct but verbose. For improved readability, consider refactoring:

-  title="{spec?.type ? `${formatSpecType(spec.type)}: ` : ''}{spec?.name
-    ? `${spec.name}`
-    : ''}"
+  title={[
+    spec?.type ? formatSpecType(spec.type) : null,
+    spec?.name
+  ].filter(Boolean).join(': ')}
   subtitle={spec?.description}
libs/core/kiln_ai/datamodel/spec_properties.py (1)

1-19: Consider adding docstrings and explicit exports.

To improve maintainability and discoverability:

  1. Add a module docstring explaining the purpose of these property types
  2. Add class docstrings to describe when each property type should be used
  3. Consider adding __all__ to explicitly declare the public API

Example:

"""TypedDict definitions for spec properties supporting structured tool-use and undesired-behavior specifications."""

from typing import Literal

from typing_extensions import TypedDict


class AppropriateToolUseProperties(TypedDict, total=True):
    """Properties for specs that define appropriate tool usage guidelines."""
    spec_type: Literal["appropriate_tool_use"]
    tool_id: str
    appropriate_tool_use_guidelines: str
    inappropriate_tool_use_guidelines: str | None


class UndesiredBehaviourProperties(TypedDict, total=True):
    """Properties for specs that define undesired behavior patterns."""
    spec_type: Literal["undesired_behaviour"]
    undesired_behaviour_guidelines: str
    examples: str


SpecProperties = AppropriateToolUseProperties | UndesiredBehaviourProperties

__all__ = [
    "AppropriateToolUseProperties",
    "UndesiredBehaviourProperties",
    "SpecProperties",
]
libs/core/kiln_ai/datamodel/test_spec.py (2)

405-470: Negative properties validation scenarios are good; consider adding tests for empty-but-present required fields

  • test_spec_properties_validation_missing_required_fields correctly checks that omitting required keys from the properties dict results in a ValidationError from Pydantic’s TypedDict handling.
  • test_spec_properties_validation_wrong_spec_type ensures incorrect spec_type literals on both property variants also raise ValidationError, exercising the Literal-based constraints.
  • test_spec_rejects_empty_dict_properties verifies that {} is rejected for properties, which is important given the validator assumes spec_type is present.
  • test_spec_with_properties_and_description nicely confirms that description and a valid AppropriateToolUseProperties payload coexist and that the description is preserved.

One possible enhancement (optional) would be to add tests where the required fields exist but are empty strings (e.g. tool_id="", appropriate_tool_use_guidelines="", undesired_behaviour_guidelines="", examples=""). Those cases are handled by Spec.validate_properties (raising ValueError), but currently aren’t covered in tests. That would fully exercise the semantic validation in addition to the structural TypedDict validation.

Also applies to: 472-503


18-52: Error message assertions are currently correct but tests could be more robust to future Pydantic upgrades

These tests correctly assert on "Field required" and "String should have at least 1 character" for Pydantic v2.9.x and newer—these messages are stable through the current versions. However, to future-proof against potential changes in newer Pydantic versions, consider asserting on error types or locations via e.errors() instead of string matching. This is optional and can be deferred.

Applies to: test_spec_valid_creation, test_spec_with_custom_values, and related required-field tests (lines 18-52, 55-83, 85-107)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57eebc3 and 4cdddfc.

📒 Files selected for processing (11)
  • app/web_ui/src/lib/api_schema.d.ts (4 hunks)
  • app/web_ui/src/lib/types.ts (1 hunks)
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte (4 hunks)
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte (2 hunks)
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/+page.svelte (3 hunks)
  • libs/core/kiln_ai/datamodel/spec.py (3 hunks)
  • libs/core/kiln_ai/datamodel/spec_properties.py (1 hunks)
  • libs/core/kiln_ai/datamodel/test_spec.py (14 hunks)
  • libs/core/kiln_ai/datamodel/test_task.py (3 hunks)
  • libs/server/kiln_server/spec_api.py (4 hunks)
  • libs/server/kiln_server/test_spec_api.py (44 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-04T06:27:58.852Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 487
File: app/web_ui/src/lib/api_schema.d.ts:3315-3320
Timestamp: 2025-09-04T06:27:58.852Z
Learning: The file app/web_ui/src/lib/api_schema.d.ts is auto-generated by openapi-typescript from the backend API schema and should never be manually edited. Changes to API types should be made in the backend, which will then be reflected in the regenerated TypeScript definitions.

Applied to files:

  • app/web_ui/src/lib/types.ts
  • app/web_ui/src/lib/api_schema.d.ts
📚 Learning: 2025-10-29T06:59:55.635Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 736
File: app/web_ui/src/lib/api_schema.d.ts:8143-8180
Timestamp: 2025-10-29T06:59:55.635Z
Learning: app/web_ui/src/lib/api_schema.d.ts is generated by openapi-typescript; do not propose manual edits. Schema changes should be made in the FastAPI backend (e.g., app/desktop/studio_server/data_gen_api.py or libs/server/kiln_server/*), then re-generate the TS types.

Applied to files:

  • app/web_ui/src/lib/types.ts
  • app/web_ui/src/lib/api_schema.d.ts
📚 Learning: 2025-10-29T11:19:31.059Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 759
File: app/web_ui/src/lib/api_schema.d.ts:2485-2490
Timestamp: 2025-10-29T11:19:31.059Z
Learning: In libs/server/kiln_server/document_api.py and the generated app/web_ui/src/lib/api_schema.d.ts, CreateVectorStoreConfigRequest.properties is now a union of three distinct public schemas: LanceDBConfigFTSPropertiesPublic, LanceDBConfigVectorPropertiesPublic, and LanceDBConfigHybridPropertiesPublic. These are intentionally split to allow independent evolution; do not suggest merging them.

Applied to files:

  • app/web_ui/src/lib/types.ts
  • app/web_ui/src/lib/api_schema.d.ts
  • libs/server/kiln_server/spec_api.py
📚 Learning: 2025-09-10T08:30:56.973Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 546
File: app/web_ui/src/lib/api_schema.d.ts:4173-4218
Timestamp: 2025-09-10T08:30:56.973Z
Learning: leonardmq correctly identified that app/web_ui/src/lib/api_schema.d.ts is auto-generated and should not be manually edited. The proper fix for type inconsistencies between TypeScript API schema and backend models is to update the backend Python models and regenerate the TypeScript definitions.

Applied to files:

  • app/web_ui/src/lib/api_schema.d.ts
📚 Learning: 2025-09-10T08:27:47.227Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 546
File: app/web_ui/src/lib/api_schema.d.ts:0-0
Timestamp: 2025-09-10T08:27:47.227Z
Learning: leonardmq correctly identifies auto-generated files and prefers not to manually edit them. When suggesting changes to generated TypeScript API schema files, the fix should be made in the source OpenAPI specification in the backend, not in the generated TypeScript file.

Applied to files:

  • app/web_ui/src/lib/api_schema.d.ts
📚 Learning: 2025-10-01T21:48:25.854Z
Learnt from: sfierro
Repo: Kiln-AI/Kiln PR: 674
File: libs/core/kiln_ai/tools/kiln_task_tool.py:30-34
Timestamp: 2025-10-01T21:48:25.854Z
Learning: In libs/core/kiln_ai/datamodel/external_tool_server.py, KilnTaskServerProperties is a TypedDict, not a Pydantic model. At runtime it's a regular Python dict, so .get() method works correctly and no .model_dump() conversion is needed.

Applied to files:

  • libs/server/kiln_server/spec_api.py
  • libs/core/kiln_ai/datamodel/spec_properties.py
  • libs/core/kiln_ai/datamodel/test_spec.py
🧬 Code graph analysis (5)
libs/core/kiln_ai/datamodel/test_task.py (1)
libs/core/kiln_ai/datamodel/task.py (1)
  • specs (177-178)
libs/server/kiln_server/spec_api.py (1)
app/web_ui/src/lib/types.ts (1)
  • SpecProperties (113-115)
libs/core/kiln_ai/datamodel/spec_properties.py (1)
app/web_ui/src/lib/types.ts (1)
  • SpecProperties (113-115)
libs/server/kiln_server/test_spec_api.py (2)
libs/core/kiln_ai/datamodel/spec_properties.py (1)
  • UndesiredBehaviourProperties (13-16)
libs/core/kiln_ai/datamodel/spec.py (3)
  • SpecType (12-44)
  • SpecStatus (47-53)
  • Spec (56-137)
libs/core/kiln_ai/datamodel/test_spec.py (3)
libs/core/kiln_ai/datamodel/spec_properties.py (2)
  • AppropriateToolUseProperties (6-10)
  • UndesiredBehaviourProperties (13-16)
libs/core/kiln_ai/datamodel/spec.py (3)
  • SpecType (12-44)
  • Spec (56-137)
  • SpecStatus (47-53)
libs/core/kiln_ai/datamodel/test_task.py (1)
  • sample_task (54-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Web UI Code Format, Lint, Typecheck, Test, and Build
  • GitHub Check: Build Desktop Apps (ubuntu-22.04)
  • GitHub Check: Build Desktop Apps (ubuntu-22.04-arm)
  • GitHub Check: Build Desktop Apps (macos-15-intel)
  • GitHub Check: Build Desktop Apps (windows-latest)
  • GitHub Check: Web UI Code Format, Lint, Typecheck, Test, and Build
🔇 Additional comments (20)
app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte (1)

70-71: LGTM! Payload correctly updated for the new schema.

The migration from definition to description and the addition of the properties field align with the PR's objective. Using || null is consistent with the existing pattern in this file (line 76).

libs/core/kiln_ai/datamodel/spec_properties.py (2)

13-16: LGTM! Clean TypedDict definition.

The structure is well-defined with clear required fields and proper use of the discriminator pattern via spec_type.


19-19: LGTM! Proper discriminated union definition.

The union correctly combines both property types, and the spec_type literals enable type discrimination at runtime when used with Pydantic validators.

app/web_ui/src/lib/api_schema.d.ts (4)

2176-2189: AppropriateToolUseProperties schema looks coherent and well-discriminated

The new AppropriateToolUseProperties shape (with literal spec_type: "appropriate_tool_use", tool_id, and guideline fields) is consistent with SpecType and should work well as a discriminated union member of Spec.properties. No issues from the TS/OpenAPI perspective. As this file is auto-generated, the main requirement is that the backend/OpenAPI schema stays in sync, which this appears to reflect.
Based on learnings


5196-5205: Spec.description + Spec.properties changes are consistent; ensure data/backfill is handled

Switching from a definition field to a required description: string on Spec aligns with naming in the rest of the API surface, and the new optional properties?: AppropriateToolUseProperties | UndesiredBehaviourProperties | null cleanly models typed spec metadata for relevant spec types while remaining nullable/omittable for others. The types themselves look correct; just make sure existing persisted specs are migrated/backfilled so that required description is always present at load time.


5248-5251: SpecUpsertRequest now requires description/properties; semantics look intentional

SpecUpsertRequest now requires a non-nullable description: string and a required but nullable properties: AppropriateToolUseProperties | UndesiredBehaviourProperties | null. This is compatible with the Spec model (where properties is optional) as long as the client always sends properties (e.g. spec.properties ?? null) on create and update, which the UI changes reportedly do. If you ever want partial/patch semantics for specs, that would be the point to relax these in the backend/OpenAPI schema rather than here in the generated file.


5751-5762: UndesiredBehaviourProperties union member is consistent with SpecType

The UndesiredBehaviourProperties schema (literal spec_type: "undesired_behaviour", plus undesired_behaviour_guidelines and examples) cleanly complements AppropriateToolUseProperties as a second discriminated member for Spec.properties. The required fields and literal discriminator all look correct and in line with the described spec model.
Based on learnings

app/web_ui/src/lib/types.ts (1)

113-115: SpecProperties alias correctly mirrors backend/public schemas

The SpecProperties union cleanly matches the two spec property schemas exposed via components["schemas"], giving the UI a stable type without touching the generated api_schema.d.ts. This is the right place to introduce the alias.
Based on learnings

app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/+page.svelte (1)

19-20: Create-spec form now aligned with description/properties API

Using description in the POST body and wiring the new “Spec Description” textarea into the unload warning keeps the UI consistent with the updated Spec model; explicitly sending properties: null is correct while there’s no properties editor yet.

Also applies to: 42-45, 88-89, 94-101

libs/core/kiln_ai/datamodel/test_task.py (1)

347-360: Task–specs relationship tests updated correctly for description

The specs relationship tests now construct and assert against description instead of definition, keeping coverage in place for the on-disk roundtrip without changing behavior.

Also applies to: 370-384

app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte (1)

388-396: Spec list PATCH payloads and table now preserve description/properties

Including description, properties: spec.properties ?? null, and eval_id: spec.eval_id ?? null in both tag-edit and archive PATCHes prevents losing structured properties or eval bindings when updating specs. The table header/cell swap to Description/spec.description matches the new model.

Also applies to: 468-475, 598-599, 683-684

libs/server/kiln_server/spec_api.py (1)

7-8: SpecUpsertRequest and handlers correctly mirror new Spec fields

Switching from definition to description and adding properties: SpecProperties | None on SpecUpsertRequest, then passing those through in create/update, keeps the API aligned with the Spec model and the new properties validation without altering read behavior.
Based on learnings

Also applies to: 25-33, 42-52, 71-78

libs/server/kiln_server/test_spec_api.py (4)

49-118: API spec tests consistently migrated to description and properties

Across create/get/update and archived-status tests, payloads and assertions now use description and include a properties field (usually None), matching the new Spec/SpecUpsertRequest contract while still asserting on persisted Spec instances. The patterns look consistent and should keep regression coverage intact.

Also applies to: 137-152, 185-212, 230-276, 281-319, 346-373, 418-447


375-416: test_create_spec_with_properties exercises end‑to‑end properties roundtrip

Using UndesiredBehaviourProperties to build the request and then asserting on both the JSON response and task.specs()[0].properties verifies that structured spec properties survive the entire create pipeline and are stored on the Spec object as intended.
Based on learnings


545-571: 422 validation tests cover required fields, enums, and tag constraints

The 422 tests for missing fields, invalid enum values, and invalid/empty/space‑containing tags give good coverage of the validation surface, checking source_errors with focused predicates rather than fragile full‑message matches; this should make failures informative but resilient to minor error formatting changes.

Also applies to: 573-727, 772-883


1056-1189: New SpecProperties validation tests align with Spec model_validator logic

The added tests for empty tool_id, empty appropriate_tool_use_guidelines, empty undesired_behaviour_guidelines, and empty examples verify that the SpecProperties validator rejects structurally correct but semantically empty data, and that the resulting 422s expose clear messages via source_errors. This directly exercises the new validate_properties logic.

libs/core/kiln_ai/datamodel/spec.py (1)

9-10: Spec model’s description/properties fields and validator enforce consistent specs

Adding description (required, min_length=1) and properties: SpecProperties | None plus the validate_properties post‑validator tightly couples type with properties["spec_type"] and enforces non‑empty tool IDs/guidelines/examples for the supported spec types, matching the new API/tests and existing TypedDict‑based patterns in the codebase.
Based on learnings

Also applies to: 59-64, 85-127

libs/core/kiln_ai/datamodel/test_spec.py (3)

6-9: Imports for spec properties look correct and aligned with model types

Bringing in AppropriateToolUseProperties and UndesiredBehaviourProperties here matches the Spec.properties union and keeps the tests close to the actual TypedDict definitions. No issues.


109-179: Enum coverage for type/priority/status and tag validation remains solid

  • The parameterized tests over all SpecType, Priority, and SpecStatus values correctly switched to description and still provide good regression coverage that every enum value is constructible.
  • The tag validation tests (empty_string, spaces, and valid tags) continue to exercise the validate_tags model validator properly; switching to description there is consistent with the rest of the suite.

No functional issues spotted here.

Also applies to: 181-237


240-366: Happy-path tests for properties and None handling look correct and aligned with TypedDict definitions

  • test_spec_with_none_properties verifies that properties=None is accepted and bypasses the validate_properties logic, which matches the model’s properties: SpecProperties | None.
  • The three positive-path tests for AppropriateToolUseProperties and UndesiredBehaviourProperties (including the optional inappropriate_tool_use_guidelines being None) correctly mirror the TypedDict shapes and check that Spec preserves them as plain dicts.
  • The *_type_matches_properties_* tests validate that when type and properties["spec_type"] agree, construction succeeds and fields are wired as expected.

These tests look accurate and give good confidence that the new properties field interoperates correctly with SpecType.

@sfierro sfierro requested a review from leonardmq November 21, 2025 08:12
name: FilenameString
definition: str
description: str
properties: SpecProperties | None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also do discriminated union here (would only need to add the Field(discriminator="spec_type") in here) - I think that will make your request in the frontend also get typechecked (i.e. not allow passing in the wrong properties given the spec_type in properties)

In the chunker API, I did this - don't really remember if that is any good or why, but probably ok pattern:

def get_properties_for_chunker_type(

Essentially just having a getter that returns the union of possible types (I think it is because the typechecker in Python does not consider valid to try to shove an object of type A into an A | B, it wants an explicitly A | B union type; or something along these lines)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't think I need the get_properties_for_chunker_type as the all properties will be defined, it looks like you had that to put some default values in for the client. but did change this to the discriminator one

@sfierro
Copy link
Contributor Author

sfierro commented Nov 25, 2025

Closing for now as the spec model object is in flux we might not do properties at all

@sfierro sfierro closed this Nov 25, 2025
@sfierro sfierro reopened this Nov 26, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte (1)

68-77: Remove the obsolete type field and handle properties correctly.

Two issues in the PATCH payload:

  1. Line 72 sends type: spec.type, but the SpecUpsertRequest model no longer includes a type field (it's now within properties.spec_type). This may cause a validation error or be silently ignored.

  2. Line 71 uses spec.properties || null, but properties is a required field in SpecUpsertRequest with no default. If spec.properties is falsy, sending null will cause a 422 validation error.

 body: {
   name: spec.name,
   description: spec.description,
-  properties: spec.properties || null,
-  type: spec.type,
+  properties: spec.properties,
   priority: spec.priority,
   status: spec.status,
   tags: tags,
   eval_id: spec.eval_id || null,
 },
app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/+page.svelte (1)

19-27: SpecType must be imported as a runtime value, not just a type.

The issue is confirmed. Line 6 imports SpecType with the type keyword (import type { SpecType }), which means it's only available at compile time. However, line 21 attempts to use SpecType.desired_behaviour as if it were a runtime enum or object with properties, which will result in a runtime error: "Cannot read properties of undefined (reading 'desired_behaviour')".

In other files like template_select/+page.svelte, the correct pattern is used: spec_type: "desired_behaviour" as a string literal. Fix line 21 to use the string literal instead:

let spec_type: SpecType = "desired_behaviour" as SpecType

Alternatively, if SpecType should be a runtime enum, it needs to be exported as a const enum or const object in $lib/types.ts, not just as a type alias.

♻️ Duplicate comments (1)
libs/core/kiln_ai/datamodel/spec_properties.py (1)

111-139: Align inappropriate_tool_use_guidelines typing with “optional if provided” semantics

Right now the field is:

  • Declared as str | None with total=True, so the key is required but may be None.
  • Validated as “if provided cannot be empty”, which conceptually treats it as optional.

If callers omit the key entirely, strict TypedDict / Pydantic validation may reject the payload despite the doc intent that this field is optional. Making the key truly optional and adjusting the validator to use .get() would better match the intended behavior.

You could apply something like:

-from typing_extensions import TypedDict
+from typing_extensions import NotRequired, TypedDict
@@
 class AppropriateToolUseProperties(TypedDict, total=True):
     spec_type: Literal[SpecType.appropriate_tool_use]
     tool_id: str
     appropriate_tool_use_guidelines: str
-    inappropriate_tool_use_guidelines: str | None
+    inappropriate_tool_use_guidelines: NotRequired[str]
@@
-    # inappropriate_tool_use_guidelines
-    inappropriate_tool_use_guidelines = properties["inappropriate_tool_use_guidelines"]
-    if (
-        inappropriate_tool_use_guidelines is not None
-        and not inappropriate_tool_use_guidelines.strip()
-    ):
+    # inappropriate_tool_use_guidelines
+    inappropriate_tool_use_guidelines = properties.get(
+        "inappropriate_tool_use_guidelines"
+    )
+    if (
+        inappropriate_tool_use_guidelines is not None
+        and not inappropriate_tool_use_guidelines.strip()
+    ):
         raise ValueError(
             "inappropriate_tool_use_guidelines if provided cannot be empty"
         )
🧹 Nitpick comments (3)
libs/server/kiln_server/spec_api.py (1)

25-34: Consider adding min_length=1 to description for early validation.

The Spec model requires description to have min_length=1, but SpecUpsertRequest doesn't enforce this. While Spec construction will catch empty descriptions, adding the constraint here provides clearer error messages at the API layer.

 class SpecUpsertRequest(BaseModel):
     name: FilenameString
-    description: str
+    description: str = Field(min_length=1)
     properties: SpecProperties = Field(
         discriminator="spec_type",
     )
libs/core/kiln_ai/datamodel/test_spec.py (1)

143-179: Consider adding dedicated tests for skipped property types.

The parameterized test skips undesired_behaviour and appropriate_tool_use because they require additional fields. While there are dedicated tests for these types later in the file (lines 296-365), this skip pattern could be cleaner by removing these entries from the parameterized list since they're covered elsewhere.

 @pytest.mark.parametrize(
     "spec_type,properties_class",
     [
         (SpecType.desired_behaviour, DesiredBehaviourProperties),
-        (SpecType.undesired_behaviour, None),
-        (SpecType.appropriate_tool_use, None),
         (SpecType.intermediate_reasoning, IntermediateReasoningProperties),
         # ... rest of the list
     ],
 )
 def test_spec_all_types(sample_task, spec_type, properties_class):
     """Test that all spec types can be created."""
-    if properties_class is None:
-        pytest.skip(f"Skipping {spec_type} - requires additional fields")
-
     properties = properties_class(spec_type=spec_type)
libs/core/kiln_ai/datamodel/spec_properties.py (1)

166-174: Simplify AfterValidator usage by passing the functions directly

The lambdas here just forward to validate_appropriate_tool_use_properties and validate_undesired_behaviour_properties. Passing the functions directly is clearer and keeps tracebacks simpler.

 AppropriateToolUsePropertiesValidator = Annotated[
-    AppropriateToolUseProperties,
-    AfterValidator(lambda v: validate_appropriate_tool_use_properties(v)),
+    AppropriateToolUseProperties,
+    AfterValidator(validate_appropriate_tool_use_properties),
 ]
@@
 UndesiredBehaviourPropertiesValidator = Annotated[
-    UndesiredBehaviourProperties,
-    AfterValidator(lambda v: validate_undesired_behaviour_properties(v)),
+    UndesiredBehaviourProperties,
+    AfterValidator(validate_undesired_behaviour_properties),
 ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf1da52 and 4637681.

📒 Files selected for processing (9)
  • app/web_ui/src/lib/api_schema.d.ts (18 hunks)
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte (2 hunks)
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/+page.svelte (3 hunks)
  • libs/core/kiln_ai/datamodel/spec.py (2 hunks)
  • libs/core/kiln_ai/datamodel/spec_properties.py (1 hunks)
  • libs/core/kiln_ai/datamodel/test_spec.py (8 hunks)
  • libs/core/kiln_ai/datamodel/test_task.py (4 hunks)
  • libs/server/kiln_server/spec_api.py (4 hunks)
  • libs/server/kiln_server/test_spec_api.py (39 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/core/kiln_ai/datamodel/test_task.py
  • libs/core/kiln_ai/datamodel/spec.py
🧰 Additional context used
📓 Path-based instructions (11)
app/web_ui/**/*.svelte

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

app/web_ui/**/*.svelte: Use Svelte v4 (not v5) for the web frontend UI
Use DaisyUI for UI components in the web frontend

Use Svelte v4 (not v5) for the web UI framework

Files:

  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/+page.svelte
app/web_ui/**/*.{svelte,ts}

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use Tailwind CSS for styling in the web frontend

Files:

  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/+page.svelte
  • app/web_ui/src/lib/api_schema.d.ts
app/web_ui/**/*.{svelte,css,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind and DaisyUI for styling the frontend

Files:

  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/+page.svelte
  • app/web_ui/src/lib/api_schema.d.ts
app/web_ui/**/*.{svelte,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Make FastAPI backend calls from the Svelte web app to communicate with backend servers

Files:

  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/+page.svelte
  • app/web_ui/src/lib/api_schema.d.ts
libs/server/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use FastAPI for creating REST servers

libs/server/**/*.py: Use FastAPI for REST server implementations
Use asyncio for asynchronous operations in Python
Use FastAPI with asyncio for the REST server wrapping the core library

Files:

  • libs/server/kiln_server/spec_api.py
  • libs/server/kiln_server/test_spec_api.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

**/*.py: Use asyncio for asynchronous operations in Python
Use Pydantic v2 (not v1) for data validation

Files:

  • libs/server/kiln_server/spec_api.py
  • libs/core/kiln_ai/datamodel/test_spec.py
  • libs/server/kiln_server/test_spec_api.py
  • libs/core/kiln_ai/datamodel/spec_properties.py
libs/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Use Pydantic v2 (not v1) for data validation and serialization

Files:

  • libs/server/kiln_server/spec_api.py
  • libs/core/kiln_ai/datamodel/test_spec.py
  • libs/server/kiln_server/test_spec_api.py
  • libs/core/kiln_ai/datamodel/spec_properties.py
libs/core/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use Python 3.10+ for the core library (libs/core) and Python 3.13 for the desktop app

Use python 3.10+ for the core library (libs/core) and python 3.13 for the desktop app

Files:

  • libs/core/kiln_ai/datamodel/test_spec.py
  • libs/core/kiln_ai/datamodel/spec_properties.py
**/*test*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use pytest for testing Python code

Use pytest for testing in python projects

Files:

  • libs/core/kiln_ai/datamodel/test_spec.py
  • libs/server/kiln_server/test_spec_api.py
app/web_ui/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use TypeScript for frontend type safety

Files:

  • app/web_ui/src/lib/api_schema.d.ts
app/web_ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript for frontend development

Files:

  • app/web_ui/src/lib/api_schema.d.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 759
File: app/web_ui/src/lib/api_schema.d.ts:2485-2490
Timestamp: 2025-10-29T11:19:31.059Z
Learning: In libs/server/kiln_server/document_api.py and the generated app/web_ui/src/lib/api_schema.d.ts, CreateVectorStoreConfigRequest.properties is now a union of three distinct public schemas: LanceDBConfigFTSPropertiesPublic, LanceDBConfigVectorPropertiesPublic, and LanceDBConfigHybridPropertiesPublic. These are intentionally split to allow independent evolution; do not suggest merging them.
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 736
File: app/web_ui/src/lib/api_schema.d.ts:8143-8180
Timestamp: 2025-10-29T06:59:55.635Z
Learning: app/web_ui/src/lib/api_schema.d.ts is generated by openapi-typescript; do not propose manual edits. Schema changes should be made in the FastAPI backend (e.g., app/desktop/studio_server/data_gen_api.py or libs/server/kiln_server/*), then re-generate the TS types.
📚 Learning: 2025-10-29T11:19:31.059Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 759
File: app/web_ui/src/lib/api_schema.d.ts:2485-2490
Timestamp: 2025-10-29T11:19:31.059Z
Learning: In libs/server/kiln_server/document_api.py and the generated app/web_ui/src/lib/api_schema.d.ts, CreateVectorStoreConfigRequest.properties is now a union of three distinct public schemas: LanceDBConfigFTSPropertiesPublic, LanceDBConfigVectorPropertiesPublic, and LanceDBConfigHybridPropertiesPublic. These are intentionally split to allow independent evolution; do not suggest merging them.

Applied to files:

  • libs/server/kiln_server/spec_api.py
  • libs/server/kiln_server/test_spec_api.py
  • app/web_ui/src/lib/api_schema.d.ts
📚 Learning: 2025-10-27T09:38:25.562Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 753
File: libs/server/kiln_server/document_api.py:279-299
Timestamp: 2025-10-27T09:38:25.562Z
Learning: In the Kiln codebase, leonardmq intentionally overrides (rather than using setdefault) certain internal config fields like include_metadata and include_prev_next_rel in semantic chunker properties, even if the frontend sends them. These fields are too granular for user control and are set to enforce config stability.

Applied to files:

  • libs/server/kiln_server/spec_api.py
📚 Learning: 2025-10-29T06:59:55.635Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 736
File: app/web_ui/src/lib/api_schema.d.ts:8143-8180
Timestamp: 2025-10-29T06:59:55.635Z
Learning: app/web_ui/src/lib/api_schema.d.ts is generated by openapi-typescript; do not propose manual edits. Schema changes should be made in the FastAPI backend (e.g., app/desktop/studio_server/data_gen_api.py or libs/server/kiln_server/*), then re-generate the TS types.

Applied to files:

  • app/web_ui/src/lib/api_schema.d.ts
📚 Learning: 2025-09-04T06:27:58.852Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 487
File: app/web_ui/src/lib/api_schema.d.ts:3315-3320
Timestamp: 2025-09-04T06:27:58.852Z
Learning: The file app/web_ui/src/lib/api_schema.d.ts is auto-generated by openapi-typescript from the backend API schema and should never be manually edited. Changes to API types should be made in the backend, which will then be reflected in the regenerated TypeScript definitions.

Applied to files:

  • app/web_ui/src/lib/api_schema.d.ts
📚 Learning: 2025-09-10T08:30:56.973Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 546
File: app/web_ui/src/lib/api_schema.d.ts:4173-4218
Timestamp: 2025-09-10T08:30:56.973Z
Learning: leonardmq correctly identified that app/web_ui/src/lib/api_schema.d.ts is auto-generated and should not be manually edited. The proper fix for type inconsistencies between TypeScript API schema and backend models is to update the backend Python models and regenerate the TypeScript definitions.

Applied to files:

  • app/web_ui/src/lib/api_schema.d.ts
📚 Learning: 2025-09-10T08:27:47.227Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 546
File: app/web_ui/src/lib/api_schema.d.ts:0-0
Timestamp: 2025-09-10T08:27:47.227Z
Learning: leonardmq correctly identifies auto-generated files and prefers not to manually edit them. When suggesting changes to generated TypeScript API schema files, the fix should be made in the source OpenAPI specification in the backend, not in the generated TypeScript file.

Applied to files:

  • app/web_ui/src/lib/api_schema.d.ts
🧬 Code graph analysis (4)
libs/server/kiln_server/spec_api.py (2)
libs/core/kiln_ai/datamodel/spec.py (2)
  • Spec (21-55)
  • SpecStatus (12-18)
app/web_ui/src/lib/types.ts (3)
  • Spec (109-109)
  • SpecStatus (111-111)
  • SpecProperties (113-115)
libs/core/kiln_ai/datamodel/test_spec.py (3)
libs/core/kiln_ai/datamodel/spec.py (2)
  • Spec (21-55)
  • SpecStatus (12-18)
libs/core/kiln_ai/datamodel/spec_properties.py (16)
  • AppropriateToolUseProperties (111-115)
  • BiasProperties (87-88)
  • CompletenessProperties (63-64)
  • ConsistencyProperties (67-68)
  • DesiredBehaviourProperties (43-44)
  • FactualCorrectnessProperties (55-56)
  • FormattingProperties (75-76)
  • HallucinationsProperties (59-60)
  • JailbreakProperties (103-104)
  • LocalizationProperties (79-80)
  • MaliciousnessProperties (91-92)
  • NsfwProperties (95-96)
  • PromptLeakageProperties (107-108)
  • ReferenceAnswerAccuracyProperties (51-52)
  • SpecType (8-40)
  • UndesiredBehaviourProperties (144-147)
libs/core/kiln_ai/datamodel/test_task.py (1)
  • sample_task (59-60)
libs/server/kiln_server/test_spec_api.py (2)
libs/core/kiln_ai/datamodel/spec.py (2)
  • Spec (21-55)
  • SpecStatus (12-18)
libs/core/kiln_ai/datamodel/spec_properties.py (5)
  • DesiredBehaviourProperties (43-44)
  • HallucinationsProperties (59-60)
  • SpecType (8-40)
  • ToxicityProperties (83-84)
  • UndesiredBehaviourProperties (144-147)
libs/core/kiln_ai/datamodel/spec_properties.py (1)
app/web_ui/src/lib/types.ts (2)
  • SpecType (110-110)
  • SpecProperties (113-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Generate Coverage Report
  • GitHub Check: Generate Coverage Report
🔇 Additional comments (14)
app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/+page.svelte (1)

42-48: LGTM on the payload structure.

The request body correctly uses name, description, and properties: { spec_type: spec_type } which aligns with the new SpecUpsertRequest model in the backend that expects a discriminated union for properties.

app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte (1)

92-95: LGTM on the title rendering using properties.spec_type.

The optional chaining (spec?.properties?.spec_type) correctly handles cases where spec or properties might be undefined during loading.

libs/server/kiln_server/spec_api.py (2)

37-54: LGTM on create_spec endpoint.

The endpoint correctly constructs a Spec with the new description and properties fields, and the discriminated union ensures type-safe property handling.


65-80: LGTM on update_spec endpoint.

The PATCH endpoint correctly updates both description and properties fields on the existing spec before saving.

libs/server/kiln_server/test_spec_api.py (3)

8-15: LGTM on the updated imports.

The imports correctly reference SpecType and property classes from kiln_ai.datamodel.spec_properties, aligning with the refactored module structure.


374-414: LGTM on the test_create_spec_with_properties test.

Good coverage for creating specs with UndesiredBehaviourProperties, verifying both the API response and persisted data include the correct property values.


1034-1163: LGTM on the validation tests for empty property fields.

The tests comprehensively verify that empty tool_id, appropriate_tool_use_guidelines, undesired_behaviour_guidelines, and examples are properly rejected with 422 responses and appropriate error messages.

libs/core/kiln_ai/datamodel/test_spec.py (3)

1-27: LGTM on the comprehensive imports.

All required property classes and SpecType are correctly imported from kiln_ai.datamodel.spec_properties, and ValidationError is imported from Pydantic for proper exception testing.


368-394: LGTM on validation tests for missing required property fields.

These tests correctly verify that ValidationError is raised when required fields like appropriate_tool_use_guidelines or examples are missing from the properties dict.


397-424: LGTM on wrong spec_type literal validation tests.

The tests correctly verify that passing an invalid spec_type literal (like "wrong_type") raises a ValidationError, ensuring the discriminated union validation works properly.

app/web_ui/src/lib/api_schema.d.ts (2)

2193-2206: Spec property schemas form a consistent discriminated union

All the new *Properties schemas use a uniform spec_type string literal discriminator, and the shapes (including the extra fields on AppropriateToolUseProperties and UndesiredBehaviourProperties) look internally consistent. This is a good fit for narrowing on properties.spec_type in the client, and is appropriate for a generated OpenAPI → TS layer. No changes requested here.

As per retrieved learnings, any further tweaks should go via the backend models/OpenAPI spec and then re‑generate this file, not by hand-editing it.

Also applies to: 2254-2261, 2551-2566, 3098-3110, 3793-3804, 4084-4091, 4119-4126, 4147-4154, 4156-4163, 4405-4412, 4427-4434, 4497-4503, 4721-4728, 4987-4993, 5397-5404, 5825-5832, 5889-5896, 5898-5909


5327-5336: Spec/SpecUpsertRequest description + properties union look aligned

Switching from definition to a required description: string and adding the properties union that mirrors all the new spec property schemas keeps Spec and SpecUpsertRequest in sync and matches the discriminator design above. From the frontend typing perspective this is coherent and should work cleanly with Svelte consumers using spec.properties.spec_type to branch. No issues from the TS/schema side.

Given this file is generated, ensure any future changes are made in the FastAPI/OpenAPI schema and then re-generated, rather than editing this declaration file directly (based on retrieved learnings).

Also applies to: 5372-5375

libs/core/kiln_ai/datamodel/spec_properties.py (2)

8-109: Discriminated union design for SpecType and property TypedDicts looks good

Enum values and spec_type: Literal[...] fields line up cleanly for a Pydantic-discriminated union, and the property groups are well-factored for future extension. No changes needed here.


176-196: SpecProperties union composition is consistent and ready for discriminated use

The union correctly mixes the two validated variants with the plain TypedDict variants, all sharing a spec_type Literal discriminator. This should work well with Field(discriminator="spec_type") in the Spec model.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte (1)

75-75: Minor: Inconsistent nullish coalescing operator.

This uses || null while the list page (+page.svelte at lines 394, 472) uses ?? null. For consistency and correctness (to avoid coercing empty strings or 0 to null), consider using ?? null here as well.

-            eval_id: spec.eval_id || null,
+            eval_id: spec.eval_id ?? null,
app/web_ui/src/lib/types.ts (1)

112-130: Remove duplicate AppropriateToolUseProperties from union.

AppropriateToolUseProperties appears twice in the SpecProperties union (lines 113 and 118). While TypeScript unions automatically deduplicate identical members, the duplicate indicates a copy-paste error and reduces code clarity.

Apply this diff to remove the duplicate:

 export type SpecProperties =
   | components["schemas"]["AppropriateToolUseProperties"]
   | components["schemas"]["BehaviourProperties"]
   | components["schemas"]["ToneProperties"]
   | components["schemas"]["FormattingProperties"]
   | components["schemas"]["LocalizationProperties"]
-  | components["schemas"]["AppropriateToolUseProperties"]
   | components["schemas"]["ReferenceAnswerAccuracyProperties"]
   | components["schemas"]["FactualCorrectnessProperties"]
   | components["schemas"]["HallucinationsProperties"]
   | components["schemas"]["CompletenessProperties"]
   | components["schemas"]["ToxicityProperties"]
   | components["schemas"]["BiasProperties"]
   | components["schemas"]["MaliciousnessProperties"]
   | components["schemas"]["NsfwProperties"]
   | components["schemas"]["TabooProperties"]
   | components["schemas"]["JailbreakProperties"]
   | components["schemas"]["PromptLeakageProperties"]
libs/core/kiln_ai/datamodel/spec_properties.py (3)

40-68: Clarify assumptions and typing in validate_string_properties.

validate_string_properties assumes that all required_fields and optional_fields map to str values (because of .strip()), but the signature is fully generic over T and doesn’t constrain properties to a mapping of str keys to stringy values. To make misuse harder and static typing clearer, consider tightening the types, e.g. by constraining T to a Mapping[str, Any] (or similar) and/or asserting isinstance(value, str) before calling .strip(). This keeps the runtime behavior the same under Pydantic (where fields are already parsed as str) but makes the helper safer for any future non‑Pydantic callers.


71-427: Revisit optional field semantics (str | None + total=True).

All the *_examples and similar “optional” fields are declared as str | None on TypedDict(..., total=True). That models them as required keys that may be null, rather than truly optional keys. Your validators also allow None (they only reject empty/whitespace strings), so at the type level there’s currently no way to express “key may be absent, but if present must be a non‑empty string”.

If the API contract is that these example fields are genuinely optional (and not always present as null), it may be clearer to flip them to NotRequired[str] instead of str | None and let the validators enforce non‑empty strings when present. If the wire format always includes these keys and uses null to mean “no examples”, then the current typing is fine as‑is, but it would be worth confirming that intent and keeping it consistent across the schemas.


10-37: Discriminated union design for SpecProperties looks solid.

The SpecType enum, per‑type spec_type: Literal[...] fields, and the SpecProperties union of *PropertiesValidator aliases form a clean discriminated union that should work well with Pydantic v2’s Field(discriminator="spec_type") on the Spec model. Coverage of all enum variants is complete and the per‑type validators correctly delegate to the shared string validator with appropriate required/optional fields. No issues from a modeling or Pydantic‑usage perspective.

As per coding guidelines, if you’d like, I can sketch the corresponding Spec model field definition using this union (with proper discriminator wiring) to double‑check end‑to‑end validation.

Also applies to: 71-77, 95-452

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4637681 and 5351f4f.

📒 Files selected for processing (13)
  • app/web_ui/src/lib/api_schema.d.ts (17 hunks)
  • app/web_ui/src/lib/types.ts (1 hunks)
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte (4 hunks)
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte (3 hunks)
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/+page.svelte (3 hunks)
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/template_select/+page.svelte (2 hunks)
  • libs/core/kiln_ai/datamodel/spec.py (2 hunks)
  • libs/core/kiln_ai/datamodel/spec_properties.py (1 hunks)
  • libs/core/kiln_ai/datamodel/test_spec.py (9 hunks)
  • libs/core/kiln_ai/datamodel/test_spec_properties.py (1 hunks)
  • libs/core/kiln_ai/datamodel/test_task.py (4 hunks)
  • libs/server/kiln_server/spec_api.py (4 hunks)
  • libs/server/kiln_server/test_spec_api.py (41 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/+page.svelte
  • libs/server/kiln_server/spec_api.py
  • libs/core/kiln_ai/datamodel/test_task.py
🧰 Additional context used
📓 Path-based instructions (11)
app/web_ui/**/*.svelte

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

app/web_ui/**/*.svelte: Use Svelte v4 (not v5) for the web frontend UI
Use DaisyUI for UI components in the web frontend

Use Svelte v4 (not v5) for the web UI framework

Files:

  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/template_select/+page.svelte
app/web_ui/**/*.{svelte,ts}

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use Tailwind CSS for styling in the web frontend

Files:

  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/template_select/+page.svelte
  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/lib/types.ts
app/web_ui/**/*.{svelte,css,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind and DaisyUI for styling the frontend

Files:

  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/template_select/+page.svelte
  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/lib/types.ts
app/web_ui/**/*.{svelte,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Make FastAPI backend calls from the Svelte web app to communicate with backend servers

Files:

  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/template_select/+page.svelte
  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/lib/types.ts
libs/core/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use Python 3.10+ for the core library (libs/core) and Python 3.13 for the desktop app

Use python 3.10+ for the core library (libs/core) and python 3.13 for the desktop app

Files:

  • libs/core/kiln_ai/datamodel/test_spec_properties.py
  • libs/core/kiln_ai/datamodel/test_spec.py
  • libs/core/kiln_ai/datamodel/spec.py
  • libs/core/kiln_ai/datamodel/spec_properties.py
**/*test*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use pytest for testing Python code

Use pytest for testing in python projects

Files:

  • libs/core/kiln_ai/datamodel/test_spec_properties.py
  • libs/server/kiln_server/test_spec_api.py
  • libs/core/kiln_ai/datamodel/test_spec.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

**/*.py: Use asyncio for asynchronous operations in Python
Use Pydantic v2 (not v1) for data validation

Files:

  • libs/core/kiln_ai/datamodel/test_spec_properties.py
  • libs/server/kiln_server/test_spec_api.py
  • libs/core/kiln_ai/datamodel/test_spec.py
  • libs/core/kiln_ai/datamodel/spec.py
  • libs/core/kiln_ai/datamodel/spec_properties.py
libs/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Use Pydantic v2 (not v1) for data validation and serialization

Files:

  • libs/core/kiln_ai/datamodel/test_spec_properties.py
  • libs/server/kiln_server/test_spec_api.py
  • libs/core/kiln_ai/datamodel/test_spec.py
  • libs/core/kiln_ai/datamodel/spec.py
  • libs/core/kiln_ai/datamodel/spec_properties.py
libs/server/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use FastAPI for creating REST servers

libs/server/**/*.py: Use FastAPI for REST server implementations
Use asyncio for asynchronous operations in Python
Use FastAPI with asyncio for the REST server wrapping the core library

Files:

  • libs/server/kiln_server/test_spec_api.py
app/web_ui/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use TypeScript for frontend type safety

Files:

  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/lib/types.ts
app/web_ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript for frontend development

Files:

  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/lib/types.ts
🧠 Learnings (10)
📓 Common learnings
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 759
File: app/web_ui/src/lib/api_schema.d.ts:2485-2490
Timestamp: 2025-10-29T11:19:31.059Z
Learning: In libs/server/kiln_server/document_api.py and the generated app/web_ui/src/lib/api_schema.d.ts, CreateVectorStoreConfigRequest.properties is now a union of three distinct public schemas: LanceDBConfigFTSPropertiesPublic, LanceDBConfigVectorPropertiesPublic, and LanceDBConfigHybridPropertiesPublic. These are intentionally split to allow independent evolution; do not suggest merging them.
📚 Learning: 2025-09-25T07:20:11.459Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 650
File: app/web_ui/src/routes/(app)/docs/library/[project_id]/[document_id]/+page.svelte:19-19
Timestamp: 2025-09-25T07:20:11.459Z
Learning: When analyzing relative import paths in SvelteKit route structures, be more careful to verify the actual directory structure exists before suggesting corrections. The import path ../../../../generate/[project_id]/[task_id]/table_button.svelte from app/web_ui/src/routes/(app)/docs/library/[project_id]/[document_id]/+page.svelte correctly resolves to the existing file at app/web_ui/src/routes/(app)/generate/[project_id]/[task_id]/table_button.svelte.

Applied to files:

  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte
📚 Learning: 2025-10-29T06:59:55.635Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 736
File: app/web_ui/src/lib/api_schema.d.ts:8143-8180
Timestamp: 2025-10-29T06:59:55.635Z
Learning: app/web_ui/src/lib/api_schema.d.ts is generated by openapi-typescript; do not propose manual edits. Schema changes should be made in the FastAPI backend (e.g., app/desktop/studio_server/data_gen_api.py or libs/server/kiln_server/*), then re-generate the TS types.

Applied to files:

  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte
  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/lib/types.ts
📚 Learning: 2025-10-07T11:59:47.217Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 704
File: libs/core/kiln_ai/datamodel/chunk.py:170-182
Timestamp: 2025-10-07T11:59:47.217Z
Learning: In the Kiln AI datamodel (libs/core/kiln_ai/datamodel), validation functions should not set defaults. The datamodel requires everything to be explicit, and all required fields must be provided by the caller rather than being populated by validation logic.

Applied to files:

  • libs/core/kiln_ai/datamodel/test_spec_properties.py
📚 Learning: 2025-10-29T11:19:31.059Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 759
File: app/web_ui/src/lib/api_schema.d.ts:2485-2490
Timestamp: 2025-10-29T11:19:31.059Z
Learning: In libs/server/kiln_server/document_api.py and the generated app/web_ui/src/lib/api_schema.d.ts, CreateVectorStoreConfigRequest.properties is now a union of three distinct public schemas: LanceDBConfigFTSPropertiesPublic, LanceDBConfigVectorPropertiesPublic, and LanceDBConfigHybridPropertiesPublic. These are intentionally split to allow independent evolution; do not suggest merging them.

Applied to files:

  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/lib/types.ts
📚 Learning: 2025-09-04T06:27:58.852Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 487
File: app/web_ui/src/lib/api_schema.d.ts:3315-3320
Timestamp: 2025-09-04T06:27:58.852Z
Learning: The file app/web_ui/src/lib/api_schema.d.ts is auto-generated by openapi-typescript from the backend API schema and should never be manually edited. Changes to API types should be made in the backend, which will then be reflected in the regenerated TypeScript definitions.

Applied to files:

  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/lib/types.ts
📚 Learning: 2025-09-10T08:30:56.973Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 546
File: app/web_ui/src/lib/api_schema.d.ts:4173-4218
Timestamp: 2025-09-10T08:30:56.973Z
Learning: leonardmq correctly identified that app/web_ui/src/lib/api_schema.d.ts is auto-generated and should not be manually edited. The proper fix for type inconsistencies between TypeScript API schema and backend models is to update the backend Python models and regenerate the TypeScript definitions.

Applied to files:

  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/lib/types.ts
📚 Learning: 2025-10-28T05:25:09.283Z
Learnt from: leonardmq
Repo: Kiln-AI/Kiln PR: 757
File: libs/core/kiln_ai/adapters/reranker_list.py:39-42
Timestamp: 2025-10-28T05:25:09.283Z
Learning: In the Kiln codebase (Python), model family and model name fields in reranker and similar model definition classes (like KilnRerankerModel in libs/core/kiln_ai/adapters/reranker_list.py) should use plain str types rather than Enum types. This is because users receive over-the-air (OTA) updates for new models, and using Enums would break when clients with older builds encounter new model families their Enum definitions don't include. Strings provide forward compatibility for the OTA update mechanism.

Applied to files:

  • libs/core/kiln_ai/datamodel/spec.py
📚 Learning: 2025-11-24T19:13:16.956Z
Learnt from: CR
Repo: Kiln-AI/Kiln PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-11-24T19:13:16.956Z
Learning: Applies to app/web_ui/**/*.ts : Use TypeScript for frontend type safety

Applied to files:

  • app/web_ui/src/lib/types.ts
📚 Learning: 2025-11-24T19:13:27.852Z
Learnt from: CR
Repo: Kiln-AI/Kiln PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:13:27.852Z
Learning: Applies to app/web_ui/**/*.{ts,tsx} : Use TypeScript for frontend development

Applied to files:

  • app/web_ui/src/lib/types.ts
🧬 Code graph analysis (6)
libs/core/kiln_ai/datamodel/test_spec_properties.py (1)
libs/core/kiln_ai/datamodel/spec_properties.py (1)
  • validate_string_properties (40-68)
libs/server/kiln_server/test_spec_api.py (3)
libs/core/kiln_ai/datamodel/spec.py (2)
  • Spec (21-57)
  • SpecStatus (12-18)
libs/core/kiln_ai/datamodel/spec_properties.py (5)
  • BehaviourProperties (71-76)
  • HallucinationsProperties (242-245)
  • SpecType (10-37)
  • ToneProperties (95-100)
  • ToxicityProperties (287-290)
libs/core/kiln_ai/datamodel/test_spec.py (1)
  • sample_tone_properties (34-41)
libs/core/kiln_ai/datamodel/test_spec.py (4)
app/web_ui/src/lib/types.ts (4)
  • Spec (109-109)
  • SpecStatus (110-110)
  • SpecType (130-130)
  • Priority (111-111)
libs/core/kiln_ai/datamodel/spec.py (2)
  • Spec (21-57)
  • SpecStatus (12-18)
libs/core/kiln_ai/datamodel/spec_properties.py (4)
  • AppropriateToolUseProperties (164-170)
  • BehaviourProperties (71-76)
  • SpecType (10-37)
  • ToneProperties (95-100)
libs/core/kiln_ai/datamodel/test_task.py (1)
  • sample_task (59-60)
libs/core/kiln_ai/datamodel/spec.py (1)
app/web_ui/src/lib/types.ts (1)
  • SpecProperties (112-129)
libs/core/kiln_ai/datamodel/spec_properties.py (2)
app/web_ui/src/lib/types.ts (2)
  • SpecType (130-130)
  • SpecProperties (112-129)
libs/core/kiln_ai/utils/filesystem_cache.py (1)
  • get (24-35)
app/web_ui/src/lib/types.ts (2)
app/web_ui/src/lib/api_schema.d.ts (1)
  • components (2157-6077)
libs/core/kiln_ai/datamodel/spec_properties.py (1)
  • SpecType (10-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Generate Coverage Report
  • GitHub Check: Build, Typecheck, and Test Python (3.13)
  • GitHub Check: Build, Typecheck, and Test Python (3.11)
  • GitHub Check: Build, Typecheck, and Test Python (3.12)
  • GitHub Check: Build, Typecheck, and Test Python (3.10)
  • GitHub Check: Build Desktop Apps (ubuntu-22.04-arm)
  • GitHub Check: Build Desktop Apps (macos-15-intel)
  • GitHub Check: Build Desktop Apps (windows-latest)
  • GitHub Check: Build Desktop Apps (macos-latest)
  • GitHub Check: Build Desktop Apps (ubuntu-22.04)
🔇 Additional comments (21)
app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte (4)

186-187: LGTM on sort logic update.

The sorting logic correctly references spec.properties.spec_type to align with the new properties-based model.


390-394: Payload structure looks correct.

The PATCH body properly includes properties: spec.properties and uses ?? null for eval_id to handle nullish values correctly.


468-472: Consistent with other PATCH payloads.

The archive flow uses the same payload structure with properties and eval_id ?? null.


683-683: Type display updated correctly.

The table cell now displays the formatted spec type from spec.properties.spec_type.

libs/core/kiln_ai/datamodel/test_spec_properties.py (1)

1-164: Comprehensive test coverage for validate_string_properties.

The tests thoroughly cover required and optional field validation scenarios including edge cases for None, empty strings, whitespace-only values, and missing fields. Good use of descriptive docstrings.

app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte (2)

91-93: Title logic correctly updated for properties-based model.

The title now dynamically prefixes the spec name with the formatted spec type from spec.properties.spec_type.


71-71: Properties field correctly used in payload and display.

The PATCH payload and Properties panel display both correctly reference spec.properties and spec.properties.spec_type.

Also applies to: 125-125

app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/create_spec/template_select/+page.svelte (2)

35-38: Template consolidation looks good.

Consolidating "desired_behaviour" and "undesired_behaviour" into a single "behaviour" type with the description "follow or avoid" is a cleaner approach.


131-140: existing_spec_types set updated correctly.

The set now includes "behaviour" and reflects the currently implemented spec types.

libs/core/kiln_ai/datamodel/spec.py (2)

28-31: Discriminator pattern correctly implemented.

The properties field with discriminator="spec_type" enables Pydantic to automatically validate and type-discriminate based on the spec type. This follows the recommendation from past reviews.


25-27: Clarify: definition field retention vs. PR title.

The PR title mentions "changed definition to description", but the definition field is retained here. Given the PR author's comment that "the spec model object is in flux", please confirm whether:

  1. This field should remain as definition, or
  2. This should be renamed to description as part of this PR.
libs/server/kiln_server/test_spec_api.py (3)

52-81: LGTM! Well-structured property helper functions.

The property dict helper functions are well-designed with clear names, documentation, and proper enum value serialization for API payloads.


83-125: LGTM! Well-structured fixtures for property objects.

The fixtures properly use TypedDict classes and provide good reusability across tests.


1129-1264: LGTM! Comprehensive validation tests for property fields.

The new validation tests provide excellent coverage for empty string validation across different property types (tool use, behaviour). The assertions properly check for 422 status codes and field-specific error messages.

libs/core/kiln_ai/datamodel/test_spec.py (5)

147-262: LGTM! Comprehensive helper for all spec property types.

The create_sample_properties helper provides excellent coverage for all 16 spec types with proper TypedDict usage and clear error handling. This is a well-designed test utility.


393-440: LGTM! Thorough tests for AppropriateToolUseProperties.

The tests properly validate both required and optional fields of AppropriateToolUseProperties, including proper type checks and field value assertions.


442-470: LGTM! Good test coverage for BehaviourProperties.

The test properly validates the BehaviourProperties structure including behavior description and example fields.


472-534: LGTM! Comprehensive property validation tests.

The validation tests provide excellent coverage for missing required fields and invalid spec_type values, properly using Pydantic's ValidationError and pytest assertions.


536-567: LGTM! Good tests for edge cases and field coexistence.

The tests properly validate that empty property dicts are rejected and that the definition field works correctly alongside properties. Note that the definition field is still present in the code, which appears inconsistent with the PR summary that mentions changing "definition to description".

app/web_ui/src/lib/api_schema.d.ts (2)

2193-2210: New Spec*Properties schemas are consistent and discriminator-friendly

All of the new *Properties schemas share a literal spec_type discriminator plus a consistent base_instruction + optional examples structure, which should work well as a discriminated union for Spec.properties and SpecUpsertRequest.properties and give good narrowing on the frontend. No structural or typing issues stand out here.

Based on learnings, this file is generated by openapi-typescript and should not be edited manually; any future shape changes should be driven from the backend models and the types regenerated.

Also applies to: 2258-2285, 2575-2588, 3811-3822, 4102-4117, 4145-4156, 4178-4188, 4431-4444, 4460-4470, 4533-4543, 4762-4772, 5031-5045, 5450-5460


5357-5433: Verify Spec/SpecUpsertRequest definition vs description and required properties

Spec and SpecUpsertRequest now expose a required properties union, but the API schema still uses definition: string and does not expose a description field. Given the PR description mentions changing “definition” to “description”, please double‑check:

  • Is the external API intentionally staying on definition for compatibility while internal models move to description, or is the rename meant to be reflected in the OpenAPI schema too?
  • Should properties be required on SpecUpsertRequest, or do you expect existing callers that only send a definition/description to remain valid?

If the current shape is not intentional, update the FastAPI/Pydantic models in the backend and regenerate this file rather than editing it directly.

Based on learnings, app/web_ui/src/lib/api_schema.d.ts is auto-generated; aligning it should be done via backend schema changes and re-generation.

@sfierro sfierro merged commit d0226e7 into sfierro/specs Nov 27, 2025
15 checks passed
@sfierro sfierro deleted the sfierro/spec_properties branch November 27, 2025 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants