Skip to content

RFC-0011: REQUIRE_CHANGES ไปจบที่ DRAFT — เปิดใช้ decision type ที่สามได้แล้ว - #22

Merged
monthop-gmail merged 1 commit into
mainfrom
feat/rfc-0011-require-changes
Aug 19, 2026
Merged

RFC-0011: REQUIRE_CHANGES ไปจบที่ DRAFT — เปิดใช้ decision type ที่สามได้แล้ว#22
monthop-gmail merged 1 commit into
mainfrom
feat/rfc-0011-require-changes

Conversation

@monthop-gmail

Copy link
Copy Markdown
Owner

RFC-0011 — REQUIRE_CHANGES มีปลายทางแล้ว: GOVERNANCE_ANALYSIS → DRAFT

หนึ่งในสามของ decision vocabulary ตายมาตั้งแต่ #5 — engine รับค่าไว้แต่ปฏิเสธการใช้งาน
เพราะไม่มีเอกสารไหนบอกว่า job ไปไหนต่อ · PR นี้ตอบคำถามนั้นและเปิดใช้งานจริง

ทำไมเป็นทางนี้

  • ตรงกับ guarantee ที่ frozen ไว้ใน approval/v1 ที่สุด — "REQUIRE_CHANGES ไม่ใช่ REJECT
    — งานยังมีชีวิตและกลับมายื่นใหม่ได้"
  • ไม่เพิ่ม vocabulary ใหม่ จึงไม่ต้องลาก agent-platform เข้ามาผ่าน RFC-0009 + ADR
    (ต่างจากทางเลือกที่เพิ่ม state ที่ 14)
  • ทางที่ใช้ REJECTED → DRAFT เดิม ถูกตัดทิ้งเพราะขัด guarantee ตรง ๆ

guarantee ยังคงอยู่จริง — แยกด้วย เส้นทาง ไม่ใช่แค่ decision record

REJECT           GOVERNANCE_ANALYSIS → REJECTED → DRAFT     ← REJECTED อยู่ใน history
REQUIRE_CHANGES  GOVERNANCE_ANALYSIS → DRAFT                ← ข้าม REJECTED

อ่านย้อนจาก trail แยกสองเคสนี้ออกได้โดยไม่ต้องเปิด decision record · มี check ใน
simulation/e2e_flow.py ที่ replay ทั้งสอง flow แล้วพิสูจน์ว่าเส้นทางต่างกันจริง

⚠️ invariant ที่พังจริงระหว่างทาง และวิธีที่แก้

การเพิ่ม edge ทำให้ DRAFT เข้าได้สองทาง และมีทางเดียวที่เป็นคำตัดสิน:

edge ต้องมีอะไร
GOVERNANCE_ANALYSIS → DRAFT คำตัดสิน — ต้องมี authority + reason + decision record
REJECTED → DRAFT ขั้นแก้ไขหลังคำตัดสินที่บันทึกไปแล้ว — ไม่ต้องมีอะไร

DECISION_BY_TARGET (map จาก ปลายทาง) จึงตอบผิดทันที — จะไปบังคับให้ REJECTED → DRAFT
ต้องมี authority และทำให้ replay ปฏิเสธ trail ที่ถูกต้องสมบูรณ์

ไม่ได้ใส่ข้อยกเว้นให้ผ่าน แต่เปลี่ยน key เป็น edge → DECISION_BY_EDGE + decision_for_edge()
ใช้ทั้งฝั่ง engine และ replay · และ ลบ DECISION_BY_TARGET ทิ้ง ไม่ทิ้งไว้คู่กัน
เพื่อให้โค้ดที่ยังอ่านแบบเก่า import ไม่ผ่าน แทนที่จะอ่านคำตอบผิดเงียบ ๆ
· บันทึกไว้ทั้งใน Consequences และ Risk ของ RFC

ผลตรวจ

ก่อน หลัง
pytest 481 503 passed
payload_check.py 17 18 passed · 0 fail
simulation/e2e_flow.py 29 34 passed · 0 fail

check ใน payload_check ที่เคยยืนยันว่า REQUIRE_CHANGES ถูกปฏิเสธ กลับด้านเป็น
ยืนยันว่ามัน พา job ไป DRAFT · ล้าง approval ทิ้ง · ข้าม REJECTED และ payload
decision: REQUIRE_CHANGES ถูก validate กับ approval/v1 ในฐานะสิ่งที่ engine ผลิตจริง

ที่รักษาไว้

states.py ยังเป็นที่เดียวที่ประกาศ transition · direction lock · expires_at · APPROVED
timeout (#17) ไม่ถูกแตะ — มีเทสเฉพาะยืนยันว่า edge ใหม่ไม่เปิดทางเข้า execution
· semantics_version ยัง 1.1

UnmappedDecision เก็บไว้ พร้อมคำอธิบายว่าใช้เมื่อไหร่ — ตอนนี้ไม่มีใครยิง แต่เป็น backstop
ของ decision type ที่ 4 ในอนาคตที่ประกาศค่าโดยไม่ประกาศปลายทาง ถ้าลบทิ้งจะกลายเป็น KeyError
กลางเครื่องยนต์แทนการปฏิเสธที่อ่านออก · มีเทสยิงมันจริง

Follow-up ที่ยังไม่ทำ

RFC-0010 ทิ้ง Future Work ไว้ว่าควรมี conformance check ที่เทียบ contract-semantics.yaml
กับ states.py ตรง ๆ — PR นี้ยืนยันด้วยมือแล้วว่าตรง แต่ยังไม่มี check ถาวร

…SIS → DRAFT

RFC-0002 ประกาศ decision ไว้สามค่าและ contract-semantics.yaml ปิดชุดนั้นไว้
แต่มีสองค่าเท่านั้นที่มีปลายทาง ค่าที่สาม engine ปฏิเสธมาตั้งแต่ issue #5
ด้วย UnmappedDecision เพราะยังไม่มี RFC บอกว่า job ที่ถูกตีกลับให้แก้ไปอยู่ไหน

RFC-0011 ตอบว่า DRAFT ผ่าน edge ใหม่ GOVERNANCE_ANALYSIS -> DRAFT — ตรงกับ
guarantee ที่ frozen ไว้ใน approval/v1 ("งานยังมีชีวิตและกลับมายื่นใหม่ได้")
และไม่เพิ่ม vocabulary ใหม่ จึงไม่ลาก agent-platform เข้ามาเกี่ยว

จุดที่ต้องอธิบายให้ชัด: REJECT กับ REQUIRE_CHANGES จบที่ DRAFT เหมือนกัน
แต่แยกกันได้ด้วยเส้นทาง ไม่ใช่แค่ด้วย decision record —
REJECT เดิน GOVERNANCE_ANALYSIS -> REJECTED -> DRAFT (state REJECTED ค้างใน
history ตลอดไป) ส่วน REQUIRE_CHANGES เดินตรงและข้าม REJECTED คนที่อ่านย้อน
จาก log อย่างเดียวจึงแยกออกได้ และสองครึ่ง (trail กับ decision) ตรวจกันเอง —
trail ที่บันทึก REJECT บน edge ตรงถูก replay ปฏิเสธ ไม่ใช่เชื่อตาม
มีเทสพิสูจน์ว่า replay แยกสอง flow นี้ออกจริง

ผลที่ตามมา: คำถามว่า transition นี้เป็นคำตัดสินไหม กลายเป็นคุณสมบัติของ edge
ไม่ใช่ของปลายทาง เพราะ DRAFT เข้าได้สองทางและมีทางเดียวที่เป็นคำตัดสิน
DECISION_BY_TARGET จึงถูกแทนด้วย DECISION_BY_EDGE ทั้งฝั่ง engine และ replay

- REQUIRE_CHANGES ล้าง approval เหมือน REJECT — ถูกสั่งให้แก้ไม่ใช่ได้รับอนุมัติ
- direction lock, expires_at, และ APPROVED timeout (#17) ไม่ถูกแตะ
- UnmappedDecision เก็บไว้เป็น backstop ของ decision type ใหม่ในอนาคต
  ที่ประกาศค่าโดยไม่ประกาศปลายทาง พร้อมเทสที่ยิงมันจริง
- semantics_version ไม่ขยับ — ไม่มีอะไรใน frozen: เปลี่ยน (แบบเดียวกับ RFC-0010)
- payload_check เช็คเดิมที่ยืนยันว่า REQUIRE_CHANGES ถูกปฏิเสธ กลับด้านแล้ว

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant