Bind generated clients to product version
This commit is contained in:
parent
47ac329947
commit
dc9500e50e
5 changed files with 40 additions and 0 deletions
|
|
@ -173,6 +173,7 @@
|
||||||
"required": [
|
"required": [
|
||||||
"status",
|
"status",
|
||||||
"schema_version",
|
"schema_version",
|
||||||
|
"docforge_version",
|
||||||
"operation",
|
"operation",
|
||||||
"action",
|
"action",
|
||||||
"client",
|
"client",
|
||||||
|
|
@ -194,6 +195,11 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"status": { "const": "ok" },
|
"status": { "const": "ok" },
|
||||||
"schema_version": { "const": 1 },
|
"schema_version": { "const": 1 },
|
||||||
|
"docforge_version": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 128
|
||||||
|
},
|
||||||
"operation": { "const": "adapter_client.configure" },
|
"operation": { "const": "adapter_client.configure" },
|
||||||
"action": { "enum": ["preview", "write"] },
|
"action": { "enum": ["preview", "write"] },
|
||||||
"client": { "enum": ["codex", "claude", "openclaw"] },
|
"client": { "enum": ["codex", "claude", "openclaw"] },
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,7 @@
|
||||||
"required": [
|
"required": [
|
||||||
"status",
|
"status",
|
||||||
"schema_version",
|
"schema_version",
|
||||||
|
"docforge_version",
|
||||||
"operation",
|
"operation",
|
||||||
"action",
|
"action",
|
||||||
"client",
|
"client",
|
||||||
|
|
@ -232,6 +233,11 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"status": { "const": "ok" },
|
"status": { "const": "ok" },
|
||||||
"schema_version": { "const": 1 },
|
"schema_version": { "const": 1 },
|
||||||
|
"docforge_version": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 128
|
||||||
|
},
|
||||||
"operation": { "const": "client.configure" },
|
"operation": { "const": "client.configure" },
|
||||||
"action": { "enum": ["preview", "write"] },
|
"action": { "enum": ["preview", "write"] },
|
||||||
"client": { "enum": ["codex", "claude", "openclaw"] },
|
"client": { "enum": ["codex", "claude", "openclaw"] },
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Literal, cast
|
from typing import Literal, cast
|
||||||
|
|
||||||
|
from ._version import __version__ as DOCFORGE_VERSION
|
||||||
from .adapter_launcher import (
|
from .adapter_launcher import (
|
||||||
AdapterLauncherV1,
|
AdapterLauncherV1,
|
||||||
AdapterSourceAvailabilityV1,
|
AdapterSourceAvailabilityV1,
|
||||||
|
|
@ -617,6 +618,8 @@ def _validate_configuration_result(
|
||||||
*,
|
*,
|
||||||
trusted_descriptor: ProjectDescriptor | None = None,
|
trusted_descriptor: ProjectDescriptor | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if result.get("docforge_version") != DOCFORGE_VERSION:
|
||||||
|
raise AssertionError("Generated client product version drifted")
|
||||||
artifact = cast(dict[str, object], result["artifact"])
|
artifact = cast(dict[str, object], result["artifact"])
|
||||||
binding = cast(dict[str, object], result["binding"])
|
binding = cast(dict[str, object], result["binding"])
|
||||||
policy = cast(dict[str, object], result["effective_policy"])
|
policy = cast(dict[str, object], result["effective_policy"])
|
||||||
|
|
@ -797,6 +800,7 @@ def _validate_configuration_result(
|
||||||
expected_hash = document_hash(
|
expected_hash = document_hash(
|
||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
|
"docforge_version": result["docforge_version"],
|
||||||
"client": result["client"],
|
"client": result["client"],
|
||||||
"server_name": result["server_name"],
|
"server_name": result["server_name"],
|
||||||
"project": project,
|
"project": project,
|
||||||
|
|
@ -1041,6 +1045,7 @@ def generate_client_configuration(
|
||||||
plan_hash = document_hash(
|
plan_hash = document_hash(
|
||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
|
"docforge_version": DOCFORGE_VERSION,
|
||||||
"client": selected_client,
|
"client": selected_client,
|
||||||
"server_name": selected_name,
|
"server_name": selected_name,
|
||||||
"project": project_binding,
|
"project": project_binding,
|
||||||
|
|
@ -1061,6 +1066,7 @@ def generate_client_configuration(
|
||||||
result: dict[str, object] = {
|
result: dict[str, object] = {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
|
"docforge_version": DOCFORGE_VERSION,
|
||||||
"operation": "client.configure",
|
"operation": "client.configure",
|
||||||
"action": "write" if output is not None else "preview",
|
"action": "write" if output is not None else "preview",
|
||||||
"client": selected_client,
|
"client": selected_client,
|
||||||
|
|
@ -1278,6 +1284,7 @@ def generate_adapter_client_configuration(
|
||||||
result: dict[str, object] = {
|
result: dict[str, object] = {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
|
"docforge_version": DOCFORGE_VERSION,
|
||||||
"operation": "adapter_client.configure",
|
"operation": "adapter_client.configure",
|
||||||
"action": "write" if output is not None else "preview",
|
"action": "write" if output is not None else "preview",
|
||||||
"client": selected_client,
|
"client": selected_client,
|
||||||
|
|
@ -1351,6 +1358,7 @@ def _adapter_configuration_hash_payload(result: dict[str, object]) -> dict[str,
|
||||||
artifact = cast(dict[str, object], result["artifact"])
|
artifact = cast(dict[str, object], result["artifact"])
|
||||||
return {
|
return {
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
|
"docforge_version": result["docforge_version"],
|
||||||
"client": result["client"],
|
"client": result["client"],
|
||||||
"server_name": result["server_name"],
|
"server_name": result["server_name"],
|
||||||
"project": result["project"],
|
"project": result["project"],
|
||||||
|
|
@ -1375,6 +1383,8 @@ def _validate_adapter_configuration_result(
|
||||||
launcher: AdapterLauncherV1,
|
launcher: AdapterLauncherV1,
|
||||||
source_availability: AdapterSourceAvailabilityV1,
|
source_availability: AdapterSourceAvailabilityV1,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if result.get("docforge_version") != DOCFORGE_VERSION:
|
||||||
|
raise AssertionError("Generated adapter client product version drifted")
|
||||||
validate_adapter_launcher(project, launcher)
|
validate_adapter_launcher(project, launcher)
|
||||||
descriptor = project.descriptor
|
descriptor = project.descriptor
|
||||||
project_binding = cast(dict[str, object], result["project"])
|
project_binding = cast(dict[str, object], result["project"])
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ from mcp import ClientSession, StdioServerParameters
|
||||||
from mcp.client.stdio import stdio_client
|
from mcp.client.stdio import stdio_client
|
||||||
from referencing import Registry, Resource
|
from referencing import Registry, Resource
|
||||||
|
|
||||||
|
from docforge._version import __version__
|
||||||
from docforge.adapter_contract import AdapterNode, AdapterProject, AdapterProjection
|
from docforge.adapter_contract import AdapterNode, AdapterProject, AdapterProjection
|
||||||
from docforge.adapter_launcher import AdapterLauncherV1
|
from docforge.adapter_launcher import AdapterLauncherV1
|
||||||
from docforge.client_config import (
|
from docforge.client_config import (
|
||||||
|
|
@ -188,6 +189,7 @@ class AdapterLauncherTests(unittest.TestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(first, second)
|
self.assertEqual(first, second)
|
||||||
ADAPTER_CONFIGURATION_VALIDATOR.validate(first)
|
ADAPTER_CONFIGURATION_VALIDATOR.validate(first)
|
||||||
|
self.assertEqual(__version__, first["docforge_version"])
|
||||||
self.assertEqual(launcher.launcher_hash, first["launcher_hash"])
|
self.assertEqual(launcher.launcher_hash, first["launcher_hash"])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
launcher.launcher_hash,
|
launcher.launcher_hash,
|
||||||
|
|
@ -265,6 +267,15 @@ class AdapterLauncherTests(unittest.TestCase):
|
||||||
AdapterLauncherV1(**payload) # type: ignore[arg-type]
|
AdapterLauncherV1(**payload) # type: ignore[arg-type]
|
||||||
|
|
||||||
result = generate_adapter_client_configuration(project, launcher, "codex")
|
result = generate_adapter_client_configuration(project, launcher, "codex")
|
||||||
|
version_drift = json.loads(json.dumps(result))
|
||||||
|
version_drift["docforge_version"] = "0.0.0"
|
||||||
|
with self.assertRaisesRegex(AssertionError, "product version"):
|
||||||
|
_validate_adapter_configuration_result(
|
||||||
|
version_drift,
|
||||||
|
project=project,
|
||||||
|
launcher=launcher,
|
||||||
|
source_availability=project_source_availability(version_drift),
|
||||||
|
)
|
||||||
result["binding"]["args"].append("--arbitrary")
|
result["binding"]["args"].append("--arbitrary")
|
||||||
with self.assertRaisesRegex(AssertionError, "Generated adapter client"):
|
with self.assertRaisesRegex(AssertionError, "Generated adapter client"):
|
||||||
_validate_adapter_configuration_result(
|
_validate_adapter_configuration_result(
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ from jsonschema import Draft202012Validator
|
||||||
from mcp import ClientSession, StdioServerParameters
|
from mcp import ClientSession, StdioServerParameters
|
||||||
from mcp.client.stdio import stdio_client
|
from mcp.client.stdio import stdio_client
|
||||||
|
|
||||||
|
from docforge._version import __version__
|
||||||
from docforge.changeset_contract import document_hash
|
from docforge.changeset_contract import document_hash
|
||||||
from docforge.cli import _parser, _run, main
|
from docforge.cli import _parser, _run, main
|
||||||
from docforge.client_config import (
|
from docforge.client_config import (
|
||||||
|
|
@ -103,6 +104,7 @@ class ClientIntegrationTests(unittest.TestCase):
|
||||||
second = generate_client_configuration(project, client, no_ast=True)
|
second = generate_client_configuration(project, client, no_ast=True)
|
||||||
self.assertEqual(first, second)
|
self.assertEqual(first, second)
|
||||||
Draft202012Validator(CONFIGURATION_SCHEMA).validate(first)
|
Draft202012Validator(CONFIGURATION_SCHEMA).validate(first)
|
||||||
|
self.assertEqual(__version__, first["docforge_version"])
|
||||||
Draft202012Validator(POLICY_SCHEMA).validate(first["effective_policy"])
|
Draft202012Validator(POLICY_SCHEMA).validate(first["effective_policy"])
|
||||||
self.assertEqual("read", first["binding"]["capability_mode"])
|
self.assertEqual("read", first["binding"]["capability_mode"])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
@ -251,6 +253,11 @@ class ClientIntegrationTests(unittest.TestCase):
|
||||||
validator.validate(result)
|
validator.validate(result)
|
||||||
_validate_configuration_result(result)
|
_validate_configuration_result(result)
|
||||||
|
|
||||||
|
version_drift = json.loads(json.dumps(result))
|
||||||
|
version_drift["docforge_version"] = "0.0.0"
|
||||||
|
with self.assertRaisesRegex(AssertionError, "product version"):
|
||||||
|
_validate_configuration_result(version_drift)
|
||||||
|
|
||||||
for field, value in (
|
for field, value in (
|
||||||
("schema_version", 1),
|
("schema_version", 1),
|
||||||
("manual", "on-demand"),
|
("manual", "on-demand"),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue