APPROVED เข้า TIMEOUTABLE — การอนุมัติต้องหมดอายุได้ (#17) - #20
Merged
Conversation
RFC-0010 decided this and deliberately left it unimplemented: TIMEOUTABLE is RFC-0007's rule, so the amendment belongs there. RFC-0007 Amendment 1 adds APPROVED to the "entered from" list for TIMED_OUT, and states.py follows. The argument is governance, not liveness. A job that can sit in APPROVED forever may start executing a week later under a verdict formed in a context that no longer holds — the same stale APPROVED that RFC-0007 Decision 1 refuses when it keeps FAILED terminal. That door was shut on one side and left open on the other. It is also a conformance gap rather than a proposal. approval/v1 has carried expires_at since it was written and says what it means: "approval ที่หมดอายุแล้ว ใช้เดินงานไม่ได้ ต้องขอใหม่ · งานที่ค้างรออนุมัติจนเลยกำหนดควรเข้าสถานะ timeout ไม่ใช่รอตลอดไป". So: * Decision carries expires_at and renders it under the contract's own name; approve()/decide() take it. Optional there and optional here — an approval with no deadline never expires, and existing jobs are untouched. * The engine refuses to enter any POST_APPROVAL state under a lapsed approval (ExpiredApproval), including the way back out of a pause. Both halves of the direction lock now guard the same set, so there is no state reachable without a valid approval but not without any approval. * Replay refuses a trail that records it happening anyway (ExecutionAfterExpiry). The deadline and the moment are both in the log, so the claim is checkable by a reader who was not there — the same reason UnauditedExecution exists. * conformance/payload_check.py validates a real approval payload carrying expires_at, and probes both refusals; simulation gains approval_expired, whose job settles at TIMED_OUT rather than at a failure. contract-semantics.yaml records timeoutable and the enforced field. No semantics_version bump: frozen is untouched, exactly as in #16. Not done, and reported instead of guessed: nothing here sets expires_at or fires a timeout — the policy values stay out of scope in RFC-0007 and RFC-0010 alike — and the lifecycle still has no edge for "ขอใหม่", so a job whose approval lapsed settles at TIMED_OUT and a re-request is a new job with nothing linking it back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ปิด #17 — การอนุมัติมีวันหมดอายุได้แล้ว และ engine บังคับมัน
ไม่ใช่ฟีเจอร์ใหม่ แต่เป็นการปิด ช่องว่างของการ conform —
approval/v1ที่เรา pin ไว้มีexpires_atพร้อมข้อความว่า "approval ที่หมดอายุแล้วใช้เดินงานไม่ได้ ต้องขอใหม่ · งานที่ค้างรออนุมัติจนเลยกำหนด
ควรเข้าสถานะ timeout ไม่ใช่รอตลอดไป" · ฝั่งเราไม่มีทั้งที่เก็บค่านั้นและไม่มีทางให้
APPROVEDหมดเวลาได้เลยเปลี่ยนอะไร
rfcs/0007TIMED_OUTเพิ่มAPPROVEDพร้อมเหตุผลเต็มrfcs/0010states.pyTIMEOUTABLE+=APPROVED— ที่เดียว ไม่มี hard-code edge ที่อื่นdecision.pyexpires_at(บังคับ tz) ·is_expired(now)· ลง payload ชื่อเดียวกับสัญญาjob.pyPOST_APPROVALด้วย approval ที่หมดอายุ →ExpiredApprovalreplay.pyExecutionAfterExpiry— จับจาก trail ได้ด้วย ไม่ใช่แค่ตอน runtimecontract-semantics.yamltimeoutableตรงกับโค้ด · invariant เรื่อง expired approval · ไม่ bumpsemantics_versionผลตรวจ
pytestpayload_check.pysimulation/e2e_flow.pyเทสเดิมที่ต้องแก้มีข้อเดียว —
test_timeout_not_reachable_from_draft_or_approvedซึ่งเข้ารหัสกฎเก่าไว้ตรง ๆ · ไม่มี guarantee อื่นถูกลดความเข้ม
จุดตัดสินที่ต้องรีวิวเป็นพิเศษ
DEPLOYABLE → COMPLETEDไม่ถูกบล็อกด้วย expiry — approval ที่หมดอายุห้าม เดินงานใหม่แต่การปิดงานที่ทำเสร็จแล้วไม่ใช่การเดินงานใหม่ · ถ้าบล็อกด้วย จะบีบให้ job ที่งานเสร็จแล้วไปจบที่
FAILEDซึ่ง เล่าเรื่องผิด · ขอบเขตการ refuse จึงเป็นPOST_APPROVALทั้งชุดรวมresumeจาก
AWAITING_APPROVALแต่เว้นทางปิดงานสัญญาบอกว่า "ต้องขอใหม่" แต่ lifecycle ไม่มีทางให้ขอใหม่
ไม่มี edge จาก
APPROVEDกลับGOVERNANCE_ANALYSISและsupersede()ใช้ได้เฉพาะจากFAILEDซึ่ง
APPROVEDเข้าไม่ได้ (RFC-0010) → job ที่ approval หมดอายุจบได้แค่TIMED_OUT/CANCELLEDและการยื่นใหม่กลายเป็น job ใหม่ที่ไม่มีอะไรลิงก์กลับ — audit chain ขาดตรงนั้น
เป็น lifecycle change ต้องมี RFC จึงไม่สร้าง edge เอง แต่เขียนเป็นเทสบันทึกสภาพจริงไว้
และยังไม่มีใครตั้งค่า
expires_at— policy values อยู่นอกขอบเขตทั้ง RFC-0007 และ RFC-0010ตามเดิม · สิ่งที่ PR นี้เปลี่ยนคือ deadline มีความหมายที่ engine บังคับได้ และมี state ให้ลง
ตัวเลขจริงต้องมาจาก orchestration
Closes #17