vis-agent 0.2.37.dev7443__tar.gz → 0.2.38.dev7473__tar.gz

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 (99) hide show
  1. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/PKG-INFO +1 -1
  2. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/gateway.json +1 -1
  3. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/pyproject.toml +1 -1
  4. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/engine/_client.py +2 -2
  5. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/extension_package.py +57 -23
  6. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_extension_package.py +101 -0
  7. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_goals.py +1 -1
  8. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/.gitignore +0 -0
  9. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/LICENSE +0 -0
  10. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/README.md +0 -0
  11. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/activity-arguments.json +0 -0
  12. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/activity-cases.json +0 -0
  13. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/activity-copy.json +0 -0
  14. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/activity-execution.json +0 -0
  15. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/activity-groups.json +0 -0
  16. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/activity-read-session.json +0 -0
  17. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/activity-reads.json +0 -0
  18. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/activity-results.json +0 -0
  19. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/activity-tables.json +0 -0
  20. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/activity.json +0 -0
  21. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/diff.json +0 -0
  22. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/live-primitives.json +0 -0
  23. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/session-health.json +0 -0
  24. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/fixtures/view.json +0 -0
  25. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/activity.json +0 -0
  26. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/agents.json +0 -0
  27. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/automations.json +0 -0
  28. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/common.json +0 -0
  29. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/config.json +0 -0
  30. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/content.json +0 -0
  31. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/council.json +0 -0
  32. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/diff.json +0 -0
  33. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/improve.json +0 -0
  34. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/provider.json +0 -0
  35. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/rooms.json +0 -0
  36. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/symbol.json +0 -0
  37. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/toggle.json +0 -0
  38. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/contracts/schema/view.json +0 -0
  39. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/examples/greeter/tests/test_greeter.py +0 -0
  40. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/hatch_build.py +0 -0
  41. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/__init__.py +0 -0
  42. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/_contracts.py +0 -0
  43. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/_outside.py +0 -0
  44. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/_wire.py +0 -0
  45. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/activity.py +0 -0
  46. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/engine/__init__.py +0 -0
  47. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/engine/_agent.py +0 -0
  48. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/engine/_agents.py +0 -0
  49. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/engine/_council.py +0 -0
  50. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/engine/_extensions.py +0 -0
  51. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/engine/_local.py +0 -0
  52. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/extension.py +0 -0
  53. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/py.typed +0 -0
  54. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/rooms.py +0 -0
  55. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/src/blockether/vis/views.py +0 -0
  56. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/conftest.py +0 -0
  57. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_activity.py +0 -0
  58. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_agent.py +0 -0
  59. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_agent_extension_lifecycle.py +0 -0
  60. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_agent_extensions.py +0 -0
  61. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_agents.py +0 -0
  62. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_api_guides.py +0 -0
  63. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_authoring.py +0 -0
  64. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_catalog.py +0 -0
  65. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_catalog_recipe.py +0 -0
  66. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_ci_report_recipe.py +0 -0
  67. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_client.py +0 -0
  68. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_client_extensions_engine.py +0 -0
  69. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_code_quality_recipe.py +0 -0
  70. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_contracts.py +0 -0
  71. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_council.py +0 -0
  72. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_declarations.py +0 -0
  73. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_engine.py +0 -0
  74. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_engine_docstrings.py +0 -0
  75. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_extension_reference.py +0 -0
  76. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_gateway_performance.py +0 -0
  77. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_live_primitives.py +0 -0
  78. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_live_wait.py +0 -0
  79. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_local.py +0 -0
  80. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_method_typing.py +0 -0
  81. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_monitor_recipe.py +0 -0
  82. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_namespace.py +0 -0
  83. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_outside.py +0 -0
  84. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_providers.py +0 -0
  85. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_record_arguments.py +0 -0
  86. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_registration.py +0 -0
  87. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_repair_hooks.py +0 -0
  88. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_result_sequence.py +0 -0
  89. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_rooms.py +0 -0
  90. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_scoped_settings.py +0 -0
  91. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_sdk_bootstrap.py +0 -0
  92. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_sdk_guide.py +0 -0
  93. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_sdk_typing.py +0 -0
  94. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_structured_live.py +0 -0
  95. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_symbol_contract.py +0 -0
  96. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_testing.py +0 -0
  97. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_text_list_arguments.py +0 -0
  98. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_tools.py +0 -0
  99. {vis_agent-0.2.37.dev7443 → vis_agent-0.2.38.dev7473}/tests/test_views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vis-agent
3
- Version: 0.2.37.dev7443
3
+ Version: 0.2.38.dev7473
4
4
  Summary: The Vis Python SDK — author extensions and control local or remote engines.
5
5
  Project-URL: Homepage, https://vis.blockether.com
6
6
  Project-URL: Documentation, https://vis.blockether.com
@@ -1779,7 +1779,7 @@
1779
1779
  "objective": {
1780
1780
  "type": "string",
1781
1781
  "minLength": 1,
1782
- "maxLength": 8192,
1782
+ "maxLength": 44000,
1783
1783
  "pattern": "\\S"
1784
1784
  },
1785
1785
  "status": {
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
8
8
 
9
9
  [project]
10
10
  name = "vis-agent"
11
- version = "0.2.37.dev7443"
11
+ version = "0.2.38.dev7473"
12
12
  description = "The Vis Python SDK — author extensions and control local or remote engines."
13
13
  readme = "README.md"
14
14
  requires-python = ">=3.11"
@@ -3251,8 +3251,8 @@ class Session:
3251
3251
  replies. The last iteration can run its tools, and then the limit blocks
3252
3252
  the next model request. Token usage is recorded only as a statistic.
3253
3253
  """
3254
- if not isinstance(objective, str) or not 1 <= len(objective.strip()) <= 8192:
3255
- raise ValueError("objective must contain 1–8192 characters")
3254
+ if not isinstance(objective, str) or not 1 <= len(objective.strip()) <= 44000:
3255
+ raise ValueError("objective must contain 1–44000 characters")
3256
3256
  if iteration_budget is not None and (
3257
3257
  type(iteration_budget) is not int
3258
3258
  or not 1 <= iteration_budget <= 9007199254740991
@@ -954,6 +954,59 @@ def install(
954
954
  return _admit(source, directory, subdirectory, revision, vis_version, release)
955
955
 
956
956
 
957
+ def _replace_managed(directory, active, current, release, vis_version):
958
+ """Activate a managed version and keep its sync ownership in the same operation."""
959
+ name = current["name"]
960
+ lock = directory / ".sync-lock"
961
+ fd = _acquire(lock)
962
+ try:
963
+ records = _sync_records(directory)
964
+ owned = records.get(name)
965
+ destination = _destination(directory, name)
966
+ if owned is not None and not _sync_owned(destination, owned):
967
+ raise ValueError(
968
+ "Existing extension was changed externally; no files replaced"
969
+ )
970
+ spec = _sync_spec(owned["spec"]) if owned is not None else None
971
+ previous_target = str(active)
972
+ snapshot = destination.parent / release["version"]
973
+ receipt_path = snapshot / "receipt.json"
974
+ previous_receipt = receipt_path.read_bytes() if receipt_path.is_file() else None
975
+ result = _admit(
976
+ current["repository_url"],
977
+ directory,
978
+ current["subdirectory"],
979
+ release["revision"],
980
+ vis_version,
981
+ release,
982
+ name,
983
+ active.name,
984
+ )
985
+ if spec is None:
986
+ return result
987
+ # Configuration pins remain authoritative on the next reconciliation.
988
+ if spec["revision"] is not None:
989
+ spec["revision"] = result["revision"]
990
+ elif spec["version"] not in (None, LATEST):
991
+ spec["version"] = result["version"]
992
+ try:
993
+ _save_sync_records(
994
+ directory,
995
+ {
996
+ **records,
997
+ name: {"spec": spec, "target": result["path"], "result": result},
998
+ },
999
+ )
1000
+ except BaseException:
1001
+ _restore_saved_link(directory, name, result["path"], previous_target)
1002
+ if previous_receipt is not None:
1003
+ receipt_path.write_bytes(previous_receipt)
1004
+ raise
1005
+ return result
1006
+ finally:
1007
+ _release(lock, fd)
1008
+
1009
+
957
1010
  def update(
958
1011
  source, directory, trust=False, version=None, vis_version=None, subdirectory=None
959
1012
  ):
@@ -988,16 +1041,7 @@ def update(
988
1041
  }
989
1042
  if Version(release["version"]) == Version(current["version"]):
990
1043
  raise ValueError("A published version cannot change its approved commit")
991
- return _admit(
992
- current["repository_url"],
993
- directory,
994
- current["subdirectory"],
995
- release["revision"],
996
- vis_version,
997
- release,
998
- name,
999
- active.name,
1000
- )
1044
+ return _replace_managed(directory, active, current, release, vis_version)
1001
1045
 
1002
1046
 
1003
1047
  def rollback(
@@ -1016,7 +1060,6 @@ def rollback(
1016
1060
  folder = None
1017
1061
  active, current = _installed_repository(directory, repository, folder)
1018
1062
  assert active is not None and current is not None # A required lookup raises.
1019
- name = current["name"]
1020
1063
  if version is not None:
1021
1064
  release = _select(
1022
1065
  _releases(current["repository_url"], current["subdirectory"]), version
@@ -1031,24 +1074,15 @@ def rollback(
1031
1074
  raise ValueError(
1032
1075
  "No previous installation; choose an approved older --version"
1033
1076
  )
1034
- release = _receipt(active.parent / _version(previous), name)
1077
+ release = _receipt(active.parent / _version(previous), current["name"])
1035
1078
  if (
1036
1079
  release.get("repository_url") != current["repository_url"]
1037
1080
  or release.get("subdirectory") != current["subdirectory"]
1038
- or release.get("name") != name
1081
+ or release.get("name") != current["name"]
1039
1082
  or not re.fullmatch(r"[0-9a-f]{40}", release.get("revision", ""))
1040
1083
  ):
1041
1084
  raise ValueError("Invalid previous package receipt")
1042
- return _admit(
1043
- current["repository_url"],
1044
- directory,
1045
- current["subdirectory"],
1046
- release["revision"],
1047
- vis_version,
1048
- release,
1049
- name,
1050
- active.name,
1051
- )
1085
+ return _replace_managed(directory, active, current, release, vis_version)
1052
1086
 
1053
1087
 
1054
1088
  def _sync_spec(spec):
@@ -1075,6 +1075,107 @@ def test_sync_pins_updates_refresh_and_rollback(releases):
1075
1075
  )
1076
1076
 
1077
1077
 
1078
+ def test_explicit_update_and_rollback_keep_sync_ownership(releases):
1079
+ metadata, target, _ = releases
1080
+ newer = metadata.pop(1)
1081
+ configured = {
1082
+ "vis-greeter": {"source": REPOSITORY, "subdirectory": "plugins/greeting"}
1083
+ }
1084
+ package.sync(configured, target, trust=True)
1085
+ metadata.append(newer)
1086
+ package.update(REPOSITORY, target, trust=True)
1087
+ updated = package.sync(configured, target, trust=True)[0]
1088
+ assert (updated["status"], updated.get("version")) == ("cached", "1.1.0")
1089
+ package.rollback(REPOSITORY, target, trust=True)
1090
+ restored = package.sync(configured, target, trust=True)[0]
1091
+ assert (restored["status"], restored.get("version")) == ("cached", "1.0.0")
1092
+
1093
+
1094
+ @pytest.mark.parametrize("operation", ["update", "rollback"])
1095
+ def test_managed_activation_restores_ownership_when_sync_receipt_write_fails(
1096
+ releases, monkeypatch, operation
1097
+ ):
1098
+ metadata, target, _ = releases
1099
+ newer = metadata.pop(1)
1100
+ configured = {
1101
+ "vis-greeter": {"source": REPOSITORY, "subdirectory": "plugins/greeting"}
1102
+ }
1103
+ package.sync(configured, target, trust=True)
1104
+ metadata.append(newer)
1105
+ if operation == "rollback":
1106
+ package.update(REPOSITORY, target, trust=True)
1107
+ pointer = target / "vis-greeter/current"
1108
+ previous_target = pointer.resolve()
1109
+ previous_records = (target / ".sync.json").read_bytes()
1110
+ receipt_path = target / "vis-greeter/1.0.0/receipt.json"
1111
+ previous_receipt = receipt_path.read_bytes()
1112
+
1113
+ def fail(*_):
1114
+ raise OSError("Fixture receipt write failure")
1115
+
1116
+ monkeypatch.setattr(package, "_save_sync_records", fail)
1117
+ with pytest.raises(OSError, match="Fixture receipt write failure"):
1118
+ getattr(package, operation)(REPOSITORY, target, trust=True)
1119
+ assert pointer.resolve() == previous_target
1120
+ assert (target / ".sync.json").read_bytes() == previous_records
1121
+ assert receipt_path.read_bytes() == previous_receipt
1122
+ assert package._sync_owned(pointer, package._sync_records(target)["vis-greeter"])
1123
+ assert package.sync(configured, target, trust=True)[0]["status"] == "cached"
1124
+
1125
+
1126
+ @pytest.mark.parametrize("operation", ["update", "rollback"])
1127
+ def test_managed_activation_refuses_an_externally_changed_sync_pointer(
1128
+ releases, operation
1129
+ ):
1130
+ metadata, target, _ = releases
1131
+ newer = metadata.pop(1)
1132
+ configured = {
1133
+ "vis-greeter": {"source": REPOSITORY, "subdirectory": "plugins/greeting"}
1134
+ }
1135
+ package.sync(configured, target, trust=True)
1136
+ metadata.append(newer)
1137
+ package.update(REPOSITORY, target, trust=True)
1138
+ pointer = target / "vis-greeter/current"
1139
+ pointer.unlink()
1140
+ pointer.symlink_to("1.0.0")
1141
+ before = (target / ".sync.json").read_bytes()
1142
+ kwargs = {"version": "1.1.0"} if operation == "update" else {}
1143
+ with pytest.raises(ValueError):
1144
+ getattr(package, operation)(REPOSITORY, target, trust=True, **kwargs)
1145
+ assert pointer.resolve().name == "1.0.0"
1146
+ assert (target / ".sync.json").read_bytes() == before
1147
+
1148
+
1149
+ @pytest.mark.parametrize("pin", ["version", "revision"])
1150
+ def test_explicit_update_does_not_override_a_configuration_pin(releases, pin):
1151
+ metadata, target, _ = releases
1152
+ spec = {"source": REPOSITORY, "subdirectory": "plugins/greeting"}
1153
+ spec[pin] = metadata[0][pin]
1154
+ configured = {"vis-greeter": spec}
1155
+ package.sync(configured, target, trust=True)
1156
+ package.update(REPOSITORY, target, trust=True)
1157
+ restored = package.sync(configured, target, trust=True)[0]
1158
+ assert (restored["status"], restored.get("version")) == ("updated", "1.0.0")
1159
+
1160
+
1161
+ def test_update_validates_sync_spec_before_changing_the_active_version(releases):
1162
+ metadata, target, _ = releases
1163
+ newer = metadata.pop(1)
1164
+ configured = {
1165
+ "vis-greeter": {"source": REPOSITORY, "subdirectory": "plugins/greeting"}
1166
+ }
1167
+ package.sync(configured, target, trust=True)
1168
+ metadata.append(newer)
1169
+ records = package._sync_records(target)
1170
+ records["vis-greeter"]["spec"] = {}
1171
+ package._save_sync_records(target, records)
1172
+ before = (target / ".sync.json").read_bytes()
1173
+ with pytest.raises(ValueError, match="source"):
1174
+ package.update(REPOSITORY, target, trust=True)
1175
+ assert (target / "vis-greeter/current").resolve().name == "1.0.0"
1176
+ assert (target / ".sync.json").read_bytes() == before
1177
+
1178
+
1078
1179
  def test_sync_latest_tracks_approved_stable_releases(releases):
1079
1180
  metadata, target, commands = releases
1080
1181
  newer = metadata.pop(1)
@@ -40,7 +40,7 @@ def test_goal_uses_slash_turn(budget):
40
40
  [
41
41
  ("", None),
42
42
  (" ", None),
43
- ("x" * 8193, None),
43
+ ("x" * 44001, None),
44
44
  (None, None),
45
45
  ("work", 0),
46
46
  ("work", -1),