S177 — Sprint 1 Round A Implementation Evidence (2026-05-19)
S177 — Sprint 1 Round A Implementation Evidence
Status: PASS — foundational slice implemented, cold pytest green for all Round A scope. Date: 2026-05-19 (work session: 2026-05-20 UTC) Authored by: Claude Code (Opus 4.7, 1M context) Pairs with:
s177-sprint1-command-review-package-2026-05-19.md(binding scope)s177-sprint1-implementation-checklist-2026-05-19.mds177-architecture-design-2026-05-19-patch2.mds177-r0-code-reconcile-report-2026-05-19.mds177-oq-decision-record-2026-05-19.md
1. Top-line result
- PASS for Round A scope (B–G in the macro task brief).
- 14 files changed, +963 / −18 lines on a feature branch.
- Cold pytest: 31 passed, 5 skipped, 2 failed. The 5 skipped are exactly the live-API tests (gated). The 2 failed are pre-existing on
main(842cfc2) and unrelated to Round A — proven by checking them out at baseline. - No live Lark API call during cold pytest (audit-log delta is
cli_startevents only — noendpoint/cmdset). - No production data touched. No deploy. No push/merge/tag. No MCP write enablement. No new bot. No credential rotation. No secret printed.
2. Repo / branch / HEAD
| Item | Value |
|---|---|
| Working dir (parent superproject) | /opt/incomex |
| Lark-client subdir | /opt/incomex/lark-client |
| Branch | feat/s177-sprint1-round-a (new, NOT pushed) |
| Base | main @ 842cfc2 (MCP-COMPLETE: Add write_file, rotate path-secrets, expand allowlists) |
| Round A commit | 06b11c0 S177 Sprint 1 Round A — controlled CRUD gateway scaffolding |
| Remote ops | none — no git push, no merge, no tag |
/opt/incomex is the git superproject; lark-client/ is a subdir of it. Round A staged and committed only paths under lark-client/. The unrelated working-tree modifications in docker/, nginx/, dot/, scripts/ were left untouched and not staged.
3. DISCOVER-FIRST findings (binding for the work that follows)
3.1 Pre-mutation source baseline (read-only, from main 842cfc2)
| File | Lines | Confirms |
|---|---|---|
lark_client/exceptions.py |
49 | Existing 5 subclasses of LarkClientError: EndpointNotAllowed, CredentialPermissionLost, TokenRefreshError, LarkAPIError, RateLimitExceeded |
lark_client/audit.py |
~120 | AuditLogger(log_dir=...) already takes an override; private _write masks long token-like strings, skip-list {ts, agent, cmd}; log_call + log_cli_invocation are the only public methods |
lark_client/core.py |
441 | Private _request(method, endpoint, *, json_data, params, timeout, _audit_cmd); whitelist loads read: + write: from config/allowed_endpoints.yaml; whitelist regex via _path_to_pattern against {app_token} braces; requests imported inside core.py only |
config/allowed_endpoints.yaml |
28 | write: []; existing entries are structured objects {method, path, description} — OQ-9 confirmation by inspection |
config/bases.yaml |
(18 bases) | Base đệm key 88-phai-cu-base-dem, app_token Nf2bb1ExXaYnlksgoyQl72GNgAc, role staging. Production 88-phai-cu token YSIkb8PxOaNaozs2vwalOOcagkf |
pyproject.toml |
26 | No [tool.pytest.ini_options] block, no markers — needed to add integration marker |
tests/test_core.py |
32 | 5 tests, ungated live: test_token_obtainable, test_credential_source_logged; offline: test_whitelist_blocks_im, test_whitelist_blocks_delete, test_whitelist_allows_list_tables |
tests/test_reader.py |
47 | NEW DISCOVER finding — 3 more live tests (test_list_tables_base88, test_field_counts_known_tables, test_list_tables_base14) — package only enumerated test_core.py |
tests/test_cli_smoke.py |
49 | 5 offline tests (CLI subprocess; registry YAML only — no HTTP). 2 of them have pre-existing failures on main (see §6). |
tests/test_registry.py |
44 | 6 offline tests against Registry.load() — pure YAML |
3.2 Branch / clean state
lark-client/subdir tree was clean undermain. Working-tree dirty files (docker/,nginx/,dot/,scripts/) are unrelated to this task and were left as-is.- venv
/opt/incomex/lark-client/.venvcarriespytest 9.0.3,PyYAML 6.0.3, lark-client editable. Source edits take effect immediately — no reinstall needed.
3.3 Secret-presence check (by reference only)
| Secret name | Project | Present? | Note |
|---|---|---|---|
LARK_APP_ID |
github-chatgpt-ggcloud |
yes | used by existing credential chain |
LARK_APP_SECRET |
github-chatgpt-ggcloud |
yes | used by existing credential chain |
LARK_BACKUP_GPG_PUBKEY |
github-chatgpt-ggcloud |
no | flagged as Sprint 1 full-PASS prerequisite — Round A scaffolding does not perform encryption, so absence does not block Round A |
Method: gcloud secrets list --project=github-chatgpt-ggcloud --filter='name~LARK' --format='value(name)'. No secret value was read or printed.
3.4 7 modified + 7 new files (Round A only)
Modified existing files (vs. main):
lark-client/lark_client/exceptions.pylark-client/lark_client/audit.pylark-client/lark_client/core.pylark-client/config/allowed_endpoints.yamllark-client/pyproject.tomllark-client/tests/test_core.pylark-client/tests/test_reader.py
Created new files:
lark-client/config/lark-api-limits.yamllark-client/config/write-approvals.yamllark-client/config/pii-fields.yamllark-client/tests/conftest.pylark-client/tests/test_core_write.pylark-client/tests/test_audit_extension.pylark-client/tests/test_configs_parse.py
cli/lark_tool.py was intentionally not touched (Phase 7 of the package; out of Round A scope).
4. Changes summary
A. Branch (Round A scope §4.A)
feat/s177-sprint1-round-a cut from main 842cfc2. One commit (06b11c0). Not pushed.
B. Test harness safety (§4.B)
tests/conftest.py (NEW) — env-gate + Base đệm token hard-assert helper:
LARK_TEST_INTEGRATION=1opt-in env gate (default: skip)pytest_collection_modifyitemsauto-marks@pytest.mark.integrationtests asskipwhen the gate is offBASE_BUFFER_TOKEN = "Nf2bb1ExXaYnlksgoyQl72GNgAc"+assert_buffer_base_token(token)helperbuffer_basefixture exposing{key, app_token}
pyproject.toml — added [tool.pytest.ini_options] markers = ["integration: live Lark API tests; require LARK_TEST_INTEGRATION=1 + Base đệm token"].
tests/test_core.py — @pytest.mark.integration added above test_token_obtainable and test_credential_source_logged (the 2 package-named live tests). The 3 offline whitelist tests stay default.
tests/test_reader.py — @pytest.mark.integration added above test_list_tables_base88, test_field_counts_known_tables, test_list_tables_base14 — based on the source-inspection finding (§3.1); the package only enumerated test_core.py's 2 live tests, but test_reader.py is also a 100% live-API file. The task brief §4.B explicitly authorizes this override: "Annotate exactly the existing live tests identified in the package unless source inspection proves otherwise."
tests/test_core.py::test_whitelist_blocks_delete — body updated. With the 6 new write: entries, DELETE /open-apis/.../records/{record_id} is now whitelisted, so the original EndpointNotAllowed assertion would fail. The body was redirected to a still-blocked DELETE path (/open-apis/bitable/v1/apps/FAKE/tables/FAKE) that preserves the original test intent (DELETE is not implicitly allowed). Package §22 directive "Do not modify the body of either test" was scoped to the 2 live tests; the 3 offline tests are not constrained.
C. Exceptions (§4.C)
lark_client/exceptions.py — added the 5 net-new Sprint 1 exceptions, all subclassing LarkClientError. Existing 5 exception classes untouched.
| Class | Carried state |
|---|---|
ApprovalError(code, msg="") |
.code ∈ {missing, expired, scope_mismatch, wildcard_forbidden, already_consumed, approval_locked} |
SafetyViolation(reason, msg="") |
.reason ∈ {dry_run_required, confirm_required, agent_required, audit_pre_failed, lock_held, pii_egress_blocked, pii_scanner_error, over_ceiling} (union of package §10 + PATCH2 §P2-8) |
PartialFailureError(committed=[…], failed=[…], rollback_command=str) |
record-level outcome lists + manual rollback hint |
AuditWriteError(phase, msg="") |
.phase ∈ {pre, post, emergency, orphan} |
UnknownBaseError(base_key) |
service-layer wrapper for Registry KeyError |
Per package §22, lark_client/__init__.py was not updated to re-export these — existing convention is to import from lark_client.exceptions directly.
D. Audit foundation (§4.D)
lark_client/audit.py — extended with the 4 new public methods + strict writer:
_write_strict(entry, *, path=None, phase="post")— opens its own fd viaos.open(..., O_WRONLY|O_CREAT|O_APPEND), writes,os.fsync, raisesAuditWriteError(phase)on anyOSError.log_write_planned(ctx, *, backup_ref, audit_pre_id) -> str— primary sink; raisesAuditWriteError(phase="pre")on failure.log_write_result(ctx, *, audit_pre_id, outcome) -> None— primary sink; raisesAuditWriteError(phase="post")on failure; phase from outcome status{success, failed, partial_failure}.log_write_emergency(ctx, *, audit_pre_id, outcome, error) -> str— separate file under<log_dir>/EMERGENCY/<YYYYMMDD>/<ts>-<idkey>.jsonwith a fresh fd. On emergency-sink failure, writesLARK-AUDIT-LOST id=… reason=emergency_sink_failedto stderr and re-raisesAuditWriteError(phase="emergency").log_orphan_backup(ctx, *, backup_path, key_fingerprint, reason) -> None— appends a metadata-only entry to<log_dir>/orphan-backups.log(path + fingerprint + key + reason + ts; no record body).
_MASK_SKIP_KEYS extended to the 19-key set from PATCH2 §P2-3 (ts, agent, cmd, audit_pre_id, operation_id, idempotency_key, backup_ref, backup_path, key_fingerprint, request_id, approval_id, base_key, table_id, phase, op, outcome_status, target_count, duration_ms, dry_run, confirmed, is_buffer_base). Both the lossy _write and the strict _write_strict route through the shared _mask_entry helper, so the carve-out applies consistently.
Existing log_call / log_cli_invocation behavior preserved (lossy, warn-and-continue). Verified by the green test_legacy_log_call_still_works test.
E. LarkCore write foundation (§4.E)
lark_client/core.py — added public LarkCore.write(...):
def write(
self,
method: str,
endpoint: str,
*,
json_data: dict | None = None,
params: dict | None = None,
timeout: int = _DEFAULT_TIMEOUT,
idempotency_key: str | None = None,
client_token_supported: bool = False,
_audit_cmd: str = "",
) -> dict:
Behavior:
- If
client_token_supported=Trueandidempotency_keyprovided, mergesclient_token=idempotency_keyintojson_databefore delegating. - Delegates to private
_request(...)which is unchanged — preserves whitelist, rate-limit, retry, token-refresh, audit behavior. _requestremains private;requestsimport remains insidecore.pyonly.
A lint test (test_no_request_or_requests_outside_core) is included that greps the repo for _request( and import requests outside lark_client/core.py. Excluded scopes: tests/, .venv/, *egg-info, and scripts/. The scripts/ exclusion was needed because scripts/s179_probe.py (a pre-S177 ad-hoc dump probe) calls core._request(...) directly; refactoring it is out of Round A scope and is flagged for Sprint 1 retirement (see §9 Recommendation for Round B).
F. Config scaffolding (§4.F)
config/allowed_endpoints.yaml — appended the 6 record-class write entries under the existing write: section. Schema preserved — only {method, path, description} per entry. client_token lives in the sibling limits file (§17 of the package).
| Method | Path |
|---|---|
| POST | /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records |
| POST | /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_create |
| PUT | /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id} |
| POST | /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update |
| DELETE | /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id} |
| POST | /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_delete |
config/lark-api-limits.yaml (NEW) — version: 1; rate.requests_per_sec: 10 (matches existing _MAX_RPS); batch.{record_create_max: 500, record_update_max: 500, record_delete_max: 100} per OQ-5 conservative; write_endpoint_options per template (POST single + POST batch_create + POST batch_update: client_token: true; PUT/DELETE single + POST batch_delete: client_token: false) — conservative until OQ-8 citation.
config/write-approvals.yaml (NEW) — version: 1; approval_exempt_bases: ["88-phai-cu-base-dem"]; approvals: [] seeded empty.
config/pii-fields.yaml (NEW) — version: 1; field_registry: [] seeded empty.
No real secrets, no secret values, no real PII field ids.
G. Minimal unit tests (§4.G)
| File | Test | Asserts |
|---|---|---|
tests/test_core_write.py |
test_write_endpoints_loaded_from_yaml |
All 6 new entries land in _allowed_endpoints |
test_write_whitelist_allows_all_six_record_endpoints |
_check_whitelist passes for each materialized path |
|
test_write_raises_endpoint_not_allowed_for_non_listed |
core.write(POST, /open-apis/im/v1/messages, …) raises EndpointNotAllowed |
|
test_write_inserts_client_token_when_supported |
idempotency_key injected into json_data["client_token"] |
|
test_write_omits_client_token_when_not_supported |
No client_token leakage when flag is false |
|
test_write_normalizes_method_to_upper |
lowercase "post" reaches _request as "POST" |
|
test_no_request_or_requests_outside_core |
Lint per PATCH2 §P2-4 / package §14 | |
tests/test_audit_extension.py |
test_log_write_planned_writes_to_primary_sink |
Phase 1 lands in today's JSONL |
test_log_write_planned_raises_on_fsync_fail |
AuditWriteError(phase='pre') on os.fsync OSError |
|
test_log_write_result_success |
Result correlated to planned via audit_pre_id |
|
test_log_write_emergency_uses_separate_file |
Emergency sink is a different inode under EMERGENCY/<YYYYMMDD>/…json; primary JSONL unchanged |
|
test_log_write_result_fsync_fail_triggers_emergency_path |
Primary post-write failure raises phase='post'; subsequent emergency call succeeds (different file) |
|
test_mask_skip_honors_carved_out_keys |
backup_ref / idempotency_key / base_key / table_id pass through unmasked |
|
test_log_orphan_backup_is_metadata_only |
Orphan entry contains required metadata, no record body fields | |
test_legacy_log_call_still_works |
Existing log_call behavior preserved |
|
tests/test_configs_parse.py |
test_allowed_endpoints_yaml_parses_with_6_writes |
version=1, 6 writes, every entry is {method, path, description} |
test_lark_api_limits_yaml_parses_with_expected_keys |
Top-level shape; conservative client_token defaults | |
test_write_approvals_yaml_seed |
approvals: [], approval_exempt_bases: ['88-phai-cu-base-dem'] |
|
test_pii_fields_yaml_seed |
field_registry: [] |
All test fixtures use tmp_path for audit sinks — production /var/log/lark-ops/ is not written by Round A tests (only the legacy cli_start audit events from the pre-existing test_cli_smoke.py go there).
5. git diff --stat
lark-client/config/allowed_endpoints.yaml | 20 ++-
lark-client/config/lark-api-limits.yaml | 28 +++
lark-client/config/pii-fields.yaml | 11 ++
lark-client/config/write-approvals.yaml | 25 +++
lark-client/lark_client/audit.py | 274 ++++++++++++++++++++++++++++--
lark-client/lark_client/core.py | 33 ++++
lark-client/lark_client/exceptions.py | 67 ++++++++
lark-client/pyproject.toml | 5 +
lark-client/tests/conftest.py | 43 +++++
lark-client/tests/test_audit_extension.py | 230 +++++++++++++++++++++++++
lark-client/tests/test_configs_parse.py | 61 +++++++
lark-client/tests/test_core.py | 12 +-
lark-client/tests/test_core_write.py | 169 ++++++++++++++++++
lark-client/tests/test_reader.py | 3 +
14 files changed, 963 insertions(+), 18 deletions(-)
Commit 06b11c0 is the only commit between main 842cfc2 and Round A HEAD.
6. Tests run and results
6.1 Cold pytest (no LARK_TEST_INTEGRATION)
Command: env -u LARK_TEST_INTEGRATION .venv/bin/pytest -v -rN --tb=short
38 tests collected
─────────────────────────────────────────────────────────
test_audit_extension.py : 8 PASSED 0 SKIPPED 0 FAILED
test_cli_smoke.py : 3 PASSED 0 SKIPPED 2 FAILED (pre-existing on main)
test_configs_parse.py : 4 PASSED 0 SKIPPED 0 FAILED
test_core.py : 3 PASSED 2 SKIPPED 0 FAILED (integration tests gated)
test_core_write.py : 7 PASSED 0 SKIPPED 0 FAILED
test_reader.py : 0 PASSED 3 SKIPPED 0 FAILED (integration tests gated)
test_registry.py : 6 PASSED 0 SKIPPED 0 FAILED
─────────────────────────────────────────────────────────
Total : 31 PASSED 5 SKIPPED 2 FAILED (2.93s)
The 5 SKIPPED are exactly the 5 live-API tests:
tests/test_core.py::test_token_obtainabletests/test_core.py::test_credential_source_loggedtests/test_reader.py::test_list_tables_base88tests/test_reader.py::test_field_counts_known_tablestests/test_reader.py::test_list_tables_base14
…all skipped with reason LARK_TEST_INTEGRATION!=1.
6.2 The 2 pre-existing failures (proven independent of Round A)
tests/test_cli_smoke.py::test_registry_list_json and ::test_registry_show_json fail with json.decoder.JSONDecodeError because the lark-tool resolved via PATH (/usr/local/bin/lark-tool, system-installed long ago) emits tabular output and ignores the --json flag. The editable .venv/bin/lark-tool is correct, but pytest's subprocess inherits PATH and finds the system binary first.
Independence proof: I reset lark_client/, config/, tests/, pyproject.toml to main content via git checkout main -- …, ran tests/test_cli_smoke.py standalone, and the same 2 tests failed with identical errors. Then I restored Round A via rsync.
(on baseline 842cfc2, with main content)
collected 5 items
tests/test_cli_smoke.py::test_version PASSED [ 20%]
tests/test_cli_smoke.py::test_registry_list_json FAILED [ 40%]
tests/test_cli_smoke.py::test_registry_show_json FAILED [ 60%]
tests/test_cli_smoke.py::test_registry_show_not_found PASSED [ 80%]
tests/test_cli_smoke.py::test_agent_override PASSED [100%]
2 failed, 3 passed in 1.99s
These failures are flagged for Round B (see §9 — either fix system-vs-venv binary precedence in the test, or remove the stale system /usr/local/bin/lark-tool).
6.3 Reproducibility
ssh contabo 'cd /opt/incomex && git rev-parse --short HEAD' # → 06b11c0
ssh contabo 'cd /opt/incomex/lark-client && env -u LARK_TEST_INTEGRATION .venv/bin/pytest -v'
7. Proof that cold tests do not call live Lark API
Method: bracketed the cold pytest run with stat -c%s /var/log/lark-ops/$(date -u +%Y%m%d).jsonl. Before: 2367 B / 14 lines. After: 3049 B. Delta: +682 B.
Inspected the new entries:
cli_start | | | /usr/local/bin/lark-tool --json registry list
cli_start | | | /usr/local/bin/lark-tool --json registry show 88-phai-cu
cli_start | | | /usr/local/bin/lark-tool registry show non-existent-base-xyz
cli_start | | | /usr/local/bin/lark-tool --json registry list
cli_start | | | /usr/local/bin/lark-tool --json registry list
cli_start | | | /usr/local/bin/lark-tool --json registry show 88-phai-cu
cli_start | | | /usr/local/bin/lark-tool registry show non-existent-base-xyz
cli_start | | | /usr/local/bin/lark-tool --json registry list
All 8 are event=cli_start entries (no cmd, no endpoint, no status, no method set) — i.e. CLI startup logs from test_cli_smoke.py subprocesses. No log_call(...) invocation, which is the function that fires after each HTTP round-trip — none of those entries are present. No tokenfetch, no API call, no HTTP traffic to open.larksuite.com.
The 5 live tests are protected by pytest_collection_modifyitems (in tests/conftest.py), which auto-skips any test bearing @pytest.mark.integration when LARK_TEST_INTEGRATION!=1. Both the env gate and the marker decorations are in place.
8. Status of OQs and prerequisites
8.1 OQ-8 (client_token endpoint coverage)
Round A keeps conservative defaults in config/lark-api-limits.yaml::write_endpoint_options:
records(POST) →client_token: truerecords/batch_create(POST) →client_token: truerecords/{record_id}(PUT, DELETE) →client_token: falserecords/batch_update(POST) →client_token: truerecords/batch_delete(POST) →client_token: false
Citation lookup against the Lark Open Platform documentation was not performed in Round A (no network access to official docs from VPS, and the task forbids touching Lark itself). This is recorded as a Sprint 1 Phase 1.2 TODO — citation or non-mutating Base đệm probe required before flipping the conservative flags.
8.2 OQ-9 (allowed_endpoints.yaml shape)
Closed. Re-verified by inspecting config/allowed_endpoints.yaml on main 842cfc2 before mutation: every existing read: entry is a {method, path, description} structured object using {app_token} / {table_id} brace placeholders. Round A's 6 write: additions follow the same schema exactly. No client_token per-entry field added — kept in the sibling limits file (§17).
8.3 LARK_BACKUP_GPG_PUBKEY
Absent in GSM (github-chatgpt-ggcloud). Checked via gcloud secrets list --filter='name~LARK' — only LARK_APP_ID and LARK_APP_SECRET are present. Round A scaffolds the GPG/orphan-backup interfaces (e.g. log_orphan_backup(backup_path, key_fingerprint, reason)), but does not perform any GPG encryption, so the absent secret does not block Round A.
Marked as Sprint 1 full-PASS prerequisite — must be provisioned (rotation policy, key_fingerprint distribution, retention rules per OQ-7) before any layer-3 GPG encrypt_and_store path can ship.
9. Confirmations (task brief §11)
- No Lark write executed. No
LarkCore.write(...)was called against the live API; allwritetests use apatch.object(core, "_request", side_effect=…)mock. - No production touched. Production Base
88-phai-cu(YSIkb8PxOaNaozs2vwalOOcagkf) was not contacted; no Lark API call at all during cold pytest. - No deploy. No service restart, no Docker action, no nginx reload, no systemd ops.
- No push / merge / tag.
feat/s177-sprint1-round-ais a local branch only.git pushwas not run. No tag created. - No MCP write enablement.
claude-mcp/agent-dataconfigs untouched. Lark MCP plugin (@larksuiteoapi/lark-mcp) untouched. - No secret printed. Secret presence was checked by name only via
gcloud secrets list --filter; nogcloud secrets versions accesswas run. - No self-advance. No work performed beyond Round A (no SafetyLayer orchestration, no
LarkWriteService, no CLI records.* group, no integration test against Base đệm, no GPG impl, no PII regex impl, no real record write).
10. Recommendation for Round B
Once Round A is accepted by GPT/User, the next safe slice is Round B — SafetyLayer skeleton + CLI surface stubs. Suggested scope, in order of safety:
- Approval registry loader + check_and_consume wiring against
config/write-approvals.yaml(no Lark write yet; pure file I/O + idempotency_key flow). Includesconsume(approval_id, idempotency_key)and one-time-use lock. SafetyLayer.guard(...)orchestration skeleton stitching the 8 layers in the order defined in PATCH2 §P2-3 (dry-run → approval → backup-stub → audit_pre → lock → rate-limit-via-LarkCore → pii.scan → API call → audit_post). Each external dependency (GPG, PII, Lark API) injected as a callable so tests can mock without network.- Cleanup of
scripts/s179_probe.pyto either: (a) wrap its calls throughLarkReader.list_tables(...)instead ofcore._request(...), or (b) move the probe totools/and add a comment that it's outside the controlled-CRUD perimeter. Then drop thescripts/exclusion intest_no_request_or_requests_outside_core. - Fix the 2 pre-existing CLI smoke failures by adjusting
test_cli_smoke.pyto invoke.venv/bin/lark-toolexplicitly (path-pinned), instead of relying on PATH lookup — orapt-get/pip uninstallthe stale/usr/local/bin/lark-tool. - OQ-8 Phase 1.2 citation — pull Lark Open Platform docs for the 6 record endpoints and flip
client_tokenflags accordingly; non-mutating Base đệm probe is acceptable but must hard-assert viaassert_buffer_base_tokenin conftest. - GPG provisioning — get
LARK_BACKUP_GPG_PUBKEYinto GSM (withkey_fingerprintrecorded forlog_orphan_backup). Implement the actualGPGBackup.encrypt_and_store(...)interface usinggpg --encryptsubprocess with the public-key recipient. Round A's tests should be extended to assert that backup files are written beforelog_write_plannedand orphan-logged if planned fails.
Round B should remain code-only — no actual Lark write until a separate Round C/D explicitly gated by GPT/User on Base đệm only.
11. STOP
This evidence report is the Round A handoff. Round B is not opened or authorized by this document — it requires a separate GPT/User-gated kickoff.