unaltraweb 0.5.0 → 0.6.0
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.
- checksums.yaml +4 -4
- data/Makefile +18 -11
- data/README.md +4 -4
- data/_plugins/retained_documents.rb +62 -0
- data/docs/_documentation/en/02-tools.md +3 -3
- data/docs/_documentation/en/32-development.md +1 -1
- data/docs/_documentation/en/40-distribution.md +157 -12
- data/docs/_documentation/en/42-docker-image.md +7 -7
- data/docs/_documentation/en/45-retained-documents.md +138 -0
- data/docs/agents/mcp-contract.md +64 -2
- data/docs/agents/owner-closeout-76.md +205 -0
- data/docs/agents/owner-followup-76.md +144 -0
- data/docs/agents/owner-preparation-2026-09-29.md +254 -0
- data/docs/agents/retained-letter-import.md +231 -0
- data/docs/agents/visual-companions-0.4.0.md +5 -0
- data/lib/unaltraweb.rb +1 -0
- data/requirements.txt +2 -0
- data/scripts/manual/Dockerfile +7 -4
- data/scripts/manual/build_pdf.py +28 -0
- data/scripts/manual/templates/manual.tex +1 -0
- data/scripts/test_gem_build.py +28 -1
- data/scripts/test_wheel_install.py +51 -0
- data/scripts/unaltraweb-mcp-bootstrap.sh +33 -6
- data/scripts/validate_distribution.py +21 -2
- data/src/unaltraweb_mcp/__init__.py +4 -0
- data/src/unaltraweb_mcp/artifact-handoff-v1.schema.json +102 -0
- data/src/unaltraweb_mcp/artifact_handoff_v1.py +435 -0
- data/src/unaltraweb_mcp/artifact_imports.py +368 -0
- data/src/unaltraweb_mcp/bundler_runtime.py +214 -0
- data/src/unaltraweb_mcp/component-contract.json +20 -20
- data/src/unaltraweb_mcp/distribution.py +887 -0
- data/src/unaltraweb_mcp/letter_bundle.py +223 -0
- data/src/unaltraweb_mcp/pdf_probe.py +63 -0
- metadata +16 -2
data/scripts/test_gem_build.py
CHANGED
|
@@ -62,8 +62,11 @@ def build(output: Path, *, source: Path = ROOT) -> str:
|
|
|
62
62
|
|
|
63
63
|
def inspect_gem(path: Path) -> None:
|
|
64
64
|
editorial_files = ["scripts/editorial_check.py", "src/unaltraweb_mcp/editorial.py", "src/unaltraweb_mcp/editorial_sources.py"]
|
|
65
|
-
inspection_files = editorial_files + ["scripts/image_background_check.py", "src/unaltraweb_mcp/image_backgrounds.py",
|
|
65
|
+
inspection_files = editorial_files + ["src/unaltraweb_mcp/bundler_runtime.py", "scripts/image_background_check.py", "src/unaltraweb_mcp/image_backgrounds.py",
|
|
66
66
|
"src/unaltraweb_mcp/image_probe.py", "src/unaltraweb_mcp/processes.py"]
|
|
67
|
+
inspection_files += [f"src/unaltraweb_mcp/{name}" for name in (
|
|
68
|
+
"__init__.py", "distribution.py", "component-contract.json", "component-contract.schema.json",
|
|
69
|
+
"artifact_imports.py", "artifact_handoff_v1.py", "artifact-handoff-v1.schema.json", "letter_bundle.py", "pdf_probe.py")]
|
|
67
70
|
with tarfile.open(path, mode="r") as package:
|
|
68
71
|
data_member = package.extractfile("data.tar.gz")
|
|
69
72
|
if data_member is None:
|
|
@@ -83,6 +86,7 @@ def inspect_gem(path: Path) -> None:
|
|
|
83
86
|
"_plugins/figure_captions.rb",
|
|
84
87
|
"_plugins/manual_release_metadata.rb",
|
|
85
88
|
"_plugins/reproducible_build_time.rb",
|
|
89
|
+
"_plugins/retained_documents.rb",
|
|
86
90
|
"assets/js/content-search-match.js",
|
|
87
91
|
"assets/js/content-search.js",
|
|
88
92
|
"_config.yml",
|
|
@@ -134,6 +138,29 @@ def inspect_gem(path: Path) -> None:
|
|
|
134
138
|
inspection = json.loads(run(image_command, cwd=site).stdout)
|
|
135
139
|
if not inspection["ok"] or inspection["images"][0]["state"] != "transparent" or not inspection["warnings"]:
|
|
136
140
|
raise RuntimeError(f"Gem-native SVG background inspection failed: {inspection}")
|
|
141
|
+
# This is the synthetic domain fixture, not published-Carta acceptance.
|
|
142
|
+
# Only its data crosses into the isolated extracted-gem subprocess.
|
|
143
|
+
sys.path.insert(0, str(ROOT / "test"))
|
|
144
|
+
from test_artifact_imports import fixture
|
|
145
|
+
|
|
146
|
+
bundle_sha, template_sha = fixture(site / "incoming/letter")
|
|
147
|
+
(site / ".gitignore").write_text("tmp/\n", encoding="utf-8")
|
|
148
|
+
run(["git", "init", "--quiet"], cwd=site)
|
|
149
|
+
isolated = """
|
|
150
|
+
import json, sys
|
|
151
|
+
from pathlib import Path
|
|
152
|
+
sys.path.insert(0, sys.argv[1])
|
|
153
|
+
from unaltraweb_mcp import artifact_imports, letter_bundle, artifact_handoff_v1, distribution, processes
|
|
154
|
+
for module in (artifact_imports, letter_bundle, artifact_handoff_v1, distribution, processes):
|
|
155
|
+
assert Path(module.__file__).is_relative_to(Path(sys.argv[1])), module.__file__
|
|
156
|
+
letter_bundle.TEMPLATES['default-letter.latex'] = sys.argv[4]
|
|
157
|
+
project = Path(sys.argv[2])
|
|
158
|
+
result = artifact_imports.import_bundle(project, 'incoming/letter/bundle.json', sys.argv[3],
|
|
159
|
+
'letter', '_pages/en/letter.md', dry_run=False, confirm_import=True)
|
|
160
|
+
print(json.dumps(result))
|
|
161
|
+
raise SystemExit(0 if result['ok'] else 1)
|
|
162
|
+
"""
|
|
163
|
+
run([sys.executable, "-I", "-c", isolated, str(core / "src"), str(site), bundle_sha, template_sha], cwd=site)
|
|
137
164
|
|
|
138
165
|
|
|
139
166
|
def main() -> int:
|
|
@@ -35,6 +35,9 @@ def main() -> int:
|
|
|
35
35
|
source,
|
|
36
36
|
ignore=shutil.ignore_patterns(
|
|
37
37
|
".git",
|
|
38
|
+
".venv",
|
|
39
|
+
".cache",
|
|
40
|
+
".jekyll-cache",
|
|
38
41
|
"build",
|
|
39
42
|
"dist",
|
|
40
43
|
"*.egg-info",
|
|
@@ -58,6 +61,21 @@ def main() -> int:
|
|
|
58
61
|
raise RuntimeError(f"Expected one wheel, found: {wheels}")
|
|
59
62
|
with zipfile.ZipFile(wheels[0]) as archive:
|
|
60
63
|
names = archive.namelist()
|
|
64
|
+
launcher_prefix = f"{wheels[0].name.split('-')[0]}-{wheels[0].name.split('-')[1]}.data/data/share/unaltraweb-launcher/"
|
|
65
|
+
launcher_files = {
|
|
66
|
+
"Makefile": "packaging/launcher/Makefile",
|
|
67
|
+
"mcp-factory.yml": "mcp-factory.yml",
|
|
68
|
+
**{f"scripts/{name}.sh": f"scripts/{name}.sh" for name in (
|
|
69
|
+
"unaltraweb-mcp-bootstrap", "unaltraweb-mcp-project-id",
|
|
70
|
+
"unaltraweb-mcp-cleanup", "unaltraweb-docker-mount",
|
|
71
|
+
)},
|
|
72
|
+
}
|
|
73
|
+
actual_launcher = {name.removeprefix(launcher_prefix) for name in names if name.startswith(launcher_prefix)}
|
|
74
|
+
if actual_launcher != set(launcher_files):
|
|
75
|
+
raise RuntimeError(f"Wheel launcher closure mismatch: {actual_launcher}")
|
|
76
|
+
for installed_path, source_path in launcher_files.items():
|
|
77
|
+
if archive.read(launcher_prefix + installed_path) != (ROOT / source_path).read_bytes():
|
|
78
|
+
raise RuntimeError(f"Wheel launcher asset differs from source: {installed_path}")
|
|
61
79
|
forbidden_roots = {"docs", "scripts", "_layouts", "_includes", "_sass"}
|
|
62
80
|
leaked = []
|
|
63
81
|
for name in names:
|
|
@@ -71,6 +89,12 @@ def main() -> int:
|
|
|
71
89
|
raise RuntimeError(f"Factory assets leaked into wheel: {leaked}")
|
|
72
90
|
required = [
|
|
73
91
|
"unaltraweb_mcp/calibre_import.py",
|
|
92
|
+
"unaltraweb_mcp/bundler_runtime.py",
|
|
93
|
+
"unaltraweb_mcp/artifact_imports.py",
|
|
94
|
+
"unaltraweb_mcp/artifact_handoff_v1.py",
|
|
95
|
+
"unaltraweb_mcp/artifact-handoff-v1.schema.json",
|
|
96
|
+
"unaltraweb_mcp/letter_bundle.py",
|
|
97
|
+
"unaltraweb_mcp/pdf_probe.py",
|
|
74
98
|
"unaltraweb_mcp/component-contract.json",
|
|
75
99
|
"unaltraweb_mcp/component-contract.schema.json",
|
|
76
100
|
"unaltraweb_mcp/manual_pdf_preview.py",
|
|
@@ -105,6 +129,33 @@ def main() -> int:
|
|
|
105
129
|
python = environment / "bin/python"
|
|
106
130
|
cli = environment / "bin/unaltraweb-mcp"
|
|
107
131
|
run([str(python), "-m", "pip", "install", "--no-deps", str(wheels[0])], cwd=temp, env=env)
|
|
132
|
+
docker_cli = environment / "bin/unaltraweb-mcp-docker"
|
|
133
|
+
launcher = Path(run([str(docker_cli), "path"], cwd=temp).stdout.strip())
|
|
134
|
+
if launcher != environment / "share/unaltraweb-launcher":
|
|
135
|
+
raise RuntimeError(f"Launcher did not resolve its installed wheel data: {launcher}")
|
|
136
|
+
if run([str(docker_cli), "manifest"], cwd=temp).stdout != (ROOT / "mcp-factory.yml").read_text(encoding="utf-8"):
|
|
137
|
+
raise RuntimeError("Installed native manifest drifted from discovery contract")
|
|
138
|
+
no_consumer_env = {key: value for key, value in env.items() if key not in {"MCP_CONSUMER_WORKSPACE", "UNALTRAWEB_PROJECT"}}
|
|
139
|
+
refused = run([str(docker_cli), "serve"], cwd=temp, env=no_consumer_env, expected=2)
|
|
140
|
+
if "requires --project" not in refused.stderr:
|
|
141
|
+
raise RuntimeError("Installed launcher accepted an implicit consumer")
|
|
142
|
+
fake_bin = temp / "fake-bin"
|
|
143
|
+
fake_bin.mkdir()
|
|
144
|
+
fake_docker = fake_bin / "docker"
|
|
145
|
+
fake_docker.write_text("#!/bin/sh\n[ \"$1 $2\" = 'image inspect' ] || exit 99\n[ \"$5\" = \"$EXPECTED_IMAGE\" ] || exit 98\nprintf 'sha256:fixture\\n'\n", encoding="utf-8")
|
|
146
|
+
fake_docker.chmod(0o755)
|
|
147
|
+
expected_image = json.loads((ROOT / "src/unaltraweb_mcp/component-contract.json").read_text())["components"]["mcp"]["reference"]
|
|
148
|
+
launcher_env = {**no_consumer_env, "PATH": f"{fake_bin}:{env['PATH']}", "EXPECTED_IMAGE": expected_image}
|
|
149
|
+
launcher_env.pop("UNALTRAWEB_MCP_IMAGE", None)
|
|
150
|
+
launcher_env.pop("MCP_RELEASE_IMAGE", None)
|
|
151
|
+
prepared_image = run([str(docker_cli), "prepare"], cwd=temp, env=launcher_env).stdout.strip()
|
|
152
|
+
if prepared_image != "sha256:fixture":
|
|
153
|
+
raise RuntimeError("Installed preparation did not use the packaged bootstrap")
|
|
154
|
+
run(["make", "--silent", "--no-print-directory", "-C", str(launcher), "mcp-build"], cwd=temp, env=launcher_env)
|
|
155
|
+
run([str(docker_cli), "prepare", "--image", "explicit:override"], cwd=temp,
|
|
156
|
+
env={**launcher_env, "EXPECTED_IMAGE": "explicit:override", "UNALTRAWEB_MCP_IMAGE": "environment:override"})
|
|
157
|
+
run([str(docker_cli), "prepare"], cwd=temp,
|
|
158
|
+
env={**launcher_env, "EXPECTED_IMAGE": "environment:override", "UNALTRAWEB_MCP_IMAGE": "environment:override"})
|
|
108
159
|
version = run([str(cli), "version"], cwd=temp).stdout.strip()
|
|
109
160
|
doctor = json.loads(run([str(cli), "doctor"], cwd=temp).stdout)
|
|
110
161
|
if not doctor["ok"] or doctor["mode"] != "wheel" or not doctor["limited"]:
|
|
@@ -3,15 +3,18 @@ set -eu
|
|
|
3
3
|
|
|
4
4
|
usage() {
|
|
5
5
|
cat <<'USAGE'
|
|
6
|
-
Usage: unaltraweb-mcp-bootstrap [--project PATH] [--image IMAGE]
|
|
6
|
+
Usage: unaltraweb-mcp-bootstrap [--project PATH] [--image IMAGE] [--prepare|--check|--smoke]
|
|
7
7
|
|
|
8
8
|
Start one Dockerized unaltraweb MCP stdio server for the selected workspace.
|
|
9
9
|
If --project is omitted, MCP_CONSUMER_WORKSPACE is used, then the current directory.
|
|
10
|
+
--prepare inspects or pulls the selected image without mounting a consumer.
|
|
11
|
+
--check and --smoke require that image locally; neither builds nor pulls.
|
|
10
12
|
USAGE
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
image="${UNALTRAWEB_MCP_IMAGE:-ghcr.io/dosquartsdedocs/unaltraweb-mcp:
|
|
15
|
+
image="${UNALTRAWEB_MCP_IMAGE:-ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:908b4ce54c7bdf355e14ed55b31ed4b9baae319e211af90004a680d1d1cb8692}"
|
|
14
16
|
project="${MCP_CONSUMER_WORKSPACE:-${UNALTRAWEB_PROJECT:-}}"
|
|
17
|
+
operation=serve
|
|
15
18
|
|
|
16
19
|
while [ "$#" -gt 0 ]; do
|
|
17
20
|
case "$1" in
|
|
@@ -25,6 +28,11 @@ while [ "$#" -gt 0 ]; do
|
|
|
25
28
|
image="$2"
|
|
26
29
|
shift 2
|
|
27
30
|
;;
|
|
31
|
+
--prepare|--check|--smoke)
|
|
32
|
+
[ "$operation" = serve ] || { printf '%s\n' 'Select only one operation.' >&2; exit 2; }
|
|
33
|
+
operation="${1#--}"
|
|
34
|
+
shift
|
|
35
|
+
;;
|
|
28
36
|
-h|--help)
|
|
29
37
|
usage
|
|
30
38
|
exit 0
|
|
@@ -37,6 +45,28 @@ while [ "$#" -gt 0 ]; do
|
|
|
37
45
|
esac
|
|
38
46
|
done
|
|
39
47
|
|
|
48
|
+
resolve_image() {
|
|
49
|
+
if ! resolved_image="$(docker image inspect --format '{{.Id}}' "$image" 2>/dev/null)"; then
|
|
50
|
+
case "$operation" in
|
|
51
|
+
check|smoke)
|
|
52
|
+
printf 'Image is not prepared: %s. Run --prepare first.\n' "$image" >&2
|
|
53
|
+
exit 1
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
docker pull "$image" >&2
|
|
57
|
+
resolved_image="$(docker image inspect --format '{{.Id}}' "$image")"
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if [ "$operation" != serve ]; then
|
|
62
|
+
resolve_image
|
|
63
|
+
case "$operation" in
|
|
64
|
+
prepare) printf '%s\n' "$resolved_image"; exit 0 ;;
|
|
65
|
+
check) exec docker run --rm --network none --user "$(id -u):$(id -g)" -e HOME=/tmp --entrypoint unaltraweb-mcp "$resolved_image" version ;;
|
|
66
|
+
smoke) exec docker run --rm --network none --user "$(id -u):$(id -g)" -e HOME=/tmp --entrypoint python3 "$resolved_image" /opt/unaltraweb/test/mcp_smoke.py ;;
|
|
67
|
+
esac
|
|
68
|
+
fi
|
|
69
|
+
|
|
40
70
|
if [ -z "$project" ]; then
|
|
41
71
|
project="$PWD"
|
|
42
72
|
fi
|
|
@@ -80,10 +110,7 @@ if [ -S "$docker_socket" ]; then
|
|
|
80
110
|
esac
|
|
81
111
|
fi
|
|
82
112
|
|
|
83
|
-
|
|
84
|
-
docker pull "$image" >/dev/null
|
|
85
|
-
resolved_image="$(docker image inspect --format '{{.Id}}' "$image")"
|
|
86
|
-
fi
|
|
113
|
+
resolve_image
|
|
87
114
|
image_reference="$image"
|
|
88
115
|
case "$image_reference" in
|
|
89
116
|
ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:*) ;;
|
|
@@ -75,7 +75,7 @@ def component_version_errors(contract: dict[str, Any]) -> list[str]:
|
|
|
75
75
|
"""Retain the real version of already-published, digest-pinned workers."""
|
|
76
76
|
version = str(contract["release"]["version"])
|
|
77
77
|
errors = []
|
|
78
|
-
reusable_workers = {"compute_python", "compute_r", "web_capture"}
|
|
78
|
+
reusable_workers = {"compute_python", "compute_r", "web_capture", "manual_pdf"}
|
|
79
79
|
for component_id in ["gem", "wheel", "runtime", "mcp", "compute_python", "compute_r", "web_capture", "manual_pdf"]:
|
|
80
80
|
selected = contract["components"][component_id]
|
|
81
81
|
if (component_id in reusable_workers and selected["kind"] == "container"
|
|
@@ -168,6 +168,20 @@ def validate(root: Path = ROOT) -> list[str]:
|
|
|
168
168
|
errors.append("component contract schema does not select version 1")
|
|
169
169
|
|
|
170
170
|
pyproject = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))
|
|
171
|
+
launcher_files = {
|
|
172
|
+
"share/unaltraweb-launcher": ["packaging/launcher/Makefile", "mcp-factory.yml"],
|
|
173
|
+
"share/unaltraweb-launcher/scripts": [
|
|
174
|
+
"scripts/unaltraweb-mcp-bootstrap.sh", "scripts/unaltraweb-mcp-project-id.sh",
|
|
175
|
+
"scripts/unaltraweb-mcp-cleanup.sh", "scripts/unaltraweb-docker-mount.sh",
|
|
176
|
+
],
|
|
177
|
+
}
|
|
178
|
+
if pyproject.get("tool", {}).get("setuptools", {}).get("data-files") != launcher_files:
|
|
179
|
+
errors.append("wheel must distribute exactly the native Docker launcher and its helper closure")
|
|
180
|
+
launcher_image = make_value(root / "Makefile", "MCP_RELEASE_IMAGE")
|
|
181
|
+
if not re.fullmatch(r"ghcr\.io/dosquartsdedocs/unaltraweb-mcp@sha256:[0-9a-f]{64}", launcher_image):
|
|
182
|
+
errors.append("MCP_RELEASE_IMAGE must select a full GHCR MCP digest")
|
|
183
|
+
if make_value(root / "packaging/launcher/Makefile", "MCP_RELEASE_IMAGE") != component_reference("mcp"):
|
|
184
|
+
errors.append("installed launcher must select its own package release image")
|
|
171
185
|
project = pyproject.get("project", {})
|
|
172
186
|
if "version" in project or project.get("dynamic") != ["version"]:
|
|
173
187
|
errors.append("pyproject version must be derived from the package component contract")
|
|
@@ -242,7 +256,7 @@ def validate(root: Path = ROOT) -> list[str]:
|
|
|
242
256
|
pinned_text = {
|
|
243
257
|
"scripts/computations/render.py": [component_reference("compute_python"), component_reference("compute_r")],
|
|
244
258
|
"scripts/web_captures/render.py": [component_reference("web_capture")],
|
|
245
|
-
"scripts/unaltraweb-mcp-bootstrap.sh": [
|
|
259
|
+
"scripts/unaltraweb-mcp-bootstrap.sh": [launcher_image],
|
|
246
260
|
"Dockerfile.mcp": [component_reference("runtime")],
|
|
247
261
|
}
|
|
248
262
|
for relative, references in pinned_text.items():
|
|
@@ -485,6 +499,11 @@ def publish_ref_errors(
|
|
|
485
499
|
if not isinstance(component, dict):
|
|
486
500
|
errors.append(f"unknown publish component: {component_id}")
|
|
487
501
|
continue
|
|
502
|
+
if (component_id == "manual_pdf" and component.get("release_status") == "released"
|
|
503
|
+
and re.fullmatch(r"ghcr\.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:[0-9a-f]{64}", str(component.get("reference", "")))):
|
|
504
|
+
# Already-published, unchanged worker. It is not a new candidate for
|
|
505
|
+
# this coordinated core release and receives no new semver identity.
|
|
506
|
+
continue
|
|
488
507
|
if str(component.get("version") or "") != version:
|
|
489
508
|
errors.append(f"{component_id} publish version does not match distribution release {version}")
|
|
490
509
|
if str(component.get("release") or "") != release_tag:
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "MCP artifact handoff v1 (review candidate)",
|
|
4
|
+
"oneOf": [
|
|
5
|
+
{"$ref": "#/$defs/bundle"},
|
|
6
|
+
{"$ref": "#/$defs/integration"}
|
|
7
|
+
],
|
|
8
|
+
"$defs": {
|
|
9
|
+
"id": {"type": "string", "pattern": "^[a-z][a-z0-9._-]{0,79}$"},
|
|
10
|
+
"hash": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
|
11
|
+
"revision": {"type": "string", "pattern": "^(git:([0-9a-f]{40}|[0-9a-f]{64})|sha256:[0-9a-f]{64})$"},
|
|
12
|
+
"path": {"type": "string", "minLength": 1, "maxLength": 1024},
|
|
13
|
+
"runtime": {
|
|
14
|
+
"type": "object", "additionalProperties": false,
|
|
15
|
+
"required": ["name", "revision"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"name": {"$ref": "#/$defs/id"},
|
|
18
|
+
"revision": {"$ref": "#/$defs/revision"}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"actor": {
|
|
22
|
+
"type": "object", "additionalProperties": false,
|
|
23
|
+
"required": ["name", "version", "revision", "runtimes"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"name": {"$ref": "#/$defs/id"},
|
|
26
|
+
"version": {"type": "string", "minLength": 1, "maxLength": 80, "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*$"},
|
|
27
|
+
"revision": {"$ref": "#/$defs/revision"},
|
|
28
|
+
"runtimes": {"type": "array", "maxItems": 32, "items": {"$ref": "#/$defs/runtime"}}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"dependency": {
|
|
32
|
+
"type": "object", "additionalProperties": false,
|
|
33
|
+
"required": ["id", "sha256"],
|
|
34
|
+
"properties": {
|
|
35
|
+
"id": {"$ref": "#/$defs/id"},
|
|
36
|
+
"sha256": {"$ref": "#/$defs/hash"}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"upstream": {
|
|
40
|
+
"type": "object", "additionalProperties": false,
|
|
41
|
+
"required": ["dependency", "file"],
|
|
42
|
+
"properties": {
|
|
43
|
+
"dependency": {"$ref": "#/$defs/id"},
|
|
44
|
+
"file": {"$ref": "#/$defs/id"}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"file": {
|
|
48
|
+
"type": "object", "additionalProperties": false,
|
|
49
|
+
"required": ["id", "path", "kind", "role", "ownership", "sha256", "bytes"],
|
|
50
|
+
"properties": {
|
|
51
|
+
"id": {"$ref": "#/$defs/id"},
|
|
52
|
+
"path": {"$ref": "#/$defs/path"},
|
|
53
|
+
"kind": {"enum": ["input", "output", "evidence"]},
|
|
54
|
+
"role": {"$ref": "#/$defs/id"},
|
|
55
|
+
"ownership": {"enum": ["author", "producer"]},
|
|
56
|
+
"sha256": {"$ref": "#/$defs/hash"},
|
|
57
|
+
"bytes": {"type": "integer", "minimum": 0, "maximum": 536870912},
|
|
58
|
+
"variant_of": {"$ref": "#/$defs/id"},
|
|
59
|
+
"from": {"$ref": "#/$defs/upstream"}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"bundle": {
|
|
63
|
+
"type": "object", "additionalProperties": false,
|
|
64
|
+
"required": ["schema_version", "kind", "producer", "request", "files", "dependencies"],
|
|
65
|
+
"properties": {
|
|
66
|
+
"schema_version": {"type": "integer", "const": 1},
|
|
67
|
+
"kind": {"const": "mcp-artifact-bundle"},
|
|
68
|
+
"producer": {"$ref": "#/$defs/actor"},
|
|
69
|
+
"request": {"$ref": "#/$defs/id"},
|
|
70
|
+
"files": {"type": "array", "minItems": 2, "maxItems": 10000, "items": {"$ref": "#/$defs/file"}},
|
|
71
|
+
"dependencies": {"type": "array", "maxItems": 63, "items": {"$ref": "#/$defs/dependency"}}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"retained_bundle": {
|
|
75
|
+
"type": "object", "additionalProperties": false,
|
|
76
|
+
"required": ["path", "sha256"],
|
|
77
|
+
"properties": {
|
|
78
|
+
"path": {"$ref": "#/$defs/path"},
|
|
79
|
+
"sha256": {"$ref": "#/$defs/hash"}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"mapping": {
|
|
83
|
+
"type": "object", "additionalProperties": false,
|
|
84
|
+
"required": ["source", "destination"],
|
|
85
|
+
"properties": {
|
|
86
|
+
"source": {"$ref": "#/$defs/path"},
|
|
87
|
+
"destination": {"$ref": "#/$defs/path"}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"integration": {
|
|
91
|
+
"type": "object", "additionalProperties": false,
|
|
92
|
+
"required": ["schema_version", "kind", "integrator", "bundle", "mappings"],
|
|
93
|
+
"properties": {
|
|
94
|
+
"schema_version": {"type": "integer", "const": 1},
|
|
95
|
+
"kind": {"const": "mcp-artifact-integration"},
|
|
96
|
+
"integrator": {"$ref": "#/$defs/actor"},
|
|
97
|
+
"bundle": {"$ref": "#/$defs/retained_bundle"},
|
|
98
|
+
"mappings": {"type": "array", "minItems": 1, "maxItems": 10000, "items": {"$ref": "#/$defs/mapping"}}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|