Added deterministic function layer

This commit is contained in:
2026-05-04 19:26:25 +02:00
parent 3840ff4617
commit 1197b39a76
11 changed files with 1305 additions and 16 deletions

View File

@@ -18,6 +18,7 @@ def test_builtin_extension_registry_lists_query_processors_and_backend():
assert "runtime.form-state" in ids
assert "runtime.assessment" in ids
assert "policy.local-label" in ids
assert "document.function" in ids
def test_builtin_processor_descriptors_capture_safety_and_provenance():
@@ -103,3 +104,22 @@ def test_builtin_policy_descriptor_exposes_cli_and_adapter_boundary():
assert "mkt policy resource-manifest" in descriptor.cli["commands"]
assert "IdentityClaimsAdapter" in descriptor.metadata["external_adapters"]
assert "RelationshipPolicyAdapter" in descriptor.metadata["external_adapters"]
def test_builtin_document_function_descriptor_exposes_deterministic_boundary():
registry = builtin_extension_registry()
descriptor = registry.get("document.function")
assert descriptor.kind == "document-function"
assert descriptor.safety["network"] is False
assert descriptor.metadata["external_policy_services_required"] is False
assert {capability.id for capability in descriptor.capabilities} >= {
"document_function",
"deterministic",
}
assert descriptor.cli["commands"] == [
"mkt function list",
"mkt function check",
"mkt function render",
]

View File

@@ -18,6 +18,7 @@ def test_collect_cli_command_specs_from_builtin_registry():
assert ("processor.uppercase", "mkt process") in commands
assert ("backend.local-sqlite", "mkt cache index") in commands
assert ("backend.local-sqlite", "mkt search") in commands
assert ("document.function", "mkt function render") in commands
def test_cli_command_spec_serializes_without_empty_fields():

View File

@@ -0,0 +1,136 @@
import json
from pathlib import Path
from click.testing import CliRunner
from markitect_tool.cli import main
from markitect_tool.document_function import (
DocumentFunctionDescriptor,
DocumentFunctionParameter,
DocumentFunctionRegistry,
default_document_function_registry,
parse_document_function_calls,
render_document_functions,
validate_document_functions,
)
from markitect_tool.extension import ProcessingContext
def test_parse_inline_and_fenced_function_calls():
text = """# Demo
Inline {{mkt:text.upper "draft"}}.
```mkt-function md.heading level=3
Decision
```
"""
calls = parse_document_function_calls(text)
assert [call.function_id for call in calls] == ["text.upper", "md.heading"]
assert calls[0].args == ["draft"]
assert calls[1].kwargs == {"level": 3}
assert calls[1].body.strip() == "Decision"
def test_render_document_functions_expands_inline_and_fenced_calls():
text = """# Demo
Inline {{mkt:text.upper "draft"}}.
```mkt-function md.heading level=3
Decision
```
"""
result = render_document_functions(text)
assert result.valid
assert "Inline DRAFT." in result.content
assert "### Decision" in result.content
assert len(result.calls) == 2
assert result.provenance[0].operation == "document_function.text.upper"
def test_pipeline_passes_previous_output_to_next_function():
result = render_document_functions('{{mkt:text.upper "draft" | text.replace DRAFT Final}}')
assert result.valid
assert result.content == "Final"
def test_context_variables_can_be_used_in_function_arguments():
context = ProcessingContext(variables={"title": "Architecture Decision"})
result = render_document_functions("{{mkt:md.heading ${title} level=2}}", context=context)
assert result.content == "## Architecture Decision"
def test_validate_document_functions_reports_forbidden_calls():
result = validate_document_functions("{{mkt:text.upper draft}}", forbidden=["text.upper"])
assert not result.valid
assert result.diagnostics[0].code == "function.forbidden"
def test_registry_can_expose_custom_function_without_core_rewrite():
registry = DocumentFunctionRegistry()
registry.register(
DocumentFunctionDescriptor(
id="demo.wrap",
summary="Wrap text.",
parameters=[DocumentFunctionParameter("value")],
implementation=lambda value: f"[{value}]",
)
)
result = render_document_functions("{{mkt:demo.wrap ok}}", registry=registry)
assert result.valid
assert result.content == "[ok]"
def test_unknown_function_is_left_in_place_with_diagnostic():
result = render_document_functions("{{mkt:nope.missing value}}")
assert not result.valid
assert result.content == "{{mkt:nope.missing value}}"
assert result.diagnostics[0].code == "function.unknown"
def test_mkt_function_list_outputs_builtin_catalog():
result = CliRunner().invoke(main, ["function", "list", "--format", "json"])
data = json.loads(result.output)
assert result.exit_code == 0
ids = {function["id"] for function in data["functions"]}
assert {"text.upper", "md.heading", "md.codeblock"} <= ids
def test_mkt_function_render_outputs_expanded_markdown(tmp_path: Path):
file = tmp_path / "functions.md"
file.write_text("# Demo\n\n{{mkt:md.bold Important}}\n", encoding="utf-8")
result = CliRunner().invoke(main, ["function", "render", str(file)])
assert result.exit_code == 0
assert "**Important**" in result.output
def test_mkt_function_check_can_restrict_allowed_functions(tmp_path: Path):
file = tmp_path / "functions.md"
file.write_text("{{mkt:text.upper draft}}\n", encoding="utf-8")
result = CliRunner().invoke(main, ["function", "check", str(file), "--allow", "md.heading"])
assert result.exit_code == 1
assert "function.not_allowed" in result.output
def test_default_registry_serializes_without_implementations():
data = default_document_function_registry().to_dict()
assert data["count"] >= 1
assert "implementation" not in data["functions"][0]