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.
@@ -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) != required:
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" != "$WORKFLOW_SHA" ]]' not in provenance_text:
1366
- errors.append("site-deploy.yml: PDF worker revision label must be compared with the defining workflow SHA")
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["core_sha"]:
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", *worker_args, "--user", f"{os.getuid()}:{os.getgid()}", "--network", network, "--ipc=host",
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.0",
6
- "tag": "v0.6.0"
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": "5cf9817489c8dc47cee726bfe42fe3071cd32b85",
11
+ "core_sha": "3332e0b93933f12317e05f1ce8d38ce80046feab",
12
12
  "site_deploy_workflow": "dosquartsdedocs/unaltraweb/.github/workflows/site-deploy.yml",
13
- "manual_pdf_image": "ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:0ba267cb87f53ebaca4e31805fe00610cd61fdf97a8d2c3692f4655700dceaed",
14
- "vegavisuals_sha": "68c0b231402ae9485cc34ce530dc5239cb0ec194"
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.6.0",
40
- "release": "v0.6.0",
65
+ "version": "0.7.1",
66
+ "release": "v0.7.1",
41
67
  "release_status": "ready",
42
- "reference": "unaltraweb (= 0.6.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.6.0",
51
- "release": "v0.6.0",
76
+ "version": "0.7.1",
77
+ "release": "v0.7.1",
52
78
  "release_status": "ready",
53
- "reference": "unaltraweb-mcp==0.6.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.6.0",
62
- "release": "v0.6.0",
87
+ "version": "0.7.1",
88
+ "release": "v0.7.1",
63
89
  "release_status": "ready",
64
- "reference": "ghcr.io/dosquartsdedocs/unaltraweb:0.6.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.6.0",
74
- "release": "v0.6.0",
99
+ "version": "0.7.1",
100
+ "release": "v0.7.1",
75
101
  "release_status": "ready",
76
- "reference": "ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.6.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.6.0",
122
- "release": "v0.6.0",
147
+ "version": "0.7.1",
148
+ "release": "v0.7.1",
123
149
  "release_status": "released",
124
- "reference": "ghcr.io/dosquartsdedocs/unaltraweb-manual-pdf@sha256:0ba267cb87f53ebaca4e31805fe00610cd61fdf97a8d2c3692f4655700dceaed",
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.4.0",
134
- "release": "v0.4.0",
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.4.0/diavisuals-0.4.0-py3-none-any.whl#sha256=bfedcc9e2554f25ce4a1c33e556f352210848fccb8da800d1c3900dd86c48c93",
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.4.0",
145
- "release": "v0.4.0",
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.4.0/vegavisuals-0.4.0-py3-none-linux_x86_64.whl#sha256=b52ffa743643dd6b5e0320e7a9aa0cd500ea06262b7d5098c0c3f94a379bc0ea",
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
- return json.loads(path.read_text(encoding="utf-8"), parse_constant=reject)
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
- return dict(_contract()["consumer_integration"])
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.6.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-02 00:00:00.000000000 Z
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