KB-5CEA

S177 — Sprint 1 Implementation Checklist (2026-05-19)

14 min read Revision 1
s177larksprint1checklistimplementer

S177 — Sprint 1 Implementation Checklist

Date: 2026-05-19 Pairs with: s177-sprint1-command-review-package-2026-05-19.md (the package)

A linear, tick-box implementer's view of Sprint 1. Each step has a verification action and an expected outcome. Do not skip the verification.


0. Prerequisites (Sprint 1 cannot start until all green)

  • PATCH2 accepted by GPT/User.
  • Sprint 1 command-review package accepted by GPT/User.
  • Actor has shell + repo write to /opt/incomex/lark-client/.
  • GSM secret LARK_BACKUP_GPG_PUBKEY exists and is ASCII-armored.
  • Actor read PATCH2 §P2-2 (exit-code routing) and §P2-3 (audit ordering).
  • Actor confirms: no production Lark write will be issued.

1. Phase 1 — Pre-code micro-tasks

  • Confirm config/allowed_endpoints.yaml schema is {method, path, description} with {app_token} braces. (Already confirmed in package §16.)
  • Read official Lark Open API docs for each of the 6 record-class endpoints.
  • Author KB note s177-sprint1-oq8-client-token-citation-2026-05-XX.md with: per-endpoint URL, retrieval date, the field that indicates client_token support.
  • Upload citation to knowledge/dev/lark/s177-controlled-crud-gateway/.
  • If a citation is silent → keep client_token_supported: false in lark-api-limits.yaml and note conservatism in the citation file.

Verify: citation file uploaded; no code committed; no Lark API write fired.

2. Phase 2 — Test-harness quarantine

  • Create tests/conftest.py exactly per package §15.
  • In pyproject.toml add [tool.pytest.ini_options] markers = ["integration: …"].
  • In tests/test_core.py add @pytest.mark.integration ABOVE the def of:
    • test_token_obtainable
    • test_credential_source_logged
  • Do NOT modify the body of either test.
  • Do NOT add the decorator to test_whitelist_blocks_im, test_whitelist_blocks_delete, test_whitelist_allows_list_tables.

Verify:

  • pytest -q (cold) → 3 passed, 2 skipped, 0 failed.
  • LARK_TEST_INTEGRATION=1 pytest -q -m integration → 2 passed.

3. Phase 3 — Config seeds + endpoint whitelist append

  • In config/allowed_endpoints.yaml, replace write: [] with the 6 entries from package §16. Keep all existing read: entries untouched.
  • Create config/lark-api-limits.yaml exactly per package §17. write_endpoint_options values come from the OQ-8 citation file.
  • Create config/write-approvals.yaml per package §13 (start with empty approvals: [] and approval_exempt_bases: ["88-phai-cu-base-dem"]).
  • Create config/pii-fields.yaml per package §18 (seed schema; real field IDs come later).
  • All four YAML files: validate with python -c "import yaml; yaml.safe_load(open('path'))".

Verify:

  • pytest -q still 3 passed + 2 skipped.
  • In a Python REPL: LarkCore()._allowed_endpoints includes the 6 new entries.

4. Phase 4 — Exceptions + LarkCore.write

  • In lark_client/exceptions.py add 5 classes per package §10. All subclass LarkClientError. Constructors set .code / .reason / .phase / etc.
  • In lark_client/core.py add public write(...) method per package §14 — additive only. _request unchanged.
  • Create tests/test_core_write.py covering:
    • whitelist accepts the 6 new entries;
    • EndpointNotAllowed raised for non-whitelisted writes;
    • client_token inserted into json_data when client_token_supported=True;
    • client_token NOT inserted when False;
    • _request is called with the right kwargs (mock).
  • Add lint test test_no_request_or_requests_outside_core per package §14.

Verify:

  • pytest -q → existing 5 + new tests all green (existing 5 = 3 passed + 2 skipped); lint test green.
  • grep -rn "_request(" lark_client cli shows matches ONLY in lark_client/core.py.
  • grep -rn "import requests" lark_client cli shows matches ONLY in lark_client/core.py.

5. Phase 5 — AuditLogger extension

  • In lark_client/audit.py:
    • Add _write_strict(entry, *, path=None) — opens its own fd, fsyncs, raises AuditWriteError(phase) on any I/O error.
    • Extend the mask-skip set in _write AND _write_strict to the keys listed in PATCH2 §P2-3 (package §11).
    • Add 4 public methods: log_write_planned, log_write_result, log_write_emergency, log_orphan_backup. Each builds the entry shape per package §11.
    • log_write_emergency writes to /var/log/lark-ops/EMERGENCY/<YYYYMMDD>/<ts>-<id>.json (separate file per call, separate fd).
    • log_orphan_backup writes to /var/log/lark-ops/orphan-backups.log (append-only).
  • Existing log_call, log_cli_invocation, _write left intact.
  • Create tests/test_audit_extension.py:
    • planned-fsync-fail raises, no API call;
    • result-fsync-fail-after-success triggers emergency path;
    • mask-skip honors the carved-out keys (a path string in backup_ref passes through unmasked);
    • emergency sink uses a separate file and separate fd;
    • orphan-log line is metadata-only and contains no record body.

Verify:

  • pytest -q all green.
  • In a REPL, write a planned entry with a long UUID idempotency_key and observe the value survives unmasked.

6. Phase 6 — Approval + GPG + PII

  • Create lark_client/approval.py per package §13 — ApprovalProvider ABC + YamlApprovalProvider.
  • Create lark_client/gpg_backup.py per package §12 — GPGBackup class, public-key input, ASCII-armored output, sidecar meta, fsync both.
  • Decide GPG implementation: prefer python-gnupg if importable; else shell out to gpg --encrypt. Record decision inline in the file's module docstring.
  • Create lark_client/pii.py per package §18 — FieldPIIRegistry + PatternPIIDetector + pii_scan(record, base_key, table_id) pure function.
  • Add test fixture tests/fixtures/test-gpg-pub.asc — a freshly generated, throwaway test-only public key (NOT the production one).
  • Create tests/test_approval_yaml.py:
    • T5: consumed one-time approval → ApprovalError("already_consumed") on second call.
    • T5b: 4 concurrent processes via multiprocessing.Process → exactly one success, 3 already_consumed; YAML used flag flips exactly once.
    • scope-mismatch and wildcard-forbidden tests.
    • approval-lock contention test (mock fcntl.flock to raise BlockingIOError).
  • Create tests/test_pii.py:
    • all 6 regex patterns from PATCH1 §D.2 match expected examples.
    • FieldPIIRegistry returns expected hits from the seed file.
    • pii_scan returns (types: set, hit_field_ids: set, detector_used: list) — never substrings.

Verify:

  • pytest -q all green including T5, T5b.
  • Manually run a one-record encrypt-and-store via the GPGBackup class in a REPL using the test-only public key; check the file header is -----BEGIN PGP MESSAGE-----; sidecar meta has no PII.

7. Phase 7 — SafetyLayer + Service + Writer + Factory + CLI

  • Create lark_client/safety.py per package §11. Implement the 10-step ordering exactly. Layer 6 (rate limit) is deferred to LarkCore.write — do NOT acquire _RATE_LIMIT_LOCK in safety.py.
  • Create lark_client/service.py per package §3.B / §7. Each record op builds a closure that calls LarkCore.write with the right endpoint template filled in, client_token_supported from lark-api-limits.yaml, and the per-op _audit_cmd.
  • Create lark_client/writer.py — thin typed façade (PATCH1 §G.1). No write logic; just delegates to service.
  • Create lark_client/factory.py — composition root. Only place that imports YamlApprovalProvider. SafetyLayer imports only the ABC.
  • Create cli/records.py:
    • register(subparsers) adds the records group with create/get/update/delete/batch-create/batch-update/batch-delete.
    • Per-command argparse follows existing _add_common convention (--json, --agent).
    • Each cmd_records_* returns its exit code.
    • handle(args) dispatch entry — called from main().
  • In cli/lark_tool.py:
    • Add import cli.records as records_cli.
    • In main(), after the subparser definitions, call records_cli.register(subparsers).
    • Add elif args.command == "records": branch that calls records_cli.handle(args).
    • Other branches and the outer try/except untouched.
  • Create unit tests:
    • tests/test_service.py (T1, T2-mock, T8, T9)
    • tests/test_safety.py (T1, T3, T11, T12, layer-order, lock acquire/release)
    • tests/test_batch.py (T6, T6b, T7)
    • tests/test_records_cli.py (T8, T10, T10b, argparse parser shape, JSON to stdout, error JSON to stderr, exit-code routing)

Verify:

  • pytest -q all green (full unit suite passes cold, integration skipped).
  • lark-tool records --help shows the new subparser structure.
  • lark-tool records create 88-phai-cu-base-dem tblPQ6N79EeOmnTm --data '{"fields":{}}' --approval APR-X (without --no-dry-run) returns a JSON outcome with status: "dry_run".
  • lark-tool registry list (existing command) still works (no regression).
  • lark-tool schema list 88-phai-cu-base-dem (existing command) still works.

8. Phase 8 — Gated integration on Base đệm

  • Create tests/test_records_integration.py with pytestmark = pytest.mark.integration. Test set T1–T12 + b-variants per package §7.
  • Each test calls assert_buffer_base_token(token) from conftest before any guarded write.
  • Approvals for the test run are loaded from a test-scoped write-approvals.yaml (e.g. via fixture pointing factory at tests/fixtures/write-approvals-test.yaml). Never mutate the production approval file.
  • Run LARK_TEST_INTEGRATION=1 pytest tests/test_records_integration.py -q.
  • Capture the integration evidence file at s177-sprint1-integration-evidence-2026-05-XX.md per package §23.

Verify:

  • All T1–T12 + b-variants green on Base đệm.
  • Production token (YSIkb8PxOaNaozs2vwalOOcagkf) is never referenced in the test run.
  • Cold pytest still 3 + N passed + 2 skipped.

9. Pre-PR cleanups

  • git status shows exactly the planned file list (no stray artifacts, no .pyc, no test outputs committed).
  • git diff --stat matches package §3 inventory.
  • No import requests outside lark_client/core.py.
  • No _request( outside lark_client/core.py.
  • No os.remove / unlink / shutil.rmtree against any path under writes/.
  • No new entry in pyproject.toml [project.scripts]. cli.lark_tool:main is the only entrypoint.
  • lark_client/__init__.py exports unchanged (no new exports added).
  • All new modules have one-line module docstrings; no multi-paragraph docstrings (per house style).
  • No emojis anywhere in code or comments.

10. Post-implementation

  • Upload Sprint 1 implementation evidence file per package §23.
  • Push the Sprint 1 commit to a feature branch (not main).
  • Do NOT open a PR to main yet — wait for GPT/User Sprint-1 sign-off.
  • Do NOT advance to Sprint 2 (MCP adapter) — requires separate authorization.

Quick verification matrix (cheat sheet)

Step Cold pytest Integration pytest Lint New files Changed files
After P2 3p + 2s 2p conftest.py test_core.py + pyproject.toml
After P3 3p + 2s 2p + 4 configs + allowed_endpoints.yaml
After P4 3p + 2s + N 2p green + exceptions, core_write tests + exceptions.py + core.py
After P5 3p + 2s + N 2p green + audit_extension tests + audit.py
After P6 3p + 2s + N 2p green + approval/gpg/pii + tests + fixture key
After P7 3p + 2s + N 2p green + service/safety/writer/factory/records.py + 4 test files + lark_tool.py
After P8 3p + 2s + N T1–T12 all green green + integration test file

p = passed, s = skipped, N = total new tests (~50–80 expected).


Common pitfalls (lookup table)

If you see... The cause is probably... Fix
EndpointNotAllowed for a record write call OQ-9 schema mismatch (path template) OR you forgot to call LarkCore()._load_whitelist() after editing YAML Re-instantiate LarkCore; check the path template uses {app_token} braces, not colons
idempotency_key shows up as *** in audit mask-skip list wasn't extended in _write Extend _MASK_SKIP_KEYS per package §11
T5b fails (two consumers both win) flock not held over the full check→write sequence Acquire LOCK_EX BEFORE yaml.safe_load; release AFTER os.rename
client_token rejected by Lark OQ-8 citation said supported but it isn't Flip client_token: false in lark-api-limits.yaml; update citation file
test_token_obtainable fires on cold pytest conftest not in tests/, or marker not declared Move conftest to tests/conftest.py; add markers to pyproject.toml
Three audit entries per write surprise reader _request's internal log_call is expected Document in integration evidence; no fix needed
Backup file present but no audit-pre entry layer-4 fsync failed → orphan path Check /var/log/lark-ops/orphan-backups.log for matching line

Sprint 1 implementation checklist complete. Do not advance to Sprint 2 without separate authorization. No production Lark writes at any point.

Back to Knowledge Hub knowledge/dev/lark/s177-controlled-crud-gateway/s177-sprint1-implementation-checklist-2026-05-19.md