KB-1528

S177 — Sprint 1 Round D Command Review Package — First Live Write on Base đệm (2026-05-20)

41 min read Revision 1
s177larksprint1round-dcommand-reviewpackagebase-demlive-write-proposalpre-execution-gate

S177 — Sprint 1 Round D Command Review Package — First Live Write on Base đệm

Status: PASS — package ready for GPT/User review. No live Lark write executed by this package; one read-only records create --dry-run was issued against Base đệm to capture the exact audit-row shape that the live execution must reproduce. No production data touched, no MCP write enabled, no secret read, no commit, no push. Date: 2026-05-20 Authored by: Claude Code (Opus 4.7, 1M context) Folder: knowledge/dev/lark/s177-controlled-crud-gateway/ Pairs with:

  • s177-sprint1-round-c-implementation-evidence-2026-05-20.md (Round C baseline)
  • s177-sprint1-round-b-implementation-evidence-2026-05-20.md
  • s177-sprint1-round-b-addendum-2026-05-20.md
  • s177-sprint1-command-review-package-2026-05-19.md (Sprint 1 base CRP)
  • s177-architecture-design-2026-05-19-patch2.md
  • s177-oq-decision-record-2026-05-19.md

1. Objective

Prepare the smallest safe live write test on Base đệm — records create --confirm against 88-phai-cu-base-dem, writing a single harmless marker string to one free-form Text field, surfacing created_record_id through the Cowork-shaped CLI JSON, and producing two audit rows (planned then success) with byte-identical operation_id / idempotency_key.

This package proposes the operation; it does NOT execute it. Execution requires (a) a separate authoring gate to wire a real api_caller (the CLI currently defaults to _round_b_refusing_api_caller) and (b) explicit sovereign approval of the exact command in §6.4.


2. Scope

In scope (planning only):

  • One record.create against Base đệm, table tblaU7kxyPTNBSrR ("Đơn hàng"), one field (Văn bản).
  • Exact command shape (CLI form, LARK_AGENT inline, --agent, --dry-run then --confirm).
  • The smallest possible source mutation to flip the CLI from refusing to real LarkCore.write.
  • Expected stdout JSON, expected audit-row shape, expected lock + PII behavior.
  • Rollback / cleanup policy.

Out of scope:

  • Any write to production base 88-phai-cu (token YSIkb8PxOaNaozs2vwalOOcagkf).
  • MCP adapter (Sprint 2).
  • Field / table / schema operations (Sprint 3/4).
  • records update / records delete / batch ops on Base đệm. These require LARK_BACKUP_GPG_PUBKEY to be provisioned in GSM (still absent — see §6.5).
  • Any execution. This package only specifies what the operator would run; the operator does not run it from this document.

3. SSOT consulted

  1. knowledge/dev/lark/s177-controlled-crud-gateway/s177-sprint1-round-c-implementation-evidence-2026-05-20.md
  2. knowledge/dev/lark/s177-controlled-crud-gateway/s177-sprint1-round-b-implementation-evidence-2026-05-20.md
  3. knowledge/dev/lark/s177-controlled-crud-gateway/s177-sprint1-command-review-package-2026-05-19.md
  4. knowledge/dev/lark/s177-controlled-crud-gateway/s177-architecture-design-2026-05-19-patch2.md (read indirectly via Round B/C extracts)
  5. knowledge/dev/lark/s177-controlled-crud-gateway/s177-oq-decision-record-2026-05-19.md
  6. knowledge/dev/lark/s177-controlled-crud-gateway/README.md (revision 12)

All six SSOTs present and readable. No STOP_AND_ESCALATE.


4. Mandatory live survey findings (2026-05-20)

All findings captured via ssh contabo from the laptop; no source mutation, no Lark write, no secret read.

Item Value
Repo root /opt/incomex/lark-client/
Branch feat/s177-sprint1-round-a
HEAD 2307265a11241f1ecc228ff92199c1f94b9c1483 (Round C)
Round C commit present? YES — 2307265 S177 Sprint 1 Round C - replace stubs + pre-write readiness
Working tree Clean modulo __pycache__ byproducts; no source modifications
Remote NONE (git remote -v empty) — feat/s177-sprint1-round-a is local-only
Cold pytest 134 passed, 5 skipped in 6.22s (env -u LARK_TEST_INTEGRATION .venv/bin/pytest -q)
config/bases.yaml SHA256 c063dc00c6b71a56d06435baba3019fcdd33d92999c770f8bc6d784866bdae5d (= Round C; unchanged)
Base đệm registry entry key=88-phai-cu-base-dem, app_token=Nf2bb1ExXaYnlksgoyQl72GNgAc, role=staging
Production registry entry (forbidden target) key=88-phai-cu, app_token=YSIkb8PxOaNaozs2vwalOOcagkf, role=core
LARK_BACKUP_GPG_PUBKEY in GSM ABSENT (only LARK_APP_ID, LARK_APP_SECRET present — names only, no value read)
LARK_BACKUP_GPG_PUBKEY_PATH env unset on the operator shell
LARK_BACKUP_GPG_PUBKEY_FPR env unset on the operator shell
LARK_AGENT env unset on the operator shell
Refusing api_caller present? YES — lark_client/service.py:45 _round_b_refusing_api_caller raises NotAuthorizedInRoundB
Factory default lark_client/factory.py:113api = api_caller if api_caller is not None else _round_b_refusing_api_caller
CLI default api_caller cli/records.py::_build_service calls build_service(agent=...) with NO api_caller override → refusing path is the live default
Audit log primary sink /var/log/lark-ops/<YYYYMMDD>.jsonl (e.g. 20260520.jsonl, root:root, 0755)
GPG binary on VPS /usr/bin/gpg (GnuPG 2.4.4) — present, but no production public key wired

4.1 Read-only dry-run probe captured live (this CRP)

A non-mutating --dry-run was issued to confirm preflight + audit-row shape on the exact base/table/payload proposed below. No Lark API call was made; the dry-run short-circuits at the api_call layer.

Command (verbatim, executed once at 2026-05-20T10:07:13 UTC):

.venv/bin/lark-tool --json records create 88-phai-cu-base-dem tblaU7kxyPTNBSrR \
  --fields '{"Văn bản":"S177-RD-DRY-RUN-PROBE-PREFLIGHT"}' \
  --dry-run --agent claude-code

Result: structured mode: dry_run JSON (full body reproduced in §6.8.1). pii.pii_redacted=false, pii.detector=["none"], field_hits=[]. Two audit rows written to /var/log/lark-ops/20260520.jsonl:

  • phase: planned, audit_pre_id=994a5851-…, operation_id=015bd6ee-…, idempotency_key=015bd6ee-…, agent=claude-code, source=cli, cmd=records.create, op=record.create, base_key=88-phai-cu-base-dem, table_id=tblaU7kxyPTNBSrR, dry_run=true, confirmed=false, is_buffer_base=true.
  • phase: success, same audit_pre_id/operation_id/idempotency_key, outcome_status=dry_run, lark_response_meta={}, request_id="", duration_ms=0, pii.pii_redacted=false.

The cli_start argv-log line confirmed argv-value masking: --fields {"Văn bản":"***"} (the value is replaced with *** in the cli_start record).


5. DISCOVER-FIRST — Base đệm tables and safe fields

Source: knowledge/dev/lark/probes/s179/base88_dem/ (laptop snapshot, 2026-04-12) + the dry-run probe in §4.1.

Base đệm contains two tables:

Table name table_id Fields (count)
TTS tblPQ6N79EeOmnTm 7 — STT (AutoNumber primary), Ngày (DateTime), Phòng (SingleSelect NB4/Nhân/Thanh), Bảng 2 (DuplexLink→Đơn hàng), Bảng 1 2 (DuplexLink→TTS self), Văn bản (Text), Xác nhận (SingleSelect "Xác nhận")
Đơn hàng tblaU7kxyPTNBSrR 5 — STT (AutoNumber primary), Bảng 1 (DuplexLink→TTS), Ngày (DateTime), Phòng (SingleSelect NB4/Nhân/Thanh), Văn bản (Text)

Field-safety analysis (binding for the proposed payload):

  • AutoNumber STT (primary) — system-assigned, do NOT set.
  • DuplexLink fields (Bảng 1, Bảng 1 2, Bảng 2) — bidirectional; writing them mutates the linked record's reverse field. AVOID for a "smallest harmless test".
  • SingleSelect Phòng (NB4 / Nhân / Thanh) — option names look team-coded, not a clear test marker. AVOID for cleanliness.
  • DateTime Ngày — could be set, but irrelevant to a "presence-of-write" probe. AVOID to keep the payload minimal.
  • SingleSelect Xác nhận on TTS — one option ("Xác nhận") that carries business meaning. AVOID.
  • Text Văn bản — free-form text on both tables. PREFERRED. Carries a marker string that we entirely control.

Table choice: tblaU7kxyPTNBSrR ("Đơn hàng"), NOT tblPQ6N79EeOmnTm ("TTS").

Rationale: config/pii-fields.yaml carries a wildcard registry entry base_key=88-phai-cu-base-dem, table_id=tblPQ6N79EeOmnTm, field_name="*", pii_types=[national_id_cccd, passport_vn, phone_vn, email, address]. ANY field write to TTS will trip detector=["registry"] even when the value is clearly non-PII (OQ-3 keeps the write proceeding, but pii_redacted=true is recorded). Đơn hàng (tblaU7kxyPTNBSrR) is NOT in the PII registry, so a write with a non-PII string yields the cleanest possible outcome: pii_redacted=false, detector=["none"], field_hits=[] — matches addendum-D1 "Preferred" outcome exactly, as verified by the live dry-run in §4.1.

No schema drift detected against the 2026-04-12 snapshot — the dry-run preflight resolved the base, accepted the Văn bản field name, and progressed through every Round C safety layer without error.


6. The package (17 items)

6.1 Objective

(Same as §1.)

6.2 Exact target

Field Value
base_key 88-phai-cu-base-dem
app_token Nf2bb1ExXaYnlksgoyQl72GNgAc (sha256 db48d36ed0b6b69116e15ed5d6a82bc1f5e3a907c83adfde3a73e0d3b3a59a6c)
Production token (forbidden) YSIkb8PxOaNaozs2vwalOOcagkf — execution must abort if registry resolves to this
Role asserted staging (must equal exactly; is_buffer_base=true derived)
table_id tblaU7kxyPTNBSrR ("Đơn hàng")
Field payload (exact) {"Văn bản": "S177-RD-LIVE-PROBE <ISO8601-UTC> <uuid7>"} where <ISO8601-UTC> is the operator's wall clock at execution time and <uuid7> is a freshly generated UUID v4's first 7 hex chars (a test marker, NOT used for idempotency)
Operation record.create

Hash, not secret dump: the app_token sha256 above is provided so the reader can printf '%s' "$LARK_BASE_DEM_TOKEN" \| sha256sum to verify their environment matches without re-printing the token.

6.3 Why this table / payload is safe

  1. Đơn hàng is NOT in pii-fields.yaml — registry detector cannot fire on this table.
  2. Văn bản is plain Text — no enum coupling, no foreign-key coupling, no system-assigned semantics.
  3. The marker string "S177-RD-LIVE-PROBE <ISO8601-UTC> <uuid7>" contains no digit runs ≥ 9 (ISO timestamp has at most 4 consecutive digits — the year), so none of the VN PII regexes match (CCCD = 12d, CMND = 9d, phone = 10d, passport = letter+7d, tax_code = 10–13d). The dry-run in §4.1 confirmed field_hits=[].
  4. No DuplexLink is set, so no reverse record on the TTS table is mutated.
  5. STT (AutoNumber primary) auto-fills — no primary-key conflict possible.
  6. is_buffer_base=trueapproval_exempt_bases carries 88-phai-cu-base-dem, so the SafetyLayer issues a synthetic Approval(id="EXEMPT") instead of consuming a real approval row.
  7. Idempotent at the Lark side: Round C lark-api-limits.yaml flips records POST → client_token: true (OQ-8 citation …/record/create); LarkCore.write injects ctx.idempotency_key (UUID v4) into the body as client_token. Replays during the same network blip will deduplicate server-side.

6.4 Exact proposed command (post-implementation-gate)

LARK_AGENT=claude-code \
  /opt/incomex/lark-client/.venv/bin/lark-tool --json records create \
    88-phai-cu-base-dem tblaU7kxyPTNBSrR \
    --fields '{"Văn bản":"S177-RD-LIVE-PROBE <ISO8601-UTC> <uuid7>"}' \
    --confirm --agent claude-code

Notes:

  • LARK_AGENT=claude-code is set inline as a defense-in-depth (the CLI also accepts --agent claude-code). Both produce audit.agent="claude-code".
  • --json selects structured-output mode; required for the post-execution audit verification in §6.16.
  • The placeholder <ISO8601-UTC> and <uuid7> are substituted by the operator at execution time. Example concrete string: "S177-RD-LIVE-PROBE 2026-05-20T11:00:00Z 9f3c2a1".

This command is INERT today — see §6.5 / §D2: with HEAD = 2307265, the CLI hits _round_b_refusing_api_caller and returns NotAuthorizedInRoundB. The command becomes meaningful only after the source-mutation gate in §D2 is satisfied and reviewed.

6.5 Preconditions

Before the operator may execute §6.4, ALL of the following must hold. Each is independently verifiable:

# Precondition Verification
P-1 Round D source-mutation gate (§D2) is reviewed and merged on the feature branch git log --oneline shows a commit titled S177 Sprint 1 Round D — wire real api_caller for Base đệm; cold pytest still green
P-2 config/bases.yaml SHA256 still equals c063dc00c6b71a56d06435baba3019fcdd33d92999c770f8bc6d784866bdae5d sha256sum config/bases.yaml
P-3 LARK_AGENT=claude-code is set inline or exported on the operator shell printenv LARK_AGENT returns claude-code; OR command line carries LARK_AGENT=claude-code prefix
P-4 GSM resolves LARK_APP_ID and LARK_APP_SECRET (already in place; required for any Lark call) gcloud secrets list --filter='name~LARK' --format='value(name)' shows both names; values not printed
P-5 Registry resolves 88-phai-cu-base-dem → app_token starting with Nf2bb1Ex and role staging lark-tool registry show 88-phai-cu-base-dem (read-only)
P-6 Sovereign written approval of the exact §6.4 command for one execution Captured in the Round D execution-authorization message, NOT auto-derived from this CRP
P-7 Network: contabo can reach open.larksuite.com over TCP/443 Tested separately (out of CRP scope; covered by existing reader tests)
P-8 LARK_BACKUP_GPG_PUBKEY is NOT required for the proposed create (see §6.7) — but the operator must confirm by reading §6.7

6.6 Safety hard guards (binding for any Round D execution attempt)

The CLI / SafetyLayer already enforces all of the below; this section pins the invariants the operator must NOT bypass:

  • G-1. base_key argument MUST equal 88-phai-cu-base-dem. If any other key is passed, abort.
  • G-2. Registry.get_by_key("88-phai-cu-base-dem").app_token MUST equal Nf2bb1ExXaYnlksgoyQl72GNgAc. The app_token in flight MUST equal that value (LarkWriteService.resolve_base is the single point of resolution; tampering requires editing config/bases.yaml — which is locked by sha256 invariant G-3).
  • G-3. sha256sum config/bases.yaml MUST equal c063dc00c6b71a56d06435baba3019fcdd33d92999c770f8bc6d784866bdae5d immediately before and immediately after execution. Production token MUST be rejected (the registry assertion + the is_buffer_base=true derived flag together exclude it).
  • G-4. --confirm MUST be present (Round B addendum §2 — mutation refuses without --dry-run or --confirm).
  • G-5. LARK_AGENT MUST resolve to claude-code (or another explicitly approved value — D4). Default fallbacks ($USER@$HOSTNAME, unknown-$PID) MUST NOT be allowed for an authorized write.
  • G-6. The api_caller wired by Round D MUST refuse if ctx.base_key != "88-phai-cu-base-dem" AND ctx.is_buffer_base != True. This is a defense-in-depth check that the Round D implementation gate (§D2) must include.
  • G-7. No edits to config/bases.yaml, config/write-approvals.yaml, or config/allowed_endpoints.yaml between dry-run and live execution. SHAs MUST be pinned.

6.7 Whether GPG is required

Not required for the proposed record.create.

Per lark_client/safety.py (Round B addendum §C5 + Round C §7.1), the SafetyLayer invokes gpg_backup(ctx, payload) only when ctx.op ∈ {record.update, record.delete, record.batch_update, record.batch_delete}. record.create and record.batch_create skip the backup layer entirely — there is no prior state to back up.

Operationally:

  • With LARK_BACKUP_GPG_PUBKEY_PATH / _FPR unset, factory.build_service wires the Round B stub backup. For create, the stub is never called.
  • Round C tests/test_round_c_scenarios.py::test_scenario_create_on_buffer_base_emits_created_record_id already proves that gpg_backup is NOT invoked for record.create.

Consequence: Round D step 1 (create) does not block on GSM provisioning. Steps 2+ (update/delete) DO block on it (see §6.12 Option C).

6.8 Expected output JSON

6.8.1 --dry-run (already verified live, §4.1)

{
  "ok": true,
  "mode": "dry_run",
  "operation": "record.create",
  "base_key": "88-phai-cu-base-dem",
  "table_id": "tblaU7kxyPTNBSrR",
  "record_id": null,
  "would_write": {"fields": {"Văn bản": "S177-RD-DRY-RUN-PROBE-PREFLIGHT"}},
  "diff": [],
  "approval_status": "exempt",
  "safety_checks_passed": [
    "preflight", "dry_run_or_confirm", "approval",
    "audit_planned", "lock", "pii_scan"
  ],
  "warnings": [],
  "operation_id": "<uuid-v4>",
  "audit_ref": "<uuid-v4>",
  "pii": {
    "pii_redacted": false,
    "redaction_types": [],
    "redacted_fields_count": 0,
    "detector": ["none"],
    "field_hits": []
  }
}

6.8.2 --confirm (expected on first live execution)

{
  "ok": true,
  "mode": "live",
  "operation": "record.create",
  "base_key": "88-phai-cu-base-dem",
  "table_id": "tblaU7kxyPTNBSrR",
  "record_id": null,
  "created_record_id": "rec<≥10 alphanumeric>",
  "data": {"code": 0, "msg": "ok", "data": {"record": {"record_id": "rec…", "fields": {"Văn bản": "S177-RD-LIVE-PROBE …"}}}},
  "approval_status": "exempt",
  "safety_checks_passed": [
    "preflight", "dry_run_or_confirm", "approval",
    "audit_planned", "lock", "pii_scan", "api_call"
  ],
  "warnings": [],
  "operation_id": "<uuid-v4>",
  "audit_ref": "<uuid-v4>",
  "pii": {
    "pii_redacted": false,
    "redaction_types": [],
    "redacted_fields_count": 0,
    "detector": ["none"],
    "field_hits": []
  }
}

The single most important field is created_record_id — the Round D evidence report MUST quote it verbatim.

6.9 Expected audit entries

Path: /var/log/lark-ops/<YYYYMMDD>.jsonl (daily-sharded, append-only, root:root, 0755 dir). Today's file is /var/log/lark-ops/20260520.jsonl.

Two new entries (in this order) must be appended for a single live execution:

Entry 1 — phase: planned

{
  "ts": "<ISO8601 with µs and tz>",
  "phase": "planned",
  "audit_pre_id": "<uuid-v4>",
  "operation_id": "<uuid-v4>",
  "idempotency_key": "<same uuid-v4 as operation_id>",
  "agent": "claude-code",
  "source": "cli",
  "cmd": "records.create",
  "op": "record.create",
  "base_key": "88-phai-cu-base-dem",
  "table_id": "tblaU7kxyPTNBSrR",
  "targets": [],
  "target_count": 0,
  "approval_id": "",
  "backup_ref": null,
  "dry_run": false,
  "confirmed": true,
  "is_buffer_base": true
}

Entry 2 — phase: success

{
  "ts": "<ISO8601 with µs and tz, > Entry 1 ts>",
  "phase": "success",
  "audit_pre_id": "<same as Entry 1>",
  "operation_id": "<same as Entry 1>",
  "idempotency_key": "<same as Entry 1>",
  "agent": "claude-code",
  "source": "cli",
  "cmd": "records.create",
  "op": "record.create",
  "base_key": "88-phai-cu-base-dem",
  "table_id": "tblaU7kxyPTNBSrR",
  "request_id": "<Lark X-Tt-Logid or response request_id>",
  "lark_response_meta": {"code": 0, "msg": "ok", "http_status": 200, "request_id": "<string>", "server_time": "<string>"},
  "duration_ms": <int>,
  "pii": {"pii_redacted": false, "redaction_types": [], "redacted_fields_count": 0, "detector": ["none"], "field_hits": []},
  "outcome_status": "success",
  "error": null
}

The cli_start line that precedes Entry 1 must show argv-value-masked fields: --fields {"Văn bản":"***"} (already verified in §4.1).

6.10 Expected lock behavior

Per lark_client/locks.py (Round C §9.2):

  • record.createtable-level lock at <LARK_LOCK_DIR>/88-phai-cu-base-dem/tblaU7kxyPTNBSrR.lock.
  • LARK_LOCK_DIR env default → /var/lock/lark-ops/ (lazily created by the acquirer; root:root expected).
  • Acquisition is fcntl.flock(LOCK_EX | LOCK_NB); concurrent contention raises SafetyViolation(reason="lock_held") immediately.
  • The lock file is not deleted after release (advisory fcntl is sufficient; on-disk file kept for forensics).

The Round D evidence report MUST capture the lockfile path and confirm it exists after execution.

6.11 Expected PII scanner behavior

Inputs (per §6.2 payload): {"Văn bản": "S177-RD-LIVE-PROBE <ISO8601-UTC> <uuid7>"}.

Detector Result Rationale
registry NO HIT Đơn hàng (tblaU7kxyPTNBSrR) is NOT in config/pii-fields.yaml (the wildcard entry scopes specifically to TTS tblPQ6N79EeOmnTm).
patternnational_id_cccd (12d isolated) NO HIT Marker string has at most 4 consecutive digits (year).
patternnational_id_cmnd (9d) NO HIT (same)
patternpassport_vn ([BCGN]\d{7}) NO HIT No letter+7-digit sequence.
patternphone_vn (03/05/07/08/09 or +84) NO HIT (same)
patternemail NO HIT No @.
patternbank_account (10–19d) NO HIT (same)
patterntax_code_vn NO HIT (same)

Expected scanner output (verified in the live dry-run in §4.1):

{
  "pii_redacted": false,
  "redaction_types": [],
  "redacted_fields_count": 0,
  "detector": ["none"],
  "field_hits": []
}

No PII metadata path needs to be exercised for OQ-3 (the layer still runs; it just records none). Even if it had matched, OQ-3 says the write proceeds and only metadata is logged — never raw values.

6.12 Rollback / cleanup

Per addendum-D3 — LARK_BACKUP_GPG_PUBKEY is absent; therefore records delete from the Gateway is not available without an out-of-band cleanup step.

Chosen policy: Option A (leave-and-mark).

  1. Create one harmless record on 88-phai-cu-base-dem :: tblaU7kxyPTNBSrR.
  2. The marker string "S177-RD-LIVE-PROBE <ISO8601-UTC> <uuid7>" clearly identifies it as test data.
  3. Record the returned created_record_id in the Round D evidence report.
  4. Leave it in the Base. Do not attempt to delete from the Gateway in this round.
  5. Delete is a separately authorized later gate, opened only after LARK_BACKUP_GPG_PUBKEY is provisioned in GSM (then the Gateway can route records delete --confirm through the GPG-backed safety chain).

Option B (manual delete via Lark web) is permitted only with explicit Huyên approval and explicit notation that the deletion bypassed the Gateway. Not the recommended path.

Option C (provision GPG then Gateway delete) is the long-term-correct path; it is out of scope for this CRP — it is the immediate next macro after Round D step 1.

6.13 PASS criteria (for the live execution, post-implementation gate)

After the proposed §6.4 command runs once, the Round D execution PASSES if and only if ALL of:

  1. CLI exit code 0.
  2. CLI JSON has ok: true, mode: "live", operation: "record.create", base_key: "88-phai-cu-base-dem", table_id: "tblaU7kxyPTNBSrR".
  3. CLI JSON has a non-empty created_record_id matching ^rec[A-Za-z0-9]{8,}$.
  4. CLI JSON safety_checks_passed contains exactly the 7 items in §6.8.2 (including "api_call").
  5. CLI JSON pii.pii_redacted === false, pii.detector === ["none"], pii.field_hits === [].
  6. /var/log/lark-ops/<YYYYMMDD>.jsonl contains a new phase: planned row matching §6.9 Entry 1.
  7. /var/log/lark-ops/<YYYYMMDD>.jsonl contains a new phase: success row matching §6.9 Entry 2 with outcome_status: "success" and a non-empty request_id.
  8. The operation_id and idempotency_key are byte-identical across the two audit rows AND match the CLI JSON operation_id and the client_token injected into the Lark POST body.
  9. lark_response_meta keys are a subset of {code, msg, http_status, request_id, server_time} — no raw record fields echoed back into audit.
  10. sha256sum config/bases.yaml still equals c063dc00c6b71a56d06435baba3019fcdd33d92999c770f8bc6d784866bdae5d.
  11. No raw value of Văn bản ever appears in stdout, audit, KB report, or anywhere outside the Lark response body itself (argv masking already verified in §4.1).
  12. The lockfile at /var/lock/lark-ops/88-phai-cu-base-dem/tblaU7kxyPTNBSrR.lock exists and fcntl is no longer holding it (i.e., the next invocation could re-acquire).

6.14 BLOCKED criteria (for this CRP)

Report this CRP as BLOCKED if any of:

  • Round C HEAD is NOT 2307265a11241f1ecc228ff92199c1f94b9c1483 (live survey would invalidate). Verified PASS in §4.
  • config/bases.yaml SHA256 has drifted. Verified PASS in §4.
  • 88-phai-cu-base-dem no longer in the registry. Verified PASS in §4.
  • Cold pytest is not green. Verified PASS in §4 (134 passed / 5 skipped).
  • Any safe non-PII field cannot be identified on Base đệm. Verified PASS in §5 (Đơn hàng :: Văn bản).
  • Source mutation to flip api_caller is required AND a separate gate cannot be opened. PASS — gate proposed below (§D2).

This CRP reports PASS on all of the above.

For the execution itself (a later step, NOT this CRP), the operator must report BLOCKED if any of:

  • Registry resolves to the production token YSIkb8Px… or any non-Base-đệm key.
  • LARK_AGENT resolves to anything other than the explicitly approved value (default fallback → BLOCKED).
  • config/bases.yaml SHA256 has drifted between dry-run and confirm.
  • --confirm is missing OR --dry-run is also present (mutually exclusive intent).
  • Round D source-mutation gate (§D2) is not yet merged.
  • Network to open.larksuite.com is not reachable.

6.15 FAIL criteria (for the live execution, post-implementation gate)

The Round D execution FAILS if any of:

  • CLI exits non-zero with error_type ∈ {SafetyViolation, NotAuthorizedInRoundB, UnknownBaseError, EndpointNotAllowed, ApprovalError} (the SafetyLayer caught a violation — this means the implementation gate §D2 is incomplete or the preconditions in §6.5 were not honored).
  • LarkAPIError with code != 0 (Lark rejected the write — must surface code, msg, request_id in the structured-error JSON; the test record was NOT created and there is nothing to clean up).
  • Two phase: planned rows but no phase: success row appears within 30 s (audit-post degraded → look for phase: emergency_post_audit in /var/log/lark-ops/EMERGENCY/<YYYYMMDD>/).
  • created_record_id is empty or absent in the JSON despite ok: true.
  • Any of the PASS-criteria-violations in §6.13 inverted.

A FAIL is recoverable — the create is idempotent (server-side client_token deduplication when supported, or absence of the record when rejected). Re-run is permitted ONLY after the cause is diagnosed AND a fresh sovereign approval is granted.

6.16 Exact evidence report expected after execution

If and when the §6.4 command is later executed under sovereign authorization, the operator MUST upload a Round D evidence report at:

knowledge/dev/lark/s177-controlled-crud-gateway/s177-sprint1-round-d-live-write-evidence-2026-05-20.md

Required sections:

  1. Top-line result (PASS / FAIL / BLOCKED).
  2. Repo / branch / HEAD (post-D2-mutation commit pinned).
  3. Pre-execution live survey (sha of bases.yaml, env state, registry resolution).
  4. Exact command run (with timestamp, exit code, full stdout JSON quoted).
  5. created_record_id quoted verbatim.
  6. Audit rows quoted verbatim (phase: planned + phase: success) with timestamps.
  7. operation_id / idempotency_key correlation table.
  8. Lockfile path + post-execution existence proof.
  9. sha256sum config/bases.yaml before and after — must match c063dc00c6b71a56d06435baba3019fcdd33d92999c770f8bc6d784866bdae5d.
  10. Cold pytest re-run post-execution (must still be 134 passed / 5 skipped or whatever Round-D-baseline replaces it).
  11. PII scanner output (must be the §6.11 "none" shape; if not, explain).
  12. Confirmation of forbidden-action negatives (no push, no MCP, no production touched, no secret printed, no source mutation beyond the §D2 gate, no bases.yaml edit).
  13. STOP route — explicit hand-back to GPT/User.

6.17 Forbidden actions

For Round D step 1 (live create) and for this CRP itself:

  • DO NOT write to any base other than 88-phai-cu-base-dem.
  • DO NOT write to TTS table (tblPQ6N79EeOmnTm) — Đơn hàng (tblaU7kxyPTNBSrR) is the chosen target.
  • DO NOT run records update, records delete, or any records batch_* against Base đệm in Round D step 1.
  • DO NOT rotate / read / print any GSM secret.
  • DO NOT edit config/bases.yaml, config/write-approvals.yaml, config/allowed_endpoints.yaml, config/lark-api-limits.yaml, or config/pii-fields.yaml.
  • DO NOT git push, git merge, git tag, or rebase. Round D mutations remain on feat/s177-sprint1-round-a local-only.
  • DO NOT enable MCP write tools or any new MCP server.
  • DO NOT deploy, restart, or rebuild any container or service.
  • DO NOT create a new bot or rotate cli_a785d634437a502f.
  • DO NOT delete the test record from the Gateway (Option A leaves it).
  • DO NOT create KB docs outside knowledge/dev/lark/s177-controlled-crud-gateway/.
  • DO NOT self-advance to Round D step 2 (update/delete) or Sprint 2 (MCP adapter) without separate sovereign authorization.
  • DO NOT execute §6.4 from this CRP. This document is a proposal.

7. Round D CRP Addendum

D1. Expected PII scanner behavior for the proposed payload

Fields used: Văn bản only (Text). Value: marker string "S177-RD-LIVE-PROBE <ISO8601-UTC> <uuid7>".

  • Văn bản is NOT in config/pii-fields.yaml (registry entries cover Base 88 production fields plus a wildcard scoped to TTS tblPQ6N79EeOmnTm; Đơn hàng tblaU7kxyPTNBSrR has no entry).
  • The marker value matches none of the Vietnamese PII regex patterns (no 9/12-digit runs, no [BCGN]\d{7}, no @, no 03|05|07|08|09|+84-prefixed 10-digit).
  • Expected scanner output: pii_redacted: false, redaction_types: [], redacted_fields_count: 0, detector: ["none"], field_hits: []. Verified live in §4.1 dry-run.

If PII somehow surfaces (e.g., operator substitutes the marker string for a real value before execution), OQ-3 governs: the write still proceeds, only metadata is logged, raw values never appear in stdout / audit / KB. But that path is explicitly NOT taken here — the marker is opaque.

D2. Factory wiring from refusing stub to real LarkCore.write

Source mutation IS required. Today (HEAD 2307265):

  • cli/records.py:251 _build_service(args) calls from lark_client.factory import build_service; return build_service(agent=getattr(args, "agent", None)) — and passes NO api_caller.
  • lark_client/factory.py:113 then defaults to _round_b_refusing_api_caller.
  • lark_client/service.py:45-47 _round_b_refusing_api_caller(ctx) raises NotAuthorizedInRoundB(ctx.op) for ALL mutating ops.

There is NO env var, CLI flag, or config toggle to flip this. The injection hook args._service_override exists only for in-process tests.

The exact pre-execution gate for Round D step 1:

Gate D-IMPL (separate authoring macro, separately reviewed, separately approved):

Add a new function _real_lark_api_caller(core: LarkCore, *, registry: Registry, limits_path: Path) -> ApiCaller to lark_client/factory.py. It returns a closure (ctx: WriteContext) -> dict that:

  1. Hard-guards Base-đệm-only at the api_caller boundary: if ctx.base_key != "88-phai-cu-base-dem" or not ctx.is_buffer_base: raise SafetyViolation("base_not_authorized_for_round_d", …). This is a defense-in-depth guard ON TOP of the existing whitelist + registry.
  2. Resolves the table-template endpoint for ctx.op (initially only record.createPOST /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records).
  3. Looks up client_token_supported from config/lark-api-limits.yaml::write_endpoint_options (already citation-backed by Round C OQ-8: POST /records → client_token: true).
  4. Calls core.write(method=..., endpoint=..., json_data=ctx.payload, idempotency_key=ctx.idempotency_key, client_token_supported=<bool>, _audit_cmd=ctx.op).
  5. Returns the dict.

Then thread an opt-in flag through cli/records.py::_build_service — preferred shape: a new top-level CLI flag --enable-live-write (defaults to OFF) which causes _build_service to pass api_caller=_real_lark_api_caller(core, …) to build_service. Without the flag, the refusing default stays.

Estimated size of the D-IMPL patch: ~60-90 net new LOC across factory.py + cli/records.py + 2-3 new unit tests in tests/test_round_d_wiring.py (mock LarkCore + assert the closure routes to core.write with the expected kwargs).

Round D step 1 (live execution of §6.4) MUST NOT happen until D-IMPL is reviewed and merged. This CRP does NOT itself open D-IMPL — D-IMPL is the next macro after this CRP is accepted.

Verification that the live invocation will route to real LarkCore.write:

  • Static: grep -n "_real_lark_api_caller\|core.write" lark_client/factory.py cli/records.py after D-IMPL must show the closure is wired conditionally on --enable-live-write.
  • Runtime: a wired dry-run (D-IMPL merged + --enable-live-write --dry-run) MUST still short-circuit before the api_caller — confirms the dry-run gate sits ABOVE api_caller as today.
  • Runtime: a wired confirm (D-IMPL merged + --enable-live-write --confirm) on a base OTHER than 88-phai-cu-base-dem MUST raise the new SafetyViolation("base_not_authorized_for_round_d"). This is the Base-đệm-only guard.

Preventing accidental production write: the three defenses are stacked:

  1. Registry: production token is at key 88-phai-cu, NOT 88-phai-cu-base-dem. Resolving by the wrong key returns the wrong token but trips the next layer.
  2. SafetyLayer: ctx.is_buffer_base is derived from registry.get_by_key(base_key).role == "staging"; 88-phai-cu has role: coreis_buffer_base=false.
  3. The new D-IMPL api_caller boundary guard explicitly checks ctx.base_key == "88-phai-cu-base-dem" AND ctx.is_buffer_base == True.

D3. Rollback / cleanup must be realistic

Per addendum-D3 binding choice: Option A (leave-and-mark) is selected.

  • Create one harmless test record (this CRP proposes exactly one).
  • Leave it on Base đệm.
  • The marker string "S177-RD-LIVE-PROBE <ISO8601-UTC> <uuid7>" self-identifies as test data.
  • created_record_id is recorded in the Round D evidence report.
  • The record persists until LARK_BACKUP_GPG_PUBKEY is provisioned, at which point a separately authorized Gateway records delete --confirm can remove it through the full safety chain (including GPG-encrypted pre-write backup of the row being deleted).

Option B (manual delete via Lark web) is permitted only with explicit Huyên approval, recorded out-of-band. Option C (provision GPG then Gateway delete) is the recommended long-term path and is the next macro after Round D step 1.

This CRP does not promise Gateway-delete cleanup, because LARK_BACKUP_GPG_PUBKEY is absent today.

D4. LARK_AGENT must be explicit

Live survey (§4):

  • printenv LARK_AGENT on the operator shell returns nothing.
  • Default fallback in lark_client/audit.py::_detect_agent is $USER@$HOSTNAMEroot@unknown on contabo SSH. NOT acceptable for an authorized live write.

The §6.4 command sets LARK_AGENT=claude-code inline (defense-in-depth — even if the operator's shell loses the env var, the CLI inherits it). The CLI ALSO accepts --agent claude-code as a redundant override.

Expected audit fields:

"agent": "claude-code",
"source": "cli"

source is hard-coded "cli" inside cli/records.py::_build_ctx (verified in §4.1 dry-run audit). It cannot be MCP/cron/api from this invocation path.

If printenv LARK_AGENT returns empty AND the inline assignment is omitted AND --agent is omitted → BLOCKED. The Round D execution-side checklist must verify that audit.agent is "claude-code" and not the default fallback.

D5. Post-execution audit verification

Path: /var/log/lark-ops/<YYYYMMDD>.jsonl (daily-sharded; today = /var/log/lark-ops/20260520.jsonl).

Locate the two rows by matching audit_pre_id against the CLI JSON's audit_ref. Concrete one-liner:

audit_ref="<from CLI JSON>"
grep -F "\"audit_pre_id\": \"$audit_ref\"" /var/log/lark-ops/$(date -u +%Y%m%d).jsonl

Required checks (each MUST pass; any failure → execution FAIL):

  1. phase: planned row exists. Schema-match §6.9 Entry 1.
  2. phase: success row exists. Schema-match §6.9 Entry 2.
  3. Correlation: phase: planned :: audit_pre_id == phase: success :: audit_pre_id. phase: planned :: operation_id == phase: success :: operation_id. Both equal phase: planned :: idempotency_key == phase: success :: idempotency_key.
  4. created_record_id correlation: CLI JSON created_record_id MUST equal the record_id extracted from the Lark response body (and from the phase: success :: lark_response_meta if request_id is the only key — i.e., created_record_id need not appear in lark_response_meta, but it MUST appear in the original Lark response stored in data of the CLI JSON).
  5. PII metadata present in phase: success, with pii_redacted: false for this clean payload. (For other payloads, OQ-3: metadata always present; never raw values.)
  6. Required fields in phase: success: agent="claude-code", source="cli", base_key="88-phai-cu-base-dem", table_id="tblaU7kxyPTNBSrR", op="record.create", idempotency_key=<uuid-v4>.
  7. No raw PII anywhere: grep -E "<\bcandidate raw values\b>" over stdout JSON + the two audit rows + the Round D evidence markdown MUST return no matches. (For the proposed payload there are no PII candidates — but the operator must still run the check.)
  8. Failure-of-success-row protocol: if phase: planned exists but phase: success is missing AFTER 30 s, look for a sibling row at /var/log/lark-ops/EMERGENCY/<YYYYMMDD>/<ts>-<idempotency_key>.json with phase: emergency_post_audit, error: audit_post_degraded. The CLI JSON in that case would carry error: "audit_post_degraded" but status: "success" — the Lark write happened but the primary audit-result sink failed; the emergency sink is the authoritative success record.

If audit verification cannot be performed (e.g., /var/log/lark-ops/ is unreadable) → BLOCKED. The execution-side checklist must include a ls -la /var/log/lark-ops/ precheck and a post-execution tail -2 /var/log/lark-ops/<YYYYMMDD>.jsonl capture.


8. PASS report for this CRP

This package reports PASS because:

  • Round D CRP authored at the canonical KB path: knowledge/dev/lark/s177-controlled-crud-gateway/s177-sprint1-round-d-command-review-package-2026-05-20.md.
  • README updated to record Round D as filed.
  • Target base / table / payload justified by §4 + §5 + §4.1 live dry-run.
  • The proposed command in §6.4 is exact and copyable.
  • Rollback / cleanup plan is clear (§6.12 Option A).
  • No live Lark write occurred — the dry-run in §4.1 is a non-mutating preflight that short-circuits before the api_call layer; no phase: success with outcome_status: "success" was emitted, only outcome_status: "dry_run".
  • No forbidden action occurred — no push, no merge, no tag, no MCP write, no production touch, no secret printed, no source mutation, no config/bases.yaml edit, no other KB folder touched.

Open blockers before execution:

  1. Round D source-mutation gate (D-IMPL per §D2) must be opened, reviewed, merged.
  2. Explicit sovereign approval of the §6.4 command must be issued in a separate authorization message.
  3. LARK_BACKUP_GPG_PUBKEY provisioning is NOT a blocker for step 1 (create skips backup), but IS a blocker for any later update/delete.

Authorized next macros (in order):

  • D-IMPL — authoring + tests for _real_lark_api_caller (+ --enable-live-write flag).
  • D-EXEC — execution of §6.4 (separate explicit authorization required).
  • D-EVIDENCE — upload s177-sprint1-round-d-live-write-evidence-2026-05-20.md per §6.16.
  • GSM provisioning of LARK_BACKUP_GPG_PUBKEY → enables Round D step 2 (update/delete on Base đệm) and Round D cleanup (Gateway delete of the leave-and-mark record).

9. STOP

Round D CRP authoring complete. STOP — route back to GPT/User for execution-authorization decision. Do not self-advance to D-IMPL or D-EXEC.

Back to Knowledge Hub knowledge/dev/lark/s177-controlled-crud-gateway/s177-sprint1-round-d-command-review-package-2026-05-20.md