Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions contracts/error/v1/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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 ต่างกัน
12 changes: 12 additions & 0 deletions contracts/error/v1/error.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
88 changes: 88 additions & 0 deletions decisions/0030-the-field-nobody-named.md
Original file line number Diff line number Diff line change
@@ -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`
3 changes: 3 additions & 0 deletions decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 บันทึกว่าใครตัดสินและเมื่อไหร่**

Expand Down Expand Up @@ -101,6 +102,8 @@ contracts/ P0 ✅ ── profiles/ ✅ ── planes/ ✅
0028 (versioning ของ tool response) ✅
0029 (ต้นทุนของการไม่ทำ) ✅
0030 (ฟิลด์ที่ไม่มีใครเอ่ยชื่อ) ✅
```

## ที่มา
Expand Down
Loading