simplest categorical addition
This commit is contained in:
@@ -85,7 +85,8 @@ how did the original provider happen to expose this thing?
|
|||||||
### Wrapper / Adapter Artifact
|
### Wrapper / Adapter Artifact
|
||||||
|
|
||||||
A wrapper artifact is a saved reusable bridge from a raw capability to a
|
A wrapper artifact is a saved reusable bridge from a raw capability to a
|
||||||
workflow capability.
|
workflow capability. In storage it is not a second artifact family: it is a
|
||||||
|
`WorkflowArtifact` with `kind="wrapper"`.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
@@ -264,10 +265,10 @@ Today:
|
|||||||
- `wf.std` owns local reusable workflow node specs
|
- `wf.std` owns local reusable workflow node specs
|
||||||
- `wf.mcp` owns workflow-facing MCP runtime helpers
|
- `wf.mcp` owns workflow-facing MCP runtime helpers
|
||||||
- discovered upstream tools can already become workflow node specs
|
- discovered upstream tools can already become workflow node specs
|
||||||
|
- saved artifacts can be tagged with `kind="workflow"` or `kind="wrapper"`
|
||||||
|
|
||||||
Not yet implemented:
|
Not yet implemented:
|
||||||
|
|
||||||
- a saved wrapper artifact model
|
|
||||||
- a direct public tool for calling arbitrary workflow node specs for authoring
|
- a direct public tool for calling arbitrary workflow node specs for authoring
|
||||||
tests
|
tests
|
||||||
- per-outcome output schemas
|
- per-outcome output schemas
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from .catalog import (
|
|||||||
)
|
)
|
||||||
from .factory import create_workflow_artifact_from_plan
|
from .factory import create_workflow_artifact_from_plan
|
||||||
from .models import (
|
from .models import (
|
||||||
|
ArtifactKind,
|
||||||
AvailableCapability,
|
AvailableCapability,
|
||||||
AvailableSource,
|
AvailableSource,
|
||||||
DependencyDiagnostic,
|
DependencyDiagnostic,
|
||||||
@@ -19,6 +20,7 @@ from .validation import validate_deployment_dependencies
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AvailableCapability",
|
"AvailableCapability",
|
||||||
|
"ArtifactKind",
|
||||||
"AvailableSource",
|
"AvailableSource",
|
||||||
"DependencyDiagnostic",
|
"DependencyDiagnostic",
|
||||||
"DiagnosticSeverity",
|
"DiagnosticSeverity",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from collections.abc import Mapping
|
|||||||
|
|
||||||
from wf_core import Workflow
|
from wf_core import Workflow
|
||||||
|
|
||||||
from .models import JsonObject, RequiredCapability, WorkflowArtifact
|
from .models import ArtifactKind, JsonObject, RequiredCapability, WorkflowArtifact
|
||||||
|
|
||||||
|
|
||||||
def create_workflow_artifact_from_plan(
|
def create_workflow_artifact_from_plan(
|
||||||
@@ -14,6 +14,7 @@ def create_workflow_artifact_from_plan(
|
|||||||
title: str,
|
title: str,
|
||||||
plan: JsonObject,
|
plan: JsonObject,
|
||||||
outcomes: tuple[str, ...],
|
outcomes: tuple[str, ...],
|
||||||
|
kind: ArtifactKind = "workflow",
|
||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
required_capabilities: Mapping[str, RequiredCapability] | None = None,
|
required_capabilities: Mapping[str, RequiredCapability] | None = None,
|
||||||
created_from_catalog_version: str | None = None,
|
created_from_catalog_version: str | None = None,
|
||||||
@@ -24,6 +25,7 @@ def create_workflow_artifact_from_plan(
|
|||||||
id=artifact_id,
|
id=artifact_id,
|
||||||
version=version,
|
version=version,
|
||||||
title=title,
|
title=title,
|
||||||
|
kind=kind,
|
||||||
description=description,
|
description=description,
|
||||||
input_schema=_required_object_field(plan, "input_schema"),
|
input_schema=_required_object_field(plan, "input_schema"),
|
||||||
output_schema=_required_object_field(plan, "output_schema"),
|
output_schema=_required_object_field(plan, "output_schema"),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from typing import Any, Literal
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
JsonObject = dict[str, Any]
|
JsonObject = dict[str, Any]
|
||||||
|
ArtifactKind = Literal["workflow", "wrapper"]
|
||||||
|
|
||||||
|
|
||||||
class DriftPolicy(StrEnum):
|
class DriftPolicy(StrEnum):
|
||||||
@@ -71,6 +72,7 @@ class WorkflowArtifact(BaseModel):
|
|||||||
id: str
|
id: str
|
||||||
version: int = Field(ge=1)
|
version: int = Field(ge=1)
|
||||||
title: str
|
title: str
|
||||||
|
kind: ArtifactKind = "workflow"
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
input_schema: JsonObject
|
input_schema: JsonObject
|
||||||
output_schema: JsonObject
|
output_schema: JsonObject
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ def test_create_workflow_artifact_from_plan_derives_boundary_schemas() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert artifact.id == "echo"
|
assert artifact.id == "echo"
|
||||||
|
assert artifact.kind == "workflow"
|
||||||
assert artifact.version == 1
|
assert artifact.version == 1
|
||||||
assert artifact.title == "Echo"
|
assert artifact.title == "Echo"
|
||||||
assert artifact.input_schema["properties"]["text"]["type"] == "string"
|
assert artifact.input_schema["properties"]["text"]["type"] == "string"
|
||||||
@@ -32,6 +33,19 @@ def test_create_workflow_artifact_from_plan_derives_boundary_schemas() -> None:
|
|||||||
assert artifact.created_from_catalog_version == "catalog-1"
|
assert artifact.created_from_catalog_version == "catalog-1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_workflow_artifact_from_plan_accepts_wrapper_kind() -> None:
|
||||||
|
artifact = create_workflow_artifact_from_plan(
|
||||||
|
artifact_id="normalize_status",
|
||||||
|
version=1,
|
||||||
|
title="Normalize Status",
|
||||||
|
plan=_plan(),
|
||||||
|
outcomes=("done",),
|
||||||
|
kind="wrapper",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert artifact.kind == "wrapper"
|
||||||
|
|
||||||
|
|
||||||
def test_create_workflow_artifact_from_plan_rejects_missing_boundary_schema() -> None:
|
def test_create_workflow_artifact_from_plan_rejects_missing_boundary_schema() -> None:
|
||||||
plan = _plan()
|
plan = _plan()
|
||||||
plan.pop("output_schema")
|
plan.pop("output_schema")
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ def test_workflow_artifact_serializes_required_capability_contract() -> None:
|
|||||||
dumped = artifact.model_dump(mode="json")
|
dumped = artifact.model_dump(mode="json")
|
||||||
|
|
||||||
assert dumped["id"] == "summarize_docs"
|
assert dumped["id"] == "summarize_docs"
|
||||||
|
assert dumped["kind"] == "workflow"
|
||||||
assert dumped["version"] == 1
|
assert dumped["version"] == 1
|
||||||
assert dumped["outcomes"] == ["done", "failed"]
|
assert dumped["outcomes"] == ["done", "failed"]
|
||||||
required = dumped["required_capabilities"]["context7.query-docs"]
|
required = dumped["required_capabilities"]["context7.query-docs"]
|
||||||
@@ -45,6 +46,23 @@ def test_workflow_artifact_serializes_required_capability_contract() -> None:
|
|||||||
assert required["input_schema_hash"] == "sha256:input"
|
assert required["input_schema_hash"] == "sha256:input"
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_artifact_can_be_marked_as_wrapper_intent() -> None:
|
||||||
|
artifact = WorkflowArtifact(
|
||||||
|
id="normalize_status",
|
||||||
|
version=1,
|
||||||
|
title="Normalize Status",
|
||||||
|
kind="wrapper",
|
||||||
|
input_schema={"type": "object", "properties": {}},
|
||||||
|
output_schema={"type": "object", "properties": {}},
|
||||||
|
outcomes=("done", "needs_input"),
|
||||||
|
plan={"name": "normalize_status", "nodes": [], "edges": []},
|
||||||
|
)
|
||||||
|
|
||||||
|
dumped = artifact.model_dump(mode="json")
|
||||||
|
|
||||||
|
assert dumped["kind"] == "wrapper"
|
||||||
|
|
||||||
|
|
||||||
def test_workflow_deployment_binds_logical_sources_to_concrete_sources() -> None:
|
def test_workflow_deployment_binds_logical_sources_to_concrete_sources() -> None:
|
||||||
deployment = WorkflowDeployment(
|
deployment = WorkflowDeployment(
|
||||||
id="summarize_docs.personal",
|
id="summarize_docs.personal",
|
||||||
|
|||||||
Reference in New Issue
Block a user