unaltraweb 0.6.0 → 0.7.1
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 +12 -10
- data/docs/_documentation/en/33-reusable-workflows.md +28 -3
- data/docs/_documentation/en/40-distribution.md +54 -24
- data/docs/_documentation/en/45-retained-documents.md +1 -1
- data/docs/_documentation/en/46-live-runtime.md +92 -0
- data/docs/agents/d0-runtime-identity.md +209 -0
- data/docs/agents/deploy-provenance-86.md +86 -0
- data/docs/agents/deployment-integration-0.7.1.md +138 -0
- data/docs/agents/mcp-contract.md +13 -4
- data/docs/agents/owner-closeout-80.md +185 -0
- data/docs/agents/owner-closeout-85.md +269 -0
- data/docs/agents/retained-letter-import.md +4 -0
- data/scripts/test_wheel_install.py +3 -3
- data/scripts/unaltraweb-mcp-bootstrap.sh +90 -9
- data/scripts/unaltraweb-mcp-cleanup.sh +41 -9
- data/scripts/validate_distribution.py +33 -4
- data/scripts/validate_workflows.py +33 -4
- data/scripts/web_captures/render.py +8 -1
- data/src/unaltraweb_mcp/component-contract.json +52 -26
- data/src/unaltraweb_mcp/component-contract.schema.json +35 -2
- data/src/unaltraweb_mcp/distribution.py +42 -2
- metadata +8 -2
|
@@ -52,6 +52,9 @@ PYPI_PUBLISH_IMAGE = (
|
|
|
52
52
|
)
|
|
53
53
|
# Canonical JSON digest of the complete unprivileged verification job.
|
|
54
54
|
PACKAGE_PUBLISH_VERIFY_POLICY_SHA256 = "e8d99c35040770a2b00d3c62497da637739e794310b253d1893078d048d51c48"
|
|
55
|
+
# Canonical JSON of the complete provider-owned PDF digest/producer gate.
|
|
56
|
+
# Extending its reviewed release records also requires published-worker tests.
|
|
57
|
+
DEPLOY_PDF_PROVENANCE_POLICY_SHA256 = "da18b92db4069655e9979308dd90279ea8bfce4b77e47158efd5ac4421e06417"
|
|
55
58
|
IMAGE_WORKFLOWS = {
|
|
56
59
|
"compute-images.yml",
|
|
57
60
|
"docker-image.yml",
|
|
@@ -119,14 +122,17 @@ def load_consumer_integration(root: Path) -> dict[str, Any]:
|
|
|
119
122
|
"manual_pdf_image",
|
|
120
123
|
"vegavisuals_sha",
|
|
121
124
|
}
|
|
122
|
-
if not isinstance(integration, dict) or set(integration)
|
|
125
|
+
if not isinstance(integration, dict) or not required <= set(integration) or set(integration) - required - {"site_deploy_workflow_sha"}:
|
|
123
126
|
raise ValueError("component contract has no exact consumer_integration object")
|
|
127
|
+
integration = dict(integration)
|
|
128
|
+
integration.setdefault("site_deploy_workflow_sha", integration["core_sha"])
|
|
124
129
|
return integration
|
|
125
130
|
|
|
126
131
|
|
|
127
132
|
def load_scaffold_deploy(path: Path, integration: dict[str, Any]) -> dict[str, Any]:
|
|
128
133
|
replacements = {
|
|
129
134
|
"CORE_SHA": str(integration["core_sha"]),
|
|
135
|
+
"SITE_DEPLOY_WORKFLOW_SHA": str(integration["site_deploy_workflow_sha"]),
|
|
130
136
|
"SITE_DEPLOY_WORKFLOW": str(integration["site_deploy_workflow"]),
|
|
131
137
|
"MANUAL_PDF_IMAGE": str(integration["manual_pdf_image"]),
|
|
132
138
|
"VEGAVISUALS_SHA": str(integration["vegavisuals_sha"]),
|
|
@@ -213,6 +219,7 @@ def validate_workflows(root: Path = WORKFLOW_ROOT) -> list[str]:
|
|
|
213
219
|
integration = {
|
|
214
220
|
"core_sha": "invalid",
|
|
215
221
|
"site_deploy_workflow": "invalid",
|
|
222
|
+
"site_deploy_workflow_sha": "invalid",
|
|
216
223
|
"manual_pdf_image": "invalid",
|
|
217
224
|
"vegavisuals_sha": "invalid",
|
|
218
225
|
}
|
|
@@ -1047,6 +1054,16 @@ def validate_workflows(root: Path = WORKFLOW_ROOT) -> list[str]:
|
|
|
1047
1054
|
if marker not in ci_text:
|
|
1048
1055
|
errors.append(f"ci.yml: missing required check {marker}")
|
|
1049
1056
|
ci_docker = ci.get("jobs", {}).get("docker", {})
|
|
1057
|
+
published_pdf_gate = _named_step(ci.get("jobs", {}).get("distribution", {}), "Test published PDF deployment provenance")
|
|
1058
|
+
if published_pdf_gate != {
|
|
1059
|
+
"name": "Test published PDF deployment provenance",
|
|
1060
|
+
"env": {
|
|
1061
|
+
"UNALTRAWEB_DEPLOY_PROVENANCE_DOCKER": "1",
|
|
1062
|
+
"UNALTRAWEB_DEPLOY_PROVENANCE_SOURCE_SHA": "${{ github.event.pull_request.head.sha || github.sha }}",
|
|
1063
|
+
},
|
|
1064
|
+
"run": "PYTHONPATH=test python -m unittest test_deploy_provenance.PublishedDeployProvenanceTests",
|
|
1065
|
+
}:
|
|
1066
|
+
errors.append("ci.yml: published PDF deployment provenance must run unconditionally with real Docker")
|
|
1050
1067
|
ruby_tests = str(_named_step(ci_docker, "Test all Ruby files").get("run") or "")
|
|
1051
1068
|
if not all(
|
|
1052
1069
|
marker in ruby_tests
|
|
@@ -1362,8 +1379,20 @@ def validate_workflows(root: Path = WORKFLOW_ROOT) -> list[str]:
|
|
|
1362
1379
|
for marker in ["MANUAL_PDF_IMAGE", "WORKFLOW_REPOSITORY", "WORKFLOW_SHA", "docker pull", "docker image inspect", "org.opencontainers.image.revision"]:
|
|
1363
1380
|
if marker not in provenance_text:
|
|
1364
1381
|
errors.append(f"site-deploy.yml: PDF worker provenance check missing {marker}")
|
|
1365
|
-
if '[[ "$image_revision" != "$
|
|
1366
|
-
errors.append("site-deploy.yml: PDF worker revision label must be compared with
|
|
1382
|
+
if '[[ "$image_revision" != "$expected_pdf_revision" ]]' not in provenance_text:
|
|
1383
|
+
errors.append("site-deploy.yml: PDF worker revision label must be compared with its recorded producer SHA")
|
|
1384
|
+
provenance_sha256 = hashlib.sha256(
|
|
1385
|
+
json.dumps(provenance_step, sort_keys=True, separators=(",", ":")).encode()
|
|
1386
|
+
).hexdigest()
|
|
1387
|
+
if provenance_sha256 != DEPLOY_PDF_PROVENANCE_POLICY_SHA256:
|
|
1388
|
+
errors.append("site-deploy.yml: PDF digest/producer gate differs from the exact reviewed structure")
|
|
1389
|
+
try:
|
|
1390
|
+
contract = json.loads((ROOT / "src/unaltraweb_mcp/component-contract.json").read_text())
|
|
1391
|
+
records = contract["deployment_contract"]["manual_pdf_workers"]
|
|
1392
|
+
if sum(row.get("reference") == integration["manual_pdf_image"] for row in records) != 1:
|
|
1393
|
+
errors.append("site-deploy.yml: selected PDF worker has no reviewed digest/producer record")
|
|
1394
|
+
except (OSError, ValueError, KeyError, TypeError):
|
|
1395
|
+
errors.append("site-deploy.yml: missing or invalid provider deployment records")
|
|
1367
1396
|
if "docker pull" in provenance_text and "docker image inspect" in provenance_text and provenance_text.index("docker pull") > provenance_text.index("docker image inspect"):
|
|
1368
1397
|
errors.append("site-deploy.yml: PDF worker digest must be pulled before its revision label is inspected")
|
|
1369
1398
|
if "${{ github.workflow_sha }}" in deploy_text or "${{ github.workflow_repository }}" in deploy_text:
|
|
@@ -1542,7 +1571,7 @@ def validate_workflows(root: Path = WORKFLOW_ROOT) -> list[str]:
|
|
|
1542
1571
|
action, separator, revision = uses.rpartition("@")
|
|
1543
1572
|
if not separator or action != integration["site_deploy_workflow"] or not FULL_SHA.fullmatch(revision):
|
|
1544
1573
|
errors.append(f"scaffold deploy.yml:{job_name}: reusable workflow must use an immutable unaltraweb SHA")
|
|
1545
|
-
elif revision != integration["
|
|
1574
|
+
elif revision != integration["site_deploy_workflow_sha"]:
|
|
1546
1575
|
errors.append(f"scaffold deploy.yml:{job_name}: reusable workflow SHA has not been reviewed")
|
|
1547
1576
|
reusable_job = scaffold.get("jobs", {}).get(job_name, {})
|
|
1548
1577
|
if reusable_job.get("needs") != "validate":
|
|
@@ -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,
|
|
@@ -2,16 +2,42 @@
|
|
|
2
2
|
"$schema": "component-contract.schema.json",
|
|
3
3
|
"schema_version": 1,
|
|
4
4
|
"release": {
|
|
5
|
-
"version": "0.
|
|
6
|
-
"tag": "v0.
|
|
5
|
+
"version": "0.7.1",
|
|
6
|
+
"tag": "v0.7.1"
|
|
7
7
|
},
|
|
8
8
|
"consumer_integration": {
|
|
9
9
|
"schema_version": 1,
|
|
10
10
|
"core_repository": "https://github.com/dosquartsdedocs/unaltraweb.git",
|
|
11
|
-
"core_sha": "
|
|
11
|
+
"core_sha": "3332e0b93933f12317e05f1ce8d38ce80046feab",
|
|
12
12
|
"site_deploy_workflow": "dosquartsdedocs/unaltraweb/.github/workflows/site-deploy.yml",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
13
|
+
"site_deploy_workflow_sha": "3332e0b93933f12317e05f1ce8d38ce80046feab",
|
|
14
|
+
"manual_pdf_image": "ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:1bb3f2dafd741e96c28639ec1cc8ccaeb81cbaa73e649f787c0b4ef59997bd4b",
|
|
15
|
+
"vegavisuals_sha": "a448cc50a5f511743a6fcfc213baeb9be80f4a27"
|
|
16
|
+
},
|
|
17
|
+
"deployment_contract": {
|
|
18
|
+
"schema_version": 1,
|
|
19
|
+
"manual_pdf_workers": [
|
|
20
|
+
{
|
|
21
|
+
"reference": "ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:9e0b3a45753c170b795e9a9d6df61580085c113436beac5bf6c8de69b6562097",
|
|
22
|
+
"producer_sha": "d857f8c9f5fea90cf450c0b30b4e77a37b541275"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"reference": "ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:0ba267cb87f53ebaca4e31805fe00610cd61fdf97a8d2c3692f4655700dceaed",
|
|
26
|
+
"producer_sha": "5cf9817489c8dc47cee726bfe42fe3071cd32b85"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"reference": "ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:1bb3f2dafd741e96c28639ec1cc8ccaeb81cbaa73e649f787c0b4ef59997bd4b",
|
|
30
|
+
"producer_sha": "b7ec1d7a139c65b3b0b88699cdbeb19080f13be1"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"caller_migrations": [
|
|
34
|
+
{
|
|
35
|
+
"id": "deploy-provenance-86",
|
|
36
|
+
"path": ".github/workflows/deploy.yml",
|
|
37
|
+
"baseline_sha256": "a789870542614d5817d30ea4328c7d1ad38d27443c009c68a48ad5aaa3106b8e",
|
|
38
|
+
"current_sha256": "c632a548d0782867be20a2a52fe03469cf63066b7364bace77fd54a226cb3b18"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
15
41
|
},
|
|
16
42
|
"receipt_contract": {
|
|
17
43
|
"schema_version": 1,
|
|
@@ -36,10 +62,10 @@
|
|
|
36
62
|
"gem": {
|
|
37
63
|
"kind": "gem",
|
|
38
64
|
"name": "unaltraweb",
|
|
39
|
-
"version": "0.
|
|
40
|
-
"release": "v0.
|
|
65
|
+
"version": "0.7.1",
|
|
66
|
+
"release": "v0.7.1",
|
|
41
67
|
"release_status": "ready",
|
|
42
|
-
"reference": "unaltraweb (= 0.
|
|
68
|
+
"reference": "unaltraweb (= 0.7.1)",
|
|
43
69
|
"repository": "https://github.com/dosquartsdedocs/unaltraweb",
|
|
44
70
|
"included_in_wheel": false,
|
|
45
71
|
"features": ["jekyll-core", "site-build"]
|
|
@@ -47,10 +73,10 @@
|
|
|
47
73
|
"wheel": {
|
|
48
74
|
"kind": "python-wheel",
|
|
49
75
|
"name": "unaltraweb-mcp",
|
|
50
|
-
"version": "0.
|
|
51
|
-
"release": "v0.
|
|
76
|
+
"version": "0.7.1",
|
|
77
|
+
"release": "v0.7.1",
|
|
52
78
|
"release_status": "ready",
|
|
53
|
-
"reference": "unaltraweb-mcp==0.
|
|
79
|
+
"reference": "unaltraweb-mcp==0.7.1",
|
|
54
80
|
"repository": "https://github.com/dosquartsdedocs/unaltraweb",
|
|
55
81
|
"included_in_wheel": true,
|
|
56
82
|
"features": ["calibre-import", "doctor", "new-web", "offline-inspection", "mcp-control-plane"]
|
|
@@ -58,10 +84,10 @@
|
|
|
58
84
|
"runtime": {
|
|
59
85
|
"kind": "container",
|
|
60
86
|
"name": "unaltraweb runtime",
|
|
61
|
-
"version": "0.
|
|
62
|
-
"release": "v0.
|
|
87
|
+
"version": "0.7.1",
|
|
88
|
+
"release": "v0.7.1",
|
|
63
89
|
"release_status": "ready",
|
|
64
|
-
"reference": "ghcr.io/dosquartsdedocs/unaltraweb:0.
|
|
90
|
+
"reference": "ghcr.io/dosquartsdedocs/unaltraweb:0.7.1",
|
|
65
91
|
"image_repository": "ghcr.io/dosquartsdedocs/unaltraweb",
|
|
66
92
|
"repository": "https://github.com/dosquartsdedocs/unaltraweb",
|
|
67
93
|
"included_in_wheel": false,
|
|
@@ -70,10 +96,10 @@
|
|
|
70
96
|
"mcp": {
|
|
71
97
|
"kind": "container",
|
|
72
98
|
"name": "unaltraweb MCP runtime",
|
|
73
|
-
"version": "0.
|
|
74
|
-
"release": "v0.
|
|
99
|
+
"version": "0.7.1",
|
|
100
|
+
"release": "v0.7.1",
|
|
75
101
|
"release_status": "ready",
|
|
76
|
-
"reference": "ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.
|
|
102
|
+
"reference": "ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.7.1",
|
|
77
103
|
"image_repository": "ghcr.io/dosquartsdedocs/unaltraweb-mcp",
|
|
78
104
|
"repository": "https://github.com/dosquartsdedocs/unaltraweb",
|
|
79
105
|
"included_in_wheel": false,
|
|
@@ -118,10 +144,10 @@
|
|
|
118
144
|
"manual_pdf": {
|
|
119
145
|
"kind": "container",
|
|
120
146
|
"name": "unaltraweb manual PDF worker",
|
|
121
|
-
"version": "0.
|
|
122
|
-
"release": "v0.
|
|
147
|
+
"version": "0.7.1",
|
|
148
|
+
"release": "v0.7.1",
|
|
123
149
|
"release_status": "released",
|
|
124
|
-
"reference": "ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:
|
|
150
|
+
"reference": "ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:1bb3f2dafd741e96c28639ec1cc8ccaeb81cbaa73e649f787c0b4ef59997bd4b",
|
|
125
151
|
"image_repository": "ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf",
|
|
126
152
|
"repository": "https://github.com/dosquartsdedocs/unaltraweb",
|
|
127
153
|
"included_in_wheel": false,
|
|
@@ -130,10 +156,10 @@
|
|
|
130
156
|
"diavisuals": {
|
|
131
157
|
"kind": "companion",
|
|
132
158
|
"name": "diavisuals",
|
|
133
|
-
"version": "0.
|
|
134
|
-
"release": "v0.
|
|
159
|
+
"version": "0.5.0",
|
|
160
|
+
"release": "v0.5.0",
|
|
135
161
|
"release_status": "released",
|
|
136
|
-
"reference": "https://github.com/dosquartsdedocs/diavisuals/releases/download/v0.
|
|
162
|
+
"reference": "https://github.com/dosquartsdedocs/diavisuals/releases/download/v0.5.0/diavisuals-0.5.0-py3-none-any.whl#sha256=f52e20f20f1fb3a2418f07adb8d6a68a5567d0c321fb7c0ccc0f4db04917b96b",
|
|
137
163
|
"repository": "https://github.com/dosquartsdedocs/diavisuals",
|
|
138
164
|
"included_in_wheel": false,
|
|
139
165
|
"features": ["mermaid", "plantuml"]
|
|
@@ -141,10 +167,10 @@
|
|
|
141
167
|
"vegavisuals": {
|
|
142
168
|
"kind": "companion",
|
|
143
169
|
"name": "vegavisuals",
|
|
144
|
-
"version": "0.
|
|
145
|
-
"release": "v0.
|
|
170
|
+
"version": "0.5.1",
|
|
171
|
+
"release": "v0.5.1",
|
|
146
172
|
"release_status": "released",
|
|
147
|
-
"reference": "https://github.com/dosquartsdedocs/vegavisuals/releases/download/v0.
|
|
173
|
+
"reference": "https://github.com/dosquartsdedocs/vegavisuals/releases/download/v0.5.1/vegavisuals-0.5.1-py3-none-linux_x86_64.whl#sha256=5aba4f267a37179c520b3cd164e1cec62ac5561872aa33b069cce73442dc52b1",
|
|
148
174
|
"repository": "https://github.com/dosquartsdedocs/vegavisuals",
|
|
149
175
|
"included_in_wheel": false,
|
|
150
176
|
"features": ["vega", "vega-lite"]
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"required": ["schema_version", "release", "consumer_integration", "receipt_contract", "wheel_contract", "components"],
|
|
8
8
|
"properties": {
|
|
9
9
|
"$schema": {"type": "string"},
|
|
10
|
-
"schema_version": {"const": 1},
|
|
10
|
+
"schema_version": {"type": "integer", "const": 1},
|
|
11
11
|
"release": {
|
|
12
12
|
"type": "object",
|
|
13
13
|
"additionalProperties": false,
|
|
@@ -22,14 +22,47 @@
|
|
|
22
22
|
"additionalProperties": false,
|
|
23
23
|
"required": ["schema_version", "core_repository", "core_sha", "site_deploy_workflow", "manual_pdf_image", "vegavisuals_sha"],
|
|
24
24
|
"properties": {
|
|
25
|
-
"schema_version": {"const": 1},
|
|
25
|
+
"schema_version": {"type": "integer", "const": 1},
|
|
26
26
|
"core_repository": {"const": "https://github.com/dosquartsdedocs/unaltraweb.git"},
|
|
27
27
|
"core_sha": {"type": "string", "minLength": 40, "maxLength": 40, "pattern": "^[0-9a-f]{40}$"},
|
|
28
28
|
"site_deploy_workflow": {"const": "dosquartsdedocs/unaltraweb/.github/workflows/site-deploy.yml"},
|
|
29
|
+
"site_deploy_workflow_sha": {"type": "string", "minLength": 40, "maxLength": 40, "pattern": "^[0-9a-f]{40}$"},
|
|
29
30
|
"manual_pdf_image": {"type": "string", "minLength": 117, "maxLength": 117, "pattern": "^ghcr\\.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:[0-9a-f]{64}$"},
|
|
30
31
|
"vegavisuals_sha": {"type": "string", "minLength": 40, "maxLength": 40, "pattern": "^[0-9a-f]{40}$"}
|
|
31
32
|
}
|
|
32
33
|
},
|
|
34
|
+
"deployment_contract": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"additionalProperties": false,
|
|
37
|
+
"required": ["schema_version", "manual_pdf_workers", "caller_migrations"],
|
|
38
|
+
"properties": {
|
|
39
|
+
"schema_version": {"type": "integer", "const": 1},
|
|
40
|
+
"manual_pdf_workers": {
|
|
41
|
+
"type": "array", "minItems": 1, "uniqueItems": true,
|
|
42
|
+
"items": {
|
|
43
|
+
"type": "object", "additionalProperties": false,
|
|
44
|
+
"required": ["reference", "producer_sha"],
|
|
45
|
+
"properties": {
|
|
46
|
+
"reference": {"type": "string", "minLength": 117, "maxLength": 117, "pattern": "^ghcr\\.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:[0-9a-f]{64}$"},
|
|
47
|
+
"producer_sha": {"type": "string", "minLength": 40, "maxLength": 40, "pattern": "^[0-9a-f]{40}$"}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"caller_migrations": {
|
|
52
|
+
"type": "array", "uniqueItems": true,
|
|
53
|
+
"items": {
|
|
54
|
+
"type": "object", "additionalProperties": false,
|
|
55
|
+
"required": ["id", "path", "baseline_sha256", "current_sha256"],
|
|
56
|
+
"properties": {
|
|
57
|
+
"id": {"type": "string", "pattern": "^[a-z][a-z0-9-]{0,63}$"},
|
|
58
|
+
"path": {"const": ".github/workflows/deploy.yml"},
|
|
59
|
+
"baseline_sha256": {"type": "string", "minLength": 64, "maxLength": 64, "pattern": "^[0-9a-f]{64}$"},
|
|
60
|
+
"current_sha256": {"type": "string", "minLength": 64, "maxLength": 64, "pattern": "^[0-9a-f]{64}$"}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
33
66
|
"receipt_contract": {
|
|
34
67
|
"type": "object",
|
|
35
68
|
"additionalProperties": false,
|
|
@@ -20,7 +20,15 @@ def _strict_json(path: Path) -> Any:
|
|
|
20
20
|
def reject(value: str) -> None:
|
|
21
21
|
raise ValueError(f"non-finite JSON number is not allowed: {value}")
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
def unique(pairs):
|
|
24
|
+
result = {}
|
|
25
|
+
for key, value in pairs:
|
|
26
|
+
if key in result:
|
|
27
|
+
raise ValueError(f"duplicate component contract key: {key}")
|
|
28
|
+
result[key] = value
|
|
29
|
+
return result
|
|
30
|
+
|
|
31
|
+
return json.loads(path.read_text(encoding="utf-8"), parse_constant=reject, object_pairs_hook=unique)
|
|
24
32
|
|
|
25
33
|
|
|
26
34
|
def _schema_type_matches(value: Any, expected: str) -> bool:
|
|
@@ -155,6 +163,9 @@ def component_contract_semantic_errors(value: dict[str, Any]) -> list[str]:
|
|
|
155
163
|
expected_workflow = f"{expected_core_repository.removeprefix('https://github.com/').removesuffix('.git')}/.github/workflows/site-deploy.yml"
|
|
156
164
|
if integration["site_deploy_workflow"] != expected_workflow:
|
|
157
165
|
errors.append("consumer integration deploy workflow must belong to the core provider")
|
|
166
|
+
workflow_sha = str(integration.get("site_deploy_workflow_sha", core_sha))
|
|
167
|
+
if re.fullmatch(r"[0-9a-f]{40}", workflow_sha) is None or workflow_sha == "0" * 40:
|
|
168
|
+
errors.append("consumer integration workflow SHA must be a nonzero full lowercase commit SHA")
|
|
158
169
|
expected_pdf_prefix = f"{value['components']['manual_pdf']['image_repository']}@sha256:"
|
|
159
170
|
manual_pdf_image = str(integration["manual_pdf_image"])
|
|
160
171
|
if re.fullmatch(rf"{re.escape(expected_pdf_prefix)}[0-9a-f]{{64}}", manual_pdf_image) is None or manual_pdf_image.endswith("0" * 64):
|
|
@@ -162,6 +173,26 @@ def component_contract_semantic_errors(value: dict[str, Any]) -> list[str]:
|
|
|
162
173
|
vegavisuals_sha = str(integration["vegavisuals_sha"])
|
|
163
174
|
if re.fullmatch(r"[0-9a-f]{40}", vegavisuals_sha) is None or vegavisuals_sha == "0" * 40:
|
|
164
175
|
errors.append("consumer integration Vega revision must be a nonzero full lowercase commit SHA")
|
|
176
|
+
deployment = value.get("deployment_contract")
|
|
177
|
+
if deployment is not None:
|
|
178
|
+
references = [row["reference"] for row in deployment["manual_pdf_workers"]]
|
|
179
|
+
if not 1 <= len(references) <= 64:
|
|
180
|
+
errors.append("deployment worker inventory must contain between 1 and 64 records")
|
|
181
|
+
if len(set(references)) != len(references):
|
|
182
|
+
errors.append("deployment worker references must be unique")
|
|
183
|
+
if manual_pdf_image not in references or value["components"]["manual_pdf"]["reference"] != manual_pdf_image:
|
|
184
|
+
errors.append("selected PDF worker must match a reviewed deployment record and the component reference")
|
|
185
|
+
for row in deployment["manual_pdf_workers"]:
|
|
186
|
+
if row["producer_sha"] == "0" * 40 or row["reference"].endswith("0" * 64):
|
|
187
|
+
errors.append("deployment worker identities must be nonzero")
|
|
188
|
+
migrations = deployment["caller_migrations"]
|
|
189
|
+
if len(migrations) > 32:
|
|
190
|
+
errors.append("deployment caller migration inventory exceeds 32 records")
|
|
191
|
+
if len({row["id"] for row in migrations}) != len(migrations) or len({(row["path"], row["baseline_sha256"], row["current_sha256"]) for row in migrations}) != len(migrations):
|
|
192
|
+
errors.append("deployment caller migrations must be unique")
|
|
193
|
+
for row in migrations:
|
|
194
|
+
if row["baseline_sha256"] == row["current_sha256"] or "0" * 64 in (row["baseline_sha256"], row["current_sha256"]):
|
|
195
|
+
errors.append("deployment caller migration requires distinct nonzero baseline and current hashes")
|
|
165
196
|
return errors
|
|
166
197
|
|
|
167
198
|
|
|
@@ -194,7 +225,16 @@ def distribution_version() -> str:
|
|
|
194
225
|
|
|
195
226
|
def consumer_integration() -> dict[str, Any]:
|
|
196
227
|
"""Return an isolated copy of the reviewed consumer runtime tuple."""
|
|
197
|
-
|
|
228
|
+
result = dict(_contract()["consumer_integration"])
|
|
229
|
+
result.setdefault("site_deploy_workflow_sha", result["core_sha"])
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def deployment_contract() -> dict[str, Any]:
|
|
234
|
+
"""Provider-owned identities and exact reviewed caller migrations."""
|
|
235
|
+
return json.loads(json.dumps(_contract().get("deployment_contract", {
|
|
236
|
+
"schema_version": 1, "manual_pdf_workers": [], "caller_migrations": [],
|
|
237
|
+
})))
|
|
198
238
|
|
|
199
239
|
|
|
200
240
|
def component(component_id: str) -> dict[str, Any]:
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: unaltraweb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- dosquartsdedocs
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: classifier-reborn
|
|
@@ -782,6 +782,7 @@ files:
|
|
|
782
782
|
- docs/_documentation/en/43-workspace-path-policies.md
|
|
783
783
|
- docs/_documentation/en/44-editorial-review.md
|
|
784
784
|
- docs/_documentation/en/45-retained-documents.md
|
|
785
|
+
- docs/_documentation/en/46-live-runtime.md
|
|
785
786
|
- docs/_documentation/en/50-faq.md
|
|
786
787
|
- docs/_pages/ca/index.md
|
|
787
788
|
- docs/_pages/en/index.md
|
|
@@ -799,9 +800,14 @@ files:
|
|
|
799
800
|
- docs/agents/action-prompts/50-bibliography-entry.txt
|
|
800
801
|
- docs/agents/action-prompts/60-bibliometrics-refresh.txt
|
|
801
802
|
- docs/agents/action-prompts/70-build-and-review.txt
|
|
803
|
+
- docs/agents/d0-runtime-identity.md
|
|
804
|
+
- docs/agents/deploy-provenance-86.md
|
|
805
|
+
- docs/agents/deployment-integration-0.7.1.md
|
|
802
806
|
- docs/agents/manual-authoring-components.md
|
|
803
807
|
- docs/agents/mcp-contract.md
|
|
804
808
|
- docs/agents/owner-closeout-76.md
|
|
809
|
+
- docs/agents/owner-closeout-80.md
|
|
810
|
+
- docs/agents/owner-closeout-85.md
|
|
805
811
|
- docs/agents/owner-followup-76.md
|
|
806
812
|
- docs/agents/owner-preparation-2026-09-29.md
|
|
807
813
|
- docs/agents/quarto-computation-adoption.md
|