From d9133fbb016756f99a15429450b26546b6d3c3a2 Mon Sep 17 00:00:00 2001 From: monthop-gmail Date: Fri, 11 Sep 2026 05:23:37 +0700 Subject: [PATCH] =?UTF-8?q?error/v1=20v1.1.0:=20details=20=E0=B8=A1?= =?UTF-8?q?=E0=B8=B5=E0=B8=81=E0=B8=8E=E0=B9=81=E0=B8=A5=E0=B9=89=E0=B8=A7?= =?UTF-8?q?=20(ADR-0030)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit care-agent-platform ไล่ปิด metadata ของ audit event ในบ้านตัวเองตามที่ตกลงกันไว้ แล้วรายงานว่าเจอช่องที่สามระหว่างทาง คือ error/v1 details เป็น type object เปล่า รูปเดียวกับ metadata เป๊ะ ต่างกันแค่ไม่มีใครพูดถึงมัน รวมทั้งตัวเขาเองที่เพิ่งไล่ปิด metadata ทั้งใบแล้วเดินผ่าน details ไปโดยไม่เห็น เปิดไฟล์ตรวจแล้วจริง และความไม่สมมาตรอยู่ห่างกันสิบบรรทัดในไฟล์เดียวกัน message ห้าม PII ไว้ตั้งแต่ v1.0.0 ส่วน details ไม่มีคำอธิบายเลยสักบรรทัด error/v1 ไม่ใช่ contract ที่ derive semantics มาจากใคร เป็นของ agent-platform เต็มใบ จึงแก้ได้เองโดยไม่ต้องมี RFC ที่ต้นทาง ต่างจาก event/v1 transition reason ซึ่งเป็นช่อง เดียวกันแต่อยู่ในสัญญาที่ derive มา เคาะ option C คือเติมกฎให้สอดคล้องกับ message พร้อมเขียนไว้ในตัวฟิลด์ว่ากฎนี้บังคับ ได้ที่ผู้ผลิตเท่านั้น ปฏิเสธการปิดด้วย propertyNames enum ด้วยเหตุผลของผู้รายงานเอง คือ schema เขียนได้แค่ว่าคีย์ไหนมีได้ เขียนไม่ได้ว่าใครเป็นคนเขียนค่านั้นซึ่งเป็นแกนจริง ของกฎ และรายการที่ต้องซิงก์สองที่จะ drift ภายในเดือนเดียว ข้อสังเกตที่ทำให้ใบนี้มีค่ากว่าการแก้ไฟล์คือประโยคของผู้รายงานว่าช่องเปิดที่อันตราย ที่สุดไม่ใช่ช่องที่มีคนเถียงกันว่าควรปิดไหม แต่เป็นช่องที่ไม่มีใครเอ่ยชื่อมันเลยในทั้ง กระทู้ metadata ถูกพูดถึงจนได้รับการปิด details อยู่ในไฟล์เดียวกันรูปเดียวกันและรอด มาได้เพราะไม่มีใครพิมพ์ชื่อมัน บทเรียนที่บันทึกไว้ใช้ต่อ ครั้งหน้าที่ไล่ปิด open bag ให้ไล่จากรายการฟิลด์ในไฟล์ ไม่ใช่ จากรายการที่มีคนพูดถึงในกระทู้ สองรายการนี้ไม่เท่ากันและส่วนต่างคือช่องที่รอดมาได้ ไม่ breaking เพิ่มคำอธิบายและกฎ ไม่แตะ type ไม่แตะ required ไม่ปิด object drift check 34/34 FAIL=0 WARN=0 Claude-Session: https://claude.ai/code/session_01LHv7HRmnnGAoKT5BvxDWHs Co-authored-by: monthop-gmail Co-Authored-By: Claude Opus 5 (1M context) --- contracts/error/v1/CHANGELOG.md | 8 +++ contracts/error/v1/error.schema.yaml | 12 ++++ decisions/0030-the-field-nobody-named.md | 88 ++++++++++++++++++++++++ decisions/README.md | 3 + 4 files changed, 111 insertions(+) create mode 100644 decisions/0030-the-field-nobody-named.md diff --git a/contracts/error/v1/CHANGELOG.md b/contracts/error/v1/CHANGELOG.md index 6fa005b..f00dc9e 100644 --- a/contracts/error/v1/CHANGELOG.md +++ b/contracts/error/v1/CHANGELOG.md @@ -1,5 +1,13 @@ # error/v1 +## v1.1.0 — 2026-09-10 +- `details` มีกฎแล้ว — เดิมเป็น `type: object` เปล่า ไม่มีคำอธิบายสักบรรทัด ขณะที่ `message` + ที่อยู่ติดกันในไฟล์เดียวกันห้าม PII ไว้ตั้งแต่ v1.0.0 · ความไม่สมมาตรนี้ไม่มีใครเห็นมาสามสัปดาห์ +- พบโดย [`care-agent-platform`](https://github.com/monthop-gmail/care-agent-platform) ระหว่างไล่ปิด + `metadata` ของ `event/v1` ในบ้านตัวเอง แล้วเดินผ่าน `details` ไปโดยไม่เห็น จนย้อนกลับมาเจอเอง + ([ADR-0030](../../../decisions/0030-the-field-nobody-named.md)) +- **ไม่ breaking** — เพิ่มคำอธิบายและกฎ ไม่แตะ type ไม่แตะ `required` ไม่ปิด object + ## v1.0.0 — 2026-08-17 - ตั้งต้นตาม `ref/agent-platform-decisions-first-plan.md` Phase 3 - แยก `policy_denied` และ `approval_required` ออกจาก `authorization` เพราะ retry semantics ต่างกัน diff --git a/contracts/error/v1/error.schema.yaml b/contracts/error/v1/error.schema.yaml index d7066b3..d64af94 100644 --- a/contracts/error/v1/error.schema.yaml +++ b/contracts/error/v1/error.schema.yaml @@ -44,5 +44,17 @@ properties: minimum: 0 details: type: object + description: >- + **ตัวชี้ ไม่ใช่ที่เก็บเนื้อหา** — ใส่ id ของ record ที่เกิดเหตุ ชื่อ resource + รหัส หรือค่าที่นับได้ · **ห้ามใส่ credential, PII, ข้อความที่ subject พูด + หรือเนื้อหา prompt ของผู้ใช้** เหมือนกฎของ `message` + + ⚠️ เป็น **open object โดยเจตนา** เพราะ platform ไม่รู้จักโดเมนของ error + แต่ open object แปลว่า **ไม่มีใครทบทวนตอนมีคนเพิ่มคีย์** — ผู้ผลิตต้องมีทะเบียน + คีย์ของตัวเองที่บังคับด้วย CI ไม่ใช่พึ่งการรีวิว + + 🔒 กฎนี้บังคับได้ที่ **ผู้ผลิต** เท่านั้น · validator บอกได้ว่าคีย์ไหนมีอยู่ + บอกไม่ได้ว่า**ใครเป็นคนเขียนค่านั้น** ซึ่งเป็นแกนจริงของกฎ + — `code` ที่ระบบสร้างเอง กับข้อความที่ผู้ใช้พิมพ์ เป็น `string` เหมือนกันทุกประการ correlation_id: $ref: https://schemas.agent-platform.internal/identity/v1/identity.schema.yaml#/$defs/Id diff --git a/decisions/0030-the-field-nobody-named.md b/decisions/0030-the-field-nobody-named.md new file mode 100644 index 0000000..50f0db2 --- /dev/null +++ b/decisions/0030-the-field-nobody-named.md @@ -0,0 +1,88 @@ +# ADR-0030: ฟิลด์ที่ไม่มีใครเอ่ยชื่อ — `error/v1.details` เป็น open bag มาสามสัปดาห์โดยไม่มีใครเห็น + +**Status:** **Accepted** (2026-09-10) +**Date:** 2026-09-10 +**Depends on:** [ADR-0006](0006-contract-versioning.md) · [ADR-0011](0011-conformance-automation.md) +**Blocking:** `contracts/error/v1` → `v1.1.0` + +## Context + +`care-agent-platform` ไล่ปิด `metadata` ของ audit event ในบ้านตัวเองตามที่ตกลงกันไว้ใน `dis-65134078` แล้วรายงานกลับมาว่าเจอช่องที่สามระหว่างทาง: + +> `error/v1` นิยาม `details` ว่า `type: object` เปล่า ๆ คือ open bag รูปเดียวกับ `metadata` เป๊ะ ต่างกันแค่ไม่มีใครพูดถึงมันตอนที่เราตกลงกันเรื่อง `attributes` **รวมทั้งผมเองที่เพิ่งไล่ปิด `metadata` ทั้งใบและเดินผ่าน `details` ไปโดยไม่เห็น** + +เปิดไฟล์ตรวจแล้ว ยืนยันว่าจริง และความไม่สมมาตรอยู่ห่างกันสิบบรรทัดในไฟล์เดียวกัน: + +```yaml + message: + type: string + description: ห้ามใส่ credential, PII หรือเนื้อหา prompt ของผู้ใช้ # ← มีตั้งแต่ v1.0.0 + + details: + type: object # ← ไม่มีอะไรเลย +``` + +`error/v1` อยู่ที่ `v1.0.0` ตั้งแต่ 17 ส.ค. ไม่เคยขยับ และ **ไม่ใช่ contract ที่ derive semantics มาจากใคร** — เป็นของ `agent-platform` เต็มใบ เราจึงแก้ได้เองโดยไม่ต้องมี RFC ที่ต้นทาง ต่างจาก `event/v1.transition.reason` ซึ่งเป็นช่องเดียวกันแต่อยู่ในสัญญาที่ derive มา + +### ข้อสังเกตที่ทำให้ใบนี้มีค่ามากกว่าการแก้ไฟล์ + +ผู้รายงานสรุปไว้เอง และเป็นประโยคที่ควรอยู่ในบันทึกทั้งประโยค: + +> ช่องเปิดที่อันตรายที่สุดไม่ใช่ช่องที่มีคนเถียงกันว่าควรปิดไหม แต่เป็น **ช่องที่ไม่มีใครเอ่ยชื่อมันเลยในทั้งกระทู้** +> +> `metadata` ถูกพูดถึงจนได้รับการปิด · `details` อยู่ในไฟล์เดียวกัน รูปเดียวกัน และรอดมาได้เพราะไม่มีใครพิมพ์ชื่อมัน + +และจับคู่กับข้อที่เขาเสนอไว้ก่อนหน้าเรื่องชื่อคีย์ ว่าเป็นคนละด้านของเรื่องเดียวกัน: + +```text +ชื่อที่กว้างเกิน → คนใส่ของผิดเข้าไปโดยไม่รู้ตัว +ฟิลด์ที่ไม่มีใครเอ่ยชื่อ → ไม่มีใครไปดูว่ามีอะไรอยู่ในนั้น +``` + +**สองอย่างนี้ไม่มีอะไรใน JSON Schema ตรวจได้เลย** — ทั้งคู่เป็นเรื่องที่ต้องมีคนอ่านแล้วเอ่ยชื่อ + +## Options + +### A. ไม่ทำอะไร — `details` เป็น open object โดยเจตนาอยู่แล้ว + +* ✅ ไม่แตะสัญญาที่นิ่งมาสามสัปดาห์ +* ❌ **ในไฟล์เดียวกัน ฟิลด์หนึ่งห้าม PII อีกฟิลด์ไม่พูดอะไรเลย** — คนอ่านจะสรุปว่าที่ไม่ห้ามคือที่ใส่ได้ ซึ่งเป็นการอ่านที่สมเหตุสมผล +* ❌ `care-agent-platform` เก็บของจริงลง audit อยู่แล้วในวันที่เริ่มปิด ไม่ใช่ความเสี่ยงเชิงทฤษฎี + +### B. ปิด `details` ด้วย `propertyNames` enum ของคีย์ที่อนุญาต + +* ✅ ดูเป็นทางการที่สุด · validator บังคับได้ทันที +* ❌ **platform ไม่รู้จักโดเมนของ error** จะเอาคีย์ของใครมาใส่ในรายการกลาง +* ❌ ผู้รายงานลองแล้วและปฏิเสธด้วยเหตุผลที่ตรงกว่า — **schema เขียนได้แค่ว่าคีย์ไหนมีได้ เขียนไม่ได้ว่าใครเป็นคนเขียนค่านั้น ซึ่งเป็นแกนจริงของกฎ** · `code` ที่ระบบสร้างเอง กับข้อความที่ผู้ใช้พิมพ์ เป็น `string` เหมือนกันทุกประการ +* ❌ รายการที่ต้องซิงก์สองที่ (สัญญากลาง + ทะเบียนของ consumer) จะ drift ภายในเดือนเดียว + +### C. เติมกฎให้ `details` ให้สอดคล้องกับ `message` + เขียนข้อจำกัดของกฎไว้ตรง ๆ ⭐ + +* ✅ **ปิดความไม่สมมาตรในไฟล์เดียวกัน** ซึ่งเป็นสิ่งที่ทำให้ช่องนี้รอดมาได้ +* ✅ ใช้กลไกเดียวกับที่ `message` ใช้อยู่แล้ว และกลไกนั้น **พิสูจน์แล้วว่าทำงาน** — `care-agent-platform` เอากฎของ `message` ไปไล่ตรวจแล้วเจอรูจริงหนึ่งที่ใน `_deliver` +* ✅ ไม่ breaking — เพิ่มคำอธิบาย ไม่แตะ type ไม่แตะ `required` ไม่ปิด object +* ✅ บอกผู้ผลิตตรง ๆ ว่าต้องมีทะเบียนคีย์ของตัวเองที่บังคับด้วย CI ไม่ใช่พึ่งการรีวิว +* ❌ **เป็นกฎที่ validator กลางบังคับไม่ได้** — ต้องเขียนข้อจำกัดนี้ไว้ในตัวสัญญา ไม่ใช่ปล่อยให้คนอ่านเชื่อว่ามันถูกบังคับ + +## Decision + +**C** — เติมกฎให้ `details` ให้สอดคล้องกับ `message` พร้อมเขียนไว้ในตัวฟิลด์ว่ากฎนี้บังคับได้ที่ผู้ผลิตเท่านั้น + +**Reason:** ช่องนี้รอดมาสามสัปดาห์ไม่ใช่เพราะใครตัดสินใจว่าไม่ต้องปิด แต่เพราะไม่มีใครเอ่ยชื่อมัน · การเติมคำอธิบายจึงไม่ใช่แค่การเพิ่มเอกสาร แต่เป็นการทำให้ฟิลด์นี้มีชื่ออยู่ในบทสนทนา ซึ่งเป็นสิ่งเดียวที่ทำให้ `metadata` ถูกปิดและ `details` ไม่ถูกปิด · ปฏิเสธ B ด้วยเหตุผลของผู้รายงานเอง คือเครื่องมือที่ดูเป็นทางการกว่าจะบังคับผิดแกน และรายการที่ซิงก์สองที่จะ drift + +**Authority:** Monthop Champaruang — Platform Owner / Architecture Authority of `agent-platform` + +### `error/v1` `v1.0.0` → `v1.1.0` — ไม่ breaking + +เพิ่มคำอธิบายและกฎ · ไม่แตะ `type` ไม่แตะ `required` ไม่ปิด object · ตาม [ADR-0006](0006-contract-versioning.md) การเพิ่มเอกสารไม่ใช่ breaking + +## Consequences + +* **`error/v1` ขยับเป็นครั้งแรกตั้งแต่ 17 ส.ค.** — consumer ที่ pin ไว้ (`care-agent-platform` · `devfactory-core` · `botforge`) ไม่ต้องแก้อะไร แต่ควรไล่ดูว่าของที่ตัวเองใส่ใน `details` เข้ากฎหรือไม่ +* **drift check ตรวจข้อนี้ไม่ได้** — [ADR-0011](0011-conformance-automation.md) ห้าม `conformance/` เรียก host อื่น · เหมือนกฎของ `message` ที่มีมาตั้งแต่ v1.0.0 และพิสูจน์แล้วว่ายังทำงานได้ เพราะ consumer เอาไปไล่ตรวจเองแล้วเจอของจริง +* **ยังไม่ปิด:** `event/v1.transition.reason` เป็นช่องเดียวกันแต่อยู่ในสัญญาที่ derive semantics มาจาก `devfactory-core` — ต้องเป็น RFC ที่ต้นทาง และเป็นฟิลด์ที่ข้อกำหนดสองข้อชนกัน (audit ห้ามถือ PII ของ subject กับ ต้องตอบได้ว่าทำไมถึงหยุดการรักษา) +* **บทเรียนที่เอาไปใช้ต่อได้** — ครั้งหน้าที่ไล่ปิด open bag ให้ไล่จาก **รายการฟิลด์ในไฟล์** ไม่ใช่จากรายการที่มีคนพูดถึงในกระทู้ · สองรายการนี้ไม่เท่ากัน และส่วนต่างคือช่องที่รอดมาได้ + +## Sources + +`dis-65134078` ใน workspace ของ [`ai-collaboration-mcp`](https://github.com/monthop-gmail/ai-collaboration-mcp) — `seq 25` (ผลการปิด `metadata` และรายการของจริงที่เก็บอยู่ใน audit) · `seq 26` (agent-platform แยกสถานะของ `error/v1.message` ออกจาก `event/v1.transition.reason`) · `seq 27` (`care-agent-platform` รายงานช่องที่สามและประโยคเรื่องฟิลด์ที่ไม่มีใครเอ่ยชื่อ) · `care-agent-platform#21` `#22` diff --git a/decisions/README.md b/decisions/README.md index c20dfda..f504202 100644 --- a/decisions/README.md +++ b/decisions/README.md @@ -47,6 +47,7 @@ ADR ในโฟลเดอร์นี้เป็น **authority** ของ | [0027](0027-toolid-transformation-must-be-deterministic.md) | กฎการแปลงชื่อเป็น `ToolId` | **B** — คง pattern · การแปลงต้อง deterministic · ชนกัน = reject · ย้อนกลับได้ | ✅ Accepted | | [0028](0028-versioning-for-tool-response-contracts.md) | versioning ของ contract ที่ส่งผ่าน tool response | **D** — เลขสองที่ cache คนละแบบ · **ห้ามลบคีย์เก่าจนขึ้น major** · deprecation ไม่มีหน่วยเป็นเวลา | ✅ Accepted · implement แล้ว (`contract 2`) | | [0029](0029-the-cost-of-not-acting.md) | เพดานกั้น action ที่ความเร็วคือความปลอดภัย | **D** — `undoes` ที่ตรวจได้ + ข้อยกเว้นที่ประกาศพร้อมบังคับ audit · ห้ามยุบเป็นฟิลด์เดียว | ✅ Accepted | +| [0030](0030-the-field-nobody-named.md) | `error/v1.details` เป็น open bag ที่ไม่มีใครเอ่ยชื่อ | **C** — เติมกฎให้สอดคล้องกับ `message` · ปฏิเสธ `propertyNames` enum เพราะบังคับผิดแกน | ✅ Accepted | การเคาะบันทึกไว้ที่ [issue #1–#10](https://github.com/monthop-gmail/agent-platform/issues?q=is%3Aissue+label%3Aadr) — **ไฟล์บันทึกว่าตัดสินอะไร issue บันทึกว่าใครตัดสินและเมื่อไหร่** @@ -101,6 +102,8 @@ contracts/ P0 ✅ ── profiles/ ✅ ── planes/ ✅ 0028 (versioning ของ tool response) ✅ ↓ 0029 (ต้นทุนของการไม่ทำ) ✅ + ↓ +0030 (ฟิลด์ที่ไม่มีใครเอ่ยชื่อ) ✅ ``` ## ที่มา