Skip to content

Commit 8e15fdf

Browse files
Zikkyingcursoragent
andcommitted
Validate Bohrium project IDs before submission
Prevent agents from serializing project IDs as strings by clarifying generated config usage and rejecting malformed Bohrium settings during input validation. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e3052eb commit 8e15fdf

3 files changed

Lines changed: 118 additions & 16 deletions

File tree

apex/skills/apex-flow/reference/submission.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,18 +138,23 @@ dflow validates workflow names against RFC 1123 subdomain regex. Names like `"Cu
138138

139139
`program_id` and `bohrium_config.project_id` **must** come from the environment
140140
variable `BOHRIUM_PROJECT_ID` (or `--project-id`). Never hardcode a personal
141-
project ID in examples or generated configs.
141+
project ID in examples or generated configs. **Do not write `global.json`
142+
manually and do not copy the placeholders below into a real file.** Run
143+
`scripts/generate_config.py`; it converts the environment string to an integer
144+
and writes both ID fields as JSON numbers.
142145

143-
```json
146+
The following is a type-annotated shape, not valid JSON:
147+
148+
```text
144149
{
145150
"dflow_host": "https://workflows.deepmodeling.com",
146151
"k8s_api_server": "https://workflows.deepmodeling.com",
147152
"batch_type": "Bohrium",
148153
"context_type": "Bohrium",
149-
"program_id": "<BOHRIUM_PROJECT_ID>",
154+
"program_id": <BOHRIUM_PROJECT_ID as an unquoted integer>,
150155
"bohrium_config": {
151156
"ticket": "<UUID from API conversion — auto-filled by generate_config.py>",
152-
"project_id": "<BOHRIUM_PROJECT_ID>"
157+
"project_id": <the same unquoted integer>
153158
},
154159
"apex_image_name": "registry.dp.tech/dptech/dp/native/prod-397637/apex-flow:1.3.0.post2",
155160
"lammps_image_name": "registry.dp.tech/dptech/dp/native/prod-397637/deepmd-kit-phonolammps:3.1.3",
@@ -160,6 +165,15 @@ project ID in examples or generated configs.
160165
}
161166
```
162167

168+
Quoted digits such as `"program_id": "..."` are invalid for DPDispatcher even
169+
though they look numeric. Before submission, always run:
170+
171+
```bash
172+
python scripts/validate_inputs.py --param param.json --global global.json
173+
```
174+
175+
Do not submit unless validation reports `Validation PASSED`.
176+
163177
> For GPU potentials (DeePMD, MACE, NEP), change `scass_type` to `"c8_m31_1 * NVIDIA T4"`.
164178
> Before submitting, run `scripts/validate_apex_combo.py check` on the chosen image × scass_type.
165179

apex/skills/apex-flow/scripts/validate_inputs.py

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,81 @@ def validate_global(global_config: dict) -> list:
4040
errors = []
4141
warnings = []
4242

43-
# Check machine config
44-
if "machine" not in global_config:
45-
errors.append("Missing 'machine' section in global.json")
43+
# generate_config.py writes the current, top-level APEX schema. Keep
44+
# accepting the older nested-machine schema for existing user configs.
45+
is_current_schema = any(
46+
key in global_config
47+
for key in ("dflow_host", "context_type", "bohrium_config", "program_id")
48+
)
49+
50+
if is_current_schema:
51+
for key in ("batch_type", "context_type"):
52+
if not global_config.get(key):
53+
errors.append(f"Missing '{key}' in global.json")
54+
55+
program_id = global_config.get("program_id")
56+
if not isinstance(program_id, int) or isinstance(program_id, bool):
57+
errors.append(
58+
"'program_id' must be an unquoted JSON integer, not a string; "
59+
"generate global.json with "
60+
"generate_config.py"
61+
)
62+
elif program_id <= 0:
63+
errors.append("'program_id' must be a positive integer")
64+
65+
bohrium_config = global_config.get("bohrium_config")
66+
if not isinstance(bohrium_config, dict):
67+
errors.append("Missing 'bohrium_config' section in global.json")
68+
else:
69+
project_id = bohrium_config.get("project_id")
70+
if not isinstance(project_id, int) or isinstance(project_id, bool):
71+
errors.append(
72+
"'bohrium_config.project_id' must be a JSON integer, "
73+
"not a quoted string"
74+
)
75+
elif project_id <= 0:
76+
errors.append(
77+
"'bohrium_config.project_id' must be a positive integer"
78+
)
79+
elif isinstance(program_id, int) and not isinstance(program_id, bool):
80+
if project_id != program_id:
81+
errors.append(
82+
"'program_id' and 'bohrium_config.project_id' must match"
83+
)
84+
85+
ticket = bohrium_config.get("ticket")
86+
if not isinstance(ticket, str) or not ticket.strip():
87+
errors.append(
88+
"Missing non-empty 'bohrium_config.ticket'; regenerate "
89+
"global.json with generate_config.py"
90+
)
91+
92+
if not global_config.get("scass_type"):
93+
errors.append("Missing 'scass_type' in global.json")
94+
95+
run_commands = (
96+
"lammps_run_command", "abacus_run_command", "vasp_run_command"
97+
)
98+
if not any(global_config.get(key) for key in run_commands):
99+
errors.append(
100+
"Missing calculator run command in global.json "
101+
"(expected one of lammps_run_command, abacus_run_command, "
102+
"vasp_run_command)"
103+
)
46104
else:
47-
machine = global_config["machine"]
48-
if "batch_type" not in machine:
49-
errors.append("Missing 'machine.batch_type'")
105+
# Legacy nested-machine schema.
106+
if "machine" not in global_config:
107+
errors.append("Missing 'machine' section in global.json")
108+
else:
109+
machine = global_config["machine"]
110+
if "batch_type" not in machine:
111+
errors.append("Missing 'machine.batch_type'")
50112

51-
# Check resources
52-
if "resources" not in global_config:
53-
warnings.append("No 'resources' section - will use defaults")
113+
if "resources" not in global_config:
114+
warnings.append("No 'resources' section - will use defaults")
54115

55-
# Check run_command
56-
if "run_command" not in global_config:
57-
errors.append("Missing 'run_command' in global.json")
116+
if "run_command" not in global_config:
117+
errors.append("Missing 'run_command' in global.json")
58118

59119
return errors, warnings
60120

tests/test_skill_scripts.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,34 @@ def test_validate_global(self):
311311
([], []),
312312
)
313313

314+
def test_validate_current_global_requires_integer_matching_project_ids(self):
315+
config = {
316+
"dflow_host": "https://workflows.deepmodeling.com",
317+
"batch_type": "Bohrium",
318+
"context_type": "Bohrium",
319+
"program_id": "42",
320+
"bohrium_config": {
321+
"ticket": "ticket",
322+
"project_id": "42",
323+
},
324+
"scass_type": "c8_m31_1 * NVIDIA T4",
325+
"lammps_run_command": "lmp -in in.lammps",
326+
}
327+
errors, warnings = self.validator.validate_global(config)
328+
self.assertTrue(any("'program_id'" in error for error in errors))
329+
self.assertTrue(
330+
any("'bohrium_config.project_id'" in error for error in errors)
331+
)
332+
self.assertFalse(warnings)
333+
334+
config["program_id"] = 42
335+
config["bohrium_config"]["project_id"] = 43
336+
errors, _ = self.validator.validate_global(config)
337+
self.assertTrue(any("must match" in error for error in errors))
338+
339+
config["bohrium_config"]["project_id"] = 42
340+
self.assertEqual(self.validator.validate_global(config), ([], []))
341+
314342
def test_validate_interaction(self):
315343
cases = (
316344
({}, "Missing 'interaction.type'"),

0 commit comments

Comments
 (0)