ในบทความนี้
In this post
ตอนที่ 7 จากสิบตอน: ตรวจสิทธิ์ก่อนผลกระทบ ผูกการอนุมัติกับ payload เฉพาะ และทดสอบ idempotency ด้วย ledger จำลองที่ไม่แตะระบบลูกค้าจริง
ฉบับร่างสำหรับตรวจในเครื่อง • โค้ดเริ่มต้นใช้ offline fixtures เท่านั้น ตัวเลขเป็นสถานการณ์สมมติ ไม่ใช่ผลวัด Jev จริง และยังไม่มีการพิสูจน์ว่าระบบเร็วขึ้นหรือประหยัดลง
เป้าหมายและสิ่งที่ต้องพร้อม
แนะนำให้เริ่มจาก companion repo และคู่มือ README บน GitHub ด้านล่าง ส่วน ZIP ของเว็บไซต์เป็นสำเนาออฟไลน์อีกทางหนึ่ง คำสั่ง Node ใช้เหมือนกันเมื่อรันจากรากโครงการ GitHub repository · README / Quickstart
ใช้โครงการ Support Ticket Decision Lab เดิมตลอดสิบตอน เตรียม Node 22.18 ขึ้นไป terminal และโฟลเดอร์ที่เขียนไฟล์ได้ รันคำสั่งจากราก repo ai-core-jev-tutorial หรือจากไดเรกทอรี ai-core-jev-lab หากใช้ ZIP ของเว็บไซต์ ไม่ต้อง npm install สำหรับออฟไลน์ เผื่อเวลาลงมือและทดลองเปลี่ยนโค้ดประมาณ 20–40 นาที โดยเวลาที่แสดงบนหัวบทความเป็นเวลาอ่านเนื้อหา ไม่รวมเวลาลองทำ
ดาวน์โหลดโครงการพร้อมโค้ดและ tests (ZIP) · อ่านคู่มือโครงการ
ตอนนี้เน้นไฟล์ src/effects.mjs และใช้ผลจาก fixture เพื่อทดสอบการประกอบระบบเท่านั้น ยังไม่ได้ติดตั้ง SDK หรือใช้ key ในการจัดทำบทเรียน เส้นทาง live ที่แยกไว้ในตอนท้ายยังไม่ได้ทดสอบผ่าน API จริง
ทำความเข้าใจขอบเขตนี้
ผลโมเดลกับสิทธิ์เป็นข้อมูลคนละชุด ข้อเสนอ route ที่มั่นใจสูงไม่ได้อนุญาตให้ทำงานภายนอก และข้อความอ้างว่าคนอนุมัติแล้วไม่ใช่ approval record guard แบบ deterministic ตรวจชื่อ action จากชุดปิด ticket ที่รู้จัก คิวที่อนุญาต และ approval แยกที่ผูกกับ action และ release เดียวกัน
ผลกระทบทั้งหมดในแล็บเป็นการเขียน JSON ledger จำลอง ชื่อ demo-reviewer เป็น placeholder สำหรับสอน ไม่ใช่ระบบยืนยันตัวตน ledger แบบลำดับเดียวสาธิตการตรวจ idempotency แต่ไม่รับประกันเมื่อมีหลาย process พร้อมกัน ระบบจริงต้องมีผู้อนุมัติที่ยืนยันตัวตน audit ที่ป้องกันแก้ไข ที่เก็บข้อมูล durable แบบ atomic และ unique-key constraint ในฐานข้อมูล
approval key รวม release, ticket ID, ชนิด action และคิว การเปลี่ยนค่าใดทำให้ approval เดิมใช้ไม่ได้ จึงไม่เอาข้อเสนอ billing ที่ตรวจแล้วไปใช้ส่ง technical หรือใช้ข้าม release ได้ การ hash อย่างเดียวไม่ได้พิสูจน์ว่าใครอนุมัติ มันเพียงผูก record กับ payload เฉพาะในเดโมนี้
ลงมือทีละเจ็ดขั้น
ขั้นที่ 1
ตรวจ effectGuard ก่อนรัน actions ฟังก์ชันเริ่มจากปฏิเสธและจะคืน allow หลังเงื่อนไขโครงสร้างทุกข้อกับ approval แยกตรงกัน provider ไม่ได้รับ approval object ดังนั้นเนื้อหาที่โมเดลสร้างไม่สามารถตั้ง approved ให้ตัวเองผ่าน response schema
ขั้นที่ 2
รัน actions โดยไม่มี flag อนุมัติ ผลที่คาดคือ blocked พร้อม needs_human_approval และเส้นทางนี้ไม่ควรสร้างไฟล์ ledger ให้นับเป็นการทดสอบปฏิเสธที่สำเร็จ ไม่ใช่เดโมเสียที่ต้องหาทางข้าม
ขั้นที่ 3
รัน ticket เดิมพร้อมการอนุมัติเดโมอย่างชัดแจ้งและระบุ ledger ใหม่ โปรแกรมจะสร้างรายการจำลอง ไม่ใช่ request ไป help-desk API ดู simulation:true ก่อนตีความว่าคำว่า simulated หมายถึงเกิดงานภายนอกจริง
ขั้นที่ 4
รันคำสั่งที่อนุมัติเดิมกับ ledger เดิมอีกครั้ง ควรได้ duplicate แทนการเพิ่มผลซ้ำ นี่คือ contract ที่ตรวจเห็นได้สำหรับการรันซ้ำตามลำดับ อย่าสรุปว่าโค้ดนี้รองรับสอง process พร้อมกัน เพราะ implementation ระบุชัดว่าไม่ได้อ้างเช่นนั้น
ขั้นที่ 5
อ่าน test เปลี่ยนคิว ซึ่งสร้าง approval ที่ถูกต้อง เปลี่ยน queue แล้วคาดว่าจะถูกปฏิเสธ เหตุผลคือ approval ควรอนุญาต payload ที่ตรวจแล้ว ไม่ใช่ความตั้งใจคลุม ๆ ว่าให้โมเดลทำอะไรสักอย่างที่เป็นประโยชน์กับ ticket
ขั้นที่ 6
อ่าน test action ต้องห้าม ซึ่งเปลี่ยน kind เป็น refund และให้ confidence สูง guard ยังต้องปฏิเสธ เพราะ confidence เป็นหลักฐานเชิงความหมาย ส่วน allow-list ของ action เป็นขอบเขตสิทธิ์เชิงโครงสร้าง ไม่ควรรวมหน้าที่สองอย่างเข้าด้วยกัน
ขั้นที่ 7
ระบุช่องว่างก่อนใช้งานจริง เขียนว่าระบบ identity ที่เก็บข้อมูล transactional และหน้าจอ review ใดจะมาแทน placeholder จนกว่าจะทำและทดสอบส่วนเหล่านั้นเสร็จ ให้ใช้เพียง simulation หรือ shadow mode ผล benchmark โมเดลไม่สามารถปิดช่องว่างของ effect boundary ได้
ไฟล์และคำสั่งที่ใช้
โค้ดประกอบบน GitHub: ai-core-jev-tutorial · README. ใช้ repo เป็นทางหลักในการทำแบบฝึก ไฟล์ต่อไปนี้ตรึงกับรุ่นที่ทดสอบแล้ว src/effects.mjs · test/effects.test.mjs
เปิดไฟล์นี้เทียบกับ source ใน ZIP ก่อนแก้ ข้อความต่อไปนี้คือโค้ดที่ใช้ในโครงการจริง ไม่ใช่ pseudocode ผลโมเดลเป็น input ให้โค้ดตรวจและวางเส้นทางต่อ ไม่ใช่สิทธิ์ให้ข้ามด่านอนุมัติ
import {createHash} from 'node:crypto';import {readFile,writeFile,mkdir} from 'node:fs/promises';import {dirname} from 'node:path';
import {QUEUES} from './contracts.mjs';
export function approvalKey(action,release) {return createHash('sha256').update(JSON.stringify([release,action.ticketId,action.kind,action.queue])).digest('hex');}
export function effectGuard(proposal,{knownIds,approval,release}) {
const a=proposal.action;
if(proposal.status!=='proposed'||!a||a.kind!=='route_ticket')return {allow:false,reason:'unsupported_or_unready'};
if(!knownIds.has(a.ticketId)||a.ticketId!==proposal.ticketId||!QUEUES.includes(a.queue))return {allow:false,reason:'out_of_scope'};
const key=approvalKey(a,release);
if(approval?.key!==key||approval?.reviewer!=='demo-reviewer'||approval?.approved!==true)return {allow:false,reason:'needs_human_approval'};
return {allow:true,key};
}
export async function simulate(proposal,context,ledger) {
const gate=effectGuard(proposal,context);if(!gate.allow)return {status:'blocked',reason:gate.reason};
let rows=[];try{rows=JSON.parse(await readFile(ledger,'utf8'))}catch(e){if(e.code!=='ENOENT')throw e}
if(rows.some(r=>r.key===gate.key))return {status:'duplicate',key:gate.key};
rows.push({key:gate.key,release:context.release,...proposal.action,simulation:true});
await mkdir(dirname(ledger),{recursive:true});await writeFile(ledger,JSON.stringify(rows,null,2)+'\n');return {status:'simulated',key:gate.key};
}
// Teaching ledger: serial, single-process only. Production requires authenticated
// approvals, a transactional unique key and atomic durable writes.
คำสั่งออฟไลน์ต่อไปนี้รันได้โดยไม่ใช้ SDK หรือ API key:
node src/cli.mjs actions --ticket T-001 --ledger out/chapter7-ledger.json
node src/cli.mjs actions --ticket T-001 --approve --ledger out/chapter7-ledger.json
node src/cli.mjs actions --ticket T-001 --approve --ledger out/chapter7-ledger.json
node --test --test-reporter=spec test/effects.test.mjs
ตรวจผลที่ควรได้
เมื่อเริ่มจาก ledger ใหม่ คำสั่งสามครั้งควรคืน blocked, simulated และ duplicate ตามลำดับ ถ้าเปิดบทเรียนซ้ำโดยมี ledger อยู่แล้วอาจเริ่มที่ duplicate ให้ใช้ชื่อไฟล์ใหม่เมื่อต้องการทำลำดับครั้งแรก ไม่มีการติดต่อกับลูกค้าหรือทำธุรกรรมการเงินในโครงการนี้
ตรวจชื่อ test และเส้นทางปฏิเสธด้วย อย่าดูเพียง exit code หรือ label สุดท้าย ข้อมูลที่ไม่ทราบต้องยังระบุว่าไม่ทราบ และรายงานทุกชิ้นต้องแยกผลจำลองออกจากข้อมูลที่วัดจริง
แก้ปัญหาเมื่อผลไม่ตรง
ถ้ามีผลเกิดก่อน effectGuard แปลว่าสถาปัตยกรรมผิดแม้ test ของ guard ผ่าน ถ้ามีรายการซ้ำเมื่อทำงานพร้อมกัน ให้ใช้ที่เก็บข้อมูลแบบ transactional ไม่ใช่เพิ่ม sleep ถ้าโหลด approval จากผลโมเดล ต้องแยกไปเป็นช่องทางที่แอปควบคุมและเชื่อถือได้
หากเปิดบทเรียนผ่าน Python http.server ให้ใช้ลิงก์ .html ที่ท้ายตอนเมื่อจำเป็น ลิงก์ chip แบบไม่มีนามสกุลในแถบซีรีส์อาศัยการ resolve ของ GitHub Pages และเป็นข้อจำกัด preview ที่มีอยู่เดิม ไม่ใช่ปัญหาในโค้ด Node
เปลี่ยนหนึ่งอย่างแล้วทดสอบ
อนุมัติข้อเสนอหนึ่งชุดแล้วเปลี่ยน release identifier ตรวจว่า approval key เดิมใช้ไม่ได้ จากนั้นอธิบายว่าผู้อนุมัติที่ยืนยันตัวตนกับฐานข้อมูล transactional จะมาแทน placeholder อย่างไร อย่าเรียก JSON ledger ที่ใช้สอนว่าเป็นบริการอนุมัติพร้อมใช้งานจริง
ทำการแก้ไขในสำเนาทดลอง เก็บไฟล์ต้นฉบับไว้เทียบ แล้วคืนค่าก่อนเริ่มตอนถัดไป หากทดลองกับ provider จริงภายหลัง ให้สร้าง experiment record ใหม่แทนเขียนทับผลเดิม
จุดตรวจถัดไป
ก่อนขยับต่อ ให้บอกได้ว่าไฟล์ใดเป็น contract ไฟล์ใดเป็น policy หลักฐานใดได้จากการรัน และข้อใดยังไม่ได้ทดสอบกับโมเดลจริง การแยกสี่อย่างนี้เป็นงานวิศวกรรมที่สำคัญกว่าการได้เดโมที่ดูไหลลื่น
แหล่งอ้างอิงและหลักฐาน
เอกสารและ source ที่ใช้
ตรวจเอกสาร Jev วันที่ 23 ตุลาคม 2026 ข้อเท็จจริง API และราคาอาจเปลี่ยน ให้ตรวจใหม่ก่อนใช้ live ส่วนผลของ fixture เป็นข้อมูลสอนที่ระบุไว้ในโครงการ
สรุปที่นำไปใช้ต่อ
- contract จำกัดโครงสร้าง ส่วน evaluation ตรวจพฤติกรรม
- อ่าน coverage คุณภาพ เวลา และต้นทุนรวมควบคู่กัน
- ข้อเสนอไม่ใช่ผลที่อนุมัติแล้ว และหลักฐาน mock ไม่ใช่หลักฐานโมเดล live
Part 7 of ten: Check permissions before effects, bind approval to one payload, and test idempotency in a simulated ledger.
Local review draft • Default code uses offline fixtures. Numbers are fictional scenarios, not measured Jev results. Faster performance and lower cost remain hypotheses.
Outcome and prerequisites
Start with the companion repository and its detailed README. The website ZIP remains an alternative offline snapshot; run the same Node commands from either project root. GitHub repository · README / Quickstart
Use the same Support Ticket Decision Lab throughout the series. Have Node 22.18+, a terminal and a writable directory. Run commands from the cloned ai-core-jev-tutorial root, or the extracted ai-core-jev-lab folder if using the website ZIP. Offline work needs no npm install. Allow roughly 20–40 minutes for experiments; the article reading-time label excludes hands-on work.
Download the complete code and tests (ZIP) · Project README
This chapter focuses on src/effects.mjs. Fixture outputs test the integration mechanics only. The author did not install the SDK or use an API key while preparing the lab. The separately documented live path has not been tested against the API.
How this boundary works
Model output and permission are different data. A high-confidence route proposal does not authorize an external action, and text claiming that a reviewer approved cannot become an approval record. Our deterministic guard checks a closed action name, a known ticket, an allowed queue and a separate approval bound to the exact action and release.
All effects in the lab are simulated JSON-ledger entries. The demo-reviewer identity is a teaching placeholder, not authentication. The serial ledger demonstrates an idempotency check but has no transactional concurrency guarantee. A production service needs authenticated reviewers, tamper-resistant audit, atomic durable storage and a unique-key constraint enforced by its database.
The approval key includes release, ticket ID, action kind and queue. Changing any of those invalidates the approval. This prevents a reviewed billing proposal from being reused for a technical route or another release. Hashing alone does not establish who approved; it merely binds the approval record to a specific payload in this demonstration.
Seven practical steps
Step 1 — Inspect effectGuard before running actions
Inspect effectGuard before running actions. It begins with refusal and returns allow only after every structural condition and the separate approval match. The provider never receives the approval object, so generated content cannot set its own approved flag through the response schema.
Step 2 — Run actions without the approval flag
Run actions without the approval flag. The expected result is blocked with needs_human_approval, and no ledger file should be created by that path. Treat this as a successful refusal test, not as a broken demonstration to be bypassed.
Step 3 — Run the same ticket with explicit demonstration approval and a fresh named ledger
Run the same ticket with explicit demonstration approval and a fresh named ledger. The program creates a simulation entry, not a help-desk API request. Inspect simulation:true before interpreting the word simulated as an action that happened outside the lab.
Step 4 — Repeat the exact approved command against that ledger
Repeat the exact approved command against that ledger. It should return duplicate rather than append another effect. This is the observable contract for serial reruns. Do not infer that the same code handles two simultaneous processes; the implementation explicitly does not make that claim.
Step 5 — Read the changed-queue test
Read the changed-queue test. It makes a valid approval, alters the queue and expects refusal. This matters because approvals should authorize the reviewed payload, not a vague intention such as let the model do something useful with this ticket.
Step 6 — Read the forbidden-action test
Read the forbidden-action test. It changes kind to refund and supplies high confidence. The guard still denies it. Confidence is semantic evidence; the action allow-list is a structural permission boundary. Their responsibilities should not be merged.
Step 7 — Name the production gap before continuing
Name the production gap before continuing. Write down which identity system, transactional store and review interface would replace the placeholders. Until those decisions are implemented and tested, keep the project in simulation or shadow mode. A model benchmark does not close an effect-boundary gap.
Files and commands
Companion code on GitHub: ai-core-jev-tutorial · README. Use the companion repository as the default working copy. These file links pin the tested revision. src/effects.mjs · test/effects.test.mjs
Compare this file with the source in the ZIP before editing. The listing is the implementation used by the lab, not pseudocode. Model output is input to validation and routing code; it grants no permission to bypass approval.
import {createHash} from 'node:crypto';import {readFile,writeFile,mkdir} from 'node:fs/promises';import {dirname} from 'node:path';
import {QUEUES} from './contracts.mjs';
export function approvalKey(action,release) {return createHash('sha256').update(JSON.stringify([release,action.ticketId,action.kind,action.queue])).digest('hex');}
export function effectGuard(proposal,{knownIds,approval,release}) {
const a=proposal.action;
if(proposal.status!=='proposed'||!a||a.kind!=='route_ticket')return {allow:false,reason:'unsupported_or_unready'};
if(!knownIds.has(a.ticketId)||a.ticketId!==proposal.ticketId||!QUEUES.includes(a.queue))return {allow:false,reason:'out_of_scope'};
const key=approvalKey(a,release);
if(approval?.key!==key||approval?.reviewer!=='demo-reviewer'||approval?.approved!==true)return {allow:false,reason:'needs_human_approval'};
return {allow:true,key};
}
export async function simulate(proposal,context,ledger) {
const gate=effectGuard(proposal,context);if(!gate.allow)return {status:'blocked',reason:gate.reason};
let rows=[];try{rows=JSON.parse(await readFile(ledger,'utf8'))}catch(e){if(e.code!=='ENOENT')throw e}
if(rows.some(r=>r.key===gate.key))return {status:'duplicate',key:gate.key};
rows.push({key:gate.key,release:context.release,...proposal.action,simulation:true});
await mkdir(dirname(ledger),{recursive:true});await writeFile(ledger,JSON.stringify(rows,null,2)+'\n');return {status:'simulated',key:gate.key};
}
// Teaching ledger: serial, single-process only. Production requires authenticated
// approvals, a transactional unique key and atomic durable writes.
Run these offline commands without an SDK installation or API key:
node src/cli.mjs actions --ticket T-001 --ledger out/chapter7-ledger.json
node src/cli.mjs actions --ticket T-001 --approve --ledger out/chapter7-ledger.json
node src/cli.mjs actions --ticket T-001 --approve --ledger out/chapter7-ledger.json
node --test --test-reporter=spec test/effects.test.mjs
Verify the expected behavior
With a fresh ledger, the three commands should return blocked, simulated and duplicate in order. Re-running a chapter with an existing ledger may start at duplicate; use a new filename to reproduce the first-run sequence. No customer contact or monetary action is implemented.
Read the named tests and refusal paths, not just the exit code or final label. Unknown values must remain unknown, and every report must distinguish simulation inputs from real measurements.
Troubleshooting
If an effect occurs before effectGuard, the architecture is wrong even if tests for the guard pass. If duplicate entries appear in concurrent execution, use a transactional storage design rather than adding a sleep. If approval is loaded from model output, separate it into a trusted application-owned channel.
When previewing with Python http.server, use the .html continuation links below when needed. Extensionless series chips rely on GitHub Pages resolution; that documented preview limitation is separate from the Node example.
Change one thing, then test
Approve one proposal, change its release identifier, and check that the old approval key no longer authorizes it. Then describe how an authenticated reviewer and transactional database would replace the demo placeholders. Do not call the teaching JSON ledger a production approval service.
Make changes in a disposable copy, retain the reference files for comparison, and restore them before the next chapter. If you later use a real provider, create a new experiment record rather than overwriting the fixture transcript.
Next checkpoint
Before moving on, identify the contract file, the policy file, what was observed in a run, and what remains untested against a real model. Keeping those four things separate matters more than making the demo look effortless.
← Previous chapter · Next chapter →
References and evidence
Documentation and implementation
Jev documentation checked on 23 October 2026. API and pricing details can change; recheck before live use. Fixture results are explicitly authored teaching data in the project.
Key takeaways
- Contracts bound structure; evaluation checks behavior.
- Coverage, quality, latency and total cost must be read together.
- A proposal is not an approved effect; mock evidence is not live model evidence.