feat(submit): require explicit confirmation for all prepare_submit_job params

Remove defaults from queue, executor_memory, executor_cores,
num_executors, and app_name in prepare_submit_job. All must now be
explicitly confirmed by the caller.

Also add extra_args to the PendingSubmission snapshot so users can
confirm non-conf spark-submit flags (e.g. --jars, --py-files) at
prepare time; confirm_submit_job passes them through to
build_spark_submit_command.

This is an intentional breaking change to the MCP tool contract:
callers can no longer rely on implicit defaults.
This commit is contained in:
Claude
2026-06-26 15:12:14 +08:00
parent da9ba657f3
commit 4b07617af0
9 changed files with 330 additions and 49 deletions
+63 -7
View File
@@ -86,7 +86,15 @@ def test_prepare_submit_job_works_via_body(tmp_path):
script.write_text("print('hi')\n")
r = c.post(
"/prepare_submit_job",
json={"connection": "prod", "script_path": str(script), "queue": "research"},
json={
"connection": "prod",
"script_path": str(script),
"queue": "research",
"executor_memory": "4G",
"executor_cores": 2,
"num_executors": 2,
"app_name": "test-app",
},
)
assert r.status_code == 200, r.text
body = r.json()
@@ -102,7 +110,15 @@ def test_prepare_rejects_nonexistent_script_path_with_400():
c.post("/save_connection", json={"name": "prod", "master": "yarn"})
r = c.post(
"/prepare_submit_job",
json={"connection": "prod", "script_path": "/nope/does_not_exist.py"},
json={
"connection": "prod",
"script_path": "/nope/does_not_exist.py",
"queue": "default",
"executor_memory": "4G",
"executor_cores": 2,
"num_executors": 2,
"app_name": "test-app",
},
)
assert r.status_code == 400
detail = r.json()["detail"]
@@ -121,7 +137,15 @@ def test_prepare_rejects_sql_policy_violation_with_400(tmp_path):
bad_script.write_text('spark.sql("DROP TABLE users")\n')
r = c.post(
"/prepare_submit_job",
json={"connection": "prod", "script_path": str(bad_script)},
json={
"connection": "prod",
"script_path": str(bad_script),
"queue": "default",
"executor_memory": "4G",
"executor_cores": 2,
"num_executors": 2,
"app_name": "test-app",
},
)
assert r.status_code == 400
detail = r.json()["detail"]
@@ -199,7 +223,15 @@ def test_list_and_get_pending_job_roundtrip_via_body(tmp_path):
script.write_text("print('hi')\n")
prep = c.post(
"/prepare_submit_job",
json={"connection": "prod", "script_path": str(script)},
json={
"connection": "prod",
"script_path": str(script),
"queue": "default",
"executor_memory": "4G",
"executor_cores": 2,
"num_executors": 2,
"app_name": "test-app",
},
).json()
pid = prep["pending_id"]
@@ -261,7 +293,15 @@ def test_unknown_connection_in_prepare_returns_404():
c = TestClient(app)
r = c.post(
"/prepare_submit_job",
json={"connection": "nope", "script_path": "/tmp/x.py"},
json={
"connection": "nope",
"script_path": "/tmp/x.py",
"queue": "default",
"executor_memory": "4G",
"executor_cores": 2,
"num_executors": 2,
"app_name": "test-app",
},
)
assert r.status_code == 404
assert "nope" in r.json()["detail"]
@@ -275,7 +315,15 @@ def test_confirm_non_pending_returns_400(tmp_path):
script.write_text("print('hi')\n")
prep = c.post(
"/prepare_submit_job",
json={"connection": "prod", "script_path": str(script)},
json={
"connection": "prod",
"script_path": str(script),
"queue": "default",
"executor_memory": "4G",
"executor_cores": 2,
"num_executors": 2,
"app_name": "test-app",
},
).json()
pid = prep["pending_id"]
c.post("/cancel_pending_job", json={"pending_id": pid})
@@ -292,7 +340,15 @@ def test_cancel_already_submitted_returns_400(tmp_path):
script.write_text("print('hi')\n")
prep = c.post(
"/prepare_submit_job",
json={"connection": "prod", "script_path": str(script)},
json={
"connection": "prod",
"script_path": str(script),
"queue": "default",
"executor_memory": "4G",
"executor_cores": 2,
"num_executors": 2,
"app_name": "test-app",
},
).json()
pid = prep["pending_id"]
# Simulate a SUBMITTED state by mutating the pending directly