@mriekki/project-forge 4.10.2 → 4.10.3

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@mriekki/project-forge",
4
- "version": "4.10.2",
4
+ "version": "4.10.3",
5
5
  "defaultProfile": "greenfield-project",
6
6
  "profiles": {
7
7
  "greenfield-project": {
@@ -21,7 +21,7 @@
21
21
  "canonicalDescriptor": "distributions/greenfield-workspace.json"
22
22
  },
23
23
  "deployment": {
24
- "revision": "5131dd0db9bbccda1647eee352f6bdcad7f5c112",
24
+ "revision": "75c272db383f1803383e8cd4ad4511537c175abe",
25
25
  "distribution": "greenfield-project",
26
26
  "descriptor": "assets/deployment/distribution.json",
27
27
  "sourceRoot": "assets/generated/deployment",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@mriekki/project-forge",
4
- "version": "4.10.2",
4
+ "version": "4.10.3",
5
5
  "components": {
6
6
  "core": {
7
7
  "revision": "8796fd8dc22df8b64078e5b460e1008a8c7a5f7d",
@@ -326,7 +326,7 @@
326
326
  ]
327
327
  },
328
328
  "deployment": {
329
- "revision": "5131dd0db9bbccda1647eee352f6bdcad7f5c112",
329
+ "revision": "75c272db383f1803383e8cd4ad4511537c175abe",
330
330
  "distribution": "greenfield-project",
331
331
  "files": [
332
332
  {
@@ -402,7 +402,7 @@
402
402
  {
403
403
  "source": "src/project_forge_deployment/cloudflare_ingress.py",
404
404
  "destination": "tools/project-forge-deployment/project_forge_deployment/cloudflare_ingress.py",
405
- "sha256": "98c77950df1d0bba36da1244001bdd157251238778e7068be0c6c20d2683d2d4"
405
+ "sha256": "ca2c80e0b64792f550a08fc51994bdfdac42644c35f23cdf7ae5c50b468109f8"
406
406
  },
407
407
  {
408
408
  "source": "src/project_forge_deployment/cloudflare_ingress_remote.py",
@@ -412,7 +412,7 @@
412
412
  {
413
413
  "source": "src/project_forge_deployment/cloudflare_route_apply.py",
414
414
  "destination": "tools/project-forge-deployment/project_forge_deployment/cloudflare_route_apply.py",
415
- "sha256": "9c41b4b35d15bc46e7828f0366dc53c58be47f4414e7d90dfd176ea922bf294a"
415
+ "sha256": "d3fe77a2f61ae281308ad1046ee1e6e5ea460f3605c5cd275cc16cff3e9bb568"
416
416
  },
417
417
  {
418
418
  "source": "src/project_forge_deployment/cli.py",
@@ -37,6 +37,11 @@ PUBLIC_INGRESS_ROUTE_WRITE_PHRASE = "[PUBLIC INGRESS ROUTE WRITE]"
37
37
  PUBLIC_INGRESS_ROUTE_REMOVE_PHRASE = "[PUBLIC INGRESS ROUTE REMOVE]"
38
38
  CLOUDFLARE_API_TOKEN_ENV = "PROJECT_FORGE_CLOUDFLARE_API_TOKEN"
39
39
 
40
+
41
+ class CloudflarePostWriteVerificationError(DeploymentKitError):
42
+ """Final public-route verification failed after mutations completed."""
43
+
44
+
40
45
  _SLUG_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$")
41
46
  _REPO_RE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$")
42
47
  _LABEL_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$")
@@ -698,27 +703,32 @@ def apply_public_route(plan: dict[str, Any], *, api_token: str) -> dict[str, Any
698
703
  pass
699
704
  raise
700
705
 
701
- final_inventory = cloudflare_route_inventory(name, scope)
702
- if _find_route(final_inventory, spec["routeId"]) != spec:
703
- raise DeploymentKitError("Public route registration returned without exact host ownership metadata.")
704
- final_config = cloudflare_api.get_tunnel_configuration(api_token, host["accountId"], host["tunnelId"])
705
- if final_config != plan["desiredTunnelConfig"]:
706
- raise DeploymentKitError("Cloudflare tunnel configuration does not match the reviewed route plan after apply.")
707
- final_dns = _exact_dns_record(
708
- cloudflare_api.list_dns_records(api_token, host["zoneId"], name=spec["publicHostname"]),
709
- spec=spec,
710
- tunnel_id=host["tunnelId"],
711
- )
712
- if final_dns is None:
713
- raise DeploymentKitError("Cloudflare DNS record is missing after public-route apply.")
714
- return {
715
- "targetName": name,
716
- "scope": scope,
717
- "route": spec,
718
- "dnsRecordId": str(final_dns["id"]),
719
- "publicUrl": f"https://{spec['publicHostname']}",
720
- "mutated": True,
721
- }
706
+ try:
707
+ final_inventory = cloudflare_route_inventory(name, scope)
708
+ if _find_route(final_inventory, spec["routeId"]) != spec:
709
+ raise DeploymentKitError("Public route registration returned without exact host ownership metadata.")
710
+ final_config = cloudflare_api.get_tunnel_configuration(api_token, host["accountId"], host["tunnelId"])
711
+ if final_config != plan["desiredTunnelConfig"]:
712
+ raise DeploymentKitError("Cloudflare tunnel configuration does not match the reviewed route plan after apply.")
713
+ final_dns = _exact_dns_record(
714
+ cloudflare_api.list_dns_records(api_token, host["zoneId"], name=spec["publicHostname"]),
715
+ spec=spec,
716
+ tunnel_id=host["tunnelId"],
717
+ )
718
+ if final_dns is None:
719
+ raise DeploymentKitError("Cloudflare DNS record is missing after public-route apply.")
720
+ return {
721
+ "targetName": name,
722
+ "scope": scope,
723
+ "route": spec,
724
+ "dnsRecordId": str(final_dns["id"]),
725
+ "publicUrl": f"https://{spec['publicHostname']}",
726
+ "mutated": True,
727
+ }
728
+ except Exception as error:
729
+ raise CloudflarePostWriteVerificationError(
730
+ f"Cloudflare public-route post-write verification failed: {error}"
731
+ ) from error
722
732
 
723
733
 
724
734
  def plan_public_route_removal(
@@ -17,12 +17,6 @@ from .errors import DeploymentKitError
17
17
  from .profiles import load_profile
18
18
  from .ssh import verify_access
19
19
 
20
- _FINAL_VERIFICATION_ERRORS = {
21
- "Public route registration returned without exact host ownership metadata.",
22
- "Cloudflare tunnel configuration does not match the reviewed route plan after apply.",
23
- "Cloudflare DNS record is missing after public-route apply.",
24
- }
25
-
26
20
 
27
21
  def _reviewed_config_matches(actual: Any, reviewed: Any) -> bool:
28
22
  """Match reviewed config after removing only the observed safe provider default."""
@@ -131,19 +125,14 @@ def apply_public_route(plan: dict[str, Any], *, api_token: str) -> dict[str, Any
131
125
  """Apply a reviewed public route with bounded post-write recovery.
132
126
 
133
127
  The underlying apply retains all pre-write stale-state checks and its write
134
- rollback. Recovery is entered only for errors that are emitted exclusively
135
- by the final verification phase, so preflight failures can never be turned
128
+ rollback. Recovery is entered only for the typed error emitted by the
129
+ post-write verification phase, so preflight failures can never be turned
136
130
  into success by observing later provider state.
137
131
  """
138
132
  try:
139
133
  return ingress.apply_public_route(plan, api_token=api_token)
140
- except Exception as original_error:
141
- if (
142
- not isinstance(original_error, DeploymentKitError)
143
- or str(original_error) not in _FINAL_VERIFICATION_ERRORS
144
- or not isinstance(plan, dict)
145
- or plan.get("action") != "register"
146
- ):
134
+ except ingress.CloudflarePostWriteVerificationError as original_error:
135
+ if not isinstance(plan, dict) or plan.get("action") != "register":
147
136
  raise
148
137
  required = {
149
138
  "targetName", "scope", "route", "previousRoute", "tunnelConfig",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mriekki/project-forge",
3
- "version": "4.10.2",
3
+ "version": "4.10.3",
4
4
  "description": "Public consumer distribution and updater for Project Forge engineering assets.",
5
5
  "type": "module",
6
6
  "bin": {