From 5a40231f7f3754a044086ebc2819cec500082295 Mon Sep 17 00:00:00 2001 From: Daniel Yudelevich Date: Sun, 20 Sep 2026 18:50:09 -0700 Subject: [PATCH 1/5] Carry include_confidence through the SDK and CLI Regenerated from the branch's own OpenAPI spec via gen_requests.py --spec rather than edited by hand, since that file says not to. The regen also picks up an integration_id description that had drifted since native-icp shipped. Ordering: contract.yml checks a PR targeting development against DEV_SPEC_URL, so this needs the platform change on api-dev before the SDK PR opens, not a prod deploy. Until then check_contract.py and gen_requests.py --check both disagree, because the spec they read does not carry the field yet. --- CHANGELOG.md | 1 + packages/discolike-cli/src/discolike_cli/discogen.py | 9 +++++++++ packages/discolike/src/discolike/_generated/requests.py | 8 +++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36bad91..d33a166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- SDK/CLI: `discogen.process` and `discogen.process_personas` take `include_confidence`, and the CLI gains `--include-confidence`. It applies to typed columns, which a DiscoGen run answers with a TypeSafe judgment model rather than generated prose; with it on, each typed column gets a sibling confidence column. Off by default, and it changes display only - the answers and their probabilities come back either way. - SDK: `contacts.generate` can run without an LLM key. Pass `integration_id=NATIVE_ENGINE` (exported from `discolike`) for DiscoLike Groove, DiscoLike's native extractor, or omit `integration_id` to get your default LLM integration and native when you have none. The native engine returns every person the search surfaces with a title and does not validate titles against `icp_text`; a search provider is still required. `JobStatus` gains `title_validation` (`"llm"` or `"none"`, `None` on non-ContaGen jobs) so you can tell which engine ran. - SDK: discover and count take `sub_industry` / `negate_sub_industry` (217 second-level labels scoped to their parent industry category; a bare label like `ROOFING` adds `CONSTRUCTION` to `category` server-side) and a multi-shape geo filter: `geo` (`lat,lon` or `lat,lon,radius`), `bbox` (`min_lat,min_lon,max_lat,max_lon`, longitudes may wrap the antimeridian) and `lat` / `lon` / `radius` (`50km`, `30mi`, or a bare number meaning kilometres; defaults to 50km). `geo` and `bbox` are lists, every circle and box is OR'd with the others and with the `lat`/`lon` centre, and a query carries at most 10 shapes. Passing a single `bbox` or `geo` string still works and is sent as a one-element list. The SDK applies the platform's own shape rules before the request leaves the client: a box is four finite numbers with `-90 <= min_lat < max_lat <= 90`, longitudes within ±180 and `min_lon` different from `max_lon`; a circle is a valid coordinate pair with an optional radius above 0 and at most 1000km. - SDK: `CompanyProfile` gains `sub_industry` (label:confidence, `None` when the domain was never scored against its category's sub-labels), `latitude`, `longitude` and `geo_precision`. The coordinates come back under their full names, and a response still using the pre-rename `lat` / `lon` keys parses into them, so the SDK works against an API on either side of that release. The `lat` / `lon` discover and count filters keep their names. diff --git a/packages/discolike-cli/src/discolike_cli/discogen.py b/packages/discolike-cli/src/discolike_cli/discogen.py index a092997..ddcaf96 100644 --- a/packages/discolike-cli/src/discolike_cli/discogen.py +++ b/packages/discolike-cli/src/discolike_cli/discogen.py @@ -32,6 +32,7 @@ WEB_SEARCH_HELP = "Toggle web search during research." CONTEXT_MODE_HELP = "Context mode; see docs.discolike.com." INCLUDE_X_SEARCH_HELP = "Toggle including X search in the research." +INCLUDE_CONFIDENCE_HELP = "Add a confidence column beside each typed column." SEARCH_PROVIDER_ID_HELP = "Search provider ID to use for web search." SEARCH_CONTEXT_SIZE_HELP = "Search context size; see docs.discolike.com." TASK_ID_HELP = "Task ID returned when the job was started." @@ -59,6 +60,9 @@ def run_command( include_x_search: bool | None = typer.Option( None, "--include-x-search/--no-include-x-search", help=INCLUDE_X_SEARCH_HELP ), + include_confidence: bool | None = typer.Option( + None, "--include-confidence/--no-include-confidence", help=INCLUDE_CONFIDENCE_HELP + ), search_provider_id: str | None = typer.Option(None, "--search-provider-id", help=SEARCH_PROVIDER_ID_HELP), search_context_size: str | None = typer.Option(None, "--search-context-size", help=SEARCH_CONTEXT_SIZE_HELP), wait: bool = typer.Option(False, "--wait", help=WAIT_HELP), @@ -81,6 +85,7 @@ def run_command( web_search=web_search, context_mode=context_mode, include_x_search=include_x_search, + include_confidence=include_confidence, search_provider_id=search_provider_id, search_context_size=search_context_size, ), @@ -100,6 +105,9 @@ def run_personas_command( include_x_search: bool | None = typer.Option( None, "--include-x-search/--no-include-x-search", help=INCLUDE_X_SEARCH_HELP ), + include_confidence: bool | None = typer.Option( + None, "--include-confidence/--no-include-confidence", help=INCLUDE_CONFIDENCE_HELP + ), search_provider_id: str | None = typer.Option(None, "--search-provider-id", help=SEARCH_PROVIDER_ID_HELP), search_context_size: str | None = typer.Option(None, "--search-context-size", help=SEARCH_CONTEXT_SIZE_HELP), wait: bool = typer.Option(False, "--wait", help=WAIT_HELP), @@ -119,6 +127,7 @@ def run_personas_command( web_search=web_search, context_mode=context_mode, include_x_search=include_x_search, + include_confidence=include_confidence, search_provider_id=search_provider_id, search_context_size=search_context_size, ), diff --git a/packages/discolike/src/discolike/_generated/requests.py b/packages/discolike/src/discolike/_generated/requests.py index 563d1bd..01bcbc1 100644 --- a/packages/discolike/src/discolike/_generated/requests.py +++ b/packages/discolike/src/discolike/_generated/requests.py @@ -1421,12 +1421,13 @@ class DiscoGenProcessRequest(DiscolikeRequest): integration_id: Annotated[ str | None, Field( - description="LLM provider integration UUID (omit for org default)", + description="LLM provider integration UUID (omit for org default), or 'native-icp' to score an ICP validation prompt with the native model", title="Integration Id", ), ] = None web_search: Annotated[bool | None, Field(title="Web Search")] = False include_x_search: Annotated[bool | None, Field(title="Include X Search")] = False + include_confidence: Annotated[bool | None, Field(title="Include Confidence")] = False search_provider_id: Annotated[ str | None, Field( @@ -1456,12 +1457,13 @@ class DiscoGenPersonaProcessRequest(DiscolikeRequest): integration_id: Annotated[ str | None, Field( - description="LLM provider integration UUID (omit for org default)", + description="LLM provider integration UUID (omit for org default), or 'native-icp' to score an ICP validation prompt with the native model", title="Integration Id", ), ] = None web_search: Annotated[bool | None, Field(title="Web Search")] = False include_x_search: Annotated[bool | None, Field(title="Include X Search")] = False + include_confidence: Annotated[bool | None, Field(title="Include Confidence")] = False search_provider_id: Annotated[ str | None, Field( @@ -1507,7 +1509,7 @@ class ValidateIcpRequest(DiscolikeRequest): integration_id: Annotated[ str | None, Field( - description="LLM provider integration UUID (omit for org default)", + description="LLM provider integration UUID (omit for org default), or 'native-icp' to score with DiscoLike's own ICP-fit model at no LLM cost", title="Integration Id", ), ] = None From 99c286328a85d8663bfeafa32d42c4470684f983 Mon Sep 17 00:00:00 2001 From: Daniel Yudelevich Date: Tue, 22 Sep 2026 10:02:54 -0700 Subject: [PATCH 2/5] Carry the typed_columns opt-in flag through the SDK and CLI DiscoGen's typed-column detector (which columns run through TypeSafe judgment instead of generated prose) is picked server-side from the query text; the client only opts in with a bool, same shape as include_confidence which already made the trip last commit. Hand-added to _generated/requests.py rather than regenerated, since api-dev does not serve this field yet (same ordering constraint as include_confidence). --- CHANGELOG.md | 1 + .../src/discolike_cli/discogen.py | 5 +++ .../discolike-cli/tests/test_discogen_cli.py | 42 +++++++++++++++++++ .../src/discolike/_generated/requests.py | 14 +++++++ packages/discolike/tests/test_discogen.py | 17 ++++++++ 5 files changed, 79 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d33a166..68dc18f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- SDK/CLI: `discogen.process` and `discogen.process_personas` take `typed_columns`, and the CLI gains `--typed-columns`. It opts the query into the detector answering yes/no, fixed-set and scale columns with a TypeSafe judgment model instead of generated prose; which columns are typed and how is decided server-side from the query text, not passed in by the caller. Off by default. See `include_confidence` above for the sibling flag that adds a confidence column to each typed column. - SDK/CLI: `discogen.process` and `discogen.process_personas` take `include_confidence`, and the CLI gains `--include-confidence`. It applies to typed columns, which a DiscoGen run answers with a TypeSafe judgment model rather than generated prose; with it on, each typed column gets a sibling confidence column. Off by default, and it changes display only - the answers and their probabilities come back either way. - SDK: `contacts.generate` can run without an LLM key. Pass `integration_id=NATIVE_ENGINE` (exported from `discolike`) for DiscoLike Groove, DiscoLike's native extractor, or omit `integration_id` to get your default LLM integration and native when you have none. The native engine returns every person the search surfaces with a title and does not validate titles against `icp_text`; a search provider is still required. `JobStatus` gains `title_validation` (`"llm"` or `"none"`, `None` on non-ContaGen jobs) so you can tell which engine ran. - SDK: discover and count take `sub_industry` / `negate_sub_industry` (217 second-level labels scoped to their parent industry category; a bare label like `ROOFING` adds `CONSTRUCTION` to `category` server-side) and a multi-shape geo filter: `geo` (`lat,lon` or `lat,lon,radius`), `bbox` (`min_lat,min_lon,max_lat,max_lon`, longitudes may wrap the antimeridian) and `lat` / `lon` / `radius` (`50km`, `30mi`, or a bare number meaning kilometres; defaults to 50km). `geo` and `bbox` are lists, every circle and box is OR'd with the others and with the `lat`/`lon` centre, and a query carries at most 10 shapes. Passing a single `bbox` or `geo` string still works and is sent as a one-element list. The SDK applies the platform's own shape rules before the request leaves the client: a box is four finite numbers with `-90 <= min_lat < max_lat <= 90`, longitudes within ±180 and `min_lon` different from `max_lon`; a circle is a valid coordinate pair with an optional radius above 0 and at most 1000km. diff --git a/packages/discolike-cli/src/discolike_cli/discogen.py b/packages/discolike-cli/src/discolike_cli/discogen.py index ddcaf96..38af7b2 100644 --- a/packages/discolike-cli/src/discolike_cli/discogen.py +++ b/packages/discolike-cli/src/discolike_cli/discogen.py @@ -32,6 +32,7 @@ WEB_SEARCH_HELP = "Toggle web search during research." CONTEXT_MODE_HELP = "Context mode; see docs.discolike.com." INCLUDE_X_SEARCH_HELP = "Toggle including X search in the research." +TYPED_COLUMNS_HELP = "Let the detector answer yes/no, fixed-set and scale columns with a TypeSafe judgment model." INCLUDE_CONFIDENCE_HELP = "Add a confidence column beside each typed column." SEARCH_PROVIDER_ID_HELP = "Search provider ID to use for web search." SEARCH_CONTEXT_SIZE_HELP = "Search context size; see docs.discolike.com." @@ -60,6 +61,7 @@ def run_command( include_x_search: bool | None = typer.Option( None, "--include-x-search/--no-include-x-search", help=INCLUDE_X_SEARCH_HELP ), + typed_columns: bool | None = typer.Option(None, "--typed-columns/--no-typed-columns", help=TYPED_COLUMNS_HELP), include_confidence: bool | None = typer.Option( None, "--include-confidence/--no-include-confidence", help=INCLUDE_CONFIDENCE_HELP ), @@ -85,6 +87,7 @@ def run_command( web_search=web_search, context_mode=context_mode, include_x_search=include_x_search, + typed_columns=typed_columns, include_confidence=include_confidence, search_provider_id=search_provider_id, search_context_size=search_context_size, @@ -105,6 +108,7 @@ def run_personas_command( include_x_search: bool | None = typer.Option( None, "--include-x-search/--no-include-x-search", help=INCLUDE_X_SEARCH_HELP ), + typed_columns: bool | None = typer.Option(None, "--typed-columns/--no-typed-columns", help=TYPED_COLUMNS_HELP), include_confidence: bool | None = typer.Option( None, "--include-confidence/--no-include-confidence", help=INCLUDE_CONFIDENCE_HELP ), @@ -127,6 +131,7 @@ def run_personas_command( web_search=web_search, context_mode=context_mode, include_x_search=include_x_search, + typed_columns=typed_columns, include_confidence=include_confidence, search_provider_id=search_provider_id, search_context_size=search_context_size, diff --git a/packages/discolike-cli/tests/test_discogen_cli.py b/packages/discolike-cli/tests/test_discogen_cli.py index 4397665..92330fe 100644 --- a/packages/discolike-cli/tests/test_discogen_cli.py +++ b/packages/discolike-cli/tests/test_discogen_cli.py @@ -67,6 +67,26 @@ def handler(request: httpx2.Request) -> httpx2.Response: } +def test_discogen_run_sends_typed_columns_when_passed(install_build_client: Callable[[Handler], None]) -> None: + captured: dict[str, object] = {} + + def handler(request: httpx2.Request) -> httpx2.Response: + captured["body"] = json.loads(request.content) + return httpx2.Response(200, json={"task_id": "dg-1c"}) + + install_build_client(handler) + result = runner.invoke( + app, + ["discogen", "run", "--query", "q", "--domain", "acme.com", "--typed-columns"], + ) + assert result.exit_code == 0, result.output + assert captured["body"] == { + "query": "q", + "domains": ["acme.com"], + "typed_columns": True, + } + + def test_discogen_run_personas_posts_persona_ids(install_build_client: Callable[[Handler], None]) -> None: captured: dict[str, object] = {} @@ -110,6 +130,28 @@ def handler(request: httpx2.Request) -> httpx2.Response: } +def test_discogen_run_personas_sends_typed_columns_when_passed( + install_build_client: Callable[[Handler], None], +) -> None: + captured: dict[str, object] = {} + + def handler(request: httpx2.Request) -> httpx2.Response: + captured["body"] = json.loads(request.content) + return httpx2.Response(200, json={"task_id": "dg-2c"}) + + install_build_client(handler) + result = runner.invoke( + app, + ["discogen", "run-personas", "--query", "q", "--persona-id", "1", "--typed-columns"], + ) + assert result.exit_code == 0, result.output + assert captured["body"] == { + "query": "q", + "persona_ids": [1], + "typed_columns": True, + } + + def test_discogen_run_with_wait_polls_to_completion(install_build_client: Callable[[Handler], None]) -> None: statuses = iter( [ diff --git a/packages/discolike/src/discolike/_generated/requests.py b/packages/discolike/src/discolike/_generated/requests.py index 01bcbc1..6959cf1 100644 --- a/packages/discolike/src/discolike/_generated/requests.py +++ b/packages/discolike/src/discolike/_generated/requests.py @@ -1427,6 +1427,13 @@ class DiscoGenProcessRequest(DiscolikeRequest): ] = None web_search: Annotated[bool | None, Field(title="Web Search")] = False include_x_search: Annotated[bool | None, Field(title="Include X Search")] = False + typed_columns: Annotated[ + bool | None, + Field( + description="Let the detector answer yes/no, fixed-set and scale columns with a TypeSafe judgment model", + title="Typed Columns", + ), + ] = False include_confidence: Annotated[bool | None, Field(title="Include Confidence")] = False search_provider_id: Annotated[ str | None, @@ -1463,6 +1470,13 @@ class DiscoGenPersonaProcessRequest(DiscolikeRequest): ] = None web_search: Annotated[bool | None, Field(title="Web Search")] = False include_x_search: Annotated[bool | None, Field(title="Include X Search")] = False + typed_columns: Annotated[ + bool | None, + Field( + description="Let the detector answer yes/no, fixed-set and scale columns with a TypeSafe judgment model", + title="Typed Columns", + ), + ] = False include_confidence: Annotated[bool | None, Field(title="Include Confidence")] = False search_provider_id: Annotated[ str | None, diff --git a/packages/discolike/tests/test_discogen.py b/packages/discolike/tests/test_discogen.py index 67e0bab..8b074d9 100644 --- a/packages/discolike/tests/test_discogen.py +++ b/packages/discolike/tests/test_discogen.py @@ -71,6 +71,8 @@ def handler(request: httpx2.Request) -> httpx2.Response: web_search=True, context_mode="website", include_x_search=False, + typed_columns=True, + include_confidence=True, search_provider_id="serper", search_context_size="medium", ) @@ -83,11 +85,26 @@ def handler(request: httpx2.Request) -> httpx2.Response: "web_search": True, "context_mode": "website", "include_x_search": False, + "typed_columns": True, + "include_confidence": True, "search_provider_id": "serper", "search_context_size": "medium", } +def test_process_sends_typed_columns_when_set(make_client: ClientFactory) -> None: + seen = {} + + def handler(request: httpx2.Request) -> httpx2.Response: + seen["body"] = json.loads(request.content) + return httpx2.Response(200, json={"task_id": "dg-typed"}) + + with make_client(handler) as client: + client.discogen.process(DiscoGenProcessRequest(query="q", domains=["a.com"], typed_columns=True)) + + assert seen["body"] == {"query": "q", "domains": ["a.com"], "typed_columns": True} + + def test_process_personas_posts_json_and_returns_job(make_client: ClientFactory) -> None: seen = {} From b7c2fa5bb5d7266371c16c611afb281f89837b66 Mon Sep 17 00:00:00 2001 From: Daniel Yudelevich Date: Tue, 22 Sep 2026 10:22:53 -0700 Subject: [PATCH 3/5] Cover --include-confidence on/off/unset in the CLI tests The flag has distinct true, false, and unset wire behavior but no dedicated test exercised any of it for run or run-personas. --- .../discolike-cli/tests/test_discogen_cli.py | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/packages/discolike-cli/tests/test_discogen_cli.py b/packages/discolike-cli/tests/test_discogen_cli.py index 4397665..19d3c56 100644 --- a/packages/discolike-cli/tests/test_discogen_cli.py +++ b/packages/discolike-cli/tests/test_discogen_cli.py @@ -67,6 +67,71 @@ def handler(request: httpx2.Request) -> httpx2.Response: } +def test_discogen_run_sends_include_confidence_true_when_flag_passed( + install_build_client: Callable[[Handler], None], +) -> None: + captured: dict[str, object] = {} + + def handler(request: httpx2.Request) -> httpx2.Response: + captured["body"] = json.loads(request.content) + return httpx2.Response(200, json={"task_id": "dg-1d"}) + + install_build_client(handler) + result = runner.invoke( + app, + ["discogen", "run", "--query", "q", "--domain", "acme.com", "--include-confidence"], + ) + assert result.exit_code == 0, result.output + assert captured["body"] == { + "query": "q", + "domains": ["acme.com"], + "include_confidence": True, + } + + +def test_discogen_run_sends_include_confidence_false_when_flag_negated( + install_build_client: Callable[[Handler], None], +) -> None: + captured: dict[str, object] = {} + + def handler(request: httpx2.Request) -> httpx2.Response: + captured["body"] = json.loads(request.content) + return httpx2.Response(200, json={"task_id": "dg-1e"}) + + install_build_client(handler) + result = runner.invoke( + app, + ["discogen", "run", "--query", "q", "--domain", "acme.com", "--no-include-confidence"], + ) + assert result.exit_code == 0, result.output + assert captured["body"] == { + "query": "q", + "domains": ["acme.com"], + "include_confidence": False, + } + + +def test_discogen_run_omits_include_confidence_when_flag_not_passed( + install_build_client: Callable[[Handler], None], +) -> None: + captured: dict[str, object] = {} + + def handler(request: httpx2.Request) -> httpx2.Response: + captured["body"] = json.loads(request.content) + return httpx2.Response(200, json={"task_id": "dg-1f"}) + + install_build_client(handler) + result = runner.invoke( + app, + ["discogen", "run", "--query", "q", "--domain", "acme.com"], + ) + assert result.exit_code == 0, result.output + assert captured["body"] == { + "query": "q", + "domains": ["acme.com"], + } + + def test_discogen_run_personas_posts_persona_ids(install_build_client: Callable[[Handler], None]) -> None: captured: dict[str, object] = {} @@ -110,6 +175,71 @@ def handler(request: httpx2.Request) -> httpx2.Response: } +def test_discogen_run_personas_sends_include_confidence_true_when_flag_passed( + install_build_client: Callable[[Handler], None], +) -> None: + captured: dict[str, object] = {} + + def handler(request: httpx2.Request) -> httpx2.Response: + captured["body"] = json.loads(request.content) + return httpx2.Response(200, json={"task_id": "dg-2c"}) + + install_build_client(handler) + result = runner.invoke( + app, + ["discogen", "run-personas", "--query", "q", "--persona-id", "1", "--include-confidence"], + ) + assert result.exit_code == 0, result.output + assert captured["body"] == { + "query": "q", + "persona_ids": [1], + "include_confidence": True, + } + + +def test_discogen_run_personas_sends_include_confidence_false_when_flag_negated( + install_build_client: Callable[[Handler], None], +) -> None: + captured: dict[str, object] = {} + + def handler(request: httpx2.Request) -> httpx2.Response: + captured["body"] = json.loads(request.content) + return httpx2.Response(200, json={"task_id": "dg-2d"}) + + install_build_client(handler) + result = runner.invoke( + app, + ["discogen", "run-personas", "--query", "q", "--persona-id", "1", "--no-include-confidence"], + ) + assert result.exit_code == 0, result.output + assert captured["body"] == { + "query": "q", + "persona_ids": [1], + "include_confidence": False, + } + + +def test_discogen_run_personas_omits_include_confidence_when_flag_not_passed( + install_build_client: Callable[[Handler], None], +) -> None: + captured: dict[str, object] = {} + + def handler(request: httpx2.Request) -> httpx2.Response: + captured["body"] = json.loads(request.content) + return httpx2.Response(200, json={"task_id": "dg-2e"}) + + install_build_client(handler) + result = runner.invoke( + app, + ["discogen", "run-personas", "--query", "q", "--persona-id", "1"], + ) + assert result.exit_code == 0, result.output + assert captured["body"] == { + "query": "q", + "persona_ids": [1], + } + + def test_discogen_run_with_wait_polls_to_completion(install_build_client: Callable[[Handler], None]) -> None: statuses = iter( [ From 9a15d49f0b6e052a947093b9e21403f2d9ab8dbf Mon Sep 17 00:00:00 2001 From: Daniel Yudelevich Date: Tue, 22 Sep 2026 10:25:48 -0700 Subject: [PATCH 4/5] Check any non-release PR against the dev spec A PR stacked on a feature branch is as far ahead of prod as the branch it targets, but base_ref only matched "development" literally, so the stack fell through to the prod spec and reported every unreleased field as a missing one. Fork PRs still get the prod spec, since they never receive DEV_SPEC_URL. --- .github/workflows/contract.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/contract.yml b/.github/workflows/contract.yml index 04eb4dc..8d4bfe3 100644 --- a/.github/workflows/contract.yml +++ b/.github/workflows/contract.yml @@ -18,14 +18,16 @@ jobs: python-version: '3.14' - run: uv sync --all-packages # SDK features land on development before the platform deploys to - # prod, so PRs targeting development check against the dev spec. + # prod, so every PR but a release into main checks against the dev + # spec — including one stacked on another feature branch, which is + # just as far ahead of prod as the branch it targets. # DEV_SPEC_URL is a repo secret; fork PRs don't receive it and # fall back to the prod spec, keeping dev infra internal-only. - env: BASE_REF: ${{ github.base_ref }} DEV_SPEC_URL: ${{ secrets.DEV_SPEC_URL }} run: | - if [ "$BASE_REF" = "development" ] && [ -n "$DEV_SPEC_URL" ]; then + if [ "$BASE_REF" != "main" ] && [ -n "$DEV_SPEC_URL" ]; then uv run python scripts/check_contract.py --spec-url "$DEV_SPEC_URL" uv run python scripts/gen_requests.py --check --spec-url "$DEV_SPEC_URL" else From f2f28c483c733c54fc74eeda642436950b8cbea9 Mon Sep 17 00:00:00 2001 From: Daniel Yudelevich Date: Tue, 22 Sep 2026 10:48:46 -0700 Subject: [PATCH 5/5] Trigger checks for the merged typed-columns stack