python-hwpx-automation 6.0.3__py3-none-any.whl
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.
- hwpx_automation/__init__.py +61 -0
- hwpx_automation/__init__.pyi +27 -0
- hwpx_automation/__main__.py +8 -0
- hwpx_automation/agent_document.py +392 -0
- hwpx_automation/api.py +136 -0
- hwpx_automation/blind_eval.py +407 -0
- hwpx_automation/capabilities.py +110 -0
- hwpx_automation/compat.py +48 -0
- hwpx_automation/configuration.py +60 -0
- hwpx_automation/core/__init__.py +2 -0
- hwpx_automation/core/content.py +762 -0
- hwpx_automation/core/context.py +111 -0
- hwpx_automation/core/diff.py +53 -0
- hwpx_automation/core/document.py +37 -0
- hwpx_automation/core/formatting.py +513 -0
- hwpx_automation/core/handles.py +24 -0
- hwpx_automation/core/locations.py +205 -0
- hwpx_automation/core/locator.py +162 -0
- hwpx_automation/core/plan.py +680 -0
- hwpx_automation/core/resources.py +42 -0
- hwpx_automation/core/search.py +296 -0
- hwpx_automation/core/transactions.py +434 -0
- hwpx_automation/core/txn.py +48 -0
- hwpx_automation/document_state.py +95 -0
- hwpx_automation/errors.py +174 -0
- hwpx_automation/execution_lock.py +15 -0
- hwpx_automation/fastmcp_adapter.py +672 -0
- hwpx_automation/form_fill.py +1177 -0
- hwpx_automation/form_output_models.py +223 -0
- hwpx_automation/handlers/__init__.py +2 -0
- hwpx_automation/handlers/_shared.py +377 -0
- hwpx_automation/handlers/agent_document.py +257 -0
- hwpx_automation/handlers/authoring.py +750 -0
- hwpx_automation/handlers/content_edit.py +1078 -0
- hwpx_automation/handlers/form_fill.py +607 -0
- hwpx_automation/handlers/layout_style.py +660 -0
- hwpx_automation/handlers/quality_render.py +566 -0
- hwpx_automation/handlers/read_export.py +1295 -0
- hwpx_automation/handlers/specialized.py +624 -0
- hwpx_automation/handlers/tracked_changes.py +589 -0
- hwpx_automation/handlers/workflow.py +105 -0
- hwpx_automation/hwp_converter.py +227 -0
- hwpx_automation/hwp_support.py +94 -0
- hwpx_automation/hwpx_ops.py +1439 -0
- hwpx_automation/identity.json +263 -0
- hwpx_automation/identity.py +18 -0
- hwpx_automation/ingest_adapters.py +85 -0
- hwpx_automation/markdown_plan.py +216 -0
- hwpx_automation/mcp_cli.py +29 -0
- hwpx_automation/metadata/tools_meta.py +40 -0
- hwpx_automation/mixed_form.py +3007 -0
- hwpx_automation/mutation_models.py +401 -0
- hwpx_automation/network_policy.py +232 -0
- hwpx_automation/office/__init__.py +14 -0
- hwpx_automation/office/agent/__init__.py +125 -0
- hwpx_automation/office/agent/_batch_verification.py +383 -0
- hwpx_automation/office/agent/blueprint/__init__.py +58 -0
- hwpx_automation/office/agent/blueprint/bundle.py +282 -0
- hwpx_automation/office/agent/blueprint/catalog.py +136 -0
- hwpx_automation/office/agent/blueprint/dump.py +520 -0
- hwpx_automation/office/agent/blueprint/mapping.py +312 -0
- hwpx_automation/office/agent/blueprint/model.py +722 -0
- hwpx_automation/office/agent/blueprint/native.py +621 -0
- hwpx_automation/office/agent/blueprint/replay.py +622 -0
- hwpx_automation/office/agent/catalog.py +252 -0
- hwpx_automation/office/agent/cli.py +647 -0
- hwpx_automation/office/agent/commands.py +1383 -0
- hwpx_automation/office/agent/document.py +801 -0
- hwpx_automation/office/agent/form_plan.py +1760 -0
- hwpx_automation/office/agent/model.py +808 -0
- hwpx_automation/office/agent/path.py +155 -0
- hwpx_automation/office/agent/query.py +230 -0
- hwpx_automation/office/agent/story.py +207 -0
- hwpx_automation/office/authoring/__init__.py +3542 -0
- hwpx_automation/office/authoring/advanced_generators.py +154 -0
- hwpx_automation/office/authoring/builder/__init__.py +52 -0
- hwpx_automation/office/authoring/builder/core.py +996 -0
- hwpx_automation/office/authoring/builder/report.py +195 -0
- hwpx_automation/office/authoring/design/__init__.py +30 -0
- hwpx_automation/office/authoring/design/_support.py +144 -0
- hwpx_automation/office/authoring/design/composer.py +282 -0
- hwpx_automation/office/authoring/design/harvest.py +305 -0
- hwpx_automation/office/authoring/design/plan.py +69 -0
- hwpx_automation/office/authoring/design/profile.py +88 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +24 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/validator.py +107 -0
- hwpx_automation/office/authoring/presets/__init__.py +22 -0
- hwpx_automation/office/authoring/presets/proposal.py +538 -0
- hwpx_automation/office/authoring/report_parser.py +141 -0
- hwpx_automation/office/authoring/style_profile.py +437 -0
- hwpx_automation/office/authoring/template_analyzer.py +657 -0
- hwpx_automation/office/compliance/__init__.py +38 -0
- hwpx_automation/office/compliance/official_lint.py +478 -0
- hwpx_automation/office/compliance/pii.py +388 -0
- hwpx_automation/office/document_ops/__init__.py +13 -0
- hwpx_automation/office/document_ops/comparison.py +62 -0
- hwpx_automation/office/document_ops/mail_merge.py +73 -0
- hwpx_automation/office/document_ops/redline.py +35 -0
- hwpx_automation/office/evalplan/__init__.py +36 -0
- hwpx_automation/office/evalplan/runtime.py +2762 -0
- hwpx_automation/office/exam/__init__.py +44 -0
- hwpx_automation/office/exam/compose.py +282 -0
- hwpx_automation/office/exam/ir.py +44 -0
- hwpx_automation/office/exam/measure.py +163 -0
- hwpx_automation/office/exam/parser.py +150 -0
- hwpx_automation/office/exam/profile.py +123 -0
- hwpx_automation/office/form_fill/__init__.py +66 -0
- hwpx_automation/office/form_fill/classification.py +108 -0
- hwpx_automation/office/form_fill/fill_residue.py +242 -0
- hwpx_automation/office/form_fill/fit/__init__.py +36 -0
- hwpx_automation/office/form_fill/fit/apply.py +24 -0
- hwpx_automation/office/form_fill/fit/engine.py +24 -0
- hwpx_automation/office/form_fill/fit/measure.py +50 -0
- hwpx_automation/office/form_fill/fit/policy.py +28 -0
- hwpx_automation/office/form_fill/fit/report.py +28 -0
- hwpx_automation/office/form_fill/fit/seal.py +457 -0
- hwpx_automation/office/form_fill/fit/wordbox.py +1343 -0
- hwpx_automation/office/form_fill/guidance.py +704 -0
- hwpx_automation/office/form_fill/quality.py +961 -0
- hwpx_automation/office/form_fill/split_run.py +333 -0
- hwpx_automation/office/form_fill/template_formfit.py +656 -0
- hwpx_automation/office/house_style/__init__.py +196 -0
- hwpx_automation/office/house_style/composition.py +68 -0
- hwpx_automation/office/house_style/data/bank.json +625 -0
- hwpx_automation/office/house_style/data/genres.json +43 -0
- hwpx_automation/office/quality/__init__.py +14 -0
- hwpx_automation/office/quality/page_guard.py +277 -0
- hwpx_automation/office/rendering/__init__.py +145 -0
- hwpx_automation/office/rendering/_hancom_open_rate.ps1 +374 -0
- hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +162 -0
- hwpx_automation/office/rendering/_render_hwpx.ps1 +72 -0
- hwpx_automation/office/rendering/_render_hwpx_mac.applescript +249 -0
- hwpx_automation/office/rendering/block_splits.py +76 -0
- hwpx_automation/office/rendering/detectors.py +151 -0
- hwpx_automation/office/rendering/diff.py +153 -0
- hwpx_automation/office/rendering/fixture_corpus.py +215 -0
- hwpx_automation/office/rendering/oracle.py +909 -0
- hwpx_automation/office/rendering/page_qa.py +245 -0
- hwpx_automation/office/rendering/qa_contracts.py +293 -0
- hwpx_automation/office/rendering/qa_metrics.py +241 -0
- hwpx_automation/office/rendering/worker.py +290 -0
- hwpx_automation/office/utilities/__init__.py +12 -0
- hwpx_automation/office/utilities/table_compute.py +477 -0
- hwpx_automation/ops_services/__init__.py +1 -0
- hwpx_automation/ops_services/_border_fill.py +283 -0
- hwpx_automation/ops_services/composition.py +55 -0
- hwpx_automation/ops_services/content_layout.py +322 -0
- hwpx_automation/ops_services/context.py +213 -0
- hwpx_automation/ops_services/form_fields.py +557 -0
- hwpx_automation/ops_services/media.py +178 -0
- hwpx_automation/ops_services/memo_style.py +477 -0
- hwpx_automation/ops_services/package_validation.py +166 -0
- hwpx_automation/ops_services/planning.py +201 -0
- hwpx_automation/ops_services/preview_export.py +585 -0
- hwpx_automation/ops_services/read_query.py +601 -0
- hwpx_automation/ops_services/save_policy.py +604 -0
- hwpx_automation/ops_services/tables.py +539 -0
- hwpx_automation/ops_services/transactions.py +616 -0
- hwpx_automation/preview_output_models.py +69 -0
- hwpx_automation/public-modules.json +206 -0
- hwpx_automation/py.typed +1 -0
- hwpx_automation/quality.py +351 -0
- hwpx_automation/quality_generation.py +725 -0
- hwpx_automation/runtime.py +321 -0
- hwpx_automation/runtime_services.py +100 -0
- hwpx_automation/server.py +259 -0
- hwpx_automation/storage.py +747 -0
- hwpx_automation/tool_bindings.py +170 -0
- hwpx_automation/tool_contract.py +982 -0
- hwpx_automation/upstream.py +755 -0
- hwpx_automation/utils/__init__.py +2 -0
- hwpx_automation/utils/helpers.py +29 -0
- hwpx_automation/visual_qa.py +667 -0
- hwpx_automation/workflow/__init__.py +55 -0
- hwpx_automation/workflow/adapters.py +482 -0
- hwpx_automation/workflow/dispatcher.py +213 -0
- hwpx_automation/workflow/models.py +243 -0
- hwpx_automation/workflow/policy.py +198 -0
- hwpx_automation/workflow/render_contracts.py +173 -0
- hwpx_automation/workflow/render_metrics.py +196 -0
- hwpx_automation/workflow/render_queue.py +482 -0
- hwpx_automation/workflow/render_security.py +172 -0
- hwpx_automation/workflow/render_transport.py +369 -0
- hwpx_automation/workflow/rendering.py +206 -0
- hwpx_automation/workflow/service.py +758 -0
- hwpx_automation/workflow/state_machine.py +65 -0
- hwpx_automation/workflow/store.py +747 -0
- hwpx_automation/workspace.py +1694 -0
- python_hwpx_automation-6.0.3.dist-info/METADATA +279 -0
- python_hwpx_automation-6.0.3.dist-info/RECORD +217 -0
- python_hwpx_automation-6.0.3.dist-info/WHEEL +5 -0
- python_hwpx_automation-6.0.3.dist-info/entry_points.txt +3 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/LICENSE +178 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/NOTICE +14 -0
- python_hwpx_automation-6.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,982 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Typed, deterministic contract for the installed FastMCP tool surface.
|
|
3
|
+
|
|
4
|
+
``BASELINE_TOOL_SPECS`` records the current classification baseline, including
|
|
5
|
+
the fixture-QA tools that became internal in 4.0.0 (the 3.0.0 census minus the
|
|
6
|
+
practice tools removed in 4.0.0 and the transition stubs removed at the 5.0.0
|
|
7
|
+
major boundary). ``TOOL_SPECS`` is the only installed/public surface.
|
|
8
|
+
Registration, capability reporting, generated documentation, health checks, and
|
|
9
|
+
the versioned contract delta all consume these objects instead of maintaining
|
|
10
|
+
parallel name lists.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import functools
|
|
16
|
+
import hashlib
|
|
17
|
+
import inspect
|
|
18
|
+
import json
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import Any, Callable, Mapping
|
|
22
|
+
|
|
23
|
+
MIN_PYTHON_HWPX = "5.0.0"
|
|
24
|
+
# The 5.0 train: core drops the workflow surfaces and the `hwpx` console name,
|
|
25
|
+
# this package picks the name up, and the plugin pins both. The three move
|
|
26
|
+
# together — a mixed set is what "no valid install has two declarers" rules out.
|
|
27
|
+
MIN_AUTOMATION_VERSION = "6.0.0"
|
|
28
|
+
# Compatibility alias retained in the 6.x contract payload for existing health
|
|
29
|
+
# and plugin consumers. New code and documentation use the automation name.
|
|
30
|
+
MIN_MCP_VERSION = MIN_AUTOMATION_VERSION
|
|
31
|
+
MIN_SKILL_VERSION = "1.0.0"
|
|
32
|
+
# Frozen release receipt for non-runtime services. Runtime construction still
|
|
33
|
+
# recomputes and verifies the bound callable/schema contract through
|
|
34
|
+
# ``contract_hash()``; this constant prevents those services from importing the
|
|
35
|
+
# runtime composer merely to stamp the approved release receipt.
|
|
36
|
+
RELEASED_CONTRACT_HASH = "0ce938371f0b55a6"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def describe_callables(entries: Any) -> Any:
|
|
40
|
+
"""Load the optional FastMCP schema adapter only for explicit binding."""
|
|
41
|
+
|
|
42
|
+
from .fastmcp_adapter import describe_callables as implementation
|
|
43
|
+
|
|
44
|
+
return implementation(entries)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def register_canonical_tool(*args: Any, **kwargs: Any) -> Any:
|
|
48
|
+
"""Load the optional FastMCP registrar only for explicit registration."""
|
|
49
|
+
|
|
50
|
+
from .fastmcp_adapter import register_canonical_tool as implementation
|
|
51
|
+
|
|
52
|
+
return implementation(*args, **kwargs)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def snapshot_runtime_tools(mcp: Any) -> Any:
|
|
56
|
+
"""Load the optional FastMCP snapshot adapter only for runtime validation."""
|
|
57
|
+
|
|
58
|
+
from .fastmcp_adapter import snapshot_runtime_tools as implementation
|
|
59
|
+
|
|
60
|
+
return implementation(mcp)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ToolClassification(str, Enum):
|
|
64
|
+
"""Product decision for one name in the 131-tool classification baseline."""
|
|
65
|
+
|
|
66
|
+
PUBLIC = "public"
|
|
67
|
+
COMPATIBILITY = "compatibility"
|
|
68
|
+
ADVANCED = "advanced"
|
|
69
|
+
DEPRECATED = "deprecated"
|
|
70
|
+
INTERNAL = "internal"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ToolProfile(str, Enum):
|
|
74
|
+
DEFAULT = "default"
|
|
75
|
+
ADVANCED = "advanced"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ToolAvailability(str, Enum):
|
|
79
|
+
"""Declared registration state of a tool in the selected profile."""
|
|
80
|
+
|
|
81
|
+
AVAILABLE = "available"
|
|
82
|
+
PROFILE_GATED = "profile-gated"
|
|
83
|
+
INTERNAL_ONLY = "internal-only"
|
|
84
|
+
UNAVAILABLE = "unavailable"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class AvailabilityReasonCode(str, Enum):
|
|
88
|
+
CALLABLE_MISSING = "CALLABLE_MISSING"
|
|
89
|
+
DEPENDENCY_MISSING = "DEPENDENCY_MISSING"
|
|
90
|
+
DEPENDENCY_VERSION_SKEW = "DEPENDENCY_VERSION_SKEW"
|
|
91
|
+
PROFILE_DISABLED = "PROFILE_DISABLED"
|
|
92
|
+
INTERNAL_ONLY = "INTERNAL_ONLY"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(frozen=True, slots=True)
|
|
96
|
+
class AvailabilityRequirement:
|
|
97
|
+
dependency: str
|
|
98
|
+
minimum_version: str
|
|
99
|
+
optional: bool = False
|
|
100
|
+
extra: str | None = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass(frozen=True, slots=True)
|
|
104
|
+
class AvailabilityReason:
|
|
105
|
+
code: AvailabilityReasonCode
|
|
106
|
+
message: str
|
|
107
|
+
dependency: str | None = None
|
|
108
|
+
required_version: str | None = None
|
|
109
|
+
installed_version: str | None = None
|
|
110
|
+
install_hint: str | None = None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SchemaBinding(str, Enum):
|
|
114
|
+
"""Authoritative source from which FastMCP builds the input schema."""
|
|
115
|
+
|
|
116
|
+
PYTHON_SIGNATURE = "python-signature"
|
|
117
|
+
INTERNAL_LIBRARY = "internal-library"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass(frozen=True, slots=True)
|
|
121
|
+
class DomainSpec:
|
|
122
|
+
key: str
|
|
123
|
+
title: str
|
|
124
|
+
intent: str
|
|
125
|
+
when_to_use: str
|
|
126
|
+
tools: tuple[str, ...]
|
|
127
|
+
public: bool = True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True, slots=True)
|
|
131
|
+
class ToolSpec:
|
|
132
|
+
"""Static release decision plus explicit callable/schema binding."""
|
|
133
|
+
|
|
134
|
+
name: str
|
|
135
|
+
domain: str
|
|
136
|
+
profile: ToolProfile
|
|
137
|
+
classification: ToolClassification
|
|
138
|
+
availability_requirement: AvailabilityRequirement
|
|
139
|
+
availability: ToolAvailability
|
|
140
|
+
availability_reason: AvailabilityReason | None
|
|
141
|
+
reason: str
|
|
142
|
+
replacement_tools: tuple[str, ...] = ()
|
|
143
|
+
skill_required: bool = False
|
|
144
|
+
callable_name: str | None = None
|
|
145
|
+
schema_binding: SchemaBinding = SchemaBinding.PYTHON_SIGNATURE
|
|
146
|
+
mutates: bool = False
|
|
147
|
+
tags: tuple[str, ...] = ()
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def installed(self) -> bool:
|
|
151
|
+
return self.classification is not ToolClassification.INTERNAL
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass(frozen=True, slots=True)
|
|
155
|
+
class SchemaParameter:
|
|
156
|
+
name: str
|
|
157
|
+
annotation: str
|
|
158
|
+
required: bool
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass(frozen=True, slots=True)
|
|
162
|
+
class BoundToolSpec:
|
|
163
|
+
"""A ToolSpec resolved to one concrete callable before registration."""
|
|
164
|
+
|
|
165
|
+
spec: ToolSpec
|
|
166
|
+
function: Callable[..., Any]
|
|
167
|
+
signature: str
|
|
168
|
+
parameters: tuple[SchemaParameter, ...]
|
|
169
|
+
description: str
|
|
170
|
+
input_schema: Mapping[str, Any]
|
|
171
|
+
output_schema: Mapping[str, Any]
|
|
172
|
+
availability: ToolAvailability = ToolAvailability.AVAILABLE
|
|
173
|
+
availability_reason: AvailabilityReason | None = None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass(frozen=True, slots=True)
|
|
177
|
+
class RegisteredToolRegistry:
|
|
178
|
+
"""Immutable record of the exact callables handed to FastMCP."""
|
|
179
|
+
|
|
180
|
+
tools: tuple[BoundToolSpec, ...]
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def names(self) -> tuple[str, ...]:
|
|
184
|
+
return tuple(item.spec.name for item in self.tools)
|
|
185
|
+
|
|
186
|
+
def callable_map(self) -> Mapping[str, Callable[..., Any]]:
|
|
187
|
+
return {item.spec.name: item.function for item in self.tools}
|
|
188
|
+
|
|
189
|
+
def by_name(self) -> Mapping[str, BoundToolSpec]:
|
|
190
|
+
return {item.spec.name: item for item in self.tools}
|
|
191
|
+
|
|
192
|
+
def payload(self) -> dict[str, Any]:
|
|
193
|
+
return {
|
|
194
|
+
"schemaVersion": "hwpx.bound-tool-registry.v1",
|
|
195
|
+
"contractHash": contract_hash(),
|
|
196
|
+
"tools": [
|
|
197
|
+
{
|
|
198
|
+
"name": item.spec.name,
|
|
199
|
+
"callableName": item.spec.callable_name,
|
|
200
|
+
"signature": item.signature,
|
|
201
|
+
"description": item.description,
|
|
202
|
+
"inputSchema": item.input_schema,
|
|
203
|
+
"outputSchema": item.output_schema,
|
|
204
|
+
"availability": item.availability.value,
|
|
205
|
+
"availabilityReason": _availability_reason_payload(
|
|
206
|
+
item.availability_reason
|
|
207
|
+
),
|
|
208
|
+
"parameters": [
|
|
209
|
+
{
|
|
210
|
+
"name": parameter.name,
|
|
211
|
+
"annotation": parameter.annotation,
|
|
212
|
+
"required": parameter.required,
|
|
213
|
+
}
|
|
214
|
+
for parameter in item.parameters
|
|
215
|
+
],
|
|
216
|
+
}
|
|
217
|
+
for item in self.tools
|
|
218
|
+
],
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
def binding_hash(self) -> str:
|
|
222
|
+
raw = json.dumps(self.payload(), ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
|
223
|
+
return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
_ADVANCED_CLASSIFICATION = {
|
|
227
|
+
"package_parts",
|
|
228
|
+
"package_get_xml",
|
|
229
|
+
"package_get_text",
|
|
230
|
+
"object_find_by_tag",
|
|
231
|
+
"object_find_by_attr",
|
|
232
|
+
"validate_structure",
|
|
233
|
+
"lint_text_conventions",
|
|
234
|
+
"score_form_fill",
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
_ADVANCED_PROFILE = _ADVANCED_CLASSIFICATION
|
|
238
|
+
|
|
239
|
+
_COMPATIBILITY_REPLACEMENTS: dict[str, tuple[str, ...]] = {
|
|
240
|
+
"apply_edits": ("apply_document_commands",),
|
|
241
|
+
"apply_evalplan_fill": ("analyze_form_fill", "apply_form_fill", "verify_form_fill"),
|
|
242
|
+
"create_comparison_table_document": ("create_document_from_plan",),
|
|
243
|
+
"create_government_report_document": ("create_document_from_plan",),
|
|
244
|
+
"create_proposal_document": ("create_document_from_plan",),
|
|
245
|
+
"fill_by_path": ("analyze_form_fill", "apply_form_fill", "verify_form_fill"),
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# At the 5.0.0 major boundary, the three template-formfit facades demote from
|
|
249
|
+
# COMPATIBILITY to DEPRECATED. Their handlers and behaviour are unchanged; they
|
|
250
|
+
# now carry the one-transition deprecation guidance toward the canonical
|
|
251
|
+
# analyze/apply/verify_form_fill trio, exactly like the former stub group.
|
|
252
|
+
_DEPRECATED_REPLACEMENTS: dict[str, tuple[str, ...]] = {
|
|
253
|
+
"analyze_template_formfit": ("analyze_form_fill", "apply_form_fill", "verify_form_fill"),
|
|
254
|
+
"apply_template_formfit": ("analyze_form_fill", "apply_form_fill", "verify_form_fill"),
|
|
255
|
+
"fill_form_field": ("analyze_form_fill", "apply_form_fill", "verify_form_fill"),
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
_INTERNAL_QA_TOOLS = {
|
|
259
|
+
"export_fixture_benchmark",
|
|
260
|
+
"run_fixture_benchmark",
|
|
261
|
+
"visual_repair_fixture",
|
|
262
|
+
"visual_review_fixture",
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
_SKILL_REQUIRED_TOOLS = {
|
|
266
|
+
"get_document_node",
|
|
267
|
+
"query_document_nodes",
|
|
268
|
+
"apply_document_commands",
|
|
269
|
+
"dump_document_blueprint",
|
|
270
|
+
"replay_document_blueprint",
|
|
271
|
+
"create_document_from_plan",
|
|
272
|
+
"create_government_report_document",
|
|
273
|
+
"mail_merge",
|
|
274
|
+
"table_compute",
|
|
275
|
+
"extract_style_profile",
|
|
276
|
+
"list_templates",
|
|
277
|
+
"get_document_map",
|
|
278
|
+
"repair_hwpx",
|
|
279
|
+
"replace_by_anchor",
|
|
280
|
+
"add_memo_by_anchor",
|
|
281
|
+
"byte_preserving_patch",
|
|
282
|
+
"render_preview",
|
|
283
|
+
"apply_edits",
|
|
284
|
+
"add_tracked_edit",
|
|
285
|
+
"undo_last_edit",
|
|
286
|
+
"compose_exam",
|
|
287
|
+
"scan_form_guidance",
|
|
288
|
+
"apply_table_ops",
|
|
289
|
+
"apply_body_ops",
|
|
290
|
+
"inspect_fill_residue",
|
|
291
|
+
"verify_form_fill",
|
|
292
|
+
"apply_evalplan_fill",
|
|
293
|
+
"score_form_fill",
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
_MUTATING_TOOLS = {
|
|
298
|
+
"apply_document_commands", "replay_document_blueprint",
|
|
299
|
+
# These render tools can materialize queue or preview artifacts when their
|
|
300
|
+
# optional output arguments are supplied, so classify them conservatively
|
|
301
|
+
# as writers for process-wide serialization.
|
|
302
|
+
"render_preview", "render_submit", "render_status", "render_cancel",
|
|
303
|
+
"start_workflow", "continue_workflow", "approve_workflow_decision",
|
|
304
|
+
"cancel_workflow", "resume_workflow",
|
|
305
|
+
"apply_table_ops", "apply_body_ops", "fill_form_field", "fill_by_path",
|
|
306
|
+
"apply_form_fill", "apply_template_formfit",
|
|
307
|
+
"apply_evalplan_fill",
|
|
308
|
+
"create_document", "create_document_from_plan", "copy_document",
|
|
309
|
+
"create_government_report_document", "create_proposal_document",
|
|
310
|
+
"create_comparison_table_document", "register_template",
|
|
311
|
+
"add_heading", "add_paragraph", "insert_paragraph", "delete_paragraph",
|
|
312
|
+
"add_page_break", "apply_edits", "undo_last_edit",
|
|
313
|
+
"replace_by_anchor", "replace_in_paragraph", "search_and_replace", "batch_replace",
|
|
314
|
+
"byte_preserving_patch", "insert_picture", "replace_picture",
|
|
315
|
+
"add_table", "set_table_cell_text", "merge_table_cells", "split_table_cell",
|
|
316
|
+
"format_table", "table_compute",
|
|
317
|
+
"create_custom_style", "set_paragraph_format", "set_list_format", "format_text",
|
|
318
|
+
"apply_style_profile_to_plan", "set_page_setup", "set_header_footer",
|
|
319
|
+
"set_page_number", "add_toc", "add_cross_reference", "add_tracked_edit",
|
|
320
|
+
"compose_exam", "place_seal", "mail_merge", "build_image_grid",
|
|
321
|
+
"build_meeting_nameplates", "build_organization_chart", "add_memo",
|
|
322
|
+
"add_memo_by_anchor", "remove_memo", "repair_hwpx",
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
BASELINE_DOMAIN_SPECS: tuple[DomainSpec, ...] = (
|
|
327
|
+
DomainSpec(
|
|
328
|
+
"agent_document",
|
|
329
|
+
"에이전트 문서 인터페이스",
|
|
330
|
+
"공유 semantic node/path/query/command 계약으로 낯선 HWPX를 탐색하고 이종 편집을 한 번에 원자 적용.",
|
|
331
|
+
"get_document_node/query_document_nodes로 revision-bound canonical path를 찾은 뒤 "
|
|
332
|
+
"apply_document_commands 한 배치로 적용한다. 도메인 증거가 필요한 작업은 전문 도구를 사용한다.",
|
|
333
|
+
(
|
|
334
|
+
"get_document_node",
|
|
335
|
+
"query_document_nodes",
|
|
336
|
+
"apply_document_commands",
|
|
337
|
+
"dump_document_blueprint",
|
|
338
|
+
"replay_document_blueprint",
|
|
339
|
+
),
|
|
340
|
+
),
|
|
341
|
+
DomainSpec(
|
|
342
|
+
"real_hancom_render",
|
|
343
|
+
"실한컴 비동기 렌더",
|
|
344
|
+
"인증된 private queue로 HWPX를 제출하고 실한컴 PDF·페이지 영수증을 비동기로 조회.",
|
|
345
|
+
"render_submit 후 job id를 받고 render_status로 폴링한다. unavailable/degraded이면 unverified로 보류한다.",
|
|
346
|
+
("render_submit", "render_status", "render_cancel", "render_health"),
|
|
347
|
+
),
|
|
348
|
+
DomainSpec(
|
|
349
|
+
"workflow",
|
|
350
|
+
"자율 문서 워크플로",
|
|
351
|
+
"서버가 상태·정책·결정·예산·검증을 강제하는 재시작 가능한 고수준 문서 작업.",
|
|
352
|
+
"호스트별 스킬 지식 없이 HWPX 작업을 안전하게 시작·진행·승인·재개할 때.",
|
|
353
|
+
(
|
|
354
|
+
"start_workflow",
|
|
355
|
+
"get_workflow",
|
|
356
|
+
"get_workflow_result",
|
|
357
|
+
"continue_workflow",
|
|
358
|
+
"approve_workflow_decision",
|
|
359
|
+
"cancel_workflow",
|
|
360
|
+
"resume_workflow",
|
|
361
|
+
),
|
|
362
|
+
),
|
|
363
|
+
DomainSpec(
|
|
364
|
+
"visual_qa",
|
|
365
|
+
"전 페이지 fixture 비전 검수",
|
|
366
|
+
"제품 QA fixture 전용 검수와 제한 수리.",
|
|
367
|
+
"설치형 제품 도구가 아니라 CI 라이브러리로만 실행한다.",
|
|
368
|
+
("visual_review_fixture", "visual_repair_fixture"),
|
|
369
|
+
public=False,
|
|
370
|
+
),
|
|
371
|
+
DomainSpec(
|
|
372
|
+
"blind_eval",
|
|
373
|
+
"블라인드 fixture 실무 평가",
|
|
374
|
+
"동결 fixture 실행·판정 증거와 익명 심사용 번들.",
|
|
375
|
+
"설치형 제품 도구가 아니라 CI 라이브러리로만 실행한다.",
|
|
376
|
+
("run_fixture_benchmark", "export_fixture_benchmark"),
|
|
377
|
+
public=False,
|
|
378
|
+
),
|
|
379
|
+
DomainSpec(
|
|
380
|
+
"read",
|
|
381
|
+
"읽기·추출·변환",
|
|
382
|
+
"HWPX 문서를 읽어 텍스트·구조·서식을 뽑거나 Markdown/HTML/JSON으로 변환.",
|
|
383
|
+
"문서 내용을 이해·인용·재작업하거나 다른 형식으로 내보낼 때.",
|
|
384
|
+
(
|
|
385
|
+
"get_document_text", "get_document_info", "get_document_outline", "get_document_map",
|
|
386
|
+
"get_paragraph_text", "get_paragraphs_text", "get_location_text", "get_table_text",
|
|
387
|
+
"get_table_map", "find_text", "list_available_documents", "hwpx_to_markdown",
|
|
388
|
+
"document_to_markdown", "hwpx_to_html", "hwpx_extract_json", "document_extract_json",
|
|
389
|
+
),
|
|
390
|
+
),
|
|
391
|
+
DomainSpec(
|
|
392
|
+
"form_fill",
|
|
393
|
+
"양식 채움 (동적)",
|
|
394
|
+
"처음 보는 실양식을 정찰하고 typed plan을 한 transaction으로 채운 뒤 검증.",
|
|
395
|
+
"analyze_form_fill → 승인 → apply_form_fill → verify_form_fill을 canonical 경로로 사용한다.",
|
|
396
|
+
(
|
|
397
|
+
"scan_form_guidance", "apply_table_ops", "apply_body_ops", "inspect_fill_residue",
|
|
398
|
+
"verify_form_fill", "list_form_fields", "fill_form_field", "find_cell_by_label",
|
|
399
|
+
"fill_by_path", "analyze_form_fill", "apply_form_fill", "analyze_template_formfit",
|
|
400
|
+
"apply_template_formfit",
|
|
401
|
+
"apply_evalplan_fill", "score_form_fill",
|
|
402
|
+
),
|
|
403
|
+
),
|
|
404
|
+
DomainSpec(
|
|
405
|
+
"author",
|
|
406
|
+
"문서 생성",
|
|
407
|
+
"계획·브리프로 새 HWPX 문서를 생성.",
|
|
408
|
+
"빈 문서에서 공문·보고서·제안서·비교표 등을 저작할 때.",
|
|
409
|
+
(
|
|
410
|
+
"create_document", "create_document_from_plan", "copy_document",
|
|
411
|
+
"create_government_report_document", "create_proposal_document",
|
|
412
|
+
"create_comparison_table_document", "get_document_plan_schema", "validate_document_plan",
|
|
413
|
+
"analyze_document_plan", "markdown_to_document_plan", "parse_government_report_text",
|
|
414
|
+
"compute_report_value", "register_template", "list_templates", "describe_template",
|
|
415
|
+
),
|
|
416
|
+
),
|
|
417
|
+
DomainSpec(
|
|
418
|
+
"edit",
|
|
419
|
+
"편집",
|
|
420
|
+
"기존 문서의 문단·그림·텍스트를 안전하게 편집.",
|
|
421
|
+
"기존 문서를 고치거나 byte-preserving 수술을 적용할 때.",
|
|
422
|
+
(
|
|
423
|
+
"add_heading", "add_paragraph", "insert_paragraph", "delete_paragraph", "add_page_break",
|
|
424
|
+
"apply_edits", "undo_last_edit",
|
|
425
|
+
"replace_by_anchor", "replace_in_paragraph", "search_and_replace", "batch_replace",
|
|
426
|
+
"byte_preserving_patch", "insert_picture", "replace_picture",
|
|
427
|
+
),
|
|
428
|
+
),
|
|
429
|
+
DomainSpec(
|
|
430
|
+
"tables", "표 조작", "표 생성·셀 편집·병합·계산.", "표를 직접 만들거나 값을 바꿀 때.",
|
|
431
|
+
("add_table", "set_table_cell_text", "merge_table_cells", "split_table_cell", "format_table", "table_compute"),
|
|
432
|
+
),
|
|
433
|
+
DomainSpec(
|
|
434
|
+
"styles", "서식·스타일", "문단/런 서식과 스타일 프로파일을 편집.", "글꼴·정렬·목록·서식을 바꿀 때.",
|
|
435
|
+
(
|
|
436
|
+
"list_styles", "create_custom_style", "set_paragraph_format", "set_list_format",
|
|
437
|
+
"format_text", "extract_style_profile", "apply_style_profile_to_plan", "compare_style_profiles",
|
|
438
|
+
),
|
|
439
|
+
),
|
|
440
|
+
DomainSpec(
|
|
441
|
+
"layout", "페이지·머리글·쪽번호", "페이지 레이아웃 요소를 설정.", "용지·머리글·쪽번호를 바꿀 때.",
|
|
442
|
+
("set_page_setup", "set_header_footer", "set_page_number"),
|
|
443
|
+
),
|
|
444
|
+
DomainSpec(
|
|
445
|
+
"toc_xref", "차례·상호참조", "한컴 네이티브 차례와 쪽 상호참조를 저작·검증.",
|
|
446
|
+
"자동 목차/상호참조가 필요할 때.", ("add_toc", "add_cross_reference", "verify_toc"),
|
|
447
|
+
),
|
|
448
|
+
DomainSpec("pii", "개인정보", "개인정보를 탐지·마스킹.", "배포 전 PII를 감사할 때.", ("scan_personal_info",)),
|
|
449
|
+
DomainSpec("redline", "변경추적", "수락/거부 가능한 추적 변경을 저작.", "검토용 redline이 필요할 때.", ("add_tracked_edit",)),
|
|
450
|
+
DomainSpec("exam", "시험지 조판", "문항 Markdown을 학교 양식에 조판.", "시험지 원안지에 문항을 앉힐 때.", ("compose_exam", "verify_question_splits")),
|
|
451
|
+
DomainSpec("seal", "직인·관인", "직인을 배치하고 규정을 검사.", "공문 직인 처리에.", ("place_seal", "check_seal_compliance")),
|
|
452
|
+
DomainSpec(
|
|
453
|
+
"generators", "대량생산·특수 산출", "메일머지·사진대지·명패·조직도를 생성.",
|
|
454
|
+
"반복 문서나 특수 레이아웃을 만들 때.",
|
|
455
|
+
("mail_merge", "inspect_mail_merge_placeholders", "build_image_grid", "build_meeting_nameplates", "build_organization_chart"),
|
|
456
|
+
),
|
|
457
|
+
DomainSpec("memo", "메모·주석", "검토 메모를 추가·삭제.", "문서에 코멘트를 달 때.", ("add_memo", "add_memo_by_anchor", "remove_memo")),
|
|
458
|
+
DomainSpec(
|
|
459
|
+
"verify_quality", "검증·품질·복구", "렌더·품질·정합·복구·서버 상태를 검사.",
|
|
460
|
+
"산출물 확언 전 검증하거나 문제를 진단할 때.",
|
|
461
|
+
(
|
|
462
|
+
"render_preview", "repair_hwpx", "mcp_server_health", "describe_capabilities", "doc_diff",
|
|
463
|
+
"validate_structure", "lint_text_conventions", "inspect_document_quality",
|
|
464
|
+
"inspect_document_authoring_quality", "inspect_operating_plan_quality",
|
|
465
|
+
"inspect_official_document_style", "inspect_reference_consistency",
|
|
466
|
+
),
|
|
467
|
+
),
|
|
468
|
+
DomainSpec(
|
|
469
|
+
"package_io", "패키지 조회", "고급 OPC 파트와 XML 객체를 조회.", "저수준 진단이 필요할 때.",
|
|
470
|
+
("package_parts", "package_get_text", "package_get_xml", "object_find_by_attr", "object_find_by_tag"),
|
|
471
|
+
),
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
DOMAIN_SPECS: tuple[DomainSpec, ...] = tuple(domain for domain in BASELINE_DOMAIN_SPECS if domain.public)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _classification(name: str) -> ToolClassification:
|
|
478
|
+
if name in _INTERNAL_QA_TOOLS:
|
|
479
|
+
return ToolClassification.INTERNAL
|
|
480
|
+
if name in _DEPRECATED_REPLACEMENTS:
|
|
481
|
+
return ToolClassification.DEPRECATED
|
|
482
|
+
if name in _COMPATIBILITY_REPLACEMENTS:
|
|
483
|
+
return ToolClassification.COMPATIBILITY
|
|
484
|
+
if name in _ADVANCED_CLASSIFICATION:
|
|
485
|
+
return ToolClassification.ADVANCED
|
|
486
|
+
return ToolClassification.PUBLIC
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _reason(classification: ToolClassification) -> str:
|
|
490
|
+
return {
|
|
491
|
+
ToolClassification.PUBLIC: "canonical installed product capability",
|
|
492
|
+
ToolClassification.COMPATIBILITY: "transition facade over a canonical typed engine",
|
|
493
|
+
ToolClassification.ADVANCED: "opt-in diagnostic or expert capability outside ordinary routing",
|
|
494
|
+
ToolClassification.DEPRECATED: "one-transition stub with explicit replacement guidance",
|
|
495
|
+
ToolClassification.INTERNAL: "fixture QA library retained for CI but removed from installed registration",
|
|
496
|
+
}[classification]
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _build_baseline_tool_specs() -> tuple[ToolSpec, ...]:
|
|
500
|
+
seen: set[str] = set()
|
|
501
|
+
specs: list[ToolSpec] = []
|
|
502
|
+
for domain in BASELINE_DOMAIN_SPECS:
|
|
503
|
+
for name in domain.tools:
|
|
504
|
+
if name in seen:
|
|
505
|
+
raise RuntimeError(f"duplicate ToolSpec name: {name}")
|
|
506
|
+
seen.add(name)
|
|
507
|
+
classification = _classification(name)
|
|
508
|
+
internal = classification is ToolClassification.INTERNAL
|
|
509
|
+
profile = ToolProfile.ADVANCED if name in _ADVANCED_PROFILE else ToolProfile.DEFAULT
|
|
510
|
+
availability = (
|
|
511
|
+
ToolAvailability.INTERNAL_ONLY
|
|
512
|
+
if internal
|
|
513
|
+
else ToolAvailability.PROFILE_GATED
|
|
514
|
+
if profile is ToolProfile.ADVANCED
|
|
515
|
+
else ToolAvailability.AVAILABLE
|
|
516
|
+
)
|
|
517
|
+
availability_reason = (
|
|
518
|
+
AvailabilityReason(
|
|
519
|
+
code=AvailabilityReasonCode.INTERNAL_ONLY,
|
|
520
|
+
message="fixture QA capability is retained only as an internal library",
|
|
521
|
+
)
|
|
522
|
+
if internal
|
|
523
|
+
else AvailabilityReason(
|
|
524
|
+
code=AvailabilityReasonCode.PROFILE_DISABLED,
|
|
525
|
+
message=(
|
|
526
|
+
"set HWPX_AUTOMATION_ADVANCED=1 before server import "
|
|
527
|
+
"(HWPX_MCP_ADVANCED remains a 6.x fallback)"
|
|
528
|
+
),
|
|
529
|
+
)
|
|
530
|
+
if profile is ToolProfile.ADVANCED
|
|
531
|
+
else None
|
|
532
|
+
)
|
|
533
|
+
tags = tuple(
|
|
534
|
+
tag
|
|
535
|
+
for tag, enabled in (
|
|
536
|
+
(domain.key, True),
|
|
537
|
+
(classification.value, True),
|
|
538
|
+
("mutation", name in _MUTATING_TOOLS),
|
|
539
|
+
("skill-required", name in _SKILL_REQUIRED_TOOLS),
|
|
540
|
+
)
|
|
541
|
+
if enabled
|
|
542
|
+
)
|
|
543
|
+
specs.append(
|
|
544
|
+
ToolSpec(
|
|
545
|
+
name=name,
|
|
546
|
+
domain=domain.key,
|
|
547
|
+
profile=profile,
|
|
548
|
+
classification=classification,
|
|
549
|
+
availability_requirement=AvailabilityRequirement(
|
|
550
|
+
dependency="python-hwpx",
|
|
551
|
+
minimum_version=MIN_PYTHON_HWPX,
|
|
552
|
+
),
|
|
553
|
+
availability=availability,
|
|
554
|
+
availability_reason=availability_reason,
|
|
555
|
+
reason=_reason(classification),
|
|
556
|
+
replacement_tools=(
|
|
557
|
+
_COMPATIBILITY_REPLACEMENTS.get(name)
|
|
558
|
+
or _DEPRECATED_REPLACEMENTS.get(name)
|
|
559
|
+
or ()
|
|
560
|
+
),
|
|
561
|
+
skill_required=name in _SKILL_REQUIRED_TOOLS,
|
|
562
|
+
callable_name=None if internal else name,
|
|
563
|
+
schema_binding=(
|
|
564
|
+
SchemaBinding.INTERNAL_LIBRARY if internal else SchemaBinding.PYTHON_SIGNATURE
|
|
565
|
+
),
|
|
566
|
+
mutates=name in _MUTATING_TOOLS,
|
|
567
|
+
tags=tags,
|
|
568
|
+
)
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
expected_sets = (
|
|
572
|
+
_ADVANCED_CLASSIFICATION,
|
|
573
|
+
set(_COMPATIBILITY_REPLACEMENTS),
|
|
574
|
+
set(_DEPRECATED_REPLACEMENTS),
|
|
575
|
+
_INTERNAL_QA_TOOLS,
|
|
576
|
+
_SKILL_REQUIRED_TOOLS,
|
|
577
|
+
)
|
|
578
|
+
missing = set().union(*(items - seen for items in expected_sets))
|
|
579
|
+
if missing:
|
|
580
|
+
raise RuntimeError(f"classified tools absent from domains: {sorted(missing)}")
|
|
581
|
+
return tuple(specs)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
BASELINE_TOOL_SPECS = _build_baseline_tool_specs()
|
|
585
|
+
TOOL_SPECS = tuple(spec for spec in BASELINE_TOOL_SPECS if spec.installed)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _validate_classification() -> None:
|
|
589
|
+
counts = classification_counts()
|
|
590
|
+
expected = {
|
|
591
|
+
ToolClassification.PUBLIC.value: 110,
|
|
592
|
+
ToolClassification.COMPATIBILITY.value: 6,
|
|
593
|
+
ToolClassification.ADVANCED.value: 8,
|
|
594
|
+
ToolClassification.DEPRECATED.value: 3,
|
|
595
|
+
ToolClassification.INTERNAL.value: 4,
|
|
596
|
+
}
|
|
597
|
+
if len(BASELINE_TOOL_SPECS) != 131 or counts != expected:
|
|
598
|
+
raise RuntimeError(
|
|
599
|
+
f"131-tool classification must be disjoint and exhaustive: {counts!r} != {expected!r}"
|
|
600
|
+
)
|
|
601
|
+
if len(TOOL_SPECS) != 127:
|
|
602
|
+
raise RuntimeError(f"installed advanced surface must contain 127 tools, got {len(TOOL_SPECS)}")
|
|
603
|
+
if sum(spec.skill_required for spec in TOOL_SPECS) != 28:
|
|
604
|
+
raise RuntimeError(
|
|
605
|
+
"installed surface must contain exactly 28 skill-required tools"
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def classification_counts() -> dict[str, int]:
|
|
610
|
+
return {
|
|
611
|
+
classification.value: sum(
|
|
612
|
+
spec.classification is classification for spec in BASELINE_TOOL_SPECS
|
|
613
|
+
)
|
|
614
|
+
for classification in ToolClassification
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
_validate_classification()
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def active_tool_specs(*, advanced: bool) -> tuple[ToolSpec, ...]:
|
|
622
|
+
return tuple(
|
|
623
|
+
spec
|
|
624
|
+
for spec in TOOL_SPECS
|
|
625
|
+
if spec.profile is ToolProfile.DEFAULT or advanced
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def expected_tool_order(*, advanced: bool) -> tuple[str, ...]:
|
|
630
|
+
return tuple(spec.name for spec in active_tool_specs(advanced=advanced))
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def expected_tool_names(*, advanced: bool) -> set[str]:
|
|
634
|
+
return set(expected_tool_order(advanced=advanced))
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def skill_required_tool_names() -> set[str]:
|
|
638
|
+
return {spec.name for spec in TOOL_SPECS if spec.skill_required}
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def classification_payload() -> dict[str, Any]:
|
|
642
|
+
return {
|
|
643
|
+
"schemaVersion": "hwpx.tool-classification.v1",
|
|
644
|
+
"baselineToolCount": len(BASELINE_TOOL_SPECS),
|
|
645
|
+
"counts": classification_counts(),
|
|
646
|
+
"tools": [_tool_payload(spec) for spec in BASELINE_TOOL_SPECS],
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def _availability_reason_payload(reason: AvailabilityReason | None) -> dict[str, Any] | None:
|
|
651
|
+
if reason is None:
|
|
652
|
+
return None
|
|
653
|
+
return {
|
|
654
|
+
"code": reason.code.value,
|
|
655
|
+
"message": reason.message,
|
|
656
|
+
"dependency": reason.dependency,
|
|
657
|
+
"requiredVersion": reason.required_version,
|
|
658
|
+
"installedVersion": reason.installed_version,
|
|
659
|
+
"installHint": reason.install_hint,
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def _tool_payload(spec: ToolSpec, bound: BoundToolSpec | None = None) -> dict[str, Any]:
|
|
664
|
+
payload: dict[str, Any] = {
|
|
665
|
+
"name": spec.name,
|
|
666
|
+
"domain": spec.domain,
|
|
667
|
+
"profile": spec.profile.value,
|
|
668
|
+
"classification": spec.classification.value,
|
|
669
|
+
"lifecycle": spec.classification.value,
|
|
670
|
+
"availabilityRequirement": {
|
|
671
|
+
"dependency": spec.availability_requirement.dependency,
|
|
672
|
+
"minimumVersion": spec.availability_requirement.minimum_version,
|
|
673
|
+
"optional": spec.availability_requirement.optional,
|
|
674
|
+
"extra": spec.availability_requirement.extra,
|
|
675
|
+
},
|
|
676
|
+
"declaredAvailability": spec.availability.value,
|
|
677
|
+
"availability": (bound.availability if bound else spec.availability).value,
|
|
678
|
+
"availabilityReason": _availability_reason_payload(
|
|
679
|
+
bound.availability_reason if bound else spec.availability_reason
|
|
680
|
+
),
|
|
681
|
+
"decisionReason": spec.reason,
|
|
682
|
+
"replacementTools": list(spec.replacement_tools),
|
|
683
|
+
"skillRequired": spec.skill_required,
|
|
684
|
+
"callableName": spec.callable_name,
|
|
685
|
+
"schemaBinding": spec.schema_binding.value,
|
|
686
|
+
"mutates": spec.mutates,
|
|
687
|
+
"tags": list(spec.tags),
|
|
688
|
+
}
|
|
689
|
+
if bound is not None:
|
|
690
|
+
payload.update(
|
|
691
|
+
{
|
|
692
|
+
"description": bound.description,
|
|
693
|
+
"signature": bound.signature,
|
|
694
|
+
"inputSchema": bound.input_schema,
|
|
695
|
+
"outputSchema": bound.output_schema,
|
|
696
|
+
}
|
|
697
|
+
)
|
|
698
|
+
return payload
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
_BOUND_TOOL_REGISTRY: RegisteredToolRegistry | None = None
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def bound_tool_registry() -> RegisteredToolRegistry:
|
|
705
|
+
"""Return the complete installed registry after explicit runtime initialization."""
|
|
706
|
+
|
|
707
|
+
if _BOUND_TOOL_REGISTRY is None:
|
|
708
|
+
raise RuntimeError(
|
|
709
|
+
"canonical ToolSpec registry is not bound; initialize "
|
|
710
|
+
"hwpx_automation.runtime first"
|
|
711
|
+
)
|
|
712
|
+
return _BOUND_TOOL_REGISTRY
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def contract_payload() -> dict[str, Any]:
|
|
716
|
+
registry = bound_tool_registry()
|
|
717
|
+
bound = registry.by_name()
|
|
718
|
+
return {
|
|
719
|
+
"schemaVersion": "hwpx.tool-contract.v2",
|
|
720
|
+
"minPythonHwpx": MIN_PYTHON_HWPX,
|
|
721
|
+
"minAutomationVersion": MIN_AUTOMATION_VERSION,
|
|
722
|
+
"minMcpVersion": MIN_MCP_VERSION,
|
|
723
|
+
"minSkillVersion": MIN_SKILL_VERSION,
|
|
724
|
+
"tools": [_tool_payload(spec, bound[spec.name]) for spec in TOOL_SPECS],
|
|
725
|
+
"baselineClassification": classification_payload(),
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def contract_hash() -> str:
|
|
730
|
+
# Non-MCP automation services use the frozen release receipt without
|
|
731
|
+
# importing or constructing FastMCP. Once the optional adapter binds the
|
|
732
|
+
# canonical registry, recompute from live schemas and fail tests on drift.
|
|
733
|
+
if _BOUND_TOOL_REGISTRY is None:
|
|
734
|
+
return RELEASED_CONTRACT_HASH
|
|
735
|
+
raw = json.dumps(contract_payload(), ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
|
736
|
+
return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def _annotation_name(annotation: Any) -> str:
|
|
740
|
+
if annotation is inspect.Parameter.empty:
|
|
741
|
+
return "untyped"
|
|
742
|
+
if isinstance(annotation, str):
|
|
743
|
+
return annotation
|
|
744
|
+
return getattr(annotation, "__name__", str(annotation).replace("typing.", ""))
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def bind_tool_specs(
|
|
748
|
+
namespace: Mapping[str, Any],
|
|
749
|
+
*,
|
|
750
|
+
advanced: bool | None = None,
|
|
751
|
+
) -> RegisteredToolRegistry:
|
|
752
|
+
"""Resolve callable identity and normalized schemas before registration.
|
|
753
|
+
|
|
754
|
+
Every missing/non-callable binding and every schema-signature failure is
|
|
755
|
+
reported together, so import/programming errors cannot produce a silently
|
|
756
|
+
degraded partial registry.
|
|
757
|
+
"""
|
|
758
|
+
|
|
759
|
+
# Keep the static ToolSpec/classification contract importable in the base
|
|
760
|
+
# automation distribution. FastMCP is an optional adapter and is loaded
|
|
761
|
+
# only when a caller explicitly binds the MCP schema surface.
|
|
762
|
+
specs = TOOL_SPECS if advanced is None else active_tool_specs(advanced=advanced)
|
|
763
|
+
resolved: list[tuple[ToolSpec, Callable[..., Any], inspect.Signature]] = []
|
|
764
|
+
errors: list[str] = []
|
|
765
|
+
schema_entries: list[tuple[str, Callable[..., Any], str, Mapping[str, Any]]] = []
|
|
766
|
+
for spec in specs:
|
|
767
|
+
callable_name = spec.callable_name
|
|
768
|
+
function = namespace.get(callable_name) if callable_name else None
|
|
769
|
+
if not callable(function):
|
|
770
|
+
errors.append(f"{spec.name}: missing callable {callable_name!r}")
|
|
771
|
+
continue
|
|
772
|
+
try:
|
|
773
|
+
signature = inspect.signature(function)
|
|
774
|
+
except (TypeError, ValueError) as exc:
|
|
775
|
+
errors.append(f"{spec.name}: invalid Python signature: {exc}")
|
|
776
|
+
continue
|
|
777
|
+
description = inspect.getdoc(function) or f"HWPX tool {spec.name}"
|
|
778
|
+
meta = {
|
|
779
|
+
"hwpxLifecycle": spec.classification.value,
|
|
780
|
+
"hwpxProfile": spec.profile.value,
|
|
781
|
+
"hwpxMutates": spec.mutates,
|
|
782
|
+
"hwpxReplacementTools": list(spec.replacement_tools),
|
|
783
|
+
}
|
|
784
|
+
resolved.append((spec, function, signature))
|
|
785
|
+
schema_entries.append((spec.name, function, description, meta))
|
|
786
|
+
if errors:
|
|
787
|
+
raise RuntimeError("ToolSpec binding failed:\n- " + "\n- ".join(errors))
|
|
788
|
+
|
|
789
|
+
try:
|
|
790
|
+
snapshots = describe_callables(schema_entries)
|
|
791
|
+
except Exception as exc:
|
|
792
|
+
raise RuntimeError(f"ToolSpec schema binding failed: {exc}") from exc
|
|
793
|
+
|
|
794
|
+
bound: list[BoundToolSpec] = []
|
|
795
|
+
for spec, function, signature in resolved:
|
|
796
|
+
parameters = tuple(
|
|
797
|
+
SchemaParameter(
|
|
798
|
+
name=parameter.name,
|
|
799
|
+
annotation=_annotation_name(parameter.annotation),
|
|
800
|
+
required=parameter.default is inspect.Parameter.empty,
|
|
801
|
+
)
|
|
802
|
+
for parameter in signature.parameters.values()
|
|
803
|
+
if parameter.kind
|
|
804
|
+
not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
|
|
805
|
+
)
|
|
806
|
+
bound.append(
|
|
807
|
+
BoundToolSpec(
|
|
808
|
+
spec=spec,
|
|
809
|
+
function=function,
|
|
810
|
+
signature=str(signature),
|
|
811
|
+
parameters=parameters,
|
|
812
|
+
description=snapshots[spec.name].description,
|
|
813
|
+
input_schema=snapshots[spec.name].input_schema,
|
|
814
|
+
output_schema=snapshots[spec.name].output_schema,
|
|
815
|
+
availability=ToolAvailability.AVAILABLE,
|
|
816
|
+
availability_reason=None,
|
|
817
|
+
)
|
|
818
|
+
)
|
|
819
|
+
return RegisteredToolRegistry(tuple(bound))
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def _deprecated_wrapper(item: BoundToolSpec) -> Callable[..., Any]:
|
|
823
|
+
replacement_tools = item.spec.replacement_tools
|
|
824
|
+
function = item.function
|
|
825
|
+
|
|
826
|
+
@functools.wraps(function)
|
|
827
|
+
def wrapped(*args: Any, **kwargs: Any) -> Any:
|
|
828
|
+
result = function(*args, **kwargs)
|
|
829
|
+
if isinstance(result, Mapping):
|
|
830
|
+
payload = dict(result)
|
|
831
|
+
payload.setdefault(
|
|
832
|
+
"deprecation",
|
|
833
|
+
{
|
|
834
|
+
"status": "deprecated",
|
|
835
|
+
"tool": item.spec.name,
|
|
836
|
+
"replacementTools": list(replacement_tools),
|
|
837
|
+
"message": "This tool is retained for one transition release.",
|
|
838
|
+
},
|
|
839
|
+
)
|
|
840
|
+
return payload
|
|
841
|
+
return result
|
|
842
|
+
|
|
843
|
+
setattr(wrapped, "__hwpx_original_callable__", function)
|
|
844
|
+
return wrapped
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def register_fastmcp_tools(
|
|
848
|
+
mcp: Any,
|
|
849
|
+
namespace: Mapping[str, Any],
|
|
850
|
+
*,
|
|
851
|
+
advanced: bool,
|
|
852
|
+
) -> RegisteredToolRegistry:
|
|
853
|
+
"""Bind the full contract, then register the selected deterministic profile."""
|
|
854
|
+
|
|
855
|
+
global _BOUND_TOOL_REGISTRY
|
|
856
|
+
canonical = bind_tool_specs(namespace, advanced=None)
|
|
857
|
+
_BOUND_TOOL_REGISTRY = canonical
|
|
858
|
+
canonical_by_name = canonical.by_name()
|
|
859
|
+
active = RegisteredToolRegistry(
|
|
860
|
+
tuple(canonical_by_name[spec.name] for spec in active_tool_specs(advanced=advanced))
|
|
861
|
+
)
|
|
862
|
+
for item in active.tools:
|
|
863
|
+
function = (
|
|
864
|
+
_deprecated_wrapper(item)
|
|
865
|
+
if item.spec.classification is ToolClassification.DEPRECATED
|
|
866
|
+
else item.function
|
|
867
|
+
)
|
|
868
|
+
register_canonical_tool(
|
|
869
|
+
mcp,
|
|
870
|
+
name=item.spec.name,
|
|
871
|
+
func=function,
|
|
872
|
+
description=item.description,
|
|
873
|
+
meta={
|
|
874
|
+
"hwpxLifecycle": item.spec.classification.value,
|
|
875
|
+
"hwpxProfile": item.spec.profile.value,
|
|
876
|
+
"hwpxMutates": item.spec.mutates,
|
|
877
|
+
"hwpxReplacementTools": list(item.spec.replacement_tools),
|
|
878
|
+
},
|
|
879
|
+
)
|
|
880
|
+
report = validate_registered_tools(mcp, active)
|
|
881
|
+
if not report["ok"]:
|
|
882
|
+
raise RuntimeError(f"FastMCP registry validation failed: {report!r}")
|
|
883
|
+
return active
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def validate_registered_tools(mcp: Any, registry: RegisteredToolRegistry) -> dict[str, Any]:
|
|
887
|
+
"""Compare live names, callable identity, description, and both schemas."""
|
|
888
|
+
|
|
889
|
+
actual_by_name = snapshot_runtime_tools(mcp)
|
|
890
|
+
expected_by_name = registry.by_name()
|
|
891
|
+
expected_names = registry.names
|
|
892
|
+
actual_names = tuple(actual_by_name)
|
|
893
|
+
order_mismatch = actual_names != expected_names
|
|
894
|
+
callable_mismatches: list[str] = []
|
|
895
|
+
input_schema_mismatches: list[str] = []
|
|
896
|
+
output_schema_mismatches: list[str] = []
|
|
897
|
+
description_mismatches: list[str] = []
|
|
898
|
+
for item in registry.tools:
|
|
899
|
+
actual = actual_by_name.get(item.spec.name)
|
|
900
|
+
if actual is None:
|
|
901
|
+
continue
|
|
902
|
+
if actual.callable is not item.function:
|
|
903
|
+
callable_mismatches.append(item.spec.name)
|
|
904
|
+
if actual.input_schema != item.input_schema:
|
|
905
|
+
input_schema_mismatches.append(item.spec.name)
|
|
906
|
+
if actual.output_schema != item.output_schema:
|
|
907
|
+
output_schema_mismatches.append(item.spec.name)
|
|
908
|
+
if actual.description != item.description:
|
|
909
|
+
description_mismatches.append(item.spec.name)
|
|
910
|
+
missing = [name for name in expected_names if name not in actual_by_name]
|
|
911
|
+
unexpected = [name for name in actual_names if name not in expected_by_name]
|
|
912
|
+
unavailable = [
|
|
913
|
+
item.spec.name
|
|
914
|
+
for item in registry.tools
|
|
915
|
+
if item.availability is not ToolAvailability.AVAILABLE
|
|
916
|
+
]
|
|
917
|
+
return {
|
|
918
|
+
"ok": not any(
|
|
919
|
+
(
|
|
920
|
+
missing,
|
|
921
|
+
unexpected,
|
|
922
|
+
callable_mismatches,
|
|
923
|
+
input_schema_mismatches,
|
|
924
|
+
output_schema_mismatches,
|
|
925
|
+
description_mismatches,
|
|
926
|
+
unavailable,
|
|
927
|
+
order_mismatch,
|
|
928
|
+
)
|
|
929
|
+
),
|
|
930
|
+
"missing": missing,
|
|
931
|
+
"unexpected": unexpected,
|
|
932
|
+
"callableMismatches": callable_mismatches,
|
|
933
|
+
"inputSchemaMismatches": input_schema_mismatches,
|
|
934
|
+
"outputSchemaMismatches": output_schema_mismatches,
|
|
935
|
+
"descriptionMismatches": description_mismatches,
|
|
936
|
+
"unavailable": unavailable,
|
|
937
|
+
"orderMismatch": order_mismatch,
|
|
938
|
+
"expectedOrder": list(expected_names),
|
|
939
|
+
"actualOrder": list(actual_names),
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
async def validate_fastmcp_tools(mcp: Any, registry: RegisteredToolRegistry) -> dict[str, Any]:
|
|
944
|
+
"""Async compatibility wrapper retained for existing diagnostics."""
|
|
945
|
+
|
|
946
|
+
return validate_registered_tools(mcp, registry)
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
__all__ = [
|
|
950
|
+
"AvailabilityReason",
|
|
951
|
+
"AvailabilityReasonCode",
|
|
952
|
+
"AvailabilityRequirement",
|
|
953
|
+
"BASELINE_DOMAIN_SPECS",
|
|
954
|
+
"BASELINE_TOOL_SPECS",
|
|
955
|
+
"BoundToolSpec",
|
|
956
|
+
"DOMAIN_SPECS",
|
|
957
|
+
"MIN_AUTOMATION_VERSION",
|
|
958
|
+
"MIN_MCP_VERSION",
|
|
959
|
+
"MIN_PYTHON_HWPX",
|
|
960
|
+
"MIN_SKILL_VERSION",
|
|
961
|
+
"RegisteredToolRegistry",
|
|
962
|
+
"RELEASED_CONTRACT_HASH",
|
|
963
|
+
"SchemaBinding",
|
|
964
|
+
"TOOL_SPECS",
|
|
965
|
+
"ToolAvailability",
|
|
966
|
+
"ToolClassification",
|
|
967
|
+
"ToolProfile",
|
|
968
|
+
"ToolSpec",
|
|
969
|
+
"active_tool_specs",
|
|
970
|
+
"bind_tool_specs",
|
|
971
|
+
"bound_tool_registry",
|
|
972
|
+
"classification_counts",
|
|
973
|
+
"classification_payload",
|
|
974
|
+
"contract_hash",
|
|
975
|
+
"contract_payload",
|
|
976
|
+
"expected_tool_names",
|
|
977
|
+
"expected_tool_order",
|
|
978
|
+
"register_fastmcp_tools",
|
|
979
|
+
"skill_required_tool_names",
|
|
980
|
+
"validate_fastmcp_tools",
|
|
981
|
+
"validate_registered_tools",
|
|
982
|
+
]
|