ในบทความนี้
In this post
1. ปัญหาทางวิศวกรรม
เป้าหมายบทนี้
ตรวจโครงสร้างร่าง path ไวยากรณ์ และภาษาที่อนุญาต ก่อนประเมินพฤติกรรมใด ๆ
input check กันคำขอผิดรูป ส่วน output check กันข้อเสนอผิดรูปหรือเกินอำนาจไม่ให้ไหลต่อ candidate เป็น dictionary ที่มี path, source, summary, evidence_id ตรวจ field เหล่านี้ใน runtime แม้ generator รับรอง structured output เพราะการผ่าน schema ไม่ได้ยืนยันว่า path มีสิทธิ์เขียน โค้ดแก้โจทย์ถูก หรือคำอธิบายตรงความจริง
แล็บรับเฉพาะ src/pricing.py และ pricing-spec-v1 จำกัด source 2,000 byte แล้ว parse AST รับเพียงฟังก์ชันเดียวตาม signature และ return expression เดียว grammar มี integer constant ชื่อ argument สองตัว และ +, -, *, // ปฏิเสธ call, import, attribute, decorator, annotation, default และชื่ออื่น นี่คือภาษา arithmetic เล็กที่ใช้หน้าตา Python เอกสาร AST อธิบายการ parse [1] ส่วน policy ของภาษานี้เป็นการออกแบบเพื่อการสอน
2. ขอบเขตที่ต้องออกแบบ
ข้อจำกัดนี้เข้มกว่าผู้ช่วยเขียนโค้ดทั่วไป การเรียก round อาจปกติในโปรแกรมอื่นแต่เกิน contract นี้ สูตรที่เขียนถูก grammar แต่คำนวณผิดยังผ่าน shape และจะถูกตรวจในตอนถัดไป การแยกคำถามทำให้ตอบความล้มเหลวได้ตรง: source_language เป็น hard reject ส่วน expression ที่อนุญาตแต่ผลผิดอาจ revise ภายในงบ ห้ามลด output gate เพราะโมเดลบอกว่าโครงสร้างแปลกนี้จำเป็น
3. อ่าน implementation
ส่วนโค้ดจาก companion เพื่ออ่านประกอบ ใช้คำสั่งในหัวข้อ 5 เพื่อรันตัวอย่างที่ครบ dependency
def output_check(candidate):
if not isinstance(candidate, dict) or set(candidate) != {"path", "source", "summary", "evidence_id"}:
return Gate("reject", "output_schema")
if not all(isinstance(v, str) for v in candidate.values()):
return Gate("reject", "output_type")
if candidate["path"] != SKILL["path"]:
return Gate("reject", "output_path")
if candidate["evidence_id"] != "pricing-spec-v1":
return Gate("review", "missing_provenance")
source = candidate["source"]
if len(source.encode()) > 2000:
return Gate("reject", "source_size")
try:
tree = ast.parse(source)
except (SyntaxError, ValueError, RecursionError):
return Gate("reject", "source_syntax")
allowed = {ast.Module, ast.FunctionDef, ast.arguments, ast.arg, ast.Return,
ast.BinOp, ast.Name, ast.Load, ast.Constant, ast.Add, ast.Sub,
ast.Mult, ast.FloorDiv}
if any(type(n) not in allowed for n in ast.walk(tree)) or len(list(ast.walk(tree))) > 50:
return Gate("reject", "source_language")
if len(tree.body) != 1 or not isinstance(tree.body[0], ast.FunctionDef):
return Gate("reject", "source_function")
f = tree.body[0]
if (f.name != SKILL["function"] or [x.arg for x in f.args.args] != SKILL["args"]
or len(f.body) != 1 or not isinstance(f.body[0], ast.Return) or f.body[0].value is None
or f.decorator_list or f.returns or f.args.defaults
or f.args.posonlyargs or f.args.kwonlyargs or f.args.vararg or f.args.kwarg
or any(x.annotation for x in f.args.args)
or getattr(f, "type_params", [])):
return Gate("reject", "source_signature")
for n in ast.walk(f.body[0]):
if isinstance(n, ast.Name) and n.id not in SKILL["args"]:
return Gate("reject", "source_name")
if isinstance(n, ast.Constant) and (type(n.value) is not int or abs(n.value) > 10000):
return Gate("reject", "source_constant")
return Gate("pass", "output_valid")
การทดลองส่ง import มาแล้วได้ source_language พร้อม executed=False ไม่มีการ eval, exec, import หรือใช้ subprocess รัน candidate ขั้นถัดไปตีความเฉพาะ expression tree ที่อนุญาต วิธีนี้ทำให้ตัวอย่างไม่สอนให้รันโค้ดใดก็ได้เป็นขั้นตรวจแบบง่าย ๆ การทดสอบ patch ทั่วไปต้องมี runner แยกและการออกแบบความปลอดภัยอีกมาก การผ่านด่านนี้หมายถึงร่างอยู่ในภาษาและ envelope ที่รับเท่านั้น ยังต้องตรวจพฤติกรรมและประโยชน์ต่อไป
4. เปรียบเทียบผลที่ได้
| เส้นทาง | ผลที่สังเกตได้ |
|---|---|
| ก่อนเพิ่มด่านนี้ | source ดูถูกต้องก็ส่งไป runner ทันที |
| หลังเพิ่มด่านนี้ | import, call และ path เกินสิทธิ์ถูกหยุดในฐานะข้อมูล |
ผลต่อไปนี้มาจาก fixture ที่กำหนดคำตอบไว้เพื่อตรวจสถาปัตยกรรม ไม่ใช่ผลวัดความแม่น ความเร็ว หรือต้นทุนของ Jev จริง
{
"gate": "source_language",
"executed": false
}
5. รัน ทำให้พัง และอธิบาย
Part 10 code · Implementation · Tests: test_output_disallows_executable_python_features · Exercise
git clone https://github.com/Anirach/ai-core-engineering-workbench.git
cd ai-core-engineering-workbench
git checkout 18cdfcd4156d3d02acb8badf3b3a0a823113e0ed
python3 -m workbench.lesson 10
python3 -m unittest tests.test_boundaries.BoundaryTests.test_output_disallows_executable_python_features -vลองทำด้วยตัวเอง
ลอง decorator, default argument ที่เป็น function call และ path traversal อธิบายว่าด่านใดปฏิเสธแต่ละกรณี
สิ่งที่ต้องส่ง: input ที่ลอง สถานะและเหตุผลที่ได้ และคำอธิบายว่าด่านใดเป็นผู้รับผิดชอบ ห้ามแก้ expected result เพียงเพื่อให้ test ผ่าน
6. แหล่งอ้างอิงและขอบเขต
เอกสารต้นทางและข้อจำกัด
ตรวจเอกสาร 2 พฤศจิกายน 2026 ตัวอย่างและ threshold เป็นการออกแบบเพื่อการสอน การอ้าง [1], [2] ตรงกับลำดับข้างล่าง ยังไม่ได้วัด live API
- Python documentation, astAST parsing; parsing is separate from execution.
1. The engineering problem
Learning outcome
Check the candidate envelope, path, syntax and allowed language before any behavior evaluation.
Input checks protect the system from a malformed request. Output checks protect downstream consumers from a malformed or overpowered proposal. The candidate is a dictionary containing path, source, summary and evidence_id. Those fields are validated at runtime even if a generator promises structured output. An API's schema guarantee does not establish that a path is authorized, that the code solves the task, or that the prose is accurate.
The lab allows exactly src/pricing.py and pricing-spec-v1, limits source to 2,000 bytes, parses an AST, and admits only one function with the required signature and one return expression. Its grammar permits integer constants, the two argument names, and +, -, *, //. Calls, imports, attributes, decorators, annotations, defaults and arbitrary names are rejected. These restrictions define a small arithmetic language embedded in Python syntax. The Python AST documentation explains parsing [1]; the particular language policy is our teaching design.
2. The boundary to design
This is deliberately stricter than a typical coding assistant. A call to round may be benign in a general program, but it is outside this contract. A syntactically valid wrong formula still passes shape validation and will be caught in the next lesson. Separating those questions gives precise failure handling: source_language is a hard reject, while a permitted expression with incorrect results can be revised within budget. Do not weaken the output gate simply because a model says the unfamiliar construct is necessary.
3. Read the implementation
Implementation excerpt from the companion. Run the complete lesson below with its imports and fixtures.
def output_check(candidate):
if not isinstance(candidate, dict) or set(candidate) != {"path", "source", "summary", "evidence_id"}:
return Gate("reject", "output_schema")
if not all(isinstance(v, str) for v in candidate.values()):
return Gate("reject", "output_type")
if candidate["path"] != SKILL["path"]:
return Gate("reject", "output_path")
if candidate["evidence_id"] != "pricing-spec-v1":
return Gate("review", "missing_provenance")
source = candidate["source"]
if len(source.encode()) > 2000:
return Gate("reject", "source_size")
try:
tree = ast.parse(source)
except (SyntaxError, ValueError, RecursionError):
return Gate("reject", "source_syntax")
allowed = {ast.Module, ast.FunctionDef, ast.arguments, ast.arg, ast.Return,
ast.BinOp, ast.Name, ast.Load, ast.Constant, ast.Add, ast.Sub,
ast.Mult, ast.FloorDiv}
if any(type(n) not in allowed for n in ast.walk(tree)) or len(list(ast.walk(tree))) > 50:
return Gate("reject", "source_language")
if len(tree.body) != 1 or not isinstance(tree.body[0], ast.FunctionDef):
return Gate("reject", "source_function")
f = tree.body[0]
if (f.name != SKILL["function"] or [x.arg for x in f.args.args] != SKILL["args"]
or len(f.body) != 1 or not isinstance(f.body[0], ast.Return) or f.body[0].value is None
or f.decorator_list or f.returns or f.args.defaults
or f.args.posonlyargs or f.args.kwonlyargs or f.args.vararg or f.args.kwarg
or any(x.annotation for x in f.args.args)
or getattr(f, "type_params", [])):
return Gate("reject", "source_signature")
for n in ast.walk(f.body[0]):
if isinstance(n, ast.Name) and n.id not in SKILL["args"]:
return Gate("reject", "source_name")
if isinstance(n, ast.Constant) and (type(n.value) is not int or abs(n.value) > 10000):
return Gate("reject", "source_constant")
return Gate("pass", "output_valid")
The experiment supplies an import statement and records source_language with executed=False. No candidate source is eval'ed, exec'ed, imported, or run in a subprocess. The next stage interprets only the admitted expression tree. This prevents the tutorial from teaching arbitrary-code execution as a casual validation step. General patch testing needs a separate isolated runner and a much larger security design. Passing this gate means only that the candidate belongs to the restricted language and envelope; it is still a proposal whose behavior and usefulness remain to be checked.
4. Compare the outcomes
| Approach | Observable outcome |
|---|---|
| Before this control | Well-formed source is passed to a runner immediately. |
| After this control | Imports, calls and unauthorized paths stop as data. |
The following output comes from scripted fixtures that verify architecture behavior. It is not a measurement of live Jev accuracy, latency, or cost.
{
"gate": "source_language",
"executed": false
}
5. Run, break, and explain
Part 10 code · Implementation · Tests: test_output_disallows_executable_python_features · Exercise
git clone https://github.com/Anirach/ai-core-engineering-workbench.git
cd ai-core-engineering-workbench
git checkout 18cdfcd4156d3d02acb8badf3b3a0a823113e0ed
python3 -m workbench.lesson 10
python3 -m unittest tests.test_boundaries.BoundaryTests.test_output_disallows_executable_python_features -vYour turn
Try a decorator, a function-call default and a traversal path. Explain which independent check rejects each case.
Keep three artifacts: the input you tried, the resulting state and reason, and an explanation of which boundary owns that result. Do not change an expected result merely to make a test pass.
6. Sources and limits
Primary sources and evidence limits
Sources checked 2 November 2026. Examples and thresholds are teaching designs. Inline [1], [2] follow the order below. Live API performance has not been measured.
- Python documentation, astAST parsing; parsing is separate from execution.