
คำตอบสั้น: ก่อนเรียกเครื่องมือที่อาจเปลี่ยนข้อมูล ให้ตรวจว่าใบอนุมัติยังใช้ได้และผูกกับ action ฉบับเดียวกันทุกช่อง โดยวางการตรวจไว้ที่ด่านสุดท้ายก่อนผลกระทบ ตัวอย่างนี้จำลองการอนุมัติรายงานฉบับ private หากมีคนเปลี่ยน visibility เป็น public ใบอนุมัติเดิมต้องใช้ไม่ได้ แม้เนื้อหาจะเหมือนเดิม
ทักษะเดียวของบทเรียนคือ ผูก receipt กับ fingerprint ของ action และปฏิเสธการเปลี่ยนแปลงหรือ replay receipt คือระเบียนอนุมัติ fingerprint คือค่าตรวจ action; hash ไม่ใช่ลายเซ็น
ปัญหา: อนุมัติเรื่องหนึ่ง แต่ระบบทำอีกเรื่อง
สมมติ agent เตรียมรายงานให้ผู้ใช้อนุมัติ ระหว่างรอคิวหรือส่งงานให้ worker รายละเอียดอาจเปลี่ยน หากระบบเก็บเพียง approved=true ของงานกว้าง ๆ ธงนั้นอาจติดตามไปยัง action ใหม่ ทั้งที่เนื้อหา revision หรือระดับการเปิดเผยเปลี่ยน ช่องว่างจึงเกิดระหว่างสิ่งที่คนเห็นกับสิ่งที่เครื่องกำลังจะทำ
สร้าง snapshot แล้วคำนวณ digest; ตอน consume คำนวณซ้ำ หากไม่ตรงให้หยุด receipt มี scope, expiry และใช้ครั้งเดียว กลไกนี้ตรวจความสอดคล้อง ไม่พิสูจน์ผู้อนุมัติหรือความปลอดภัยปลายทาง
ตรวจที่ด่านสุดท้ายก่อนผลกระทบ โดยรับ action ฉบับเต็มและตรวจ scope จากแหล่งยืนยันตัวตนที่เชื่อถือได้ ตัวอย่างหยุดที่ ALLOW_MOCK_ONLY ไม่มีการเรียก tool จริง

ขอบเขตและสมมติฐานที่ต้องพูดให้ชัด
ตัวอย่างนี้สมมติว่ามี mock server เพียงตัวเดียว และมี tenant กับ actor ที่ผ่านการยืนยันแล้วส่งเข้ามาจากชั้น authentication ซึ่งอยู่นอกตัวอย่าง คำว่า trusted หมายถึงระบบนอก mock รับรองตัวตนและ scope มาก่อน ไม่ได้หมายความว่าค่าที่ผู้เรียกส่งมาเองเชื่อถือได้ ในบทความนี้ tenant และ actor เป็นข้อมูลสมมติที่ป้อนเข้าตัวอย่างเท่านั้น ตัวอย่างไม่สร้าง session, ไม่ตรวจ token และไม่ตรวจสิทธิ์ผู้อนุมัติ
action มี tool_id, report_id, revision, visibility และ body ทุกค่าที่เปลี่ยนผลต้องอยู่ใน fingerprint ระบบที่มีผู้รับ จำนวนเงิน หรือช่องทางต้องเพิ่มฟิลด์นั้นด้วย
หยุดก่อน implementation หากยังไม่รู้ฟิลด์ที่ควบคุมผลกระทบหรือแหล่ง identity อย่าเติมค่าโดยเดา ต้องระบุ schema และแหล่งข้อมูลที่เชื่อถือได้ก่อน
การสร้าง fingerprint แบบ schema คงที่
ในโค้ด เราเข้ารหัสข้อความ body เป็น UTF-8 bytes แล้วคำนวณ SHA-256 ของ bytes เหล่านั้น จากนั้นสร้าง JSON array ตามลำดับคงที่: schema version, tenant, actor, tool, report, revision, visibility และ body hash แล้วเข้ารหัส array เป็น UTF-8 ก่อนคำนวณ SHA-256 รอบที่สอง ลำดับนี้เป็น schema เฉพาะของตัวอย่าง ไม่ใช่วิธี canonicalize JSON ทั่วไป
receipt เก็บ digest, scope, เวลาออกและหมดอายุ, สถานะ UUID ใน approval_id เป็น record key เท่านั้น ไม่ใช่ตัวตนหรือหลักฐานอำนาจ
SHA-256 เป็น digest ของ bytes ไม่ได้พิสูจน์ผู้สร้าง, consent หรืออำนาจ Hash ไม่ใช่ MAC หรือลายเซ็น และใช้แทน authentication ไม่ได้
ข้อความ Unicode ที่มองเหมือนกันอาจมีลำดับ code point ต่างกัน เช่น é แบบตัวเดียวกับ e ตามด้วย combining accent โค้ดนี้ไม่ normalize หากระบบต้องการให้รูปแบบเหล่านั้นเท่ากัน ต้องกำหนด normalization ใน schema ให้ตรงกันก่อนใช้งาน

วงจรสถานะ receipt
receipt เริ่มต้นเป็น unused มีเวลาหมดอายุ และบันทึก tenant กับ actor ที่ออกให้ เมื่อ consume เข้าสู่ gate ระบบตรวจตามลำดับสำคัญ: หา receipt, ตรวจ scope, ตรวจสถานะ invalidated หรือ consumed, ตรวจ expiry แล้วค่อยคำนวณ fingerprint เปรียบเทียบ ถ้าตรงกันและยังไม่หมดอายุ เปลี่ยนสถานะเป็น consumed ก่อนส่งผล ALLOW_MOCK_ONLY กลับ การเรียกครั้งต่อมาด้วย receipt เดิมจึงได้ DENY_USED
actor หรือ tenant ไม่ตรงได้ DENY_SCOPE โดยไม่ consume receipt เจ้าของ receipt ที่ invalidated หรือ consumed ยังคงปฏิเสธ ส่วน now >= expires_at หมายถึงเวลาที่เท่ากันก็หมดอายุ
clock เริ่มต้นใช้ int(time.monotonic()) ซึ่งเป็นวินาที monotonic แบบจำนวนเต็มและมี origin ตามระบบ ไม่ใช่เวลา 19 หรือ SLA 30 วินาที ส่วน FakeClock ใน example เริ่มที่ 100 เท่านั้น TTL 30 วินาทีเป็นค่าจำลอง ไม่มีการวัด latency
clock ถอยหลังจะ fail closed ด้วยข้อผิดพลาด ระบบจริงต้องกำหนด audit และวิธีกู้คืน
เดินตาม worked example
ตัวอย่างสมมติรายงาน R-17 รุ่น 1 และ tool ชื่อ report.publish.v1 เนื้อหาเป็น “Approved draft” action แรกระบุ visibility เป็น private แล้ว mock เรียก approve() เพื่อสร้าง receipt ใหม่ โปรดสังเกตว่า approve() เป็น synthetic fixture ซึ่งสมมติว่ามี consent ที่เชื่อถือได้อยู่ก่อน มันไม่ได้เปิดหน้าต่างให้คนตรวจ ไม่ได้รับคำยืนยันจริง และไม่ได้ตรวจว่าผู้เรียกมีสิทธิ์อนุมัติ
ตอน consume ครั้งแรก ตัวอย่างเปลี่ยน visibility เป็น public โดยคง body, report, revision และ tool เดิมไว้ เมื่อ gate คำนวณ fingerprint ใหม่ ค่าต่างกันเพราะ visibility เป็นส่วนหนึ่งของ array จึงได้ DENY_CHANGED และทำเครื่องหมาย receipt นั้นว่า invalidated ถ้าลองคืน action กลับเป็น private แล้วใช้ receipt เดิม ผลยังเป็น DENY_INVALIDATED การเปลี่ยนกลับไม่ลบประวัติว่ามีการเสนอ action ที่ไม่ตรงกับที่อนุมัติ
จากนั้นสร้าง receipt ใหม่สำหรับ private action เดิม การ consume ที่ตรงกันและยังไม่หมดอายุได้ ALLOW_MOCK_ONLY เพียงครั้งเดียว เมื่อลองซ้ำจะได้ DENY_USED ตารางนี้เป็นผลที่โค้ดออกแบบให้แสดงเมื่อรัน ตัวบทความไม่ได้รันคำสั่งเพื่อสังเกตผล จึงห้ามเรียกผลเหล่านี้ว่า runtime PASS
| เหตุการณ์ | ผลที่คาดจากการอ่านโค้ด | ความหมาย |
|---|---|---|
| เปลี่ยน private เป็น public | DENY_CHANGED | snapshot ไม่ตรงและ receipt ถูก invalidated |
| คืน private ด้วย receipt เก่า | DENY_INVALIDATED | การ restore ไม่ฟื้น receipt |
| อนุมัติใหม่และ consume snapshot เดิม | ALLOW_MOCK_ONLY | ผ่าน mock gate หนึ่งครั้งเท่านั้น |
| ใช้ receipt ใหม่ซ้ำ | DENY_USED | receipt ถูกใช้แล้ว |
ยังมีกรณีสำคัญที่ชุดทดสอบประกอบไว้: เปลี่ยน actor หรือ tenant ต้องได้ DENY_SCOPE; การตรวจด้วย actor ผิดไม่ consume receipt ของ actor เจ้าของ; เวลาถึงขอบ expiry ต้องได้ DENY_EXPIRED; เปลี่ยน body, revision, report หรือ tool ต้อง invalidates; และเวลาถอยหลังต้อง fail closed โค้ดและชุดทดสอบเหล่านี้เป็นวัสดุให้ตรวจอ่านเท่านั้น สถานะการรันทุกอย่างยังเป็น NOT RUN

อ่านโค้ดโดยไม่สับสนกับการยืนยันจริง
approval_gate.py แยก Context, Action, Status และ receipt ตรวจ revision, visibility และขนาด body ฟังก์ชัน fingerprint() ใช้ field order ที่กำหนด ส่วน consume() ตรวจ scope, state, expiry และ digest ก่อนเปลี่ยนสถานะ
example_approval_gate.py ใช้ FakeClock และพิมพ์ผลที่คาด ส่วน test_approval_gate.py มี 19 unittest ที่เขียนไว้สำหรับ snapshot, replay, field changes, scope, expiry, restore, Unicode และ clock rollback แต่ยังไม่ได้รัน
README ระบุ Python 3.12+, Standard Library และไม่มี dependency, environment variable, API, credential หรือ GPU ตัวอย่างใช้ FakeClock และ synthetic receipt; ไม่มี cost ภายนอกตามขอบเขตจำลอง แต่ไม่ได้วัดต้นทุนหรือ latency โค้ดและ tests ยัง NOT RUN
ข้อจำกัดเมื่อคิดถึงระบบจริง
state อยู่ใน dictionary ของ process จึงหายเมื่อปิด ไม่มี persistence, distributed atomicity, concurrency control หรือ exactly-once external effect ตัวอย่างไม่เรียก publisher และระบบจริงอาจ timeout หลังปลายทางทำงาน
ระบบจริงต้องพิจารณา durable atomic consume, การเรียกพร้อมกัน, audit, revocation, identity และ tool binding ที่เชื่อถือได้ ตลอดจนการกระทบยอดเมื่อผลปลายทางไม่แน่ชัด การเพิ่มฐานข้อมูลหรือ queue อย่างเดียวไม่พอ ต้องกำหนด transaction boundary และ partial success
อย่าพึ่งพา fingerprint หาก agent แก้ action ได้โดยไม่มีการตรวจ Gate ควรรับ action ที่ผ่าน schema validation และหน้าขออนุมัติควรแสดงรายละเอียดตรงกับ action จริง ความปลอดภัยยังต้องอาศัย identity, authorization และการบังคับใช้ที่ปลายทาง
OWASP Transaction Authorization ใช้เป็น design analogy ไม่ใช่การรับรอง implementation นี้ MCP แนะนำ confirmation สำหรับ tool ที่มีผลกระทบ แต่ไม่ได้กำหนดอัลกอริทึมนี้ ส่วน Python hashlib อธิบาย digest จาก bytes ไม่ใช่หลักฐาน consent
Acceptance criteria และจุดหยุด
ให้ถือว่าการออกแบบผ่านเกณฑ์เชิงเอกสารเมื่อผู้พัฒนาระบุ field order และ UTF-8 encoding ได้, แสดงกรณี changed/restore/replay/expiry/scope ได้, อธิบายสมมติฐานแยกจากพฤติกรรมที่สังเกตได้, และติดป้าย code กับ tests ว่า NOT RUN หากยังไม่ได้รัน เกณฑ์เหล่านี้ตรวจความครบถ้วนของแบบฝึก ไม่ใช่เกณฑ์อนุมัติ production
หยุดก่อนเริ่ม implementation หากไม่รู้ action field ที่มีผลต่อการกระทำ, ไม่ทราบแหล่ง identity ที่เชื่อถือได้, ไม่กำหนดว่าใครอนุมัติอะไร หรือไม่รู้ว่าระบบปลายทางตอบสนองซ้ำอย่างไร การเปลี่ยนจาก mock ไปใช้ API หรือ MCP จริงต้องมีการออกแบบสิทธิ์และ effect ใหม่ ไม่สามารถต่อ publisher หลัง ALLOW_MOCK_ONLY แล้วอ้างว่าผ่านเพราะตัวอย่างแสดง allow
แบบฝึกหัด
ทำตารางโดยเปลี่ยน body, visibility, revision, tool, report, actor และ tenant ทีละค่า ระบุผลและ state ที่คาด รวมเวลาถึง expiry กับ clock rollback หาก schema ขาด field จริง ให้ถามก่อนเขียนโค้ด
Rubric: ผ่านเมื่อทุก action-affecting field ทำให้ fingerprint เปลี่ยน, wrong scope ปฏิเสธโดยไม่กิน receipt เจ้าของ, restore หลัง DENY_CHANGED ยังคงปฏิเสธ, replay และ expiry ปฏิเสธ และรายงานผลที่ยังไม่สังเกตว่า UNKNOWN หรือ NOT RUN อย่างตรงไปตรงมา หากเผลอเรียก digest ว่าลายเซ็น หรือเรียก synthetic fixture ว่าความยินยอมจริง ให้แก้คำอธิบายก่อนส่งงาน
Copyable task brief
เป้าหมาย: สร้างแบบจำลอง approval gate ที่ผูก receipt แบบใช้ครั้งเดียวกับ action snapshot ที่แน่นอน
เครื่องมือ: Python 3.12+, Standard Library; ไม่มี dependency, environment variable, credential, API หรือ network
สิทธิ์: ทำงานใน process จำลองเท่านั้น; ห้ามเรียก publisher, MCP server หรือบริการจริง; approve() เป็น fixture ไม่ใช่ consent จริง
ส่งมอบ: schema ของ Context/Action, field order ที่คงที่, digest จาก UTF-8 bytes, receipt state และตัวอย่างผล decision
เกณฑ์ยอมรับ: wrong scope ไม่กิน receipt; changed action ได้ DENY_CHANGED และ invalidates ถาวร; restore ได้ DENY_INVALIDATED; exact unused unexpired action ได้ ALLOW_MOCK_ONLY ครั้งเดียว; replay ได้ DENY_USED; now >= expiry ได้ DENY_EXPIRED; clock rollback fail-closed
จุดหยุด: หยุดก่อน implementation หากยังระบุ action-affecting fields, แหล่ง identity ที่เชื่อถือได้, ขอบเขต server/tool หรือพฤติกรรมผลภายนอกไม่ได้
การรายงาน: ระบุคำสั่งที่แนะนำและผลที่คาดแยกจากผลสังเกต; หากไม่ได้รัน ให้คง NOT RUN และห้ามอ้าง runtime PASS
Quick Answer
สร้าง fingerprint จากทุกช่องที่มีผลต่อ action แล้วตรวจซ้ำที่ gate ก่อนผลกระทบ Receipt ต้องอยู่ใน scope เดิม ยังไม่หมดอายุ และยังไม่เคยใช้ การเปลี่ยนรายละเอียดควร invalidate receipt เดิมอย่างถาวร ส่วน hash ใช้ตรวจความเปลี่ยนแปลงเท่านั้น ไม่พิสูจน์ตัวตน อำนาจ หรือ consent
FAQ
ทำไมไม่เก็บแค่ approved=true?
เพราะ Boolean ทั่วไปไม่บอกว่าอนุมัติ action ฉบับใด หากรายละเอียดเปลี่ยน สถานะเดิมอาจถูกนำไปใช้กับการกระทำใหม่โดยไม่รู้ตัว การผูก receipt กับ snapshot ทำให้ gate เปรียบเทียบรายละเอียดได้
SHA-256 ยืนยันว่าผู้ใช้กดอนุมัติหรือไม่?
ไม่ มันเป็น digest ของ bytes ไม่มีตัวตนหรือ authority อยู่ในตัว ต้องอาศัยกระบวนการยืนยันตัวตนและบันทึกการยินยอมที่เชื่อถือได้แยกต่างหาก
ทำไม restore หลังเปลี่ยน action แล้วยังถูกปฏิเสธ?
เพราะ receipt เดิมถูก invalidated เมื่อพบ action ที่ไม่ตรง การคืนค่าทีหลังไม่ลบเหตุการณ์นั้น และระบบควรให้ผู้ใช้เห็น action ปัจจุบันแล้วอนุมัติใหม่
ALLOW_MOCK_ONLY แปลว่าเรียกเครื่องมือสำเร็จไหม?
ไม่ มันหมายถึงผ่านเงื่อนไขใน gate จำลองเท่านั้น ตัวอย่างไม่มี tool effect, publisher หรือ network call
TTL 30 วินาทีเหมาะกับ production หรือไม่?
ยังสรุปไม่ได้ ค่านี้เป็นตัวอย่างให้เห็น expiry boundary ระยะจริงต้องสอดคล้องกับความเสี่ยง ประสบการณ์ผู้ใช้ และข้อกำหนดระบบ พร้อมหลักฐานและนโยบายที่เกี่ยวข้อง
ต้อง canonicalize JSON ทุกกรณีไหม?
ตัวอย่างนี้ไม่ทำ JSON canonicalization ทั่วไป แต่สร้าง array ตาม schema คงที่และลำดับที่ระบุ หากระบบอื่นสร้าง fingerprint ต้องตกลง schema, ชนิด, ลำดับ, encoding และ normalization ให้ตรงกันก่อน
Interactive และ Labs
แผงจำลองด้านล่างช่วยให้สลับ action และสังเกตว่า field ที่เปลี่ยนทำให้ receipt เดิมถูกปฏิเสธอย่างไร ผลที่แสดงเป็นกฎของ educational simulation ไม่ใช่การเรียก tool จริง โปรดใช้ป้ายกำกับและคำอธิบายสำรองเพื่ออ่านผลเมื่อ JavaScript ไม่ทำงาน รองรับการควบคุมด้วยแป้นพิมพ์และการลด motion; การทดสอบ UI จริงยังไม่ได้ทำในงานนี้
3D LAB · แบบจำลองเชิงการศึกษา
ใบรับรองอนุมัติผูกกับคำสั่งฉบับไหน?
Receipt คือใบรับรองใช้ครั้งเดียวที่อ้างถึงคำขอซึ่งตรวจแล้ว ภาพมิติช่วยให้เห็นความสัมพันธ์ สถานะเป็นผลจำลอง ไม่มีการเข้าสู่ระบบ ไม่มี API และไม่มีการเผยแพร่จริง
มุมมองความสัมพันธ์ 3D · ควบคุมแยกจากแบบฟอร์มทดลอง
ALLOW_MOCK_ONLY · snapshot A ตรงกับ receipt และผ่านประตูจำลองหนึ่งครั้ง
ฉากนี้เปลี่ยนด้วยตัวเลือกด้านบนเท่านั้น ไม่อ่านหรือเขียนฟิลด์ในแบบฟอร์มด้านล่าง
กำหนดบริบทและคำสั่ง
TTL 30 วินาทีเป็นค่าตัวอย่างเท่านั้น ไม่มีนาฬิกาหรือหมดอายุอัตโนมัติ ปุ่มหมดอายุเป็นการตั้งสถานะด้วยมือ ไม่ได้รอ 30 วินาที
ผลที่ประตู
เริ่มต้น: ยังไม่มี receipt ลองอนุมัติ A แล้วเปลี่ยน visibility เป็น public
Fixed schema ประกอบด้วยเวอร์ชัน, tenant, actor, tool, report, revision, visibility และ body digest
digest(A)/digest(B) เป็นสัญลักษณ์ ไม่มีการคำนวณ hash จริง; hash ไม่ใช่ตัวตนหรือลายเซ็น
ลำดับตรวจ: tenant/actor → สถานะอายุ → fingerprint → ใช้ครั้งเดียว
อ่านการทดลองทีละขั้น
เริ่มจากค่า A ซึ่งเป็นรายงาน private แล้วสร้าง receipt จำลอง จากนั้นเปลี่ยน visibility เป็น public และกดตรวจ ระบบแสดง DENY_CHANGED พร้อมชื่อฟิลด์ที่ต่างกัน เพราะการอนุมัติเก่าอ้างถึงคำขอ private เท่านั้น
เมื่อ scope เดิมตรงกันแต่ฟิลด์ของ action เปลี่ยน receipt นี้จะเข้าสถานะ invalidated ถาวร ลองเปลี่ยนกลับเป็น private แล้วตรวจซ้ำเพื่อดู DENY_INVALIDATED การคืนค่าไม่ทำให้เหตุการณ์เปลี่ยนแผนหายไป ต้องเริ่มการอนุมัติใหม่สำหรับ snapshot ปัจจุบัน
กรณี tenant หรือ actor ต่างกัน ระบบแสดง DENY_SCOPE ก่อนตรวจความตรงของคำสั่ง และไม่กิน receipt ของเจ้าของเดิม กลับสู่บริบทเดิมแล้วยังใช้ receipt ที่ไม่เปลี่ยนได้หนึ่งครั้ง จากนั้นการลองซ้ำแสดง DENY_USED
ชุดฟิลด์เป็นตัวอย่างของ schema คงรูป: ทุกคำขอเปรียบเทียบค่าในช่องเดียวกันตามลำดับที่กำหนด body ถูกแทนด้วยชื่อ digest เชิงสัญลักษณ์ เพื่อให้เห็นว่าการแก้เนื้อหาก็นับเป็นการเปลี่ยน โดยไม่สร้างหรือแสดง hash จริง ระบบจริงยังต้องตรวจสิทธิ์ที่เชื่อถือได้และทำงานแบบ atomic ที่จุดก่อนเกิดผลกระทบ
ข้อความ ALLOW_MOCK_ONLY หมายถึงผลสำเร็จเฉพาะในแบบฝึก ไม่ได้ยืนยันว่าระบบจริงทำงาน exactly-once หรือว่าการอนุมัติด้วย hash ปลอดภัย TTL 30 วินาทีเป็นเพียงค่าประกอบสถานการณ์ ไม่มีเวลาผ่านจริงหรือปุ่มเร่งเวลา
Video Lab · หยุดเมื่อคำขอเปลี่ยน
ใช้ตัวเลือกและ prompt generator ของ Video Lab แล้วเทียบ prompt กับ brief หกส่วนนี้ แบบฝึกเป็นสถานการณ์จำลอง ไม่มีบัญชีหรือบริการภายนอก
1 · เป้าหมาย
ทำคลิปหรือ storyboard เป้าหมาย 60–90 วินาทีสำหรับวิศวกร อธิบายว่าอนุมัติคำขอฉบับใด และทำไมต้องหยุดเมื่อคำขอเปลี่ยน เวลานี้เป็นเป้าหมายตัวอย่าง
2 · เครื่องมือและข้อมูล
เลือกหัวข้อ แพลตฟอร์ม ความยาว คำบรรยาย การเคลื่อนไหว และเสียง จากนั้นเทียบ prompt ที่สร้างกับ brief ใช้ข้อมูลสมมติ tenant-demo, actor-demo, report-17, revision 1, private, body-A ไม่เชื่อมบัญชี ไม่เรียก API และไม่คำนวณ hash จริง
3 · ขอบเขต
แสดง private A เปลี่ยนเป็น public B แล้วหยุดด้วย DENY_CHANGED คืน private แล้วยังคง DENY_INVALIDATED จากนั้นเริ่ม snapshot และขออนุมัติใหม่ ALLOW_MOCK_ONLY เป็นป้ายจำลองเท่านั้น
4 · สิ่งส่งมอบ
ส่ง prompt หรือ storyboard ที่มี A, receipt, B, การหยุด, การคืนค่าแล้วยังปฏิเสธ และการอนุมัติใหม่ ใช้ข้อความไทยชัดเจน เทียบ prompt กับ brief ทั้งหกส่วนและจดสิ่งที่ขาด ห้ามอ้างว่า Lab สร้าง timeline หรือส่งออกคลิป
5 · เกณฑ์ยอมรับ
ระบุ visibility ที่เปลี่ยน แสดง DENY_CHANGED และ DENY_INVALIDATED หลังคืนค่า พร้อมชี้ให้อนุมัติ snapshot ใหม่ ห้ามสื่อว่า receipt เดิมกลับมาใช้ได้ แยกแบบฝึกจากผลทดสอบจริง และไม่ใส่ข้อมูลส่วนบุคคลหรือ secret
6 · จุดหยุด
หยุดเมื่อ prompt หรือ storyboard ครบเกณฑ์ หรือเมื่อเครื่องมือขอข้อมูลจริง บันทึกตามจริงหากได้เพียง prompt ไม่เผยแพร่หรืออ้างการอนุมัติ การตรวจ fingerprint และ exactly-once เป็นแนวคิดบทเรียน ไม่ใช่ความสามารถปลั๊กอิน
Interactive approval simulation แยกต่างหากและไม่มีผลภายนอก
ใช้ Video Lab ด้านล่างเลือกหัวข้อคลิป รูปแบบ ความยาว และคำบรรยาย แล้วนำ prompt ที่สร้างได้มาเทียบ brief หกส่วน เพิ่มขอบเขต เกณฑ์รับงาน และจุดหยุดที่ยังขาด ค่าเวลาและเงินใน Cost Lab เป็นสมมติฐานที่ปรับได้ ไม่ใช่ผลวัดหรือราคาบริการ การ generate/copy prompt และ UI จริงยังไม่ได้ทดสอบ
จากคลิปดิบ
สู่จักรวาลคอนเทนต์
ChatGPT ตัดต่อวิดีโอแทนคนได้จริงแค่ไหน? สำรวจระบบ AI Video Editing Agent ผ่านฉากสามมิติที่โต้ตอบได้ พร้อมแยกข้อเท็จจริง สร้างพรอมป์ต์ และจำลองต้นทุนก่อนลงมือทำจริง
ออกแบบระบบตัดต่อในมุมมอง 3D
ลากบนฉากเพื่อหมุนมุมมอง แตะโหนดเพื่อดูหน้าที่ของ AI ในแต่ละขั้นตอน
จัดระเบียบไฟล์ ตรวจคุณภาพภาพและเสียง และกำหนดสิทธิ์เข้าถึงให้ AI Agent
แยกความสามารถจริงจากคำโฆษณา
กดแต่ละประเด็นเพื่ออ่านรายละเอียดและแหล่งข้อมูล
ChatGPT Work เข้าถึงไฟล์ในเครื่องได้ตรวจสอบแล้ว / มีเงื่อนไข+
Astra ใช้โปรแกรมคอมพิวเตอร์ทำงานได้ตรวจสอบแล้ว / ไม่ใช่โปรแกรมตัดต่อในตัว+
ตัดเสร็จใน 5–10 นาทีทุกกรณียังพิสูจน์เป็นเกณฑ์ทั่วไปไม่ได้+
ถูกกว่าจ้างคน 10 เท่าต้องกำหนดต้นทุนให้เทียบกันได้+
หนึ่งไอเดีย สามรูปแบบคอนเทนต์
ปรับรูปแบบวิดีโอ แล้วดูกรอบภาพตัวอย่างและ Prompt เปลี่ยนตามแบบเรียลไทม์
ช่วยตัดต่อ?
พร้อมนำไปใช้กับ AI Agent
ตัวสร้าง Prompt นี้ไม่อัปโหลดคลิป ไม่เรียก API เสียเงิน และไม่ตัดต่อวิดีโอจริง ต้องใช้กับเครื่องมือที่ผู้ใช้อนุญาตภายหลัง
จำลองเวลาและต้นทุนของคุณเอง
เลื่อนตัวเลขเพื่อเปรียบเทียบสถานการณ์สมมติ ไม่ใช่ผล benchmark ที่ยืนยันแล้ว
ตัวเลขเป็นตัวอย่างเพื่อช่วยตัดสินใจ ไม่ใช่ผลประหยัดที่รับประกัน
AI ที่ตัดคลิปเป็น ไม่ได้ทำงานลำพัง
สิ่งที่น่าสนใจของ GPT-6 Astra และ GPT-6.1 Sol ไม่ได้อยู่ที่ว่าตัวโมเดลมีปุ่ม Render MP4 ซ่อนอยู่ แต่คือความสามารถในการจัดการงานหลายขั้นตอนร่วมกับเครื่องมืออื่น: อ่านไฟล์ วางลำดับเรื่อง ใช้แอป ตรวจผลลัพธ์ และแก้งานตามข้อเสนอแนะของมนุษย์
สำหรับคนทำคอนเทนต์ ความต่างสำคัญคือการเปลี่ยนจาก “ส่งคลิปให้ AI แล้วหวังว่าจะได้ผลงาน” ไปเป็น Workflow ที่ตรวจรับได้ทุกขั้น ตั้งแต่การเลือกช่วงเด็ด การสร้างซับ การตรวจคำผิด จนถึงการเรนเดอร์ไฟล์ตามสัดส่วนของแต่ละแพลตฟอร์ม
ข้อควรระวังคือระบบ Work ที่รันบนเว็บหรือมือถือไม่ได้หมายความว่า AI เข้าถึงคลิปบนคอมพิวเตอร์บ้านได้ทันที ต้องทำงานในสภาพแวดล้อมที่เปิดให้เข้าถึงไฟล์จริง มีเครื่องมือตัดต่อ และมีสิทธิ์ใช้งานอย่างชัดเจน
รวบรวมไฟล์ในโฟลเดอร์ที่เข้าถึงได้ ระบุสิทธิ์และข้อจำกัด
ให้ Agent เสนอ Hook, ช่วงตัด และโครงเรื่องก่อนตัดต่อ
ตรวจชื่อเฉพาะ ตัวเลข ภาษาไทย และความถูกต้องของคำกล่าวอ้าง
ตรวจความละเอียด เสียง ระยะเวลา safe zone และคำบรรยาย
การจำลอง video workflow ในบทเรียนควรเชื่อมกับทักษะเดียวกัน: ตรวจ action snapshot ก่อนจุดส่งต่อ, แสดงการเปลี่ยน visibility และหยุดเมื่อ fingerprint ไม่ตรง ให้แสดงสถานะว่าเป็นการจำลอง ไม่ส่งวิดีโอหรือเผยแพร่จริง หากแผงไม่ทำงาน ผู้อ่านยังใช้ตารางสถานะและ worked example ในบทความนี้แทนได้
แหล่งอ้างอิงและลิงก์อ่านต่อ
- OWASP Transaction Authorization Cheat Sheet — แนวทางออกแบบ authorization สำหรับธุรกรรม ใช้ในบทความเป็น design analogy ไม่ใช่การรับรอง
- MCP Specification: Tools — ข้อแนะนำเรื่องการยืนยันจากผู้ใช้กับ tool ที่มีผลกระทบ ไม่ได้กำหนดอัลกอริทึมในบทความนี้
- Python 3.12 hashlib — เอกสารการคำนวณ digest จากข้อมูล bytes
อ่านต่อ: การทบทวน argument ก่อนอนุมัติ AI Agent และ consent และ revocation ในการทำงานผ่าน MCP บทความนี้ต่อยอดเฉพาะการผูกการอนุมัติกับ action snapshot และไม่อ้างว่าทดแทนเนื้อหาหรือการตรวจของลิงก์เหล่านั้น
สถานะโค้ดและการตรวจ
ไฟล์ประกอบ: approval_gate.py, example_approval_gate.py, test_approval_gate.py คำสั่งที่ README แนะนำคือ python example_approval_gate.py และ python -m unittest -v test_approval_gate.py ผลที่คาดจากการอ่านโค้ดคือ DENY_CHANGED, DENY_INVALIDATED, ALLOW_MOCK_ONLY, DENY_USED สถานะการรันทั้งหมด: NOT RUN ไม่มีการรัน, import, compile, lint, test, ติดตั้ง dependency หรือเรียกบริการภายนอกในงานนี้
เปิดโค้ดและคำสั่งใช้งาน: code/approval_gate.py · code/example_approval_gate.py · code/test_approval_gate.py · README-code.md · ทั้งหมด NOT RUN
Source Code และวิธีลองด้วยตนเอง · NOT RUN
code/approval_gate.py · ยังไม่ได้รัน
"""Educational in-memory mock; one synthetic approval for one exact action."""
from __future__ import annotations
from dataclasses import dataclass, replace
from enum import Enum
import hashlib, json, time, uuid
SCHEMA_VERSION=1
DEFAULT_TTL_SECONDS=30
@dataclass(frozen=True,slots=True)
class Context:
tenant_id:str
actor_id:str
@dataclass(frozen=True,slots=True)
class Action:
tool_id:str
report_id:str
revision:int
visibility:str
body:str
class Status(str,Enum):
ALLOW_MOCK_ONLY="ALLOW_MOCK_ONLY"
DENY_SCOPE="DENY_SCOPE"
DENY_CHANGED="DENY_CHANGED"
DENY_USED="DENY_USED"
DENY_EXPIRED="DENY_EXPIRED"
DENY_UNKNOWN="DENY_UNKNOWN"
DENY_INVALIDATED="DENY_INVALIDATED"
@dataclass(frozen=True,slots=True)
class _Receipt:
approval_id:str
tenant_id:str
actor_id:str
digest:str
issued_at:int
expires_at:int
state:str
def _validate(context:Context,action:Action)->None:
if type(context)is not Context or type(action)is not Action: raise TypeError("ต้องใช้ Context และ Action ตาม schema")
for value in(context.tenant_id,context.actor_id,action.tool_id,action.report_id):
if type(value)is not str or not value or len(value)>128: raise ValueError("ตัวระบุต้องเป็นข้อความไม่ว่างและไม่เกิน 128 ตัว")
if type(action.revision)is not int or action.revision<1: raise ValueError("revision ต้องเป็น int บวก (bool ใช้ไม่ได้)")
if type(action.visibility)is not str or action.visibility not in("private","public"): raise ValueError("visibility ต้องเป็น private หรือ public")
if type(action.body)is not str or len(action.body)>100000: raise ValueError("body ต้องเป็นข้อความไม่เกิน 100000 ตัว")
def fingerprint(context:Context,action:Action)->str:
"""Fixed-schema digest; neither identity nor signature."""
_validate(context,action)
body_hash=hashlib.sha256(action.body.encode("utf-8")).hexdigest()
fields=[SCHEMA_VERSION,context.tenant_id,context.actor_id,action.tool_id,action.report_id,action.revision,action.visibility,body_hash]
encoded=json.dumps(fields,ensure_ascii=False,separators=(",",":")).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()
def _monotonic_seconds()->int:
return int(time.monotonic())
class ApprovalGate:
"""Single-process mock; approve() is synthetic consent fixture."""
def __init__(self,*,clock=_monotonic_seconds,ttl_seconds:int=DEFAULT_TTL_SECONDS):
if type(ttl_seconds)is not int or not 1<=ttl_seconds<=300: raise ValueError("ttl_seconds ต้องเป็น int 1..300")
self._clock,self._ttl=clock,ttl_seconds
self._last_now:int|None=None
self._receipts:dict[str,_Receipt]={}
def _now(self)->int:
value=self._clock()
if type(value)is not int or value<0: raise ValueError("clock ต้องคืน int ไม่ติดลบ")
if self._last_now is not None and value<self._last_now: raise ValueError("นาฬิกาถอยหลัง: fail-closed")
self._last_now=value
return value
def approve(self,context:Context,action:Action)->str:
"""Issue synthetic receipt; does not collect human consent."""
digest,now=fingerprint(context,action),self._now()
approval_id=str(uuid.uuid4())
self._receipts[approval_id]=_Receipt(approval_id,context.tenant_id,context.actor_id,digest,now,now+self._ttl,"unused")
return approval_id
def consume(self,context:Context,action:Action,approval_id:str)->Status:
"""Exact snapshot consumed once; ALLOW_MOCK_ONLY has no external effect."""
_validate(context,action)
if type(approval_id)is not str or not approval_id: raise ValueError("approval_id ต้องเป็นข้อความ")
now=self._now(); receipt=self._receipts.get(approval_id)
if receipt is None: return Status.DENY_UNKNOWN
if (receipt.tenant_id,receipt.actor_id)!=(context.tenant_id,context.actor_id): return Status.DENY_SCOPE
if receipt.state=="invalidated": return Status.DENY_INVALIDATED
if receipt.state=="consumed": return Status.DENY_USED
if receipt.state=="expired" or now>=receipt.expires_at:
self._receipts[approval_id]=replace(receipt,state="expired")
return Status.DENY_EXPIRED
if fingerprint(context,action)!=receipt.digest:
self._receipts[approval_id]=replace(receipt,state="invalidated")
return Status.DENY_CHANGED
self._receipts[approval_id]=replace(receipt,state="consumed")
return Status.ALLOW_MOCK_ONLY
code/example_approval_gate.py · ยังไม่ได้รัน
"""Print mock decisions only; performs no action."""
from approval_gate import Action,ApprovalGate,Context
class FakeClock:
def __init__(self,value=100): self.value=value
def __call__(self): return self.value
def main():
gate,ctx=ApprovalGate(clock=FakeClock()),Context("tenant-demo","actor-demo")
private=Action("report.publish.v1","R-17",1,"private","Approved draft")
public=Action("report.publish.v1","R-17",1,"public","Approved draft")
old=gate.approve(ctx,private) # synthetic trusted-consent fixture
print(gate.consume(ctx,public,old).value) # DENY_CHANGED
print(gate.consume(ctx,private,old).value) # DENY_INVALIDATED
fresh=gate.approve(ctx,private)
print(gate.consume(ctx,private,fresh).value) # ALLOW_MOCK_ONLY
print(gate.consume(ctx,private,fresh).value) # DENY_USED
if __name__=="__main__": main()
code/test_approval_gate.py · ยังไม่ได้รัน
"""Meaningful policy checks; not executed for this lesson."""
import unittest
from approval_gate import Action,ApprovalGate,Context,Status,fingerprint
class Clock:
def __init__(self,value=10): self.value=value
def __call__(self): return self.value
class GateTests(unittest.TestCase):
def setUp(self):
self.clock=Clock(); self.gate=ApprovalGate(clock=self.clock,ttl_seconds=30)
self.ctx=Context("t1","a1"); self.action=Action("report.publish.v1","R-1",1,"private","body")
def test_default_clock_can_issue_receipt(self):
gate=ApprovalGate(); self.assertIsInstance(gate.approve(self.ctx,self.action),str)
def test_exact_snapshot_allowed_once(self):
r=self.gate.approve(self.ctx,self.action); self.assertEqual(self.gate.consume(self.ctx,self.action,r),Status.ALLOW_MOCK_ONLY)
def test_replay_denied(self):
r=self.gate.approve(self.ctx,self.action); self.gate.consume(self.ctx,self.action,r)
self.assertEqual(self.gate.consume(self.ctx,self.action,r),Status.DENY_USED)
def test_visibility_change_invalidates(self):
r=self.gate.approve(self.ctx,self.action); a=Action("report.publish.v1","R-1",1,"public","body")
self.assertEqual(self.gate.consume(self.ctx,a,r),Status.DENY_CHANGED)
def test_body_change_invalidates(self):
r=self.gate.approve(self.ctx,self.action); a=Action("report.publish.v1","R-1",1,"private","body!")
self.assertEqual(self.gate.consume(self.ctx,a,r),Status.DENY_CHANGED)
def test_revision_change_invalidates(self):
r=self.gate.approve(self.ctx,self.action); a=Action("report.publish.v1","R-1",2,"private","body")
self.assertEqual(self.gate.consume(self.ctx,a,r),Status.DENY_CHANGED)
def test_target_change_invalidates(self):
r=self.gate.approve(self.ctx,self.action); a=Action("report.publish.v1","R-2",1,"private","body")
self.assertEqual(self.gate.consume(self.ctx,a,r),Status.DENY_CHANGED)
def test_tool_change_invalidates(self):
r=self.gate.approve(self.ctx,self.action); a=Action("report.delete.v1","R-1",1,"private","body")
self.assertEqual(self.gate.consume(self.ctx,a,r),Status.DENY_CHANGED)
def test_wrong_actor_does_not_consume_receipt(self):
r=self.gate.approve(self.ctx,self.action)
self.assertEqual(self.gate.consume(Context("t1","a2"),self.action,r),Status.DENY_SCOPE)
self.assertEqual(self.gate.consume(self.ctx,self.action,r),Status.ALLOW_MOCK_ONLY)
def test_wrong_tenant_denied(self):
r=self.gate.approve(self.ctx,self.action)
self.assertEqual(self.gate.consume(Context("t2","a1"),self.action,r),Status.DENY_SCOPE)
def test_expiry_boundary_denied(self):
r=self.gate.approve(self.ctx,self.action); self.clock.value=40
self.assertEqual(self.gate.consume(self.ctx,self.action,r),Status.DENY_EXPIRED)
def test_expiry_stays_denied(self):
r=self.gate.approve(self.ctx,self.action); self.clock.value=40; self.gate.consume(self.ctx,self.action,r)
self.clock.value=41; self.assertEqual(self.gate.consume(self.ctx,self.action,r),Status.DENY_EXPIRED)
def test_invalidated_stays_invalidated_after_restore(self):
r=self.gate.approve(self.ctx,self.action); changed=Action("report.publish.v1","R-1",1,"public","body")
self.gate.consume(self.ctx,changed,r)
self.assertEqual(self.gate.consume(self.ctx,self.action,r),Status.DENY_INVALIDATED)
def test_unknown_denied(self):
self.assertEqual(self.gate.consume(self.ctx,self.action,"missing"),Status.DENY_UNKNOWN)
def test_bool_revision_rejected(self):
with self.assertRaises(ValueError): fingerprint(self.ctx,Action("report.publish.v1","R-1",True,"private","body"))
def test_float_revision_rejected(self):
with self.assertRaises(ValueError): fingerprint(self.ctx,Action("report.publish.v1","R-1",1.0,"private","body"))
def test_exact_utf8_content_bytes_significant(self):
a=Action("report.publish.v1","R-1",1,"private","é"); b=Action("report.publish.v1","R-1",1,"private","e\u0301")
self.assertNotEqual(fingerprint(self.ctx,a),fingerprint(self.ctx,b))
def test_clock_rollback_fails_closed(self):
self.gate.approve(self.ctx,self.action); self.clock.value=9
with self.assertRaises(ValueError): self.gate.approve(self.ctx,self.action)
def test_bool_ttl_rejected(self):
with self.assertRaises(ValueError): ApprovalGate(clock=self.clock,ttl_seconds=True)
if __name__=="__main__": unittest.main()
README-code.md · ยังไม่ได้รัน
# โค้ดประกอบ: Approval Gate สำหรับ Tool Calling (Mock)
ตัวอย่างนี้ใช้ Python 3.12 ขึ้นไปและ Standard Library เท่านั้น ไม่ต้องติดตั้งแพ็กเกจเพิ่ม ไม่มี environment variables, credentials, GPU, API หรือ network call
## ขอบเขตและสิทธิ์
`ApprovalGate` เก็บ receipt ใน memory ของ process เดียว `approve()` เป็น synthetic fixture ที่สมมติ consent ไม่ได้แสดงหน้าขออนุมัติหรือยืนยันตัวตน และ `ALLOW_MOCK_ONLY` ไม่เรียก tool หรือสร้าง effect ใด ๆ ระดับการเชื่อมต่อคือ Mock เท่านั้น ไม่ได้เชื่อม Sandbox หรือบริการ Live ไม่มีสิทธิ์เผยแพร่หรือต่อ publisher
## วิธีรัน
ตรวจให้มี Python 3.12+ อยู่แล้ว จากโฟลเดอร์ `code` ไม่ต้องติดตั้ง dependencies แล้วรัน:
```text
python example_approval_gate.py
```
ไม่มี input จากผู้ใช้; example สร้าง Context, action, FakeClock และ receipt ตัวอย่างนี้กำหนด FakeClock เริ่มที่ 100 ผลที่คาดจากการอ่านโค้ด (ยังไม่ได้สังเกตจากการรัน) คือ:
```text
DENY_CHANGED
DENY_INVALIDATED
ALLOW_MOCK_ONLY
DENY_USED
```
## ชุดทดสอบที่ผู้เรียนเลือกเรียก
```text
python -m unittest -v test_approval_gate.py
```
ในงานเขียนนี้ไม่ได้รัน Python, import, compile, lint หรือ tests สถานะคือ **NOT RUN**; ผลข้างต้นเป็นผลคาดจากการอ่าน ไม่ใช่ PASS ชุดทดสอบมี 19 test methods ที่เขียนไว้ แต่ยังไม่ทราบผลการรัน
## ค่าใช้จ่ายและข้อจำกัด
ตามขอบเขตตัวอย่างไม่มีค่า API, cloud, GPU หรือบริการภายนอก ไม่มีการวัดต้นทุนหรือ latency และไม่อาจสรุปค่าใช้จ่ายของระบบจริงได้ Receipt หายเมื่อ process ปิด ไม่มี persistence, distributed atomicity, concurrency control หรือ exactly-once external effect ห้ามใช้ receipt ใน memory เป็น token จริง การต่อ API, SDK, MCP, agent, WordPress หรือ publisher ต้องออกแบบ identity, authorization และขอบเขต effect ใหม่

ใส่ความเห็น