KB-4B71

P7B — Sandbox PG Prototype Design v0.3

22 min read Revision 1
dieu38p7bsandboxofficial

P7B — Sandbox PG Prototype Design

Loại: Prototype design — Điều 38 Text as Code Phase: P7B (Sandbox Prototype), thiết kế kho mẫu PG Trạng thái: OFFICIAL v0.3 — GPT PASS. User PASS. Ngày soạn: 2026-04-26 | Phiên: S182 Agent soạn: Opus 4.6 (Desktop) GPT review: v0.1 PASS CĐK (14) → v0.2 → v0.3 PASS CĐK nhẹ (4) → FINAL PASS Inputs: P7 v0.2, P7A report (76 units), P5 v0.2, P6 v0.2, Đ33 v2.1, Đ24


1. Mục tiêu

Thiết kế sandbox PG prototype — "kho mẫu" có kệ thật nhưng KHÔNG nối production — để kiểm chứng P5 schema, birth gate checkers (P6), constraints, round-trip lossless.

P7B chỉ thiết kế. Dựng thật = bước riêng sau P7B PASS + User duyệt execution riêng (Cổng 2).

P7B PASS = permission to REQUEST User approval for sandbox execution. KHÔNG tự cho phép chạy DDL.


2. Phạm vi / Không làm

Trong: Sandbox schema design, DDL candidates, seed plan, checker plan, hash plan, round-trip plan, rollback, no-production-change guard, pre-flight dependencies, execution pipeline, PASS/FAIL.

Không: Chạy DDL thật, đụng production, Directus collection, sửa P5/P5b/P6/P7/C1–C3/LSL/L1–L5, Component/BOM (round 2), Qdrant, DOT cron, migration production, cross-read public.entity_labels.


3. Hiện trạng từ P7A

76 units (HOWTO: 8, C1A: 34, P5: 34). 3 proposed publications. 228 label mappings. 0 ERROR simulated checkers. Round-trip 0 content loss.


4. Sandbox Boundary

4.1 Phương án: PG Schema riêng trong directus DB

Tạo PG schema sandbox_tac trong database directus (Lớp KHO, Đ33 §0.1). Rollback sạch: DROP SCHEMA sandbox_tac CASCADE.

4.2 Giả định cần verify (pre-flight)

GĐ-1: Directus CMS chỉ scan schema public và không tự expose tables trong sandbox_tac. CẦN VERIFY trong pre-flight: (a) kiểm Directus config DB_SEARCH_PATH, (b) sau tạo schema, kiểm admin UI. Nếu Directus scan cả sandbox_tac → revoke usage hoặc chọn DB riêng.

4.3 Naming

Schema: sandbox_tac. Tables: giữ P5 names. Indexes: idx_sbx_{table}_{field}. Functions: fn_sbx_{purpose}.

4.4 Sandbox ≠ Production SoT

Sandbox = prototype, không authoritative, temporary. Production SoT unchanged.

4.5 User / Role

Đề xuất: workflow_admin (owner directus DB per Đ33 §14.2). Cần verify trước execution: (a) CREATE privilege, (b) phù hợp Đ33 E1 gateway rules. DDL qua docker exec postgres psql, idempotent, có action log.


5. Pre-flight Dependency Checklist

# Dependency Check Required Fallback
PF-1 pgcrypto SELECT FROM pg_extension WHERE extname='pgcrypto' Exists Remediation riêng: CREATE EXTENSION IF NOT EXISTS pgcrypto cần superuser (docker exec postgres psql -U postgres), chỉ chạy nếu User/DBA approve
PF-2 CREATE SCHEMA privilege SELECT has_database_privilege(...) TRUE Request grant
PF-3 PG version ≥ 13 SELECT version() PG 16
PF-4 Directus không scan sandbox Sau CREATE SCHEMA → check admin UI No sandbox tables visible DROP SCHEMA sandbox_tac CASCADE + ABORT
PF-5 Production clean SELECT count(*) FROM pg_tables WHERE schemaname='public' AND tablename LIKE '%logical_unit%' 0 ABORT
PF-6 Password GSM .env at /opt/incomex/.env Exists, chmod 600

6. Sandbox Table Proposal

6.1 Tables (8)

# Table Source Notes
1 section_type_vocab P5 §5.5 11 types seed
2 publication_type_vocab P5 §5.6 1 type seed
3 logical_unit P5 §5.1 76 rows
4 unit_version P5 §5.2 76 rows
5 publication P5 §5.3 3 rows
6 publication_member P5 §5.4 76 rows
7 change_set P5 §5.7 0 rows (hook)
8 change_set_member P5 §5.8 0 rows (hook)

6.2 Sandbox DDL Candidates

⚠️ NOT TO RUN UNTIL USER APPROVES SANDBOX EXECUTION (Cổng 2) ⚠️ DDL dưới đây đã executable. CHECK enum trong sandbox OK cho prototype. Production (P9) PHẢI dùng FK vocab/config per NT4 nếu enum governance mở rộng.

-- SANDBOX DDL CANDIDATE — NOT TO RUN UNTIL USER APPROVES

-- 0. Schema
-- pgcrypto PHẢI có sẵn (PF-1 pre-flight verify).
-- Nếu thiếu → PF-1 remediation riêng (cần superuser approve).
CREATE SCHEMA IF NOT EXISTS sandbox_tac;

-- 1. Vocabulary tables
CREATE TABLE sandbox_tac.section_type_vocab (
    code              TEXT PRIMARY KEY,
    name              TEXT NOT NULL,
    description       TEXT,
    lifecycle_status  TEXT NOT NULL DEFAULT 'active'
        CHECK (lifecycle_status IN ('active', 'deprecated', 'retired')),
    soft_limit_words  INTEGER DEFAULT 500,
    hard_limit_words  INTEGER DEFAULT 1500,
    description_required BOOLEAN DEFAULT TRUE,
    body_required     BOOLEAN DEFAULT TRUE,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE sandbox_tac.publication_type_vocab (
    code              TEXT PRIMARY KEY,
    name              TEXT NOT NULL,
    description       TEXT,
    lifecycle_status  TEXT NOT NULL DEFAULT 'active'
        CHECK (lifecycle_status IN ('active', 'deprecated', 'retired')),
    default_risk_tier TEXT NOT NULL DEFAULT 'medium'
        CHECK (default_risk_tier IN ('low', 'medium', 'high', 'highest')),
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- 2. Core tables
CREATE TABLE sandbox_tac.logical_unit (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    canonical_address TEXT NOT NULL UNIQUE,
    doc_code          TEXT NOT NULL,
    parent_id         UUID REFERENCES sandbox_tac.logical_unit(id),
    sort_order        INTEGER NOT NULL DEFAULT 0,
    section_type      TEXT NOT NULL
        REFERENCES sandbox_tac.section_type_vocab(code),
    section_code      TEXT,
    owner             TEXT NOT NULL,
    identity_profile  JSONB DEFAULT '{}',
    lifecycle_status  TEXT NOT NULL DEFAULT 'draft_only'
        CHECK (lifecycle_status IN ('active', 'draft_only', 'retired')),
    tier              TEXT,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_sbx_lu_doc_code ON sandbox_tac.logical_unit(doc_code);
CREATE INDEX idx_sbx_lu_parent ON sandbox_tac.logical_unit(parent_id);

CREATE TABLE sandbox_tac.unit_version (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    logical_unit_id   UUID NOT NULL
        REFERENCES sandbox_tac.logical_unit(id),
    version_number    INTEGER NOT NULL DEFAULT 1,
    title             TEXT NOT NULL,
    body              TEXT,
    description       TEXT,
    content_hash      TEXT,
    lifecycle_status  TEXT NOT NULL DEFAULT 'draft'
        CHECK (lifecycle_status IN (
            'draft', 'enacted', 'superseded', 'retired')),
    review_state      TEXT DEFAULT 'unreviewed'
        CHECK (review_state IN (
            'unreviewed', 'in_review', 'review_passed',
            'review_failed', 'needs_re_review')),
    length_flag       TEXT DEFAULT 'normal'
        CHECK (length_flag IN ('normal', 'soft_limit', 'hard_limit')),
    length_exception_reason TEXT,
    content_profile   JSONB DEFAULT '{}',
    editor            TEXT,
    provenance        TEXT DEFAULT 'PROV-AI',
    vector_sync_status TEXT DEFAULT 'pending'
        CHECK (vector_sync_status IN (
            'pending', 'synced', 'stale', 'error')),
        -- Sandbox: tất cả giữ 'pending' vì không có Qdrant.
        -- Enum giữ nguyên P5 v0.2, không thêm 'skip'.
    vector_synced_at   TIMESTAMPTZ,
    vector_chunk_count INTEGER DEFAULT 0,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    enacted_at        TIMESTAMPTZ,
    UNIQUE (logical_unit_id, version_number)
);
CREATE INDEX idx_sbx_uv_lu ON sandbox_tac.unit_version(logical_unit_id);
CREATE INDEX idx_sbx_uv_lifecycle ON sandbox_tac.unit_version(lifecycle_status);

CREATE TABLE sandbox_tac.publication (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    doc_code          TEXT NOT NULL,
    version           TEXT NOT NULL,
    publication_type  TEXT NOT NULL
        REFERENCES sandbox_tac.publication_type_vocab(code),
    name              TEXT NOT NULL,
    owner             TEXT NOT NULL,
    description       TEXT,
    lifecycle_status  TEXT NOT NULL DEFAULT 'proposed'
        CHECK (lifecycle_status IN (
            'proposed', 'enacted', 'superseded', 'retired')),
    enacted_at        TIMESTAMPTZ,
    council_score     NUMERIC,
    approved_by       TEXT,
    risk_tier         TEXT NOT NULL DEFAULT 'medium'
        CHECK (risk_tier IN ('low', 'medium', 'high', 'highest')),
    publication_profile JSONB DEFAULT '{}',
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (doc_code, version)
);

CREATE TABLE sandbox_tac.publication_member (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    publication_id    UUID NOT NULL
        REFERENCES sandbox_tac.publication(id),
    logical_unit_id   UUID NOT NULL
        REFERENCES sandbox_tac.logical_unit(id),
    unit_version_id   UUID NOT NULL
        REFERENCES sandbox_tac.unit_version(id),
    render_order      INTEGER NOT NULL DEFAULT 0,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (publication_id, logical_unit_id)
);

-- 3. Hook tables (empty)
CREATE TABLE sandbox_tac.change_set (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    publication_id    UUID REFERENCES sandbox_tac.publication(id),
    scope_description TEXT NOT NULL,
    lifecycle_status  TEXT NOT NULL DEFAULT 'draft'
        CHECK (lifecycle_status IN (
            'draft', 'submitted', 'review_passed',
            'approval_passed', 'enacted', 'rejected', 'withdrawn')),
    apr_ref           TEXT,
    owner             TEXT NOT NULL,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    submitted_at      TIMESTAMPTZ,
    enacted_at        TIMESTAMPTZ
);

CREATE TABLE sandbox_tac.change_set_member (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    change_set_id     UUID NOT NULL
        REFERENCES sandbox_tac.change_set(id),
    logical_unit_id   UUID NOT NULL
        REFERENCES sandbox_tac.logical_unit(id),
    change_type       TEXT NOT NULL
        CHECK (change_type IN (
            'create', 'new_version', 'retire', 'structural')),
    old_version_id    UUID REFERENCES sandbox_tac.unit_version(id),
    new_version_id    UUID REFERENCES sandbox_tac.unit_version(id),
    snapshot_data     JSONB,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (change_set_id, logical_unit_id)
);

6.3 Function — content hash

-- SANDBOX DDL CANDIDATE
CREATE OR REPLACE FUNCTION sandbox_tac.fn_sbx_compute_content_hash(
    p_title TEXT, p_body TEXT, p_description TEXT, p_profile JSONB
) RETURNS TEXT AS $$
BEGIN
    RETURN encode(sha256(convert_to(
        COALESCE(p_title,'') || '||' || COALESCE(p_body,'') || '||' ||
        COALESCE(p_description,'') || '||' || COALESCE(p_profile::text,'{}'),
        'UTF8')), 'hex');
END;
$$ LANGUAGE plpgsql IMMUTABLE;

6.4 Trigger — auto hash + length flag

-- SANDBOX DDL CANDIDATE
CREATE OR REPLACE FUNCTION sandbox_tac.fn_sbx_uv_before_insert()
RETURNS TRIGGER AS $$
DECLARE v_wc INTEGER; v_soft INTEGER; v_hard INTEGER;
BEGIN
    NEW.content_hash := sandbox_tac.fn_sbx_compute_content_hash(
        NEW.title, NEW.body, NEW.description, NEW.content_profile);
    IF NEW.body IS NOT NULL THEN
        v_wc := array_length(regexp_split_to_array(trim(NEW.body),'\s+'),1);
    ELSE v_wc := 0; END IF;
    SELECT soft_limit_words, hard_limit_words INTO v_soft, v_hard
    FROM sandbox_tac.section_type_vocab stv
    JOIN sandbox_tac.logical_unit lu ON lu.section_type = stv.code
    WHERE lu.id = NEW.logical_unit_id;
    IF v_wc > COALESCE(v_hard,1500) THEN NEW.length_flag := 'hard_limit';
    ELSIF v_wc > COALESCE(v_soft,500) THEN NEW.length_flag := 'soft_limit';
    ELSE NEW.length_flag := 'normal'; END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_sbx_uv_before_insert
    BEFORE INSERT ON sandbox_tac.unit_version
    FOR EACH ROW EXECUTE FUNCTION sandbox_tac.fn_sbx_uv_before_insert();

6.5 Trigger — parent same doc_code

-- SANDBOX DDL CANDIDATE
CREATE OR REPLACE FUNCTION sandbox_tac.fn_sbx_lu_parent_same_doc()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.parent_id IS NOT NULL THEN
        IF NOT EXISTS (SELECT 1 FROM sandbox_tac.logical_unit
            WHERE id = NEW.parent_id AND doc_code = NEW.doc_code) THEN
            RAISE EXCEPTION 'Parent doc_code mismatch: unit % doc_code %',
                NEW.canonical_address, NEW.doc_code;
        END IF;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_sbx_lu_parent_doc
    BEFORE INSERT OR UPDATE ON sandbox_tac.logical_unit
    FOR EACH ROW EXECUTE FUNCTION sandbox_tac.fn_sbx_lu_parent_same_doc();

6.6 Đ24 cross-read — DEFERRED round 1

Không cross-read public.entity_labels. Cần explicit read-only approval. Round 1: verify label format trong P7A staging JSON (đã PASS).


7. Data Seed Plan

7.1 Seed order

  1. section_type_vocab (11) → 2. publication_type_vocab (1) → 3. logical_unit ROOTs (3) → 4. depth-1 (~38) → 5. depth-2 (~35) → 6. unit_version (76) → 7. publication (3) → 8. publication_member (76).

7.2 Address→UUID mapping for parent_id

Agent seed script: INSERT ROOTs (parent=NULL), capture UUID via RETURNING id. Use CTE/temp lookup canonical_address → UUID để resolve parent_id cho children.

7.3 Vocabulary seed

-- SANDBOX DDL CANDIDATE — NOT TO RUN UNTIL USER APPROVES
INSERT INTO sandbox_tac.section_type_vocab
    (code, name, description, soft_limit_words, hard_limit_words,
     description_required, body_required) VALUES
('heading',            'Heading',            'Heading có authority',
    500, 1500, FALSE, FALSE),
    -- body_required=FALSE, description_required=FALSE
    -- Structural nodes dùng heading nhưng body=NULL, description=NULL
('paragraph',          'Paragraph',          'Đoạn văn thường',           500, 1500, TRUE, TRUE),
('definition',         'Definition',         'Định nghĩa thuật ngữ',      500, 1500, TRUE, TRUE),
('principle',          'Principle',          'Nguyên tắc/doctrine',        500, 1500, TRUE, TRUE),
('process',            'Process',            'Quy trình multi-step',       500, 1500, TRUE, TRUE),
('technical_spec',     'Technical Spec',     'Đặc tả kỹ thuật',           800, 2000, TRUE, TRUE),
('governance_process', 'Governance Process', 'Quy trình governance',       500, 1500, TRUE, TRUE),
('reference_mapping',  'Reference Mapping',  'Bảng crosswalk/mapping',     500, 1500, TRUE, TRUE),
('invariant_list',     'Invariant List',     'Danh sách invariants',       500, 1500, TRUE, TRUE),
('open_decision_list', 'Open Decision List', 'Danh sách ODs',              500, 1500, TRUE, TRUE),
('changelog',          'Changelog',          'Nhật ký thay đổi',          1000, 3000, FALSE, TRUE);

INSERT INTO sandbox_tac.publication_type_vocab
    (code, name, description, default_risk_tier) VALUES
('design_note', 'Design Note', 'Tài liệu thiết kế tiền-schema', 'medium');

7.4 Body, structural nodes, vector fields

Body = actual source content từ 3 KB docs. 5 structural nodes: body=NULL. vector_sync_status = 'pending' (no Qdrant). Trigger auto-computes content_hash + length_flag.


8. Hash Plan

Content hash: SHA-256(title||body||description||profile) via fn_sbx_compute_content_hash(). Source file hash: sha256sum trên VPS. Post-seed verify: recompute → 0 mismatches.


9. Checker Plan

Birth gate BG-LU/UV 01→06, pre-enactment PE-PUB 01→06 (trivially PASS), daily DOT-LU/UV/PUB 01→03 — as SQL queries on sandbox_tac.

Round-trip with DFS sort_path:

WITH RECURSIVE tree AS (
    SELECT lu.id, lu.canonical_address, lu.parent_id, lu.sort_order,
           lu.section_type, 0 AS depth, ARRAY[lu.sort_order] AS sort_path
    FROM sandbox_tac.logical_unit lu
    WHERE lu.doc_code = 'D38-HOWTO' AND lu.parent_id IS NULL
    UNION ALL
    SELECT lu.id, lu.canonical_address, lu.parent_id, lu.sort_order,
           lu.section_type, t.depth + 1, t.sort_path || lu.sort_order
    FROM sandbox_tac.logical_unit lu JOIN tree t ON lu.parent_id = t.id
)
SELECT t.depth, t.canonical_address, t.section_type,
       uv.title, uv.body, uv.length_flag, t.sort_path
FROM tree t
JOIN sandbox_tac.unit_version uv ON uv.logical_unit_id = t.id
    AND uv.version_number = 1
ORDER BY t.sort_path;

10. No-Production-Change Guard

10.1 Isolation

Separate schema. Verify GĐ-1 (PF-4). No cross-schema FK. No cross-schema read. No production mutation.

10.2 Execution pipeline with action log

PHASE 0: PRE-FLIGHT → PF-1→PF-6 all PASS → If FAIL → ABORT
PHASE 1: CREATE → BEGIN → schema + tables + functions + triggers → COMMIT
  → PF-4 Directus check → If sees sandbox → DROP CASCADE + ABORT
  → If ERROR → ROLLBACK + DROP + report
PHASE 2: SEED → vocab → LU ROOTs → depth-1 → depth-2 → UV → pub → pm
  → If ERROR → DROP CASCADE + report
PHASE 3: VERIFY → checkers → hash recompute → round-trip → PF-5 re-run
PHASE 4: REPORT → action log → PASS/FAIL → report to User/GPT

Rollback on error: DROP SCHEMA sandbox_tac CASCADE. Action log: reports/p7b-sandbox-execution-log-YYYY-MM-DD.md.

10.3 Đ33 compliance

directus DB ✅, Lớp KHO ✅, naming ✅, E1 DDL ✅, password GSM ✅.

10.4 DOT registration — Open decision

OD-P7B-02: Option A (register temporary DOT) nếu Đ33 §13.3 literal. Option B (action log đủ) nếu sandbox exception. User quyết.


11. Risks

# Risk Mitigation
1 Directus scan sandbox PF-4 verify + DROP+ABORT
2 pgcrypto missing PF-1 + remediation
3 CREATE SCHEMA privilege PF-2 + request grant
4 Seed FK violation Strict order §7.1
5 Hash mismatch Trigger + post-verify
6 Scope creep 8 tables only

12. PASS/FAIL Criteria

12.1 P7B design PASS (Cổng 1)

8 DDL candidates consistent, pre-flight covers deps, seed plan clear, checker covers P6, DFS round-trip, pipeline + rollback, no production mutation, Đ33/P5/P6 compliance.

12.2 Sandbox execution PASS (Cổng 2)

PF-1→PF-6 PASS, 8 tables created, 76+76+3+76 seeded, 0 constraint violations, parent trigger works, 76/76 hashes, 76/76 length flags, 0 ERROR checkers, 3/3 round-trip, 10/10 code blocks, production unchanged.


13. Open Decisions

Code Câu hỏi Phase
OD-P7B-01 pgcrypto cần superuser? Execution
OD-P7B-02 DOT registration? A=register, B=action log Execution
OD-P7B-03 technical_spec 800/2000? Execution
OD-P7B-04 Sandbox lifespan (DROP khi nào)? Post-P7B
OD-P7B-05 Đ24 cross-read approval? Post-execution
OD-P7B-06 CHECK→FK cho production? P8

14. Constitutional Check

NT1/NT13 PASS (sandbox≠SoT). NT2 PASS (constraints+triggers). NT4 PASS CĐK (CHECK sandbox OK, production FK). NT11 PASS. Đ24 PASS (defer). Đ32 PASS. Đ33 PASS (E1, GSM). Đ35 PASS. P5 PASS (8/8). P6 PASS (BG+DOT coverage).


15. Recommendation

Cổng 1 — Duyệt tài liệu: P7B v0.3 PASS → upload KB. ✅ DONE.

Cổng 2 — Duyệt execution: User approve riêng → agent chạy DDL+seed+verify trên VPS. Xem Appendix A.

Roadmap: P7B design PASS → User approve execution (Cổng 2) → sandbox execution PASS → P8 implementation design → P9 production migration (approval riêng).


Appendix A — Draft Execution Prompt

⚠️ NOT TO RUN UNTIL USER APPROVES CỔNG 2 ⚠️

Mục tiêu: Tạo sandbox_tac, seed 76 units, chạy checkers, round-trip. KHÔNG đụng production.

Pre-flight PF-1→PF-6 PHẢI PASS. PF fail → ABORT. PF-4 fail → DROP+ABORT.

DDL/DML chỉ sandbox_tac. Không cross-read public.entity_labels. Không Directus collection.

Action log: reports/p7b-sandbox-execution-log-YYYY-MM-DD.md. Bất kỳ lỗi → DROP SCHEMA sandbox_tac CASCADE.

PASS/FAIL theo §12.2.


Patch log

v0.1 → v0.2 (14 patches)

# Điểm
1 Directus scan = giả định GĐ-1
2 User/role verify + approval
3 Pre-flight PF-1→PF-6
4 DDL candidate naming
5 CHECK vs FK (sandbox vs production)
6 Đ24 cross-read defer
7 Seed count 11
8 vector_sync_status giữ P5 enum
9 Address→UUID mapping
10 Round-trip sort_path DFS
11 Execution pipeline + action log
12 P7B PASS = request approval
13 DOT registration = OD
14 Sandbox execution wording

v0.2 → v0.3 (4 patches)

# Điểm
1 heading.body_required=FALSE — structural nodes body=NULL pass BG-UV-03
2 heading.description_required=FALSE — structural nodes pass BG-UV-02
3 CREATE EXTENSION pgcrypto tách khỏi DDL, PF-1 remediation riêng
4 PF-4: Directus sees sandbox → DROP CASCADE + ABORT

P7B v0.3 | OFFICIAL | S182 | 2026-04-26 | Opus 4.6 GPT: v0.1 PASS CĐK (14) → v0.2 → v0.3 PASS CĐK nhẹ (4) → FINAL PASS User: PASS