gridlinegpu 0.1.7__tar.gz → 0.1.8__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 (53) hide show
  1. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/PKG-INFO +3 -1
  2. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/README.md +2 -0
  3. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/pyproject.toml +1 -1
  4. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_interfaces.py +34 -4
  5. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_job.py +22 -8
  6. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_release.py +1 -1
  7. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu.egg-info/PKG-INFO +3 -1
  8. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_client_source.py +1 -1
  9. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_contract.py +35 -3
  10. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_job.py +6 -6
  11. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_job_results.py +2 -2
  12. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/setup.cfg +0 -0
  13. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/UNICODE-LICENSE.txt +0 -0
  14. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/__init__.py +0 -0
  15. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/__main__.py +0 -0
  16. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_admission.py +0 -0
  17. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_capture.py +0 -0
  18. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_casefold.json +0 -0
  19. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_contract.py +0 -0
  20. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_credits.py +0 -0
  21. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_errors.py +0 -0
  22. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_history.py +0 -0
  23. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_outputs.py +0 -0
  24. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_public.py +0 -0
  25. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_results.py +0 -0
  26. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_run.py +0 -0
  27. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_submission.py +0 -0
  28. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/_source_transfer.py +0 -0
  29. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/cli.py +0 -0
  30. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/cli_auth.py +0 -0
  31. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/client.py +0 -0
  32. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu/errors.py +0 -0
  33. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu.egg-info/SOURCES.txt +0 -0
  34. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu.egg-info/dependency_links.txt +0 -0
  35. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu.egg-info/entry_points.txt +0 -0
  36. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu.egg-info/requires.txt +0 -0
  37. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/src/gridlinegpu.egg-info/top_level.txt +0 -0
  38. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_cli.py +0 -0
  39. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_cli_auth.py +0 -0
  40. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_client.py +0 -0
  41. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_admission.py +0 -0
  42. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_canary.py +0 -0
  43. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_capture.py +0 -0
  44. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_capture_gaps.py +0 -0
  45. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_credits.py +0 -0
  46. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_curated_run.py +0 -0
  47. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_errors.py +0 -0
  48. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_history.py +0 -0
  49. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_release.py +0 -0
  50. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_results.py +0 -0
  51. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_run.py +0 -0
  52. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_submission.py +0 -0
  53. {gridlinegpu-0.1.7 → gridlinegpu-0.1.8}/tests/test_source_transfer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gridlinegpu
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Python client for Gridline YAML workload routing
5
5
  Author: Gridline GPU
6
6
  License: Proprietary
@@ -213,6 +213,8 @@ print(job.report()["settlement"]) # reserved, held, charged and released credit
213
213
  ```
214
214
 
215
215
  `job.status()` reads the current state without waiting, and `job.cancel()` requests a stop.
216
+ `job.isolation_tier()` names the isolation tier the job's code ran under (`kernel_cgroup` or
217
+ `hardened_user_separation`, see the security page), or `None` before a machine is allocated.
216
218
  `client.history(limit=20)` lists your recent jobs, newest first. `report()` and `history()`
217
219
  raise `report_unavailable` / `history_unavailable` until those reads are switched on for
218
220
  the beta; the job itself is unaffected. A complete minimal
@@ -195,6 +195,8 @@ print(job.report()["settlement"]) # reserved, held, charged and released credit
195
195
  ```
196
196
 
197
197
  `job.status()` reads the current state without waiting, and `job.cancel()` requests a stop.
198
+ `job.isolation_tier()` names the isolation tier the job's code ran under (`kernel_cgroup` or
199
+ `hardened_user_separation`, see the security page), or `None` before a machine is allocated.
198
200
  `client.history(limit=20)` lists your recent jobs, newest first. `report()` and `history()`
199
201
  raise `report_unavailable` / `history_unavailable` until those reads are switched on for
200
202
  the beta; the job itself is unaffected. A complete minimal
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gridlinegpu"
7
- version = "0.1.7"
7
+ version = "0.1.8"
8
8
  description = "Python client for Gridline YAML workload routing"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -603,7 +603,7 @@ def validate_source_job_status(value):
603
603
  # Status v3 (job.ts): v2 plus the current task's stage, a coarse packaging step and the failure
604
604
  # envelope. failureCode, error.stage and error.code are open words (H6): a newer server's code
605
605
  # or stage is accepted and arrives with its own text. Structural vocabularies stay closed.
606
- SOURCE_JOB_STATUS_VERSIONS = (2, 3, 4, 5, 6, 7)
606
+ SOURCE_JOB_STATUS_VERSIONS = (2, 3, 4, 5, 6, 7, 8)
607
607
  SOURCE_JOB_TASK_STAGES = ("runtime_resolution", "runtime_packaging", "compute_routing")
608
608
  SOURCE_JOB_PACKAGING_STEPS = ("dependencies", "wheels", "image", "qualification", "handoff")
609
609
  _open_word = _pattern(r"[a-z_]{1,64}")
@@ -1244,10 +1244,38 @@ def validate_source_job_status_v7(value):
1244
1244
  return value
1245
1245
 
1246
1246
 
1247
+ # Status v8 (job.ts, O6-C PR-D): v7 plus the run's isolation tier. The shared vectors are
1248
+ # job-status-v8.json.
1249
+ SOURCE_JOB_ISOLATION_TIERS = ("kernel_cgroup", "hardened_user_separation", "user_separation")
1250
+ SOURCE_JOB_STATUS_V8_FIELDS = SOURCE_JOB_STATUS_V7_FIELDS + ("isolationTier",)
1251
+ _JOB_STATUS_V8 = {
1252
+ **_JOB_STATUS_V7,
1253
+ "schemaVersion": _number_literal(8),
1254
+ "isolationTier": _nullable(_one_of(*SOURCE_JOB_ISOLATION_TIERS)),
1255
+ }
1256
+
1257
+
1258
+ def validate_source_job_status_v8(value):
1259
+ """GET /jobs/{jobId}?schemaVersion=8: v7 plus ``isolationTier``, at most 64 KiB (job.ts
1260
+ sourceJobStatusV8Schema). ``isolationTier`` is the isolation tier of the machine the job's
1261
+ code ran on: ``kernel_cgroup``, ``hardened_user_separation`` (the disclosed hardened tier,
1262
+ see the security page) or ``user_separation`` (the owner canary only); null before such a
1263
+ machine. Never a location, SKU or resource id."""
1264
+ _require(value, _JOB_STATUS_V8, "jobStatus")
1265
+ _status_v6_rules(value)
1266
+ attempts = value["launchAttempts"]
1267
+ if attempts is not None and (
1268
+ attempts["refusals"] > attempts["attempt"]
1269
+ or (attempts["lastRefusal"] is None) != (attempts["refusals"] == 0)):
1270
+ _record_fail("jobStatus.launchAttempts")
1271
+ _bounded(value, SOURCE_JOB_LIMITS["statusBytes"], "jobStatus")
1272
+ return value
1273
+
1274
+
1247
1275
  def validate_source_job_status_answer(value):
1248
- """A status answer of any version this SDK reads (2 to 7), chosen by its schemaVersion. A
1249
- server that does not serve v7, v6, v5, v4 or v3 (older, or switched off) answers the highest
1250
- it serves; v2 to v5 stay readable for jobs without Step 10 facts (the server refuses them for
1276
+ """A status answer of any version this SDK reads (2 to 8), chosen by its schemaVersion. A
1277
+ server that does not serve v8, v7, v6, v5, v4 or v3 (older, or switched off) answers the
1278
+ highest it serves; v2 to v5 stay readable for jobs without Step 10 facts (the server refuses them for
1251
1279
  a job with a Step 10 outcome or execution state: status_version_unsupported)."""
1252
1280
  version = value.get("schemaVersion") if type(value) is dict else None
1253
1281
  if _number_literal(2)(version):
@@ -1262,6 +1290,8 @@ def validate_source_job_status_answer(value):
1262
1290
  return validate_source_job_status_v6(value)
1263
1291
  if _number_literal(7)(version):
1264
1292
  return validate_source_job_status_v7(value)
1293
+ if _number_literal(8)(version):
1294
+ return validate_source_job_status_v8(value)
1265
1295
  _record_fail("jobStatus.schemaVersion")
1266
1296
 
1267
1297
 
@@ -5,10 +5,12 @@ SDK gate is on; it is off by default.
5
5
  - The fields are the receipt's (spec 5.2), snake-cased. The receipt is historical: status
6
6
  may already have advanced past ``initial_phase``.
7
7
  - ``status()`` reads the current bounded status (spec 5.2/5.7); it writes nothing. It asks
8
- for status v7 (credits S4b: v6 plus ``launchAttempts``) and also accepts v6, v5, v4, v3 or
9
- v2, which an older server or a switched-off v7 (or v6, v5, v4, v3) answers; a server refuses
8
+ for status v8 (O6-C PR-D: v7 plus ``isolationTier``) and also accepts v7 (credits S4b: v6
9
+ plus ``launchAttempts``), v6, v5, v4, v3 or v2, which an older server or a switched-off v8
10
+ (or v7, v6, v5, v4, v3) answers; a server refuses
10
11
  v2-v5 for a job with a Step 10 outcome (``status_version_unsupported``). ``route()`` returns
11
- the v4-v7 route summary (spec 8.6), or None.
12
+ the v4-v8 route summary (spec 8.6), or None. ``isolation_tier()`` returns the v8 tier of the
13
+ machine the code ran on, or None (before one, or from an older answer).
12
14
  - ``funding_action()`` returns the server-owned Add funds action (status v5, spec 9.3), or
13
15
  None; ``recheck_funding()`` sends exactly that action's binding back to ask for one bounded
14
16
  recheck of the provider's funds. Neither starts a payment; the customer pays on the
@@ -60,8 +62,8 @@ from .errors import WaitTimeout
60
62
 
61
63
  _JOBS = "/api/v2/source/jobs/"
62
64
  _JOB_ID = re.compile(r"job_[a-f0-9]{32}")
63
- # Status version asked for; the answer's schemaVersion (2 to 7) names the shape served.
64
- _STATUS_VERSION = 7
65
+ # Status version asked for; the answer's schemaVersion (2 to 8) names the shape served.
66
+ _STATUS_VERSION = 8
65
67
  # Asked once more after an answer of HTTP 500: v3 reads no route plan, launch row or Step 10
66
68
  # record, so a server failure confined to those projections never makes status (or wait)
67
69
  # unavailable (8-ix, 9-xii). A Step 10 job refuses v3 (S10-R16): the 500 then stands.
@@ -220,7 +222,8 @@ class JobHandle:
220
222
  def status(self):
221
223
  """Current owner-authorized job status, bound to this job and its immutable receipt.
222
224
 
223
- Returns the validated answer as served: v6 (v5 with ``financial`` replaced by
225
+ Returns the validated answer as served: v8 (v7 plus ``isolationTier``; see
226
+ ``isolation_tier()``), v7 (v6 plus ``launchAttempts``), v6 (v5 with ``financial`` replaced by
224
227
  ``cost``, plus the Step 10 ``execution``, ``result``, ``logs`` and ``reminder`` blocks),
225
228
  v5 (v4 plus the launch phases, the ``provider_launch`` or ``execution`` task,
226
229
  ``cleanup``, ``financial`` and the funding action; see ``funding_action()``), v4 (v3
@@ -239,6 +242,17 @@ class JobHandle:
239
242
  """
240
243
  return _read_status(self._client, self.job_id, self._fail, self.receipt())
241
244
 
245
+ def isolation_tier(self):
246
+ """The isolation tier of the machine this job's code ran on (status v8), or None.
247
+
248
+ Reads status exactly as ``status()`` does (same errors) and returns ``"kernel_cgroup"``,
249
+ ``"hardened_user_separation"`` (the disclosed hardened tier: per-process memory and
250
+ file-size limits and a sampled memory check; see the security page) or
251
+ ``"user_separation"`` (the owner canary only). None before such a machine, and from a
252
+ server that answers v7 or older.
253
+ """
254
+ return self.status().get("isolationTier")
255
+
242
256
  def funding_action(self):
243
257
  """The job's current Add funds action (status v5, spec 9.3 L1721), or None.
244
258
 
@@ -455,12 +469,12 @@ def _job_handle(receipt, client):
455
469
 
456
470
 
457
471
  def _read_status(client, job_id, fail, receipt=None):
458
- """The job's validated status, asked as v7 (once more as v3 after a 500), bound to the job
472
+ """The job's validated status, asked as v8 (once more as v3 after a 500), bound to the job
459
473
  (and to its receipt when known)."""
460
474
  status, headers, data = _request_status(client, job_id, fail, _STATUS_VERSION)
461
475
  if status == 500:
462
476
  fallback = _request_status(client, job_id, fail, _FALLBACK_STATUS_VERSION)
463
- # A Step 10 job refuses v3 (S10-R16): the v7 failure stays an unavailable read.
477
+ # A Step 10 job refuses v3 (S10-R16): the v8 failure stays an unavailable read.
464
478
  if not (fallback[0] == 409 and _error_code(fallback[2]) == "status_version_unsupported"):
465
479
  status, headers, data = fallback
466
480
  if status == 200:
@@ -72,5 +72,5 @@ def sdk_surface_digest(bounds=None):
72
72
  # RECORD content digest plus the candidate SHA).
73
73
  SOURCE_RELEASE_COMPATIBILITY = {
74
74
  "contract": SOURCE_RELEASE_CONFIG_CONTRACT,
75
- "surfaces": {"sdk": "ad9e2daf472c76affddd93a36aabe19f7686b0394a953345ffae931201efedae"},
75
+ "surfaces": {"sdk": "2d6b1bd3408b61f57f788f3e68d502cdb1626c1bf0f486b4dfefc5dc9bd47f24"},
76
76
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gridlinegpu
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Python client for Gridline YAML workload routing
5
5
  Author: Gridline GPU
6
6
  License: Proprietary
@@ -213,6 +213,8 @@ print(job.report()["settlement"]) # reserved, held, charged and released credit
213
213
  ```
214
214
 
215
215
  `job.status()` reads the current state without waiting, and `job.cancel()` requests a stop.
216
+ `job.isolation_tier()` names the isolation tier the job's code ran under (`kernel_cgroup` or
217
+ `hardened_user_separation`, see the security page), or `None` before a machine is allocated.
216
218
  `client.history(limit=20)` lists your recent jobs, newest first. `report()` and `history()`
217
219
  raise `report_unavailable` / `history_unavailable` until those reads are switched on for
218
220
  the beta; the job itself is unaffected. A complete minimal
@@ -531,7 +531,7 @@ class ClientJobTests(ClientSourceTestCase):
531
531
  self.assertEqual((status["schemaVersion"], status["phase"], status["outcome"]),
532
532
  (3, "terminal", "cancelled"))
533
533
  self.assertIsNone(status["error"])
534
- self.assertEqual(self.server.queries, [("GET", "schemaVersion=7")] * 2)
534
+ self.assertEqual(self.server.queries, [("GET", "schemaVersion=8")] * 2)
535
535
  terminal = job_status_v3(self.server.job, "terminal")
536
536
  _, reason, action = describe("build_failed")
537
537
  failed = {**terminal, "outcome": "failed", "failureCode": "build_failed",
@@ -44,7 +44,7 @@ from gridlinegpu._source_interfaces import (
44
44
  validate_source_admission_receipt, validate_source_admission_request, validate_source_funding_recheck_answer,
45
45
  validate_source_job_status, validate_source_job_status_answer, validate_source_job_status_v3,
46
46
  validate_source_job_status_v4, validate_source_job_status_v5, validate_source_job_status_v6,
47
- validate_source_job_status_v7,
47
+ validate_source_job_status_v7, validate_source_job_status_v8,
48
48
  validate_source_job_stop_acknowledgement,
49
49
  validate_source_preflight_response, validate_source_preparation_envelope, validate_source_preparation_receipt,
50
50
  validate_source_verified_set,
@@ -602,7 +602,7 @@ class SourceInterfaceTests(unittest.TestCase):
602
602
 
603
603
  def test_status_answers_dispatch_on_their_version(self):
604
604
  answers = fixtures("interfaces.json")["answers"]
605
- self.assertEqual(SOURCE_JOB_STATUS_VERSIONS, (2, 3, 4, 5, 6, 7))
605
+ self.assertEqual(SOURCE_JOB_STATUS_VERSIONS, (2, 3, 4, 5, 6, 7, 8))
606
606
  for name in ("jobStatus", "jobStatusV3"):
607
607
  for case in answers[name]["valid"]:
608
608
  with self.subTest(answer=name, case=case["name"]):
@@ -614,7 +614,7 @@ class SourceInterfaceTests(unittest.TestCase):
614
614
  validate_source_job_status_answer(case["value"])
615
615
  v3 = answers["jobStatusV3"]["valid"][0]["value"]
616
616
  without = {key: item for key, item in v3.items() if key != "schemaVersion"}
617
- wrong = [{**v3, "schemaVersion": version} for version in (1, 8, "3", "4", True, None, 2.5)]
617
+ wrong = [{**v3, "schemaVersion": version} for version in (1, 9, "3", "4", True, None, 2.5)]
618
618
  for value in wrong + [without, [], "3", None]:
619
619
  with self.subTest(value=repr(value)[:40]):
620
620
  with self.assertRaises(SourceRecordError) as caught:
@@ -865,6 +865,38 @@ class SourceInterfaceTests(unittest.TestCase):
865
865
  with self.assertRaises(SourceRecordError):
866
866
  validate_source_job_status_v7({**status_v6_answer({}), "schemaVersion": 7})
867
867
 
868
+ def test_shared_status_v8_vectors(self):
869
+ # O6-C PR-D parity with job.ts sourceJobStatusV8Schema: v7 plus isolationTier.
870
+ from gridlinegpu import _source_interfaces as interfaces
871
+ vectors = fixtures("job-status-v8.json")
872
+ self.assertEqual(vectors["contract"], "gridline-source-job-status-v8-fixtures-v1")
873
+
874
+ def v8(patch):
875
+ return status_v6_answer(patch, "job-status-v8.json")
876
+
877
+ answers = vectors["answers"]
878
+ self.assertTrue(answers["valid"] and answers["invalid"])
879
+ for name, patch in answers["valid"]:
880
+ with self.subTest(case=name):
881
+ value = v8(patch)
882
+ self.assertIs(validate_source_job_status_v8(value), value)
883
+ self.assertIs(validate_source_job_status_answer(value), value)
884
+ for name, patch, path in answers["invalid"]:
885
+ with self.subTest(case=name):
886
+ with self.assertRaises(SourceRecordError) as caught:
887
+ validate_source_job_status_v8(v8(patch))
888
+ self.assertEqual(caught.exception.code, "schema_invalid")
889
+ with self.assertRaises(SourceRecordError):
890
+ validate_source_job_status_answer(v8(patch))
891
+ self.assertEqual(interfaces.SOURCE_JOB_STATUS_V8_FIELDS, tuple(vectors["fields"]))
892
+ self.assertEqual(list(v8({})), vectors["fields"])
893
+ self.assertEqual(list(interfaces.SOURCE_JOB_ISOLATION_TIERS), vectors["vocabularies"]["isolationTiers"])
894
+ # A v8 answer is never a v7 answer, and the reverse.
895
+ with self.assertRaises(SourceRecordError):
896
+ validate_source_job_status_v7({**v8({}), "schemaVersion": 7})
897
+ with self.assertRaises(SourceRecordError):
898
+ validate_source_job_status_v8({**status_v6_answer({}, "job-status-v7.json"), "schemaVersion": 8})
899
+
868
900
  def test_funding_surfaces_match_the_shared_vectors(self):
869
901
  vectors = fixtures("job-status-v5.json")
870
902
  billing = vectors["billing"]
@@ -247,7 +247,7 @@ class JobHandleTests(unittest.TestCase):
247
247
  self.assertEqual(job.status(), job_status(RECEIPT))
248
248
  self.server.status_version = 3
249
249
  self.assertEqual(job.status(), job_status_v3(RECEIPT))
250
- self.assertEqual(self.server.queries, [("GET", "schemaVersion=7")] * 2)
250
+ self.assertEqual(self.server.queries, [("GET", "schemaVersion=8")] * 2)
251
251
  for name, patch in fixtures("job-status-v4.json")["answers"]["valid"]:
252
252
  with self.subTest(name=name):
253
253
  answer = status_v4_answer(patch)
@@ -299,7 +299,7 @@ class JobHandleTests(unittest.TestCase):
299
299
  self.server.phases = [_json(200, answer)]
300
300
  self.assertEqual(job.route(), expected)
301
301
  self.assertEqual(best_effort["route"]["estimate"], None)
302
- self.assertEqual(self.server.queries, [("GET", "schemaVersion=7")] * len(cases))
302
+ self.assertEqual(self.server.queries, [("GET", "schemaVersion=8")] * len(cases))
303
303
  sunk = status_v4_answer({"route": "$route:sunkUnknown"})
304
304
  self.server.phases = [_json(200, sunk)]
305
305
  self.assertEqual(job.route()["estimate"]["totalUnknownReason"], "sunk_cost_unknown")
@@ -325,7 +325,7 @@ class JobHandleTests(unittest.TestCase):
325
325
  v3 = v3_vector("routing pending after commit")
326
326
  self.server.phases = [(500, {}, b""), _json(200, v3)]
327
327
  self.assertEqual(job.status(), v3)
328
- self.assertEqual(self.server.queries, [("GET", "schemaVersion=7"), ("GET", "schemaVersion=3")])
328
+ self.assertEqual(self.server.queries, [("GET", "schemaVersion=8"), ("GET", "schemaVersion=3")])
329
329
  # route() over the fallback is None: v3 carries no route.
330
330
  self.server.phases = [(500, {}, b""), _json(200, v3)]
331
331
  self.assertIsNone(job.route())
@@ -340,13 +340,13 @@ class JobHandleTests(unittest.TestCase):
340
340
  with self.assertRaises(SourceSubmissionError) as caught:
341
341
  job.status()
342
342
  self.assertEqual((caught.exception.code, caught.exception.status), ("job_status_unavailable", 500))
343
- self.assertEqual(self.server.queries, [("GET", "schemaVersion=7"), ("GET", "schemaVersion=3")])
343
+ self.assertEqual(self.server.queries, [("GET", "schemaVersion=8"), ("GET", "schemaVersion=3")])
344
344
  # Other failures are not retried as v3.
345
345
  self.server.queries.clear()
346
346
  self.server.phases = [(503, {}, b"{}")]
347
347
  with self.assertRaises(SourceSubmissionError):
348
348
  job.status()
349
- self.assertEqual(self.server.queries, [("GET", "schemaVersion=7")])
349
+ self.assertEqual(self.server.queries, [("GET", "schemaVersion=8")])
350
350
 
351
351
  def test_unknown_failure_codes_and_error_stages_never_crash(self):
352
352
  # H6: failure codes and error stages are open words. A newer server's code arrives with
@@ -576,7 +576,7 @@ class JobFundingTests(unittest.TestCase):
576
576
  expected = None if answer["fundingAction"] is None else {
577
577
  key: answer[key] for key in ("actionRequired", "fundingAction", "billing")}
578
578
  self.assertEqual(job.funding_action(), expected)
579
- self.assertEqual(set(self.server.queries), {("GET", "schemaVersion=7")})
579
+ self.assertEqual(set(self.server.queries), {("GET", "schemaVersion=8")})
580
580
  # Older servers (v4, v3, v2) carry no action.
581
581
  for answer in (status_v4_answer({}), v3_vector(CLAIMED), job_status(RECEIPT)):
582
582
  self.server.phases = [_json(200, answer)]
@@ -950,7 +950,7 @@ class JobStatusV6Tests(unittest.TestCase):
950
950
  with self.assertRaises(SourceSubmissionError) as caught:
951
951
  job.status()
952
952
  self.assertEqual(caught.exception.code, "remote_contract_invalid")
953
- self.assertEqual(set(self.server.queries), {("GET", "schemaVersion=7")})
953
+ self.assertEqual(set(self.server.queries), {("GET", "schemaVersion=8")})
954
954
 
955
955
  def test_wait_returns_the_structured_result_with_separate_states(self):
956
956
  running = vector("running: started, advisory progress, result pending, no receipt yet")
@@ -1014,7 +1014,7 @@ class JobStatusV6Tests(unittest.TestCase):
1014
1014
  with self.assertRaises(SourceSubmissionError) as caught:
1015
1015
  job.status()
1016
1016
  self.assertEqual((caught.exception.code, caught.exception.status), ("job_status_unavailable", 500))
1017
- self.assertEqual(self.server.queries, [("GET", "schemaVersion=7"), ("GET", "schemaVersion=3")])
1017
+ self.assertEqual(self.server.queries, [("GET", "schemaVersion=8"), ("GET", "schemaVersion=3")])
1018
1018
 
1019
1019
 
1020
1020
  class JobCostTests(unittest.TestCase):
File without changes