KB-2022

Council Review — Đ35 v5.1 DRAFT rev 2 — GPT Round 2

18 min read Revision 1
council-reviewdieu35v5.1gptround2

Council Review — Đ35 v5.1 DRAFT rev 2 — GPT Round 2

Tóm 1 câu

APPROVE WITH MINOR CHANGES: rev 2 đã đóng được phần lớn 5 blocker R1 và mạnh hơn rev 1 rõ rệt, nhưng còn vài chỗ inline fix nên chốt ngay trong PR ban hành để tránh “luật chạy được trên giấy nhưng vấp ở seed/migration đầu tiên”.

Câu 1 — Verify 5 blocker R1 đã đóng đúng cách

B1: NỬA VỜI

Evidence: draft đã thêm định nghĩa fn_is_in_grace_period() ở §4.1B, có SECURITY DEFINER, SET search_path = public, pg_catalog, owner workflow_admin. Về cú pháp PL/pgSQL trên PG16 thì đoạn function này compile được. Logic v_days INT, v_enacted TIMESTAMPTZ, rồi NOW() < v_enacted + interval cũng hợp lệ.

Nhưng có 3 vấn đề chưa đóng gọn:

  1. Seed dot_config tự mâu thuẫn với schema. Ở §4.4, dot_config.value TEXT NOT NULL, nhưng ngay bên dưới lại seed ('law_v5_1_enacted_at', NULL, ...). Câu này sẽ fail INSERT ngay nếu chạy đúng schema đã viết. Tức là blocker B1 chưa đóng trọn vì hàm đã có nhưng seed key đầu vào của hàm lại chưa cài được.

  2. Fail-open = hợp lý về vận hành, nhưng thiếu còi báo “grace đang kẹt vĩnh viễn”. Trong function, nếu grace_period_days hoặc law_v5_1_enacted_at NULL thì RETURN TRUE. Với ca cấp cứu này, triết lý “không chắc thì đừng block hệ đang chạy” là hiểu được và hợp với tinh thần NT9 thực dụng hơn là block mù. Nhưng hiện draft không có health check riêng kiểu H14 để bắt trường hợp law_v5_1_enacted_at chưa từng được set. Nghĩa là admin quên update thì hệ có thể grace mãi mãi mà không ai báo.

  3. Timezone chưa chết ngay, nhưng còn lỏng. §4.4 hướng dẫn UPDATE dot_config SET value=NOW()::TEXT. Nếu text này mang timezone của session/server thì cast ngược value::TIMESTAMPTZ vẫn đọc được trong đa số trường hợp, nhưng đây là kiểu “đọc được thì sống”, chưa phải “chặt chẽ tuyệt đối”. Dùng UTC ISO rõ ràng sẽ an toàn hơn.

Kết luận B1: đóng được phần “thiếu định nghĩa function”, nhưng chưa đóng sạch vòng seed + giám sát grace. Nôm na: đã làm cái đồng hồ hẹn giờ, nhưng lại chưa chắc pin có lắp vào và chưa có ai kiểm tra đồng hồ có đang đứng hay không.

B2: NỬA VỜI

Evidence: §4.1B đã định nghĩa fn_log_issue() với 5 tham số: source, severity, category, summary, entity_code; có SECURITY DEFINER, có issue_signature, có ON CONFLICT ... WHERE status='open'. So với rev 1 thì đây là bước tiến thật, vì đã biến “ý tưởng wrapper” thành contract cụ thể.

Nhưng còn 4 mép hở:

  1. Chữ ký dedupe đang đưa severity vào hash. Draft dùng: md5(source|category|entity_code|severity). Điều này làm cùng một issue khi đi từ warning sang critical sẽ thành signature mới, nghĩa là dễ mở 2 row song song thay vì nâng cấp severity của 1 row đang mở. Theo Đ31, severity là mức báo động của cùng một vấn đề; ở đây tôi thấy mong muốn tự nhiên hơn là 1 issue leo thang, không phải issue sinh đôi.

  2. Issue đã resolved rồi tái phát: với partial unique WHERE status='open', insert mới sẽ tạo row mới — điều này là chấp nhận được. Nhưng draft chưa nói rõ đây là chủ đích. Nếu đã chọn mô hình “mỗi đợt tái phát = row mới”, nên ghi rõ để tránh người triển khai tự ý chuyển sang reopen logic khác.

  3. Migration rủi ro với dữ liệu hiện hữu. §4.1B nói thêm issue_signature + unique partial index trong BLOCK 1 nếu chưa có. Nhưng draft chưa nói bước dedupe dữ liệu system_issues trước khi tạo index. Nếu production hiện có nhiều open issue cùng bản chất, CREATE UNIQUE INDEX sẽ fail giống hệt bài toán file_path.

  4. 5 tham số dùng được, nhưng hơi hẹp cho đời thực. Với health path hiện tại vẫn đủ chạy. Nhưng nếu sau này muốn log thêm context như field_name, old_value, new_value, detected_by_dot, thì không có chỗ ngoài việc nhét vào summary. Thiếu metadata JSONB chưa phải blocker cho Đ35 v5.1, nhưng là nợ thiết kế nhỏ.

Race condition: INSERT ... ON CONFLICT là cách chuẩn để giảm race; tôi không thấy lỗi khái niệm lớn ở đây. Tuy vậy, nếu 2 health path đập cùng lúc vào cùng signature thì vẫn có thể tranh chấp nhẹ ở index, nhưng PostgreSQL xử được theo hướng một thằng insert, một thằng update. Đây là chấp nhận được.

Kết luận B2: đóng được phần “chưa có contract function”, nhưng chưa đóng sạch logic leo thang severity và precheck migration của index.

B3: ĐÓNG ĐÚNG

Evidence: rev 2 đã làm _dot_origin thành cột chính thức ở §4.1; §5.1 payload 11 fields có _dot_origin; §8.3 fn_birth_gate kiểm _dot_origin; §9.2 BLOCK 2 backfill _dot_origin='legacy_pre_v5_1'. Về mặt nhất quán văn bản, blocker R1 đã được xử lý đúng gốc.

Một vài nhận xét thêm:

  • NOT NULL DEFAULT 'unknown' là hợp lý cho migration vì constant default trên PG16 không phải kiểu quá nặng với bảng cỡ 272 row.
  • Dùng TEXT free-form ở thời điểm này hợp lý hơn enum/reference table, vì _dot_origin bản chất là dấu vết kỹ thuật, có thể phát sinh giá trị mới (dot-dot-register, directus-ui, legacy_pre_v5_1, repair_cron, manual_hotfix) và việc thêm mỗi origin mới mà phải sửa reference table chưa chắc đáng công. Với NT4, TEXT mềm hơn.
  • Điểm duy nhất chưa tối ưu là backfill _dot_origin='legacy_pre_v5_1' cho toàn bộ 272 DOT cũ sẽ gom cả DOT trước S151 lẫn DOT sau S151 nhưng trước rev 2 vào cùng một rổ. Tách pre_s151_legacypost_s151_pre_fix sẽ điều tra đẹp hơn, nhưng đây là nice-to-have, không còn là blocker.

Kết luận B3: đóng đúng. Nôm na: trước đây hồ sơ có một cột hay được nhắc tới nhưng không có thật; giờ cột đó đã được dựng thành cột thật, giấy tờ và công cụ nói cùng một tiếng.

B4: ĐÓNG ĐÚNG, nhưng cần thêm 2 miếng đệm nhỏ

Evidence: §2 và §4.1 đã sửa wording UNIQUE partial cho đúng semantics PG: chỉ cho phép nhiều NULL, không cho duplicate non-null. §9.2 BLOCK 4 cũng đã ép thứ tự “dedupe trước, create index sau”. Đây chính là blocker GPT R1, và rev 2 đã sửa đúng gốc.

Checklist 12 bước nhìn chung tốt hơn rev 1 nhiều, nhưng còn 2 điểm nên vá inline:

  1. Bước 4.3 có nguy cơ mất dấu lịch sử file_path cũ. Draft viết row dư: UPDATE status='retired', file_path=NULL. Nếu chỉ NULL như vậy thì mất dấu “row này từng trỏ file nào” trừ khi production đang có audit trigger ngoài luật. Nên ghi rõ: trước khi NULL file_path, phải lưu old_file_path vào extra_metadata hoặc audit log.

  2. CREATE UNIQUE INDEX có nên CONCURRENTLY không? Với bảng 272 row thì không bắt buộc, nhưng nếu draft muốn thành mẫu pháp lý dùng về sau cho bảng to hơn, thêm chú thích “ưu tiên CONCURRENTLY khi môi trường/scope cho phép” sẽ đẹp hơn.

Kết luận B4: blocker chính đã đóng đúng. Hai chỗ còn lại là đệm chống va, không còn là hố sâu.

B5: NỬA VỜI

Evidence: rev 2 đã thay flow PATCH-after-insert bằng mô hình mới ở §5.2:

  • trigger chính = dot-dot-register infer 11/11 fields trước POST
  • trigger phụ = cron dot-metadata-repair mỗi giờ
  • H13 ở §8.1 kiểm tra repair DOT không tồn tại hoặc stale > 2h

Đây là sửa đúng hướng và đúng blocker GPT R1: lưới đỡ đã chuyển từ “vá sau khi ngã” sang “có người đi quét gom người rơi lại”. Vì vậy phần cốt lõi của blocker đã được chữa.

Tuy nhiên vẫn còn 3 mép chưa nói đủ:

  1. Infer fail chưa ghi thành mệnh lệnh cứng ở path đăng ký. §5.1 nói “infer fail → APR”, §11.2.1 nói “mơ hồ → APR”, nhưng chưa có câu pháp lý thật dứt: infer fail thì CẤM POST placeholder/partial row. Ý này đã ngầm có, nhưng nên viết rõ để khỏi có agent hiểu lầm là “post trước cho có row, cron sửa sau”.

  2. H13 tạo self-paradox nhẹ. dot-dot-health canh dot-metadata-repair, nhưng nếu chính repair DOT hỏng thì ai sửa nó? Về lý thuyết vẫn ổn vì health DOT chỉ cần phát hiện và đẩy issue/APR. Nhưng draft nên nói rõ H13 chỉ là báo động, còn fix qua APR/owner, không phải auto-self-repair kín hoàn toàn.

  3. Tần suất 1 giờ là chấp nhận được, nhưng dễ tạo APR/issue storm nếu engine upstream còn bệnh. Draft có fn_log_issue dedupe phần issue, nhưng chưa có cơ chế throttle APR tương ứng.

Kết luận B5: blocker “dual-trigger chết khi mode=block” đã được chữa đúng hướng, nhưng vẫn nên viết thêm 1 câu cứng để chặn hẳn hành vi POST placeholder.

Câu 2 — Edge case phát sinh (tối thiểu 5)

Scenario 1: law_v5_1_enacted_at seed NULL nhưng dot_config.value là NOT NULL — HIGH

Nếu deploy đúng như §4.4 đang viết, seed key law_v5_1_enacted_at có thể fail ngay từ đầu. Khi đó cả B1 kéo theo B5/H10-H13 đều lệch dây.

Biện pháp: sửa inline một trong hai cách:

  • cho phép dot_config.value nullable, hoặc
  • seed bằng chuỗi sentinel hợp lệ rồi bắt buộc update ngay khi ban hành, đồng thời thêm health check báo nếu key chưa được set đúng.

Scenario 2: fn_log_issue unique index fail do open issue cũ trùng signature — HIGH

Nếu production đã có nhiều system_issues mở cùng source/category/entity, CREATE UNIQUE INDEX ... WHERE status='open' sẽ fail giống hệt bài toán duplicate file_path.

Biện pháp: thêm bước preflight/dedupe cho system_issues trong BLOCK 1 trước khi tạo unique partial index.

Scenario 3: severity leo thang tạo 2 row open song song — MEDIUM

Cùng một drift metadata có thể đầu tiên là warning, sau grace thành critical. Với signature hiện tại có severity, hệ sẽ mở 2 row open cho cùng một bệnh.

Biện pháp: bỏ severity khỏi signature, hoặc ON CONFLICT update severity lên mức cao hơn thay vì tách row.

Scenario 4: 2 agent cùng chạy dot-dot-register cho cùng file mới — MEDIUM

Cả hai cùng infer 11 field rồi cùng POST. Nếu không có uniqueness đủ mạnh ở code/path ngoài code, có thể tạo race giữa cùng file_path hoặc cùng code tùy cách infer code.

Biện pháp: dùng lock nhẹ theo file_path, hoặc pre-insert check + DB unique trên code và cuối cùng xem file_path như guard thứ hai.

Scenario 5: BLOCK 4 fail giữa chừng sau khi đã retire/null một phần duplicate — HIGH

Ví dụ bước 4.3 xong 20 cặp, bước 4.6 FILL fail. Hệ rơi vào trạng thái “đã sửa dở”.

Biện pháp: thêm bước 4.0 snapshot baseline dot_tools + system_issues + mapping duplicate trước khi vào BLOCK 4; mọi bước destructive/logical phải có rollback note.

Scenario 6: dot-metadata-repair sinh APR hàng loạt lặp lại — MEDIUM

Nếu infer fail cho cùng 1 row mỗi giờ, cron có thể tạo APR backfill_metadata lặp liên tục dù issue đã dedupe.

Biện pháp: dedupe cả APR theo (entity_code, request_type, status in pending/open) hoặc cho repair DOT kiểm “đã có APR open chưa” trước khi tạo mới.

Scenario 7: chuyển birth_gate_mode='block' đúng lúc repair cron đang PATCH — LOW/MEDIUM

Nếu admin đổi mode giữa phiên repair, cùng một batch có row patch thành công và row sau bị reject.

Biện pháp: repair DOT đọc snapshot mode ngay đầu run và log mode đó cho toàn batch; đổi mode chỉ có hiệu lực batch sau.

Câu 3 — Stress test BLOCK 4 (12 bước)

a) Rollback giữa chừng

Hiện draft có checklist tốt, nhưng rollback story chưa đủ rõ. Nếu fail giữa 4.3-4.8, trạng thái có thể là “đã retire/null một số row, chưa fill xong, chưa index, chưa NOT NULL”. Đây không phải thảm hoạ không cứu được, nhưng nên có baseline snapshot trước khi vào BLOCK 4.

Kết luận: rollback chưa vô phương, nhưng chưa được viết đủ rõ.

b) Thứ tự 4.5 vs 4.6

Giữ như draft là hợp lý: 4.5 DOT_BIRTH_BACKFILL trước, 4.6 DOT_METADATA_FILL sau.

Lý do:

  • Đ0-G/Birth là “có giấy khai sinh chưa”; metadata fill là “điền hồ sơ cho đẹp và đủ”.
  • Có birth record trước giúp audit/fix về sau có nền truy vết thống nhất.
  • Nếu fill trước rồi mới backfill birth, có nguy cơ hồ sơ metadata đã đẹp nhưng sổ khai sinh vẫn khuyết.

Nôm na: phải làm giấy khai sinh trước rồi mới hoàn thiện hồ sơ chi tiết.

c) CONCURRENTLY

Với 272 row thì không bắt buộc dùng CONCURRENTLY. Nhưng ở mức luật mẫu, tôi nghiêng về việc nên ghi:

  • môi trường nhỏ/maintenance window: index thường chấp nhận được
  • môi trường lớn hoặc cần tránh lock: ưu tiên CREATE UNIQUE INDEX CONCURRENTLY

Kết luận: nên thêm chú thích, không cần biến thành blocker.

d) 4.11 vs 4.12

Giữ thứ tự 4.11 enable cron rồi 4.12 quan sát 24h là đúng. Không bật cron thì lấy gì mà quan sát “sống ổn 24h”.

Đúng là cron sẽ tạo noise nếu còn lỗi, nhưng đó chính là mục tiêu của giai đoạn quan sát: bật máy lên rồi mới biết nó kêu ở đâu. Quan sát khi chưa bật cron thì giống ngồi nhìn một chiếc quạt chưa cắm điện rồi kết luận “quạt chạy êm”.

e) Bước 4.0 snapshot

Có, nên thêm. Đây là đề xuất inline tốt nhất của Round 2.

Tối thiểu snapshot:

  • dot_tools trước retrofit
  • danh sách duplicate file_path
  • system_issues open liên quan DOT
  • mapping canonical row ↔ row bị retire

f) ALTER NOT NULL atomic

ALTER TABLE ... ALTER COLUMN ... SET NOT NULL nên được coi là một đơn vị atomic trong transaction rõ ràng. Nếu tách lẻ từng câu rồi fail giữa chừng thì có thể thành nửa bảng NOT NULL, nửa bảng chưa.

Với PostgreSQL, DDL transaction là có thể dùng được trong trường hợp này. Tôi đề nghị draft ghi rõ: Bước 4.10 chạy trong BEGIN/COMMIT; fail thì rollback toàn bộ bước 4.10.

Câu 4 — Audit chéo Đ36/37/38/39/41

Luật Tác động bảng nào? Có data cũ trước ban hành không? Có §retrofit clause chưa? Risk nếu KHÔNG amend Ưu tiên amend
Đ36 Collection Protocol collection_registry, birth_registry, species mapping, quan hệ collection/entity . Ngay trong file Đ36 v5.0 draft nêu rõ đang đứng trên Birth Registry, collection_registry và hệ thống collection đã có sẵn Không thấy §retrofit clause trong file hiện có collection cũ có thể tiếp tục lệch governance/species mapping, đúng kiểu AP-19 “luật mới không hồi tố cho data cũ” CRITICAL
Đ37 Tổ chức Bộ máy normative_registry, law_jurisdiction, governance_registry, law_dot_enforcement, governance_relations, governance_audit_log . Đ37 v3.3 nói rõ seed dựa trên luật/cơ quan/DOT đã tồn tại; còn có ghi chú chuyển law_registrynormative_registry theo Đ38 Không thấy §retrofit clause riêng; có bootstrap/seed nhưng không phải retrofit meta-rule cho data cũ luật/cơ quan cũ dễ thành “vô chủ” hoặc liên kết top-down thiếu, đặc biệt sau đổi law_registrynormative_registry HIGH
Đ38 Văn bản Quy phạm normative_registry, binding/metadata/annotation văn bản quy phạm Không chắc đầy đủ, vì file hiện có ở KB là Đ38 v3.0 DRAFT, không phải bản enacted đầy đủ; nhưng nội dung cho thấy đang đứng trên kho văn bản/quy phạm đã có Không thấy §retrofit clause trong file hiện có nếu không retrofit, văn bản cũ có thể không được chia thẻ/binding đầy đủ, làm DOT/AI đọc lệch MEDIUM (kèm ghi chú không chắc do file khả dụng là draft)
Đ39 Knowledge Graph universal_edges, các bảng kg_*, scaffold_dependency_map, logs chất lượng, embeddings/Qdrant . Đ39 v2.3 ghi rõ graph ăn từ data PG hiện hữu, Đ38 output, entity đã khai sinh trước đó Không thấy §retrofit clause riêng graph có thể “học trên nền dữ liệu cũ chưa được retrofit”, dẫn tới quan hệ thiếu provenance/freshness hoặc độ phủ lệch giữa cũ và mới HIGH
Đ41 VPS Operation vps_deploy_log, cấu trúc release trên filesystem, .env.production, DOT guard liên quan deploy . Đ41 §10 đã nói rõ “snapshot mã hiện tại”, “audit backfill cho các thay đổi gần đây chưa có sổ tay” Có dạng chuyển tiếp/retrofit thực chất ở §10, dù không dùng đúng tên “retrofit clause” nếu không amend thêm thì rủi ro thấp hơn 4 luật trên vì Đ41 đã có cơ chế chuyển đổi từ hiện trạng LOW

Xếp hạng ưu tiên amend

  1. Đ36 — đụng collection/birth/species là đụng xương sống data; thiếu hồi tố thì bệnh lan rất giống ca Đ35.
  2. Đ37 — quản trị top-down mà không hồi tố thì luật/cơ quan cũ thành “bộ máy có sơ đồ mới nhưng hồ sơ cũ chưa nhập”.
  3. Đ39 — KG dùng dữ liệu cũ làm đầu vào; dữ liệu nguồn không retrofit thì graph rất dễ “thông minh trên nền lệch”.
  4. Đ38 — cần amend, nhưng mức chắc chắn thấp hơn vì file hiện khả dụng là draft v3.0 chứ không phải enacted full text.
  5. Đ41 — đã có §10 chuyển đổi/backfill khá giống retrofit rồi, nên ít gấp nhất trong 5 luật này.

Câu 5 — Kết luận ban hành

(b) APPROVE WITH MINOR CHANGES

Không cần Round 3. Các fix dưới đây làm inline trong PR ban hành là đủ.

  1. §4.4 / dot_config seed

    • Sửa mâu thuẫn value TEXT NOT NULL nhưng seed law_v5_1_enacted_at = NULL.
    • Chọn 1 trong 2:
      • cho value nullable, hoặc
      • seed bằng chuỗi sentinel hợp lệ và bắt buộc update ngay khi ban hành.
  2. §8.1 hoặc thêm H14 mới

    • Thêm health check bắt law_v5_1_enacted_at chưa set hoặc không parse được.
    • Mục tiêu: chặn tình trạng grace mãi mãi mà không ai biết.
  3. §4.1B / fn_log_issue()

    • Bỏ severity khỏi issue_signature, hoặc ghi rõ policy escalate severity trên cùng issue open.
    • Thêm một câu migration note: dedupe system_issues open trước khi tạo unique partial index.
  4. §5.2 hoặc §8.3

    • Viết cứng: dot-dot-register infer fail = CẤM POST partial/placeholder row; phải tạo APR.
  5. §9.2 BLOCK 4, bước 4.3

    • Trước khi file_path=NULL, lưu old_file_path vào extra_metadata hoặc audit log.
  6. §9.2 BLOCK 4

    • Thêm bước 4.0 snapshot baseline trước 4.1.
  7. §9.2 BLOCK 4.10

    • Ghi rõ ALTER TABLE ... SET NOT NULL chạy trong transaction BEGIN/COMMIT; fail thì rollback toàn bộ bước 4.10.

Điểm số 1-10 (so với rev 1)

  • Rev 1: 8.3/10
  • Rev 2: 9.1/10
  • Delta: +0.8
  • Giải thích delta: rev 2 đã sửa trúng 5 blocker R1 ở đúng chỗ đau nhất: có function thật, có _dot_origin thật, sửa đúng semantics UNIQUE partial, có dual-trigger sống được khi mode=block, và có wording cứng hơn cho retrofit áp cả luật đã ban hành. Phần còn lại chủ yếu là fix mép migration/seed/rollback, không còn là sai hướng kiến trúc.