tangle-cli 0.1.12__tar.gz → 0.1.13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/PKG-INFO +1 -1
  2. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/__init__.py +1 -1
  3. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipeline_validation.py +166 -11
  4. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/pyproject.toml +1 -1
  5. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/pyproject.toml.orig +1 -1
  6. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/README.md +0 -0
  7. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/api_cli.py +0 -0
  8. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/api_schema.py +0 -0
  9. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/api_transport.py +0 -0
  10. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/args_container.py +0 -0
  11. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/artifacts.py +0 -0
  12. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/artifacts_cli.py +0 -0
  13. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/cli.py +0 -0
  14. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/cli_helpers.py +0 -0
  15. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/cli_options.py +0 -0
  16. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/client.py +0 -0
  17. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/component_from_func.py +0 -0
  18. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/component_generator.py +0 -0
  19. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/component_inspector.py +0 -0
  20. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/component_publisher.py +0 -0
  21. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/components_cli.py +0 -0
  22. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/dynamic_discovery_client.py +0 -0
  23. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/generated_model_extensions.py +0 -0
  24. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/handler.py +0 -0
  25. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/hydration_trust.py +0 -0
  26. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/logger.py +0 -0
  27. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/models.py +0 -0
  28. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/module_bundler.py +0 -0
  29. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/openapi/__init__.py +0 -0
  30. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/openapi/codegen.py +0 -0
  31. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/openapi/parser.py +0 -0
  32. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipeline_compiler.py +0 -0
  33. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipeline_dehydrator.py +0 -0
  34. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipeline_hydrator.py +0 -0
  35. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipeline_run_annotations.py +0 -0
  36. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipeline_run_details.py +0 -0
  37. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipeline_run_manager.py +0 -0
  38. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipeline_run_search.py +0 -0
  39. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipeline_runner.py +0 -0
  40. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipeline_runs_cli.py +0 -0
  41. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipeline_spec_utils.py +0 -0
  42. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipelines.py +0 -0
  43. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/pipelines_cli.py +0 -0
  44. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/published_components_cli.py +0 -0
  45. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/py.typed +0 -0
  46. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/__init__.py +0 -0
  47. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/cfg.py +0 -0
  48. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/compiler_context.py +0 -0
  49. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/dynamic_data.py +0 -0
  50. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/emit.py +0 -0
  51. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/errors.py +0 -0
  52. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/graph.py +0 -0
  53. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/ids.py +0 -0
  54. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/pipeline.py +0 -0
  55. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/placeholders.py +0 -0
  56. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/raw.py +0 -0
  57. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/ref.py +0 -0
  58. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/registered.py +0 -0
  59. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/subpipeline.py +0 -0
  60. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/task.py +0 -0
  61. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/task_env.py +0 -0
  62. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/trace.py +0 -0
  63. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/python_pipeline/types.py +0 -0
  64. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/quickstart.py +0 -0
  65. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/schema_validation.py +0 -0
  66. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/schemas/__init__.py +0 -0
  67. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/schemas/dehydrated_pipeline_schema.json +0 -0
  68. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/schemas/pipeline_schema.json +0 -0
  69. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/secrets.py +0 -0
  70. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/secrets_cli.py +0 -0
  71. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/utils.py +0 -0
  72. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/packages/tangle-cli/src/tangle_cli/version_manager.py +0 -0
  73. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/OSS-CONVENTIONS.md +0 -0
  74. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/PORT-README.md +0 -0
  75. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/SKILL.md +0 -0
  76. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/agents/auth-wizard.md +0 -0
  77. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/agents/builder.md +0 -0
  78. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/agents/debugger.md +0 -0
  79. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/agents/reporter.md +0 -0
  80. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/agents/researcher.md +0 -0
  81. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/agents/reviewer.md +0 -0
  82. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/agents/scenario-builder.md +0 -0
  83. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/data-sources.md +0 -0
  84. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/event-log.md +0 -0
  85. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/example-scenarios/01-mslr-ranking.md +0 -0
  86. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/example-scenarios/02-text-classification.md +0 -0
  87. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/example-scenarios/INDEX.md +0 -0
  88. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/iterating-on-runs.md +0 -0
  89. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/knowledge-corpus.md +0 -0
  90. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/secrets.md +0 -0
  91. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/setup.md +0 -0
  92. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/step-0-initialize.md +0 -0
  93. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/step-1-analyze.md +0 -0
  94. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/step-2-hypothesize.md +0 -0
  95. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/step-3-submit.md +0 -0
  96. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/step-4-monitor.md +0 -0
  97. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/step-5-evaluate.md +0 -0
  98. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/step-6-synthesize.md +0 -0
  99. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/step-7-decide.md +0 -0
  100. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/tangle-tools.md +0 -0
  101. {tangle_cli-0.1.12 → tangle_cli-0.1.13}/skills/tangent/references/uploading-artifacts.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: tangle-cli
3
- Version: 0.1.12
3
+ Version: 0.1.13
4
4
  Summary: CLI for Tangle, the open-source ML pipeline orchestration platform
5
5
  Author: Alexey Volkov, Tangle authors, Volv Grebennikov
6
6
  Author-email: Alexey Volkov <alexey.volkov@ark-kun.com>, Volv Grebennikov <volv.grebennikov@shopify.com>
@@ -14,6 +14,6 @@ from tangle_cli.dynamic_discovery_client import TangleDynamicDiscoveryClient
14
14
  try:
15
15
  __version__ = metadata_version("tangle-cli")
16
16
  except PackageNotFoundError:
17
- __version__ = "0.1.12"
17
+ __version__ = "0.1.13"
18
18
 
19
19
  __all__ = ["TangleDynamicDiscoveryClient", "__version__"]
@@ -8,7 +8,9 @@ Validation covers:
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
+ import difflib
11
12
  import json
13
+ import os
12
14
  from functools import lru_cache
13
15
  from importlib import resources
14
16
  from typing import Any, Iterable, Mapping
@@ -20,7 +22,15 @@ from .pipeline_spec_utils import _extract_task_output_refs
20
22
  PIPELINE_GRAPH_PATH = "implementation.graph"
21
23
  TASKS_PATH = f"{PIPELINE_GRAPH_PATH}.tasks"
22
24
 
25
+ # Escape hatch for repositories that still carry undeclared task arguments.
26
+ # Set to a truthy value to downgrade the undeclared-argument check to a no-op.
27
+ # Follows the ``TANGLE_*`` boolean env-var convention used elsewhere in the CLI
28
+ # (see ``tangle_verbose_enabled`` / ``TANGLE_TRUSTED_HYDRATION_ALLOW_ALL``).
29
+ ALLOW_UNDECLARED_TASK_ARGUMENTS_ENV = "TANGLE_ALLOW_UNDECLARED_TASK_ARGUMENTS"
30
+ _TRUTHY_ENV_VALUES = ("1", "true", "yes", "on")
31
+
23
32
  __all__ = [
33
+ "ALLOW_UNDECLARED_TASK_ARGUMENTS_ENV",
24
34
  "PipelineValidationError",
25
35
  "collect_pipeline_spec_errors",
26
36
  "load_pipeline_schema",
@@ -186,6 +196,138 @@ def _get_component_spec(task: Mapping[str, Any]) -> Mapping[str, Any] | None:
186
196
  return None
187
197
 
188
198
 
199
+ def _undeclared_arguments_allowed() -> bool:
200
+ """Return True when the undeclared-argument check is disabled by env var."""
201
+
202
+ value = os.environ.get(ALLOW_UNDECLARED_TASK_ARGUMENTS_ENV, "")
203
+ return value.strip().lower() in _TRUTHY_ENV_VALUES
204
+
205
+
206
+ def _name_tokens(name: str) -> list[str]:
207
+ """Split an input name into lowercase alphanumeric tokens.
208
+
209
+ Component authors mix ``snake_case``, ``kebab-case`` and spaces for the
210
+ same concept, so tokenizing lets the hint survive separator drift.
211
+ """
212
+
213
+ token = ""
214
+ tokens: list[str] = []
215
+ for char in name:
216
+ if char.isalnum():
217
+ token += char.lower()
218
+ elif token:
219
+ tokens.append(token)
220
+ token = ""
221
+ if token:
222
+ tokens.append(token)
223
+ return tokens
224
+
225
+
226
+ def _contains_tokens(haystack: list[str], needle: list[str]) -> bool:
227
+ """Return True when needle appears as a contiguous run inside haystack."""
228
+
229
+ if not needle or len(needle) > len(haystack):
230
+ return False
231
+ return any(
232
+ haystack[start : start + len(needle)] == needle
233
+ for start in range(len(haystack) - len(needle) + 1)
234
+ )
235
+
236
+
237
+ def _nearest_declared_input(name: str, declared_names: Iterable[str]) -> str | None:
238
+ """Return the declared input ``name`` most plausibly meant, if any.
239
+
240
+ Three deterministic passes over sorted candidates, strongest first:
241
+
242
+ 1. separator-insensitive equality (``bq_table`` vs ``bq-table``);
243
+ 2. ``difflib`` near-match, which catches ordinary typos;
244
+ 3. token containment, which catches qualification renames such as
245
+ ``snapshot_date`` -> ``merchant_match_reference_snapshot_date``.
246
+
247
+ Semantic renames with no lexical overlap (``wait`` -> ``timeout``) return
248
+ None on purpose: a wrong hint is worse than none.
249
+ """
250
+
251
+ candidates = sorted(declared_names)
252
+ tokens = _name_tokens(name)
253
+ if not tokens:
254
+ return None
255
+
256
+ for candidate in candidates:
257
+ if _name_tokens(candidate) == tokens:
258
+ return candidate
259
+
260
+ close = difflib.get_close_matches(name, candidates, n=1, cutoff=0.8)
261
+ if close:
262
+ return close[0]
263
+
264
+ containing = [
265
+ candidate
266
+ for candidate in candidates
267
+ if _contains_tokens(_name_tokens(candidate), tokens)
268
+ or _contains_tokens(tokens, _name_tokens(candidate))
269
+ ]
270
+ if containing:
271
+ return min(containing, key=lambda candidate: (len(_name_tokens(candidate)), candidate))
272
+ return None
273
+
274
+
275
+ def _suggest_declared_input(name: str, declared_names: Iterable[str]) -> str:
276
+ """Return a ``Did you mean ...`` clause, or an empty string when unsure.
277
+
278
+ Real-world undeclared arguments are overwhelmingly typos or renames, so a
279
+ nearest-match hint is far more actionable than the declared-input list
280
+ alone.
281
+ """
282
+
283
+ nearest = _nearest_declared_input(name, declared_names)
284
+ return f" Did you mean '{nearest}'?" if nearest else ""
285
+
286
+
287
+ def _validate_undeclared_arguments(
288
+ full_task_name: str,
289
+ component_spec: Mapping[str, Any],
290
+ component_inputs: Any,
291
+ task_arguments: Mapping[str, Any],
292
+ declared_names: set[str],
293
+ ) -> list[str]:
294
+ """Return errors for task arguments the component does not declare.
295
+
296
+ Deliberately fails OPEN in the cases where the answer is unknowable rather
297
+ than wrong:
298
+
299
+ * the caller only invokes this when ``_get_component_spec`` resolved a spec,
300
+ so unhydrated / digest-pinned / URL-only component refs are skipped;
301
+ * a malformed (non-list) ``inputs`` field is skipped, because the declared
302
+ set cannot be trusted.
303
+
304
+ An empty-but-well-formed ``inputs`` list is NOT skipped: a component that
305
+ declares no inputs genuinely accepts no arguments.
306
+
307
+ ``TaskSpec`` keeps ``annotations``, ``executionOptions`` and ``isEnabled``
308
+ as siblings of ``arguments`` (see the vendored pipeline schema), so there
309
+ are no reserved/metadata keys inside ``arguments`` to allowlist.
310
+ """
311
+
312
+ if _undeclared_arguments_allowed():
313
+ return []
314
+ if component_inputs is not None and not isinstance(component_inputs, list):
315
+ return []
316
+
317
+ undeclared = sorted(str(name) for name in task_arguments if str(name) not in declared_names)
318
+ if not undeclared:
319
+ return []
320
+
321
+ component_name = component_spec.get("name") or "<unnamed component>"
322
+ declared_display = sorted(declared_names)
323
+ return [
324
+ f"Task '{full_task_name}': argument '{name}' is not a declared input of "
325
+ f"component '{component_name}'.{_suggest_declared_input(name, declared_names)} "
326
+ f"Declared inputs: {declared_display}"
327
+ for name in undeclared
328
+ ]
329
+
330
+
189
331
  def _is_input_required(input_spec: Mapping[str, Any]) -> bool:
190
332
  """Return True when a component input is non-optional and lacks a default."""
191
333
 
@@ -283,7 +425,8 @@ def _validate_task_inputs(
283
425
  ) -> list[str]:
284
426
  """Return component-input wiring errors for one task.
285
427
 
286
- Required component inputs must be present in task arguments. When declared
428
+ Required component inputs must be present in task arguments, and supplied
429
+ arguments must correspond to a declared component input. When declared
287
430
  inputs are explicitly supplied, graph/task output references are checked
288
431
  regardless of whether the input is required. Nested graph component specs
289
432
  are validated recursively.
@@ -309,13 +452,27 @@ def _validate_task_inputs(
309
452
  if not component_spec:
310
453
  return errors
311
454
 
312
- component_inputs = component_spec.get("inputs", [])
313
- if not isinstance(component_inputs, list):
314
- component_inputs = []
455
+ raw_component_inputs = component_spec.get("inputs", [])
456
+ component_inputs = raw_component_inputs if isinstance(raw_component_inputs, list) else []
315
457
  task_arguments = task_spec.get("arguments", {}) or {}
316
458
  if not isinstance(task_arguments, Mapping):
317
459
  task_arguments = {}
318
460
 
461
+ declared_names = {
462
+ str(spec["name"])
463
+ for spec in component_inputs
464
+ if isinstance(spec, Mapping) and spec.get("name")
465
+ }
466
+ errors.extend(
467
+ _validate_undeclared_arguments(
468
+ full_task_name,
469
+ component_spec,
470
+ raw_component_inputs,
471
+ task_arguments,
472
+ declared_names,
473
+ )
474
+ )
475
+
319
476
  for input_spec in component_inputs:
320
477
  if not isinstance(input_spec, Mapping):
321
478
  continue
@@ -344,12 +501,7 @@ def _validate_task_inputs(
344
501
  implementation = component_spec.get("implementation", {})
345
502
  nested_graph = implementation.get("graph") if isinstance(implementation, Mapping) else None
346
503
  if isinstance(nested_graph, Mapping):
347
- subgraph_inputs = {
348
- str(inp.get("name"))
349
- for inp in component_inputs
350
- if isinstance(inp, Mapping) and inp.get("name")
351
- }
352
- errors.extend(_validate_graph_inputs(nested_graph, subgraph_inputs, f"{full_task_name} > "))
504
+ errors.extend(_validate_graph_inputs(nested_graph, declared_names, f"{full_task_name} > "))
353
505
 
354
506
  return errors
355
507
 
@@ -389,7 +541,10 @@ def _validate_graph_inputs(
389
541
 
390
542
 
391
543
  def validate_component_inputs(pipeline_spec: Mapping[str, Any]) -> list[str]:
392
- """Return required-input and reference-wiring errors for a pipeline.
544
+ """Return input-wiring errors for a pipeline.
545
+
546
+ Covers missing required inputs, undeclared task arguments, and dangling
547
+ graph-input / task-output references.
393
548
 
394
549
  Validation uses embedded component specs when present. If the pipeline has
395
550
  no object-shaped implementation graph, this component-input pass returns no
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tangle-cli"
3
- version = "0.1.12"
3
+ version = "0.1.13"
4
4
  description = "CLI for Tangle, the open-source ML pipeline orchestration platform"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tangle-cli"
3
- version = "0.1.12"
3
+ version = "0.1.13"
4
4
  description = "CLI for Tangle, the open-source ML pipeline orchestration platform"
5
5
  readme = "README.md"
6
6
  authors = [
File without changes