fdai-deployment-cli 0.1.2__py3-none-any.whl

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.
Files changed (203) hide show
  1. fdai_deployment_cli/__about__.py +3 -0
  2. fdai_deployment_cli/__init__.py +5 -0
  3. fdai_deployment_cli/__main__.py +5 -0
  4. fdai_deployment_cli/acr_publish.py +585 -0
  5. fdai_deployment_cli/aks_catalog_review.py +137 -0
  6. fdai_deployment_cli/aks_historical_reconciliation.py +732 -0
  7. fdai_deployment_cli/aks_job_environment.py +59 -0
  8. fdai_deployment_cli/aks_job_execution.py +794 -0
  9. fdai_deployment_cli/aks_kubernetes_defaults.py +66 -0
  10. fdai_deployment_cli/aks_preflight.py +422 -0
  11. fdai_deployment_cli/aks_readiness.py +220 -0
  12. fdai_deployment_cli/aks_service_update.py +180 -0
  13. fdai_deployment_cli/aks_workload_jobs.py +281 -0
  14. fdai_deployment_cli/application_state_adoption.py +347 -0
  15. fdai_deployment_cli/azure_actor.py +61 -0
  16. fdai_deployment_cli/azure_naming.py +252 -0
  17. fdai_deployment_cli/azure_retail_prices.py +173 -0
  18. fdai_deployment_cli/bootstrap_approval.py +99 -0
  19. fdai_deployment_cli/bootstrap_channel.py +321 -0
  20. fdai_deployment_cli/bootstrap_executor.py +311 -0
  21. fdai_deployment_cli/bootstrap_plan.py +237 -0
  22. fdai_deployment_cli/bootstrap_reconcile.py +402 -0
  23. fdai_deployment_cli/bootstrap_support.py +138 -0
  24. fdai_deployment_cli/bootstrap_worker.py +448 -0
  25. fdai_deployment_cli/bundle.py +352 -0
  26. fdai_deployment_cli/capacity.py +143 -0
  27. fdai_deployment_cli/catalog_review_profile.py +306 -0
  28. fdai_deployment_cli/cli.py +798 -0
  29. fdai_deployment_cli/cli_help.py +214 -0
  30. fdai_deployment_cli/cli_parser.py +791 -0
  31. fdai_deployment_cli/cli_plan.py +450 -0
  32. fdai_deployment_cli/compiler.py +99 -0
  33. fdai_deployment_cli/console_artifact.py +218 -0
  34. fdai_deployment_cli/console_config.py +214 -0
  35. fdai_deployment_cli/console_update.py +832 -0
  36. fdai_deployment_cli/contracts.py +307 -0
  37. fdai_deployment_cli/control_package.py +140 -0
  38. fdai_deployment_cli/deadline_transport.py +70 -0
  39. fdai_deployment_cli/deployment_cost.py +131 -0
  40. fdai_deployment_cli/deployment_deadline.py +28 -0
  41. fdai_deployment_cli/deployment_kit.py +790 -0
  42. fdai_deployment_cli/deployment_kit_cache.py +191 -0
  43. fdai_deployment_cli/deployment_progress.py +648 -0
  44. fdai_deployment_cli/development_authority_cli.py +56 -0
  45. fdai_deployment_cli/development_authority_profile.py +319 -0
  46. fdai_deployment_cli/doctor.py +136 -0
  47. fdai_deployment_cli/entitlement_preflight.py +68 -0
  48. fdai_deployment_cli/entitlement_transport.py +49 -0
  49. fdai_deployment_cli/entra_bootstrap.py +579 -0
  50. fdai_deployment_cli/entra_control_profile.py +214 -0
  51. fdai_deployment_cli/entra_graph.py +349 -0
  52. fdai_deployment_cli/entra_profiles.py +215 -0
  53. fdai_deployment_cli/entra_source.py +244 -0
  54. fdai_deployment_cli/execution_bundle.py +478 -0
  55. fdai_deployment_cli/execution_copies.py +63 -0
  56. fdai_deployment_cli/foundation_adoption.py +438 -0
  57. fdai_deployment_cli/foundation_adoption_evidence.py +245 -0
  58. fdai_deployment_cli/foundation_adoption_host.py +157 -0
  59. fdai_deployment_cli/foundation_adoption_transport.py +51 -0
  60. fdai_deployment_cli/foundation_failure.py +72 -0
  61. fdai_deployment_cli/foundation_image.py +168 -0
  62. fdai_deployment_cli/foundation_input.py +235 -0
  63. fdai_deployment_cli/foundation_output.py +154 -0
  64. fdai_deployment_cli/foundation_plan.py +381 -0
  65. fdai_deployment_cli/foundation_process.py +64 -0
  66. fdai_deployment_cli/foundation_progress.py +231 -0
  67. fdai_deployment_cli/github_actions.py +171 -0
  68. fdai_deployment_cli/github_apply_artifact.py +259 -0
  69. fdai_deployment_cli/github_artifact_io.py +48 -0
  70. fdai_deployment_cli/github_dispatch.py +248 -0
  71. fdai_deployment_cli/github_plan_artifact.py +181 -0
  72. fdai_deployment_cli/github_provider_artifact.py +170 -0
  73. fdai_deployment_cli/github_transport.py +46 -0
  74. fdai_deployment_cli/github_workflow_values.py +312 -0
  75. fdai_deployment_cli/hub_image_deploy.py +354 -0
  76. fdai_deployment_cli/hub_image_stage.py +162 -0
  77. fdai_deployment_cli/hub_image_trust.py +281 -0
  78. fdai_deployment_cli/hub_ownership.py +161 -0
  79. fdai_deployment_cli/hub_workload_handoff.py +264 -0
  80. fdai_deployment_cli/hub_workload_handoff_inputs.py +227 -0
  81. fdai_deployment_cli/identity_profile.py +227 -0
  82. fdai_deployment_cli/image_targets.py +197 -0
  83. fdai_deployment_cli/installation_scope.py +280 -0
  84. fdai_deployment_cli/license.py +311 -0
  85. fdai_deployment_cli/license_issue.py +239 -0
  86. fdai_deployment_cli/lifecycle_authorization.py +252 -0
  87. fdai_deployment_cli/lifecycle_commands.py +173 -0
  88. fdai_deployment_cli/lifecycle_configuration.py +722 -0
  89. fdai_deployment_cli/lifecycle_configuration_artifact.py +45 -0
  90. fdai_deployment_cli/lifecycle_configuration_cli.py +324 -0
  91. fdai_deployment_cli/lifecycle_configuration_document.py +74 -0
  92. fdai_deployment_cli/lifecycle_configuration_entities.py +146 -0
  93. fdai_deployment_cli/lifecycle_configuration_package.py +492 -0
  94. fdai_deployment_cli/lifecycle_configuration_publication.py +277 -0
  95. fdai_deployment_cli/lifecycle_configuration_publication_cli.py +191 -0
  96. fdai_deployment_cli/lifecycle_configuration_sealing.py +127 -0
  97. fdai_deployment_cli/lifecycle_enrollment.py +45 -0
  98. fdai_deployment_cli/lifecycle_infrastructure_agent.py +174 -0
  99. fdai_deployment_cli/lifecycle_installation_binding.py +65 -0
  100. fdai_deployment_cli/lifecycle_native.py +247 -0
  101. fdai_deployment_cli/lifecycle_native_reconciliation.py +568 -0
  102. fdai_deployment_cli/lifecycle_native_transition.py +339 -0
  103. fdai_deployment_cli/lifecycle_ownership_exclusion.py +78 -0
  104. fdai_deployment_cli/lifecycle_plan.py +800 -0
  105. fdai_deployment_cli/lifecycle_promotion.py +185 -0
  106. fdai_deployment_cli/lifecycle_release.py +308 -0
  107. fdai_deployment_cli/lifecycle_self_upgrade.py +202 -0
  108. fdai_deployment_cli/lifecycle_upgrade_bundle.py +176 -0
  109. fdai_deployment_cli/notification_recipients_input.py +222 -0
  110. fdai_deployment_cli/observer_artifact.py +106 -0
  111. fdai_deployment_cli/oci_archive.py +353 -0
  112. fdai_deployment_cli/offline_kit.py +797 -0
  113. fdai_deployment_cli/offline_prepare.py +135 -0
  114. fdai_deployment_cli/operational_evidence_verifier_input.py +575 -0
  115. fdai_deployment_cli/plan_input.py +101 -0
  116. fdai_deployment_cli/private_output.py +192 -0
  117. fdai_deployment_cli/profile.py +70 -0
  118. fdai_deployment_cli/progress.py +183 -0
  119. fdai_deployment_cli/readiness.py +286 -0
  120. fdai_deployment_cli/run_command_receiver.py +418 -0
  121. fdai_deployment_cli/runtime_build.py +386 -0
  122. fdai_deployment_cli/runtime_profile.py +287 -0
  123. fdai_deployment_cli/runtime_release.py +745 -0
  124. fdai_deployment_cli/runtime_stage.py +82 -0
  125. fdai_deployment_cli/runtime_support_installation.py +215 -0
  126. fdai_deployment_cli/simulation.py +80 -0
  127. fdai_deployment_cli/source_application_inputs.py +397 -0
  128. fdai_deployment_cli/source_azure.py +586 -0
  129. fdai_deployment_cli/source_change_cli.py +107 -0
  130. fdai_deployment_cli/source_change_parser.py +75 -0
  131. fdai_deployment_cli/source_change_plan.py +181 -0
  132. fdai_deployment_cli/source_changed_update.py +196 -0
  133. fdai_deployment_cli/source_deploy.py +170 -0
  134. fdai_deployment_cli/source_foundation.py +174 -0
  135. fdai_deployment_cli/source_host_tools.py +163 -0
  136. fdai_deployment_cli/source_image_stage.py +400 -0
  137. fdai_deployment_cli/source_input.py +173 -0
  138. fdai_deployment_cli/source_projection.py +164 -0
  139. fdai_deployment_cli/source_receiver.py +76 -0
  140. fdai_deployment_cli/source_recovery.py +264 -0
  141. fdai_deployment_cli/source_runtime.py +81 -0
  142. fdai_deployment_cli/source_runtime_support.py +368 -0
  143. fdai_deployment_cli/source_service_update.py +726 -0
  144. fdai_deployment_cli/source_service_update_cli.py +47 -0
  145. fdai_deployment_cli/source_snapshot.py +178 -0
  146. fdai_deployment_cli/source_teardown.py +574 -0
  147. fdai_deployment_cli/source_terraform.py +85 -0
  148. fdai_deployment_cli/source_transport.py +399 -0
  149. fdai_deployment_cli/standalone_aks_inventory.py +558 -0
  150. fdai_deployment_cli/standalone_aks_job_execution.py +769 -0
  151. fdai_deployment_cli/standalone_aks_nodepool_guard.py +141 -0
  152. fdai_deployment_cli/standalone_application.py +977 -0
  153. fdai_deployment_cli/standalone_application_completion.py +151 -0
  154. fdai_deployment_cli/standalone_application_input_recovery.py +107 -0
  155. fdai_deployment_cli/standalone_application_recovery.py +116 -0
  156. fdai_deployment_cli/standalone_approval_input.py +24 -0
  157. fdai_deployment_cli/standalone_catalog_checkpoint.py +122 -0
  158. fdai_deployment_cli/standalone_catalog_review.py +251 -0
  159. fdai_deployment_cli/standalone_checkpoint_failure.py +127 -0
  160. fdai_deployment_cli/standalone_console_publish.py +174 -0
  161. fdai_deployment_cli/standalone_database_principals.py +41 -0
  162. fdai_deployment_cli/standalone_deploy.py +638 -0
  163. fdai_deployment_cli/standalone_development_authority.py +166 -0
  164. fdai_deployment_cli/standalone_foundation_adoption.py +87 -0
  165. fdai_deployment_cli/standalone_foundation_transition.py +767 -0
  166. fdai_deployment_cli/standalone_host.py +4608 -0
  167. fdai_deployment_cli/standalone_host_rebind.py +226 -0
  168. fdai_deployment_cli/standalone_host_residual.py +104 -0
  169. fdai_deployment_cli/standalone_host_state.py +145 -0
  170. fdai_deployment_cli/standalone_host_values.py +563 -0
  171. fdai_deployment_cli/standalone_kubernetes_tools.py +100 -0
  172. fdai_deployment_cli/standalone_license_installation.py +177 -0
  173. fdai_deployment_cli/standalone_management_egress.py +83 -0
  174. fdai_deployment_cli/standalone_migration_evidence.py +38 -0
  175. fdai_deployment_cli/standalone_operational_evidence.py +356 -0
  176. fdai_deployment_cli/standalone_planned_outputs.py +147 -0
  177. fdai_deployment_cli/standalone_private_access_probe.py +100 -0
  178. fdai_deployment_cli/standalone_product_profile.py +110 -0
  179. fdai_deployment_cli/standalone_remote_prepare.py +442 -0
  180. fdai_deployment_cli/standalone_residual_apply.py +540 -0
  181. fdai_deployment_cli/standalone_residual_successor.py +431 -0
  182. fdai_deployment_cli/standalone_review.py +266 -0
  183. fdai_deployment_cli/standalone_service_update_finalization.py +116 -0
  184. fdai_deployment_cli/standalone_service_update_recovery.py +277 -0
  185. fdai_deployment_cli/standalone_stage_targets.py +325 -0
  186. fdai_deployment_cli/standalone_status.py +67 -0
  187. fdai_deployment_cli/standalone_terraform_environment.py +237 -0
  188. fdai_deployment_cli/standalone_transfer_cleanup.py +237 -0
  189. fdai_deployment_cli/standalone_trial_activation.py +193 -0
  190. fdai_deployment_cli/state.py +434 -0
  191. fdai_deployment_cli/state_handoff.py +253 -0
  192. fdai_deployment_cli/status_projection.py +97 -0
  193. fdai_deployment_cli/support_install.py +257 -0
  194. fdai_deployment_cli/target.py +46 -0
  195. fdai_deployment_cli/trust/capabilities.json +62 -0
  196. fdai_deployment_cli/trust/deployment-bundle-root.pub +3 -0
  197. fdai_deployment_cli/trust/deployment-release-root.pub +3 -0
  198. fdai_deployment_cli/trust/upstream-signing-key.pub +3 -0
  199. fdai_deployment_cli/trust_roots.py +50 -0
  200. fdai_deployment_cli-0.1.2.dist-info/METADATA +112 -0
  201. fdai_deployment_cli-0.1.2.dist-info/RECORD +203 -0
  202. fdai_deployment_cli-0.1.2.dist-info/WHEEL +4 -0
  203. fdai_deployment_cli-0.1.2.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,164 @@
1
+ """Derive bounded source snapshots for one service image build."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shlex
7
+ from pathlib import PurePosixPath
8
+
9
+ from fdai_deployment_cli.runtime_release import RUNTIME_SERVICES
10
+ from fdai_deployment_cli.source_input import SourceDeploymentInput, _git, _read_tracked
11
+
12
+ _SUPPORT_FILES = frozenset(
13
+ {
14
+ ".dockerignore",
15
+ "scripts/automation/run-bounded-command.py",
16
+ "scripts/deployment/azure/genesis_checks.py",
17
+ "scripts/deployment/azure/genesis_subprocess.py",
18
+ "scripts/deployment/azure/source_image_build.py",
19
+ }
20
+ )
21
+
22
+
23
+ def service_source_projection(
24
+ source: SourceDeploymentInput,
25
+ service: str,
26
+ ) -> tuple[str, ...]:
27
+ """Return the exact tracked files needed to build one baseline service image.
28
+
29
+ The Dockerfile remains the source of its copy inputs. Unsupported Dockerfile
30
+ syntax fails closed instead of broadening the projection to the repository.
31
+ """
32
+ if service not in RUNTIME_SERVICES:
33
+ raise ValueError("source projection service is outside the baseline inventory")
34
+ entries = _tracked_entries(source)
35
+ dockerfile = f"services/{service}/docker/Dockerfile"
36
+ if dockerfile not in entries:
37
+ raise ValueError("source projection Dockerfile is unavailable")
38
+ content = _read_tracked(source.root, dockerfile, mode=entries[dockerfile]).decode("utf-8")
39
+ selected = set(_SUPPORT_FILES)
40
+ explicit_filtered: set[str] = set()
41
+ selected.add(dockerfile)
42
+ for item in _docker_copy_sources(content):
43
+ expanded = _expand_tracked_source(item, entries)
44
+ selected.update(expanded)
45
+ if _explicit_filtered_source(item):
46
+ explicit_filtered.update(expanded)
47
+ missing = selected - entries.keys()
48
+ if missing:
49
+ raise ValueError("source projection requires unavailable tracked inputs")
50
+ selected = {
51
+ path for path in selected if path in explicit_filtered or _included_build_input(path)
52
+ }
53
+ _include_symlink_targets(source, selected, entries)
54
+ return tuple(sorted(selected))
55
+
56
+
57
+ def _tracked_entries(source: SourceDeploymentInput) -> dict[str, str]:
58
+ entries: dict[str, str] = {}
59
+ for entry in _git(source.root, "ls-tree", "-rz", "--full-tree", source.commit).split(b"\0"):
60
+ if not entry:
61
+ continue
62
+ metadata, raw_path = entry.split(b"\t", 1)
63
+ mode, kind, _blob = metadata.decode("ascii").split()
64
+ if kind != "blob" or mode not in {"100644", "100755", "120000"}:
65
+ raise ValueError("source projection contains an unsupported tracked input")
66
+ entries[raw_path.decode("utf-8")] = mode
67
+ return entries
68
+
69
+
70
+ def _docker_copy_sources(content: str) -> tuple[str, ...]:
71
+ logical: list[str] = []
72
+ pending = ""
73
+ for raw_line in content.splitlines():
74
+ line = raw_line.strip()
75
+ if not line or line.startswith("#"):
76
+ continue
77
+ pending = f"{pending} {line}".strip()
78
+ if pending.endswith("\\"):
79
+ pending = pending[:-1].rstrip()
80
+ continue
81
+ logical.append(pending)
82
+ pending = ""
83
+ if pending:
84
+ raise ValueError("source projection Dockerfile has an incomplete instruction")
85
+
86
+ sources: list[str] = []
87
+ for instruction in logical:
88
+ parts = shlex.split(instruction)
89
+ if not parts:
90
+ continue
91
+ if parts[0].upper() == "ADD":
92
+ raise ValueError("source projection does not accept Docker ADD inputs")
93
+ if parts[0].upper() != "COPY":
94
+ continue
95
+ if any(part.startswith("--from=") for part in parts[1:]):
96
+ continue
97
+ operands = [part for part in parts[1:] if not part.startswith("--")]
98
+ if len(operands) < 2:
99
+ raise ValueError("source projection COPY instruction is invalid")
100
+ for value in operands[:-1]:
101
+ if any(character in value for character in "*?[") or value.startswith("$"):
102
+ raise ValueError("source projection does not accept dynamic COPY inputs")
103
+ path = PurePosixPath(value)
104
+ if path.is_absolute() or any(part in {"", ".", ".."} for part in path.parts):
105
+ raise ValueError("source projection COPY input is invalid")
106
+ sources.append(path.as_posix())
107
+ if not sources:
108
+ raise ValueError("source projection Dockerfile has no local COPY inputs")
109
+ return tuple(sources)
110
+
111
+
112
+ def _expand_tracked_source(value: str, entries: dict[str, str]) -> set[str]:
113
+ if value in entries:
114
+ return {value}
115
+ prefix = value.rstrip("/") + "/"
116
+ matches = {path for path in entries if path.startswith(prefix)}
117
+ if not matches:
118
+ raise ValueError("source projection COPY input is unavailable")
119
+ return matches
120
+
121
+
122
+ def _included_build_input(path: str) -> bool:
123
+ if "/tests/" in path and not path.startswith("services/core-control-plane/tests/scenarios/"):
124
+ return False
125
+ if path.endswith(".md") and path not in {
126
+ "README.md",
127
+ "rule-catalog/README.md",
128
+ "extensions/cost-governance/README.md",
129
+ "extensions/cost-governance/README-ko.md",
130
+ }:
131
+ return False
132
+ parts = PurePosixPath(path).parts
133
+ return not (
134
+ "__pycache__" in parts
135
+ or "node_modules" in parts
136
+ or "dist" in parts
137
+ or path.endswith(".pyc")
138
+ )
139
+
140
+
141
+ def _explicit_filtered_source(path: str) -> bool:
142
+ parts = PurePosixPath(path).parts
143
+ return "tests" in parts or "docs" in parts or path.endswith(".md")
144
+
145
+
146
+ def _include_symlink_targets(
147
+ source: SourceDeploymentInput,
148
+ selected: set[str],
149
+ entries: dict[str, str],
150
+ ) -> None:
151
+ pending = [path for path in selected if entries[path] == "120000"]
152
+ while pending:
153
+ relative = pending.pop()
154
+ target = os.fsdecode(_read_tracked(source.root, relative, mode="120000"))
155
+ resolved = PurePosixPath(relative).parent / target
156
+ if resolved.is_absolute() or ".." in resolved.parts:
157
+ raise ValueError("source projection link leaves the tracked file set")
158
+ normalized = resolved.as_posix()
159
+ if normalized not in entries:
160
+ raise ValueError("source projection link target is unavailable")
161
+ if normalized not in selected:
162
+ selected.add(normalized)
163
+ if entries[normalized] == "120000":
164
+ pending.append(normalized)
@@ -0,0 +1,76 @@
1
+ """Build a dependency-free receiver from the exact selected source, never a release kit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import io
7
+ import zipfile
8
+ from pathlib import Path
9
+
10
+ from fdai_deployment_cli.private_output import read_private_bytes, write_private_bytes
11
+ from fdai_deployment_cli.source_input import _read_tracked
12
+ from fdai_deployment_cli.source_snapshot import verify_source_snapshot
13
+ from fdai_deployment_cli.source_transport import _records
14
+
15
+ _MODULES = (
16
+ "__init__",
17
+ "__about__",
18
+ "contracts",
19
+ "private_output",
20
+ "source_input",
21
+ "source_snapshot",
22
+ "source_transport",
23
+ )
24
+ _ENTRY = b"from fdai_deployment_cli.source_transport import main\nraise SystemExit(main())\n"
25
+ _MAX_BOOTSTRAP = 4 * 1024 * 1024
26
+
27
+
28
+ def prepare_source_receiver(snapshot: Path, work_dir: Path, *, snapshot_digest: str) -> str:
29
+ """Retain a deterministic private zipapp and return its independently transferable digest.
30
+
31
+ Only verified snapshot modules and a fixed launcher are packaged. This does not install
32
+ dependencies, execute source, authorize effects, or grant signed-release provenance.
33
+ A retained bootstrap must match exactly; ambiguous partial outputs are never overwritten.
34
+ """
35
+ verify_source_snapshot(snapshot, expected_digest=snapshot_digest)
36
+ records = {
37
+ record["path"]: record
38
+ for record in _records(
39
+ read_private_bytes(snapshot / "source-input.json", max_bytes=16 * 1024 * 1024),
40
+ snapshot_digest,
41
+ )
42
+ }
43
+ payload = io.BytesIO()
44
+ with zipfile.ZipFile(payload, "w", compression=zipfile.ZIP_STORED) as archive:
45
+ _add(archive, "__main__.py", _ENTRY)
46
+ for module in _MODULES:
47
+ relative = f"packages/deployment-cli/src/fdai_deployment_cli/{module}.py"
48
+ content = _read_tracked(snapshot / "tree", relative, mode="100644")
49
+ record = records.get(relative)
50
+ if (
51
+ record is None
52
+ or record["mode"] != "100644"
53
+ or hashlib.sha256(content).hexdigest() != record["sha256"]
54
+ ):
55
+ raise ValueError("source receiver module differs from the selected snapshot")
56
+ if len(content) > _MAX_BOOTSTRAP or payload.tell() + len(content) > _MAX_BOOTSTRAP:
57
+ raise ValueError("source receiver bootstrap exceeds its bound")
58
+ _add(archive, f"fdai_deployment_cli/{module}.py", content)
59
+ verify_source_snapshot(snapshot, expected_digest=snapshot_digest)
60
+ content = payload.getvalue()
61
+ if len(content) > _MAX_BOOTSTRAP:
62
+ raise ValueError("source receiver bootstrap exceeds its bound")
63
+ destination = work_dir / "source-receiver.pyz"
64
+ if destination.exists() or destination.is_symlink():
65
+ if read_private_bytes(destination, max_bytes=_MAX_BOOTSTRAP) != content:
66
+ raise ValueError("source receiver bootstrap differs; preserve the run")
67
+ else:
68
+ write_private_bytes(destination, content)
69
+ return hashlib.sha256(content).hexdigest()
70
+
71
+
72
+ def _add(archive: zipfile.ZipFile, name: str, content: bytes) -> None:
73
+ member = zipfile.ZipInfo(name)
74
+ member.create_system = 3
75
+ member.external_attr = 0o100600 << 16
76
+ archive.writestr(member, content)
@@ -0,0 +1,264 @@
1
+ """Resume an immutable source installation with explicitly selected recovery execution code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+
10
+ from fdai_deployment_cli.contracts import canonical_digest, load_json_object
11
+ from fdai_deployment_cli.deployment_deadline import DeploymentDeadline
12
+ from fdai_deployment_cli.private_output import read_private_bytes
13
+ from fdai_deployment_cli.runtime_profile import RuntimeDeploymentProfile
14
+ from fdai_deployment_cli.source_input import inspect_source
15
+ from fdai_deployment_cli.source_snapshot import verify_source_snapshot
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class RecoveredApplicationEvidence:
20
+ """Verified recovered Foundation inputs for source application continuation."""
21
+
22
+ status: dict[str, object]
23
+ run_binding: str
24
+ state_handoff_digest: str
25
+ source_snapshot_digest: str
26
+
27
+
28
+ def retained_source_run(
29
+ work_dir: Path,
30
+ *,
31
+ runtime_profile_digest: str,
32
+ region: str,
33
+ monthly_cost_ceiling: int,
34
+ ) -> dict[str, object]:
35
+ """Verify original application source and selected settings without rewriting the run."""
36
+ intent = load_json_object(
37
+ read_private_bytes(work_dir / "source-intent.json", max_bytes=65536),
38
+ label="original source intent",
39
+ )
40
+ preparation = load_json_object(
41
+ read_private_bytes(work_dir / "source-preparation.json", max_bytes=65536),
42
+ label="original source preparation",
43
+ )
44
+ digest = preparation.pop("receipt_digest", None)
45
+ if (
46
+ canonical_digest(preparation) != digest
47
+ or preparation.get("schema_version") != "fdai.source-deployment-preparation.v1"
48
+ or preparation.get("state") != "prepared"
49
+ or preparation.get("provenance") != "operator-selected-source"
50
+ or preparation.get("release_signature_verified") is not False
51
+ or any(
52
+ preparation.get(key) is not False
53
+ for key in (
54
+ "apply_authorized",
55
+ "mutation_performed",
56
+ "deployment_ready",
57
+ "subscription_ready",
58
+ )
59
+ )
60
+ or preparation.get("intent_digest") != canonical_digest(intent)
61
+ or intent.get("schema_version") != "fdai.source-deployment-intent.v1"
62
+ or intent.get("environment") != "dev"
63
+ or intent.get("region") != region
64
+ or intent.get("monthly_cost_ceiling") != monthly_cost_ceiling
65
+ or preparation.get("runtime_profile_digest") != runtime_profile_digest
66
+ or not isinstance(intent.get("runtime_profile"), dict)
67
+ or canonical_digest(intent["runtime_profile"]) != runtime_profile_digest
68
+ ):
69
+ raise ValueError("source recovery must preserve the original installation settings")
70
+ snapshot = verify_source_snapshot(
71
+ work_dir / "source-snapshot", expected_digest=str(preparation.get("source_snapshot_digest"))
72
+ )
73
+ if (
74
+ snapshot != intent.get("source")
75
+ or snapshot.get("source_commit") != preparation.get("source_commit")
76
+ or canonical_digest(snapshot) != preparation.get("source_input_digest")
77
+ or canonical_digest(snapshot) != intent.get("source_input_digest")
78
+ ):
79
+ raise ValueError("source recovery application snapshot differs from the original run")
80
+ preparation["receipt_digest"] = digest
81
+ return preparation
82
+
83
+
84
+ def recovered_application_evidence(
85
+ *,
86
+ work_dir: Path,
87
+ recovery_directory: Path,
88
+ progress: dict[str, object],
89
+ runtime_profile_digest: str,
90
+ region: str,
91
+ monthly_cost_ceiling: int,
92
+ ) -> RecoveredApplicationEvidence:
93
+ """Reverify the complete recovered handoff before source application effects."""
94
+
95
+ prepared = retained_source_run(
96
+ work_dir,
97
+ runtime_profile_digest=runtime_profile_digest,
98
+ region=region,
99
+ monthly_cost_ceiling=monthly_cost_ceiling,
100
+ )
101
+ if (
102
+ progress.get("stage") != "application-plan"
103
+ or progress.get("reason_code") != "source_application_continuation_ready"
104
+ or progress.get("next_action") != "continue_with_original_source_snapshot"
105
+ ):
106
+ raise ValueError("source recovery is not ready for application continuation")
107
+
108
+ foundation_root = work_dir / "foundation"
109
+ marker = _private_record(foundation_root / "source-genesis.json", "source Genesis marker")
110
+ marker_digest = marker.pop("receipt_digest", None)
111
+ recovery = _private_record(
112
+ recovery_directory / "recovery-apply-receipt.json",
113
+ "Foundation recovery receipt",
114
+ )
115
+ recovery_digest = recovery.pop("receipt_digest", None)
116
+ enrollment = _private_record(
117
+ recovery_directory / "runner-enrollment-receipt.json",
118
+ "recovered runner enrollment receipt",
119
+ )
120
+ enrollment_digest = enrollment.pop("receipt_digest", None)
121
+ state = _private_record(
122
+ recovery_directory / "foundation-state-handoff-receipt.json",
123
+ "recovered Foundation state receipt",
124
+ )
125
+ state_digest = state.pop("receipt_digest", None)
126
+ handoff = read_private_bytes(
127
+ recovery_directory / "recovery-private-handoff.json",
128
+ max_bytes=1024 * 1024,
129
+ )
130
+ status = _private_record(foundation_root / "status.json", "source Foundation status")
131
+ required_state = (
132
+ "effect_verified",
133
+ "runner_attested",
134
+ "remote_backend_authority_verified",
135
+ "zero_change_verified",
136
+ "remote_transient_deleted",
137
+ "local_state_deleted",
138
+ )
139
+ if (
140
+ canonical_digest(marker) != marker_digest
141
+ or marker.get("source_commit") != prepared["source_commit"]
142
+ or marker.get("run_binding") != progress.get("run_binding")
143
+ or marker.get("target_binding") != progress.get("target_binding")
144
+ or canonical_digest(recovery) != recovery_digest
145
+ or recovery.get("schema_version") != "fdai.foundation-recovery-receipt.v1"
146
+ or recovery.get("state") != "verified"
147
+ or recovery.get("source_commit") != prepared["source_commit"]
148
+ or recovery.get("control_plane_readback_verified") is not True
149
+ or recovery.get("zero_change_verified") is not True
150
+ or recovery_digest != progress.get("foundation_receipt_digest")
151
+ or canonical_digest(enrollment) != enrollment_digest
152
+ or enrollment.get("schema_version") != "fdai.genesis-runner-enrollment-receipt.v1"
153
+ or enrollment.get("state") != "attested"
154
+ or enrollment.get("foundation_receipt_digest") != recovery_digest
155
+ or enrollment_digest != progress.get("enrollment_receipt_digest")
156
+ or canonical_digest(state) != state_digest
157
+ or state.get("schema_version") != "fdai.genesis-foundation-state-handoff-receipt.v1"
158
+ or state.get("state") != "verified"
159
+ or state.get("source_commit") != prepared["source_commit"]
160
+ or state.get("target_binding") != progress.get("target_binding")
161
+ or state.get("foundation_receipt_digest") != recovery_digest
162
+ or state.get("enrollment_receipt_digest") != enrollment_digest
163
+ or any(state.get(field) is not True for field in required_state)
164
+ or state_digest != progress.get("state_handoff_digest")
165
+ or canonical_digest(load_json_object(handoff, label="recovered Foundation handoff"))
166
+ != recovery.get("handoff_digest")
167
+ ):
168
+ raise ValueError("recovered source application evidence differs from the verified handoff")
169
+ return RecoveredApplicationEvidence(
170
+ status=status,
171
+ run_binding=str(marker["run_binding"]),
172
+ state_handoff_digest=str(state_digest),
173
+ source_snapshot_digest=str(prepared["source_snapshot_digest"]),
174
+ )
175
+
176
+
177
+ def _private_record(path: Path, label: str) -> dict[str, object]:
178
+ return load_json_object(
179
+ read_private_bytes(path, max_bytes=1024 * 1024),
180
+ label=label,
181
+ max_bytes=1024 * 1024,
182
+ )
183
+
184
+
185
+ def resume_source_installation(
186
+ *,
187
+ source_root: Path,
188
+ work_dir: Path,
189
+ recovery_directory: Path,
190
+ runtime_profile: RuntimeDeploymentProfile,
191
+ region: str,
192
+ monthly_cost_ceiling: int,
193
+ timeout_seconds: int,
194
+ approval_file: Path | None,
195
+ ) -> dict[str, object]:
196
+ """Invoke current recovery code while retaining the original application's exact source.
197
+
198
+ This explicit route never starts a new installation, renews an approval, or
199
+ repeats Foundation apply. Only receipt-bound enrollment and migration may advance.
200
+ """
201
+ from fdai_deployment_cli.source_azure import _capture
202
+
203
+ deadline = DeploymentDeadline(timeout_seconds)
204
+ source = inspect_source(source_root)
205
+ work_dir, recovery_directory = work_dir.absolute(), recovery_directory.absolute()
206
+ prepared = retained_source_run(
207
+ work_dir,
208
+ runtime_profile_digest=runtime_profile.digest,
209
+ region=region,
210
+ monthly_cost_ceiling=monthly_cost_ceiling,
211
+ )
212
+ environment = {
213
+ key: value
214
+ for key, value in os.environ.items()
215
+ if key
216
+ in {"HOME", "PATH", "AZURE_CONFIG_DIR", "LANG", "SSL_CERT_FILE", "REQUESTS_CA_BUNDLE"}
217
+ }
218
+ environment["PYTHONPATH"] = str(source.root / "packages/deployment-cli/src")
219
+ source.reverify()
220
+ result = _capture(
221
+ (
222
+ sys.executable,
223
+ str(source.root / "scripts/deployment/azure/source_recovery.py"),
224
+ "--work-dir",
225
+ str(work_dir),
226
+ "--recovery-directory",
227
+ str(recovery_directory),
228
+ "--runtime-profile-digest",
229
+ runtime_profile.digest,
230
+ "--region",
231
+ region,
232
+ "--monthly-cost-ceiling",
233
+ str(monthly_cost_ceiling),
234
+ "--timeout-seconds",
235
+ str(deadline.remaining(14400)),
236
+ *(
237
+ ("--approval-file", str(approval_file.absolute()))
238
+ if approval_file is not None
239
+ else ()
240
+ ),
241
+ ),
242
+ source.root,
243
+ environment,
244
+ deadline.remaining(14400),
245
+ )
246
+ source.reverify()
247
+ if (
248
+ result.get("schema_version") != "fdai.source-recovery-progress.v1"
249
+ or result.get("state") != "review"
250
+ or result.get("source_commit") != prepared["source_commit"]
251
+ or result.get("execution_source_commit") != source.commit
252
+ or result.get("runtime_profile_digest") != runtime_profile.digest
253
+ or result.get("provenance") != "operator-selected-source"
254
+ or any(
255
+ result.get(key) is not False
256
+ for key in ("apply_authorized", "deployment_ready", "release_signature_verified")
257
+ )
258
+ or canonical_digest(
259
+ {key: value for key, value in result.items() if key != "receipt_digest"}
260
+ )
261
+ != result.get("receipt_digest")
262
+ ):
263
+ raise ValueError("source recovery returned progress for a different execution context")
264
+ return result
@@ -0,0 +1,81 @@
1
+ """Verify source-bound runtime content without release trust or execution authority."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+
8
+ from fdai_deployment_cli.contracts import canonical_digest
9
+ from fdai_deployment_cli.runtime_build import _regular_digest
10
+ from fdai_deployment_cli.runtime_release import load_runtime_release, validate_runtime_images
11
+ from fdai_deployment_cli.source_snapshot import verify_source_snapshot
12
+
13
+
14
+ def verify_source_runtime(
15
+ *,
16
+ snapshot: Path,
17
+ snapshot_digest: str,
18
+ runtime_root: Path,
19
+ runtime_digest: str,
20
+ deployment_bundle: Path,
21
+ bundle_digest: str,
22
+ platform_tag: str,
23
+ ) -> dict[str, object]:
24
+ """Return no-authority content evidence for independently pinned local inputs.
25
+
26
+ This read-only check rejects changed snapshots, catalogs, payloads, revision,
27
+ platform or bundle bytes. OCI validation checks all service images and ClamAV;
28
+ other payloads remain opaque. No signature, bundle-source correspondence,
29
+ toolchain, support semantics, host identity or execution eligibility is proved.
30
+ Callers must preserve exclusive ownership and reverify inputs before later use.
31
+ """
32
+ for digest in (snapshot_digest, runtime_digest, bundle_digest):
33
+ if not isinstance(digest, str) or re.fullmatch(r"[0-9a-f]{64}", digest) is None:
34
+ raise ValueError("source runtime requires independently pinned SHA-256 digests")
35
+ if any(not path.is_absolute() for path in (snapshot, runtime_root, deployment_bundle)):
36
+ raise ValueError("source runtime paths must be absolute")
37
+ source = verify_source_snapshot(snapshot, expected_digest=snapshot_digest)
38
+ commit = source.get("source_commit")
39
+ if not isinstance(commit, str) or re.fullmatch(r"[0-9a-f]{40}", commit) is None:
40
+ raise ValueError("source runtime source commit is invalid")
41
+ release = load_runtime_release(
42
+ runtime_root, expected_source_commit=commit, expected_platform_tag=platform_tag
43
+ )
44
+ if release.digest != runtime_digest:
45
+ raise ValueError("source runtime catalog differs from the retained digest")
46
+ if (
47
+ release.deployment_bundle_sha256 != bundle_digest
48
+ or _regular_digest(deployment_bundle, label="source deployment bundle") != bundle_digest
49
+ ):
50
+ raise ValueError("source runtime deployment bundle differs from the retained digest")
51
+ images = validate_runtime_images(runtime_root, release)
52
+ observed = load_runtime_release(
53
+ runtime_root, expected_source_commit=commit, expected_platform_tag=platform_tag
54
+ )
55
+ if (
56
+ observed.digest != runtime_digest
57
+ or verify_source_snapshot(snapshot, expected_digest=snapshot_digest) != source
58
+ or _regular_digest(deployment_bundle, label="source deployment bundle") != bundle_digest
59
+ ):
60
+ raise ValueError("source runtime inputs changed during verification")
61
+ result: dict[str, object] = {
62
+ "schema_version": "fdai.source-runtime-content.v1",
63
+ "state": "content-verified",
64
+ "source_commit": commit,
65
+ "source_snapshot_digest": snapshot_digest,
66
+ "runtime_release_digest": runtime_digest,
67
+ "deployment_bundle_sha256": bundle_digest,
68
+ "platform_tag": platform_tag,
69
+ "artifact_count": len(release.artifact_paths),
70
+ "image_content_digests": images,
71
+ "provenance": "operator-selected-source",
72
+ "release_signature_verified": False,
73
+ "bundle_source_verified": False,
74
+ "support_contents_verified": False,
75
+ "registry_published": False,
76
+ "apply_authorized": False,
77
+ "deployment_ready": False,
78
+ "mutation_performed": False,
79
+ }
80
+ result["receipt_digest"] = canonical_digest(result)
81
+ return result