From 0823c51321a8903f5657e1c72071414e64960f09 Mon Sep 17 00:00:00 2001 From: Manush Murali Date: Thu, 15 Oct 2026 17:40:57 -0700 Subject: [PATCH 1/4] Say that the explanation refusal belongs to one deployment Both documents scoped the refusal to the shipping deployment and then described it in a way that reads as a property of the field. Explanation has been measured working against another relational deployment, returned and parsed onto PredictionResult.explanation, so a reader deciding whether to build on it was being told the wrong thing. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Manush Murali --- docs/about/architecture.md | 13 ++++++++++--- skills/relational/SKILL.md | 5 ++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/about/architecture.md b/docs/about/architecture.md index 7c2313bb4..9123db87b 100644 --- a/docs/about/architecture.md +++ b/docs/about/architecture.md @@ -558,9 +558,16 @@ build: | `multiclass_classification` | `prediction`, `probabilities` | So `scores`, `rankings`, `embeddings` and `explanation` are declared by the -client and refused by that deployment on every task. `rankings` is the one -that still works end to end: `RANK TOP k` is decomposed into a classification -per candidate, so the ranking is assembled here rather than asked for. +client and refused by that deployment on every task. That is a fact about this +build, not about the fields: `explanation` has been measured working against +another relational deployment, which returns an explanation the client parses +and hands back on `PredictionResult.explanation`. A deployment that refuses it is +narrower than the model family, not the client declaring something nothing +serves. + +`rankings` is the one that still works end to end against the narrower build: +`RANK TOP k` is decomposed into a classification per candidate, so the ranking +is assembled here rather than asked for. `forecasting` and `temporal_link_prediction` are declared as tasks and are not served either — a forecast is sent as a regression per step, and a bare diff --git a/skills/relational/SKILL.md b/skills/relational/SKILL.md index 6c02f1d25..72a62606b 100644 --- a/skills/relational/SKILL.md +++ b/skills/relational/SKILL.md @@ -49,7 +49,10 @@ serves `prediction` and `quantiles` on a regression, `prediction` and ``` So ask for an explanation, catch the refusal, and fall back. Do not build a -flow that assumes one arrives. +flow that assumes one arrives. Do not assume one never does either: another +relational deployment serves `explanation`, and the same code gets a parsed +one back there, so the refusal belongs to the deployment and not to the +request. The task is not an argument. It follows from the query: see [`pql.md`](pql.md). From 301f4dcbe42f49cc02b8b062ca63173749dd6829 Mon Sep 17 00:00:00 2001 From: Manush Murali Date: Thu, 15 Oct 2026 17:43:28 -0700 Subject: [PATCH 2/4] Stop crediting the shipping build with an explanation it refuses The comment explaining why explanation_formats is empty named `kumo_rfm_v2_1` as coming from the shipping build. That build returns 422 UNSUPPORTED_OUTPUT for the field; the format name comes from the deployment that does serve it. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Manush Murali --- .../src/sdm_client/models.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/structured-data-models-client/src/sdm_client/models.py b/packages/structured-data-models-client/src/sdm_client/models.py index c4d7fb8d7..d9b00ac6c 100644 --- a/packages/structured-data-models-client/src/sdm_client/models.py +++ b/packages/structured-data-models-client/src/sdm_client/models.py @@ -131,9 +131,11 @@ # Empty, and not because the model does not explain. Nothing on this # client takes a format argument, the contract's `OutputSpec` is only # `{fields: [...]}`, and the endpoint stamps its own name on what it - # returns: `kumo_rfm_v2_1` from the shipping build. Two names were - # declared here that appear in no response on any task or graph, which - # is a list of formats to ask for on a surface that has no such knob. + # returns: `kumo_rfm_v2_1` from the deployment that serves + # explanations, which is not the one this client is measured against. + # Two names were declared here that appear in no response on any task + # or graph, which is a list of formats to ask for on a surface that + # has no such knob. explanation_formats=(), limits=RELATIONAL_LIMITS, unread_feature_stypes=frozenset({'text'}), From b3fb6b1976c82c155936af8db292f8caadcf5ea8 Mon Sep 17 00:00:00 2001 From: Manush Murali Date: Thu, 15 Oct 2026 17:46:48 -0700 Subject: [PATCH 3/4] Keep the wire format name out of prose It is a protocol value the endpoint stamps on an explanation, and the engine branches on it to parse one. The comment beside an empty tuple is not where a reader needs to meet it. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Manush Murali --- .../src/sdm_client/models.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/structured-data-models-client/src/sdm_client/models.py b/packages/structured-data-models-client/src/sdm_client/models.py index d9b00ac6c..547e90cca 100644 --- a/packages/structured-data-models-client/src/sdm_client/models.py +++ b/packages/structured-data-models-client/src/sdm_client/models.py @@ -128,14 +128,9 @@ 'explanation', } ), - # Empty, and not because the model does not explain. Nothing on this - # client takes a format argument, the contract's `OutputSpec` is only - # `{fields: [...]}`, and the endpoint stamps its own name on what it - # returns: `kumo_rfm_v2_1` from the deployment that serves - # explanations, which is not the one this client is measured against. - # Two names were declared here that appear in no response on any task - # or graph, which is a list of formats to ask for on a surface that - # has no such knob. + # Empty because nothing here takes a format argument: the contract's + # `OutputSpec` is only `{fields: [...]}`, and the endpoint names the + # shape it returns. Two names declared here appeared in no response. explanation_formats=(), limits=RELATIONAL_LIMITS, unread_feature_stypes=frozenset({'text'}), From dd996b9e8cb39764c228e878dc420e27b631c0f2 Mon Sep 17 00:00:00 2001 From: Manush Murali Date: Thu, 15 Oct 2026 18:03:40 -0700 Subject: [PATCH 4/4] Stop pointing at a file in a repository nobody can open A payload docstring cited the path of the smoke test it was mirrored from, inside a private repository. The hygiene check gains that name, so the bare term for the model a test talks to still passes while a path into that tree does not. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Manush Murali --- .../tests/client/rfm_nim_payloads.py | 2 +- scripts/check_repo_hygiene.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/structured-data-models-relational-engine/tests/client/rfm_nim_payloads.py b/packages/structured-data-models-relational-engine/tests/client/rfm_nim_payloads.py index 47c5f47d3..adc3d2fe7 100644 --- a/packages/structured-data-models-relational-engine/tests/client/rfm_nim_payloads.py +++ b/packages/structured-data-models-relational-engine/tests/client/rfm_nim_payloads.py @@ -112,7 +112,7 @@ def nim_v1_smoke_payload() -> dict[str, Any]: - """Payload mirrored from kumo_rfm_nim/scripts/run_container_smoke.sh.""" + """Payload mirrored from the relational NIM's container smoke test.""" return deepcopy(_NIM_V1_SMOKE_PAYLOAD) diff --git a/scripts/check_repo_hygiene.py b/scripts/check_repo_hygiene.py index 4997e70a1..246748d08 100644 --- a/scripts/check_repo_hygiene.py +++ b/scripts/check_repo_hygiene.py @@ -93,6 +93,10 @@ # ships either way. re.compile(r'\btfm-serving\b', re.IGNORECASE), re.compile(r'\bstructured-data-nims\b', re.IGNORECASE), + # A workspace inside that repository. The bare `rfm_nim` names the model + # a test talks to and stays; this is a path into a tree nobody outside + # can open, which is what shipped in a docstring until it was removed. + re.compile(r'\bkumo_rfm_nim\b', re.IGNORECASE), # Internal build names for the servers behind a model id. A reader # cannot look either up, and which build answers is not the client # story. The public repository and the five distributions it publishes