unaltraweb 0.5.1 → 0.7.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 +13 -11
- data/_plugins/retained_documents.rb +62 -0
- data/docs/_documentation/en/40-distribution.md +73 -19
- data/docs/_documentation/en/45-retained-documents.md +138 -0
- data/docs/_documentation/en/46-live-runtime.md +89 -0
- data/docs/agents/d0-runtime-identity.md +200 -0
- data/docs/agents/mcp-contract.md +45 -2
- data/docs/agents/owner-closeout-76.md +205 -0
- data/docs/agents/owner-closeout-80.md +185 -0
- data/docs/agents/retained-letter-import.md +235 -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 +27 -0
- data/scripts/test_wheel_install.py +7 -2
- data/scripts/unaltraweb-mcp-bootstrap.sh +90 -9
- data/scripts/unaltraweb-mcp-cleanup.sh +41 -9
- data/scripts/web_captures/render.py +8 -1
- 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/component-contract.json +27 -27
- 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
|
@@ -3,18 +3,28 @@ set -eu
|
|
|
3
3
|
|
|
4
4
|
usage() {
|
|
5
5
|
cat <<'USAGE'
|
|
6
|
-
Usage: unaltraweb-mcp-bootstrap [--project PATH] [--
|
|
6
|
+
Usage: unaltraweb-mcp-bootstrap [--project PATH] [--host-project PATH] [--image IMAGE]
|
|
7
|
+
[--expected-image-id ID] [--managed] [--offline] [--session-id ID] [--worker-images JSON]
|
|
8
|
+
[--prepare|--check|--smoke]
|
|
7
9
|
|
|
8
10
|
Start one Dockerized unaltraweb MCP stdio server for the selected workspace.
|
|
9
11
|
If --project is omitted, MCP_CONSUMER_WORKSPACE is used, then the current directory.
|
|
10
12
|
--prepare inspects or pulls the selected image without mounting a consumer.
|
|
11
|
-
--check and --smoke require that image locally;
|
|
13
|
+
Launch, --check and --smoke require that exact image locally; none builds or pulls.
|
|
14
|
+
Managed mode requires an immutable reference or an expected full image ID.
|
|
12
15
|
USAGE
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
image="${UNALTRAWEB_MCP_IMAGE:-ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:
|
|
18
|
+
image="${UNALTRAWEB_MCP_IMAGE:-ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:736c4ddd0a543454e3edaeac2e9cfd97a1279ce472923ac54e326c1281b2ba05}"
|
|
16
19
|
project="${MCP_CONSUMER_WORKSPACE:-${UNALTRAWEB_PROJECT:-}}"
|
|
17
20
|
operation=serve
|
|
21
|
+
expected_image="${UNALTRAWEB_EXPECTED_IMAGE_ID:-}"
|
|
22
|
+
managed="${UNALTRAWEB_MANAGED_RUNTIME:-0}"
|
|
23
|
+
session_id=""
|
|
24
|
+
host_project=""
|
|
25
|
+
network=bridge
|
|
26
|
+
worker_images="${UNALTRAWEB_WORKER_IMAGES:-}"
|
|
27
|
+
[ -n "$worker_images" ] || worker_images='{}'
|
|
18
28
|
|
|
19
29
|
while [ "$#" -gt 0 ]; do
|
|
20
30
|
case "$1" in
|
|
@@ -28,6 +38,18 @@ while [ "$#" -gt 0 ]; do
|
|
|
28
38
|
image="$2"
|
|
29
39
|
shift 2
|
|
30
40
|
;;
|
|
41
|
+
--host-project|--expected-image-id|--session-id|--worker-images)
|
|
42
|
+
[ "$#" -ge 2 ] || { printf 'Missing value for %s\n' "$1" >&2; exit 2; }
|
|
43
|
+
case "$1" in
|
|
44
|
+
--host-project) host_project=$2 ;;
|
|
45
|
+
--expected-image-id) expected_image=$2 ;;
|
|
46
|
+
--session-id) session_id=$2 ;;
|
|
47
|
+
--worker-images) worker_images=$2 ;;
|
|
48
|
+
esac
|
|
49
|
+
shift 2
|
|
50
|
+
;;
|
|
51
|
+
--managed) managed=1; shift ;;
|
|
52
|
+
--offline) network=none; shift ;;
|
|
31
53
|
--prepare|--check|--smoke)
|
|
32
54
|
[ "$operation" = serve ] || { printf '%s\n' 'Select only one operation.' >&2; exit 2; }
|
|
33
55
|
operation="${1#--}"
|
|
@@ -45,10 +67,33 @@ while [ "$#" -gt 0 ]; do
|
|
|
45
67
|
esac
|
|
46
68
|
done
|
|
47
69
|
|
|
70
|
+
valid_image_id() {
|
|
71
|
+
case "$1" in sha256:*) hex=${1#sha256:} ;; *) return 1 ;; esac
|
|
72
|
+
[ "${#hex}" -eq 64 ] || return 1
|
|
73
|
+
case "$hex" in *[!0-9a-f]*) return 1 ;; esac
|
|
74
|
+
}
|
|
75
|
+
case "$image" in ''|*[!A-Za-z0-9._/:@-]*) printf '%s\n' 'Invalid image reference' >&2; exit 2 ;; esac
|
|
76
|
+
case "$image" in *://*) printf '%s\n' 'Docker references are not URLs' >&2; exit 2 ;; esac
|
|
77
|
+
case "$image" in
|
|
78
|
+
*@*)
|
|
79
|
+
valid_image_id "${image##*@}" || { printf '%s\n' 'Invalid digest reference' >&2; exit 2; }
|
|
80
|
+
case "${image%@*}" in *@*) printf '%s\n' 'Invalid digest reference' >&2; exit 2 ;; esac
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
if [ -n "$expected_image" ]; then
|
|
84
|
+
valid_image_id "$expected_image" || { printf '%s\n' 'Expected image ID must be a full lowercase sha256' >&2; exit 2; }
|
|
85
|
+
managed=1
|
|
86
|
+
fi
|
|
87
|
+
case "$image" in
|
|
88
|
+
sha256:*) valid_image_id "$image" || exit 2; managed=1 ;;
|
|
89
|
+
*@sha256:*) valid_image_id "sha256:${image##*@sha256:}" || exit 2; managed=1 ;;
|
|
90
|
+
*) if [ "$managed" = 1 ] && [ -z "$expected_image" ]; then printf '%s\n' 'Managed mutable references require --expected-image-id' >&2; exit 2; fi ;;
|
|
91
|
+
esac
|
|
92
|
+
|
|
48
93
|
resolve_image() {
|
|
49
94
|
if ! resolved_image="$(docker image inspect --format '{{.Id}}' "$image" 2>/dev/null)"; then
|
|
50
95
|
case "$operation" in
|
|
51
|
-
check|smoke)
|
|
96
|
+
serve|check|smoke)
|
|
52
97
|
printf 'Image is not prepared: %s. Run --prepare first.\n' "$image" >&2
|
|
53
98
|
exit 1
|
|
54
99
|
;;
|
|
@@ -56,14 +101,19 @@ resolve_image() {
|
|
|
56
101
|
docker pull "$image" >&2
|
|
57
102
|
resolved_image="$(docker image inspect --format '{{.Id}}' "$image")"
|
|
58
103
|
fi
|
|
104
|
+
valid_image_id "$resolved_image" || { printf '%s\n' 'Docker returned an invalid image identity' >&2; exit 2; }
|
|
105
|
+
case "$image" in sha256:*) [ "$image" = "$resolved_image" ] || { printf '%s\n' 'Selected image ID mismatch' >&2; exit 1; } ;; esac
|
|
106
|
+
if [ -n "$expected_image" ] && [ "$expected_image" != "$resolved_image" ]; then
|
|
107
|
+
printf '%s\n' 'Prepared image does not match the expected image ID; no fallback was attempted' >&2; exit 1
|
|
108
|
+
fi
|
|
59
109
|
}
|
|
60
110
|
|
|
61
111
|
if [ "$operation" != serve ]; then
|
|
62
112
|
resolve_image
|
|
63
113
|
case "$operation" in
|
|
64
114
|
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 ;;
|
|
115
|
+
check) exec docker run --rm --pull never --network none --user "$(id -u):$(id -g)" -e HOME=/tmp --entrypoint unaltraweb-mcp "$resolved_image" version ;;
|
|
116
|
+
smoke) exec docker run --rm --pull never --network none --user "$(id -u):$(id -g)" -e HOME=/tmp --entrypoint python3 "$resolved_image" /opt/unaltraweb/test/mcp_smoke.py ;;
|
|
67
117
|
esac
|
|
68
118
|
fi
|
|
69
119
|
|
|
@@ -85,10 +135,28 @@ if [ ! -d "$project" ]; then
|
|
|
85
135
|
fi
|
|
86
136
|
|
|
87
137
|
project="$(CDPATH= cd -- "$project" && pwd -P)"
|
|
138
|
+
launcher_project="$project"
|
|
139
|
+
if [ -z "$host_project" ]; then
|
|
140
|
+
host_project="${UNALTRAWEB_DOCKER_ROOT:-}"
|
|
141
|
+
if [ -n "$host_project" ]; then
|
|
142
|
+
inherited_workspace="${MCP_CONSUMER_WORKSPACE:-}"
|
|
143
|
+
if [ -z "$inherited_workspace" ] || [ ! -d "$inherited_workspace" ] || [ "$(CDPATH= cd -- "$inherited_workspace" && pwd -P)" != "$launcher_project" ]; then
|
|
144
|
+
printf '%s\n' 'Inherited daemon mapping belongs to another or unknown workspace; pass --host-project explicitly' >&2; exit 2
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
fi
|
|
148
|
+
if [ -z "$host_project" ] && [ -f /.dockerenv ]; then
|
|
149
|
+
printf '%s\n' 'A containerized launcher needs --host-project with the explicit daemon-host mapping' >&2; exit 2
|
|
150
|
+
fi
|
|
151
|
+
host_project="${host_project:-$project}"
|
|
152
|
+
case "$host_project" in /*) ;; *) printf '%s\n' 'Host project must be absolute' >&2; exit 2 ;; esac
|
|
153
|
+
case "$host_project" in *"$newline"*|*"$carriage_return"*) exit 2 ;; esac
|
|
154
|
+
[ "$host_project" != / ] && [ "$(realpath -ms -- "$host_project")" = "$host_project" ] || { printf '%s\n' 'Host project must be normalized' >&2; exit 2; }
|
|
155
|
+
project="$host_project"
|
|
88
156
|
owner="${UNALTRAWEB_PROJECT_USER:-$(id -u):$(id -g)}"
|
|
89
157
|
docker_socket="${UNALTRAWEB_DOCKER_SOCKET:-/var/run/docker.sock}"
|
|
90
158
|
script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)"
|
|
91
|
-
project_id="$(
|
|
159
|
+
project_id="$(printf '%s' "$project" | sha256sum | cut -c 1-16)"
|
|
92
160
|
workspace_mount="$(/bin/sh "$script_dir/unaltraweb-docker-mount.sh" "$project" /workspace)"
|
|
93
161
|
mirror_mount="$(/bin/sh "$script_dir/unaltraweb-docker-mount.sh" "$project" "$project")"
|
|
94
162
|
|
|
@@ -103,7 +171,7 @@ if [ -S "$docker_socket" ]; then
|
|
|
103
171
|
exit 1
|
|
104
172
|
fi
|
|
105
173
|
case "$docker_socket" in
|
|
106
|
-
"$project"|"$project"/*)
|
|
174
|
+
"$project"|"$project"/*|"$launcher_project"|"$launcher_project"/*)
|
|
107
175
|
printf '%s\n' 'UNALTRAWEB_DOCKER_SOCKET must be outside the consumer project so the persistent preview cannot inherit it.' >&2
|
|
108
176
|
exit 1
|
|
109
177
|
;;
|
|
@@ -111,6 +179,9 @@ if [ -S "$docker_socket" ]; then
|
|
|
111
179
|
fi
|
|
112
180
|
|
|
113
181
|
resolve_image
|
|
182
|
+
if [ -z "$session_id" ]; then session_id=$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n'); fi
|
|
183
|
+
[ "${#session_id}" -eq 32 ] || { printf '%s\n' 'Session ID must contain 32 lowercase hex characters' >&2; exit 2; }
|
|
184
|
+
case "$session_id" in *[!0-9a-f]*) exit 2 ;; esac
|
|
114
185
|
image_reference="$image"
|
|
115
186
|
case "$image_reference" in
|
|
116
187
|
ghcr.io/dosquartsdedocs/unaltraweb-mcp@sha256:*) ;;
|
|
@@ -123,10 +194,13 @@ case "$image_reference" in
|
|
|
123
194
|
;;
|
|
124
195
|
esac
|
|
125
196
|
|
|
126
|
-
set -- docker run --rm -i \
|
|
197
|
+
set -- docker run --rm --pull never --init -i \
|
|
198
|
+
--network "$network" \
|
|
199
|
+
--name "unaltraweb-stdio-$session_id" --cpus 2 --memory 4g --pids-limit 512 \
|
|
127
200
|
--label io.context.mcp-factory=unaltraweb \
|
|
128
201
|
--label io.context.mcp-role=stdio \
|
|
129
202
|
--label "io.context.mcp-project=$project_id" \
|
|
203
|
+
--label "io.context.mcp-session=$session_id" \
|
|
130
204
|
--user "$owner" \
|
|
131
205
|
-e HOME=/tmp \
|
|
132
206
|
-e COMPUTE_CORE=/opt/unaltraweb \
|
|
@@ -136,6 +210,13 @@ set -- docker run --rm -i \
|
|
|
136
210
|
-e UNALTRAWEB_FACTORY_DIR=/opt/unaltraweb \
|
|
137
211
|
-e "UNALTRAWEB_MCP_IMAGE=$resolved_image" \
|
|
138
212
|
-e "UNALTRAWEB_MCP_IMAGE_REFERENCE=$image_reference" \
|
|
213
|
+
-e "UNALTRAWEB_MCP_REQUESTED_IMAGE=$image" \
|
|
214
|
+
-e "UNALTRAWEB_EXPECTED_IMAGE_ID=$resolved_image" \
|
|
215
|
+
-e "UNALTRAWEB_RUNTIME_SESSION=$session_id" \
|
|
216
|
+
-e "UNALTRAWEB_MANAGED_RUNTIME=$managed" \
|
|
217
|
+
-e "UNALTRAWEB_RUNTIME_NETWORK=$network" \
|
|
218
|
+
-e "UNALTRAWEB_LAUNCHER_PROJECT=$launcher_project" \
|
|
219
|
+
-e "UNALTRAWEB_WORKER_IMAGES=$worker_images" \
|
|
139
220
|
-e "UNALTRAWEB_PROJECT_USER=$owner" \
|
|
140
221
|
--mount "$workspace_mount" \
|
|
141
222
|
--mount "$mirror_mount" \
|
|
@@ -58,16 +58,48 @@ case "$project_id" in
|
|
|
58
58
|
;;
|
|
59
59
|
esac
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
# Explicit workspace-wide shutdown. Managed per-client detach uses live drain,
|
|
62
|
+
# stdio EOF and the separate session-status/reap-session native adapter instead.
|
|
63
|
+
containers=$(docker ps -aq --no-trunc \
|
|
62
64
|
--filter "label=io.context.mcp-factory=unaltraweb" \
|
|
63
65
|
--filter "label=io.context.mcp-project=$project_id")
|
|
64
|
-
|
|
65
|
-
docker rm -f $containers >/dev/null
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
networks=$(docker network ls -q \
|
|
66
|
+
networks=$(docker network ls -q --no-trunc \
|
|
69
67
|
--filter "label=io.context.mcp-factory=unaltraweb" \
|
|
70
68
|
--filter "label=io.context.mcp-project=$project_id")
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
|
|
70
|
+
valid_id() {
|
|
71
|
+
[ "${#1}" -eq 64 ] || return 1
|
|
72
|
+
case "$1" in *[!0-9a-f]*) return 1 ;; esac
|
|
73
|
+
}
|
|
74
|
+
count=0
|
|
75
|
+
for cid in $containers; do
|
|
76
|
+
count=$((count + 1))
|
|
77
|
+
[ "$count" -le 64 ] && valid_id "$cid" || { printf '%s\n' 'Container inventory is invalid or exceeds bounds' >&2; exit 2; }
|
|
78
|
+
labels=$(docker container inspect --format '{{index .Config.Labels "io.context.mcp-factory"}}|{{index .Config.Labels "io.context.mcp-role"}}|{{index .Config.Labels "io.context.mcp-project"}}' "$cid")
|
|
79
|
+
case "$labels" in
|
|
80
|
+
"unaltraweb|stdio|$project_id"|"unaltraweb|preview|$project_id"|"unaltraweb|computation|$project_id"|"unaltraweb|manual-pdf|$project_id"|"unaltraweb|web-capture|$project_id"|"unaltraweb|web-capture-site|$project_id") ;;
|
|
81
|
+
*) printf '%s\n' 'Refusing unknown container ownership' >&2; exit 2 ;;
|
|
82
|
+
esac
|
|
83
|
+
done
|
|
84
|
+
for nid in $networks; do
|
|
85
|
+
count=$((count + 1))
|
|
86
|
+
[ "$count" -le 64 ] && valid_id "$nid" || { printf '%s\n' 'Network inventory is invalid or exceeds bounds' >&2; exit 2; }
|
|
87
|
+
labels=$(docker network inspect --format '{{index .Labels "io.context.mcp-factory"}}|{{index .Labels "io.context.mcp-role"}}|{{index .Labels "io.context.mcp-project"}}' "$nid")
|
|
88
|
+
[ "$labels" = "unaltraweb|web-capture|$project_id" ] || { printf '%s\n' 'Refusing unknown network ownership' >&2; exit 2; }
|
|
89
|
+
done
|
|
90
|
+
if [ -n "$containers" ]; then docker rm -f $containers >/dev/null; fi
|
|
91
|
+
if [ -n "$networks" ]; then docker network rm $networks >/dev/null; fi
|
|
92
|
+
for cid in $containers; do
|
|
93
|
+
if observed=$(docker container inspect --format '{{.Id}}' "$cid" 2>&1); then
|
|
94
|
+
printf '%s\n' 'Container termination is not confirmed' >&2; exit 1
|
|
95
|
+
else
|
|
96
|
+
case "$observed" in *"No such container"*|*"no such container"*|*"No such object"*) ;; *) printf '%s\n' 'Container absence is unknown' >&2; exit 1 ;; esac
|
|
97
|
+
fi
|
|
98
|
+
done
|
|
99
|
+
for nid in $networks; do
|
|
100
|
+
if observed=$(docker network inspect --format '{{.Id}}' "$nid" 2>&1); then
|
|
101
|
+
printf '%s\n' 'Network removal is not confirmed' >&2; exit 1
|
|
102
|
+
else
|
|
103
|
+
case "$observed" in *"No such network"*|*"no such network"*|*"No such object"*) ;; *) printf '%s\n' 'Network absence is unknown' >&2; exit 1 ;; esac
|
|
104
|
+
fi
|
|
105
|
+
done
|
|
@@ -588,6 +588,8 @@ def internal_network(network: str) -> bool:
|
|
|
588
588
|
|
|
589
589
|
def build_image(image: str) -> None:
|
|
590
590
|
available = inspect_image(image)["available"] == "true"
|
|
591
|
+
if not available and os.environ.get("UNALTRAWEB_MANAGED_RUNTIME") == "1":
|
|
592
|
+
raise WebCaptureError("Selected capture image is not prepared; managed execution never pulls")
|
|
591
593
|
if available:
|
|
592
594
|
return
|
|
593
595
|
completed = subprocess.run(["docker", "pull", image], check=False)
|
|
@@ -722,6 +724,9 @@ def run_worker(project: Path, image: str, config_path: Path) -> None:
|
|
|
722
724
|
"project": os.environ.get("UNALTRAWEB_WORKER_PROJECT", "").strip(),
|
|
723
725
|
"token": os.environ.get("UNALTRAWEB_WORKER_TOKEN", "").strip(),
|
|
724
726
|
}
|
|
727
|
+
session = os.environ.get("UNALTRAWEB_RUNTIME_SESSION", "")
|
|
728
|
+
if session and not re.fullmatch(r"[0-9a-f]{32}", session):
|
|
729
|
+
raise WebCaptureError("Invalid runtime session identity")
|
|
725
730
|
worker_args: list[str] = []
|
|
726
731
|
cidfile: Path | None = None
|
|
727
732
|
if any(worker_values.values()):
|
|
@@ -739,7 +744,9 @@ def run_worker(project: Path, image: str, config_path: Path) -> None:
|
|
|
739
744
|
"--label", f"io.context.mcp-worker-token={worker_values['token']}",
|
|
740
745
|
]
|
|
741
746
|
command = [
|
|
742
|
-
"docker", "run", "--rm",
|
|
747
|
+
"docker", "run", "--rm", "--pull", "never", *worker_args,
|
|
748
|
+
*(["--label", f"io.context.mcp-session={session}"] if session else []),
|
|
749
|
+
"--user", f"{os.getuid()}:{os.getgid()}", "--network", network, "--ipc=host",
|
|
743
750
|
"--read-only", "--cap-drop", "ALL", "--security-opt", "no-new-privileges", "--pids-limit", "256", "--cpus", "2", "--memory", "2g",
|
|
744
751
|
"--tmpfs", "/tmp:rw,noexec,nosuid,size=512m", "-e", "HOME=/tmp",
|
|
745
752
|
"--mount", docker_bind_mount(project, "/project"), "-w", "/project", image,
|
|
@@ -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
|
+
}
|