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_PUBKEYexists 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.yamlschema 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.mdwith: per-endpoint URL, retrieval date, the field that indicatesclient_tokensupport. - Upload citation to
knowledge/dev/lark/s177-controlled-crud-gateway/. - If a citation is silent → keep
client_token_supported: falseinlark-api-limits.yamland 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.pyexactly per package §15. - In
pyproject.tomladd[tool.pytest.ini_options] markers = ["integration: …"]. - In
tests/test_core.pyadd@pytest.mark.integrationABOVE thedefof:test_token_obtainabletest_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, replacewrite: []with the 6 entries from package §16. Keep all existingread:entries untouched. - Create
config/lark-api-limits.yamlexactly per package §17.write_endpoint_optionsvalues come from the OQ-8 citation file. - Create
config/write-approvals.yamlper package §13 (start with emptyapprovals: []andapproval_exempt_bases: ["88-phai-cu-base-dem"]). - Create
config/pii-fields.yamlper 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 -qstill 3 passed + 2 skipped. - In a Python REPL:
LarkCore()._allowed_endpointsincludes the 6 new entries.
4. Phase 4 — Exceptions + LarkCore.write
- In
lark_client/exceptions.pyadd 5 classes per package §10. All subclassLarkClientError. Constructors set.code/.reason/.phase/ etc. - In
lark_client/core.pyadd publicwrite(...)method per package §14 — additive only._requestunchanged. - Create
tests/test_core_write.pycovering:- whitelist accepts the 6 new entries;
EndpointNotAllowedraised for non-whitelisted writes;client_tokeninserted intojson_datawhenclient_token_supported=True;client_tokenNOT inserted whenFalse;_requestis called with the right kwargs (mock).
- Add lint test
test_no_request_or_requests_outside_coreper 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 clishows matches ONLY inlark_client/core.py. -
grep -rn "import requests" lark_client clishows matches ONLY inlark_client/core.py.
5. Phase 5 — AuditLogger extension
- In
lark_client/audit.py:- Add
_write_strict(entry, *, path=None)— opens its own fd, fsyncs, raisesAuditWriteError(phase)on any I/O error. - Extend the mask-skip set in
_writeAND_write_strictto 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_emergencywrites to/var/log/lark-ops/EMERGENCY/<YYYYMMDD>/<ts>-<id>.json(separate file per call, separate fd).log_orphan_backupwrites to/var/log/lark-ops/orphan-backups.log(append-only).
- Add
- Existing
log_call,log_cli_invocation,_writeleft 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_refpasses through unmasked); - emergency sink uses a separate file and separate fd;
- orphan-log line is metadata-only and contains no record body.
Verify:
-
pytest -qall green. - In a REPL, write a planned entry with a long UUID
idempotency_keyand observe the value survives unmasked.
6. Phase 6 — Approval + GPG + PII
- Create
lark_client/approval.pyper package §13 —ApprovalProviderABC +YamlApprovalProvider. - Create
lark_client/gpg_backup.pyper package §12 —GPGBackupclass, public-key input, ASCII-armored output, sidecar meta, fsync both. - Decide GPG implementation: prefer
python-gnupgif importable; else shell out togpg --encrypt. Record decision inline in the file's module docstring. - Create
lark_client/pii.pyper 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, 3already_consumed; YAMLusedflag flips exactly once. - scope-mismatch and wildcard-forbidden tests.
- approval-lock contention test (mock
fcntl.flockto raiseBlockingIOError).
- T5: consumed one-time approval →
- Create
tests/test_pii.py:- all 6 regex patterns from PATCH1 §D.2 match expected examples.
FieldPIIRegistryreturns expected hits from the seed file.pii_scanreturns(types: set, hit_field_ids: set, detector_used: list)— never substrings.
Verify:
-
pytest -qall 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.pyper package §11. Implement the 10-step ordering exactly. Layer 6 (rate limit) is deferred toLarkCore.write— do NOT acquire_RATE_LIMIT_LOCKinsafety.py. - Create
lark_client/service.pyper package §3.B / §7. Each record op builds a closure that callsLarkCore.writewith the right endpoint template filled in,client_token_supportedfromlark-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 importsYamlApprovalProvider. SafetyLayer imports only the ABC. - Create
cli/records.py:register(subparsers)adds therecordsgroup with create/get/update/delete/batch-create/batch-update/batch-delete.- Per-command argparse follows existing
_add_commonconvention (--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, callrecords_cli.register(subparsers). - Add
elif args.command == "records":branch that callsrecords_cli.handle(args). - Other branches and the outer try/except untouched.
- Add
- 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 -qall green (full unit suite passes cold, integration skipped). -
lark-tool records --helpshows 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 withstatus: "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.pywithpytestmark = 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 attests/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.mdper package §23.
Verify:
- All T1–T12 + b-variants green on Base đệm.
- Production token (
YSIkb8PxOaNaozs2vwalOOcagkf) is never referenced in the test run. - Cold
pyteststill 3 + N passed + 2 skipped.
9. Pre-PR cleanups
-
git statusshows exactly the planned file list (no stray artifacts, no.pyc, no test outputs committed). -
git diff --statmatches package §3 inventory. - No
import requestsoutsidelark_client/core.py. - No
_request(outsidelark_client/core.py. - No
os.remove/unlink/shutil.rmtreeagainst any path underwrites/. - No new entry in
pyproject.toml [project.scripts].cli.lark_tool:mainis the only entrypoint. -
lark_client/__init__.pyexports 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.