S177 — Sprint 1 Round B Addendum — Cowork Consumption + Base Safety (2026-05-20)
S177 — Sprint 1 Round B Addendum — Cowork Consumption + Base Safety
Status: Binding for Round B (when Round B is authorized). Date: 2026-05-20 Source: User-issued addendum, verbatim, on the Round A handoff thread. Pairs with:
s177-sprint1-command-review-package-2026-05-19.md(base spec)s177-sprint1-implementation-checklist-2026-05-19.mds177-architecture-design-2026-05-19-patch2.mds177-sprint1-round-a-implementation-evidence-2026-05-19.md(Round A handoff)
This addendum supplements the Sprint 1 command-review package and PATCH2 design; it does not supersede them. Where this addendum sets a stricter or more specific rule than the base package, the addendum wins.
Round B is not yet authorized by virtue of this addendum existing. Execution requires a separate explicit GPT/User authorization on top of the existing gates.
1. Structured CLI output for Cowork/MCP consumption (binding)
All lark-tool records … commands implemented or stubbed in Round B must produce structured JSON when --json is used. This applies to:
- success output
- dry-run output
- validation output
- error output
Dry-run output must be machine-readable, not human-text-only.
Required dry-run shape (extensible — implementations may add fields, but must include these):
{
"ok": true,
"mode": "dry_run",
"operation": "record.create",
"base_key": "88-phai-cu-base-dem",
"table_id": "tblXXX",
"record_id": null,
"would_write": {},
"diff": [],
"approval_status": "valid|exempt|missing|not_required",
"safety_checks_passed": ["preflight", "policy", "approval"],
"warnings": [],
"operation_id": "uuid-or-deterministic-id",
"audit_ref": null
}
For non-dry-run stubs in Round B (where real write execution is not authorized yet), the JSON output must clearly state that real write execution is not authorized (e.g. "ok": false, "error": true, "error_type": "NotAuthorized", "message": "real write execution not authorized in Round B; rerun with --dry-run").
2. Structured error output (binding)
When --json is used, errors must be JSON to stderr or stdout according to the existing CLI convention, and must be machine-readable.
Required error shape:
{
"ok": false,
"error": true,
"exit_code": 1,
"error_type": "SafetyViolation",
"message": "approval missing",
"reason": "approval_missing",
"retry_after_seconds": null,
"operation_id": "uuid-if-created",
"audit_ref": "path-or-null",
"details": {}
}
For Lark/API errors, include retry information when available:
{
"ok": false,
"error": true,
"exit_code": 2,
"error_type": "LarkAPIError",
"message": "429 Too Many Requests",
"retry_after_seconds": 5,
"operation_id": "uuid",
"audit_ref": "path-or-null"
}
Do not output raw PII in error JSON. PII fields surfaced in details must be redacted (metadata only — match audit pii convention from PATCH2 §P2-3).
3. Add records get (binding)
Round B must add the argparse surface and a service/read path for:
lark-tool records get <base-key> <table-id> <record-id>
Purpose:
- Read one record by ID.
- Support Cowork workflows.
- Support future diff preview for
update/delete.
Risk tier — safe read:
- No approval required.
- No backup.
- No write.
- No Lark mutation.
Mandatory invariants — even on a read path:
- Validate
base_key/table_idviaRegistry(no bare-token Base access). - Use the existing
LarkReaderor a sanctioned read path. - Produce structured JSON output per §1.
- Audit lightly if the existing read audit convention supports it (
log_callis already present). - Never bypass registry/config.
If the current LarkReader lacks get-by-ID, Round B may implement a no-write read method only if it uses the existing LarkCore read whitelist correctly. If this requires endpoint-whitelist changes:
- DISCOVER-FIRST against the current
config/allowed_endpoints.yamlschema. - Document the addition in the Round B evidence report (use the same
{method, path, description}schema confirmed by OQ-9). - Append to
read:, notwrite:.
4. Approval-exempt-base test (binding)
Round B SafetyLayer tests must include a test that exercises the Base-đệm-exempt path:
- An operation targeting a base listed in
config/write-approvals.yaml :: approval_exempt_basesbypasses the Approval Check layer. - The same operation still runs every other applicable layer:
- preflight
- policy
- dry-run / confirm
- audit
planned/resultwhere applicable - lock if applicable
- PII scan if applicable
- no direct bypass to the operation call
Base đệm exemption means "approval not required", not "safety disabled". The test must assert the per-layer execution evidence (e.g. presence of planned-audit entry, presence of PII scan call, presence of lock acquisition).
5. WriteContext.source field (binding)
Add a source field to the operation context / WriteContext.
Required values (enum):
"cli""mcp""cron""api"
Round B default: source = "cli".
Future: the MCP adapter (Sprint 2) sets source = "mcp".
Audit entries must include both:
agent(from$LARK_AGENT/ detection chain)source(from call path)
Rationale: the operator must be able to distinguish whether an operation came from CLI, MCP/Cowork, cron, or API even when the same agent name is used.
Audit-schema implications (binding for Round B):
_MASK_SKIP_KEYSinlark_client/audit.pymust be extended to include"source"so the value passes through unmasked.- The planned / result / emergency / orphan entry builders (already added in Round A) must include
sourcein their output. - The 19-key mask-skip extension in Round A becomes a 20-key set in Round B.
6. Protect config/bases.yaml (binding)
Round B must not modify config/bases.yaml. This file contains production registry / app_token data and is immutable for Sprint 1.
Allowed:
- read it
- validate against it
- use it in tests through fixtures/mocks
- hard-assert Base đệm token via
assert_buffer_base_token
Forbidden:
- edit it
- rewrite it
- normalize it (no formatting, no key reordering, no comment changes)
- add / remove bases
- change
role/staging/coremetadata - change
app_tokenvalues
If a Round B test needs altered registry data, the test must create a temp fixture under tmp_path (e.g. write a synthetic bases.yaml to a tmp path and pass config_dir=tmp_path to LarkCore / Registry), not modify the production config.
7. Partial progress rule for Round B (binding)
Round B is intentionally larger than Round A. If the agent completes a coherent subset but cannot finish all scope safely, it must report PARTIAL / BLOCKED rather than force completion.
Acceptable partial boundaries (in order):
- B1: baseline fixes + request-leak cleanup
- fix the pre-existing CLI smoke
--jsonfailures (path-pinned subprocess or remove stale/usr/local/bin/lark-tool) - refactor
scripts/s179_probe.pyto route throughLarkReaderinstead ofcore._request(...)(and drop thescripts/exclusion in the lint test) WriteContext.sourceplumbing inlark_client/audit.py(_MASK_SKIP_KEYS+="source"; entry builders includesource)
- fix the pre-existing CLI smoke
- B2:
ApprovalProvider+ tests- YAML-backed registry against
config/write-approvals.yaml check_and_consume(approval_id, ctx, idempotency_key)with one-time-use lock (file-lock viafcntl.flockon the YAML directory)approval_exempt_basesbypass with the test from §4 of this addendum
- YAML-backed registry against
- B3:
SafetyLayerskeleton + tests- 8-layer orchestration per PATCH2 §P2-3
- injectable dependencies (GPG=stub, PII=stub, API=mock) for unit-testability
- emits structured
WriteOutcome audit_post_degradedpath test
- B4: Service + CLI
recordssurface + testslark-tool records get|create|update|delete|batch_*argparse surface- stubs only for mutating commands — they enter SafetyLayer up to API call, then refuse with the §1/§2 structured "not authorized" JSON
records getis fully implemented per §3- all 5 commands produce structured JSON per §1, §2
If stopping partially:
- Commit only the coherent passing subset, on the feature branch (no push).
- Report exactly what passed (subset boundary B-n), what remains, and why.
- Do not continue blindly.
8. Inheritance from Round A / Sprint 1 base
The following Round A + base-package constraints remain in force for Round B (this addendum does not override them):
- No live Lark write executed (Round B's mutating commands stay as authorize-refusing stubs).
- No production touched.
- No deploy / push / merge / tag.
- No MCP write enablement.
- No new bot, no credential rotation, no secret printed.
- No KB docs created outside
knowledge/dev/lark/s177-controlled-crud-gateway/. - DISCOVER-FIRST is mandatory before any new file / config / module / secret reference.
LARK_BACKUP_GPG_PUBKEYremains a Sprint 1 full-PASS prerequisite (still absent in GSM as of 2026-05-20); Round B may scaffold the interface, but a GPG-touching slice ships only after the secret lands.- Round B may not self-advance to Sprint 2 (MCP adapter).
9. Required KB output for Round B
On completion (PASS, PARTIAL, or BLOCKED), Round B must upload:
knowledge/dev/lark/s177-controlled-crud-gateway/s177-sprint1-round-b-implementation-evidence-2026-05-20.md
with the same fields the Round A evidence report has, plus:
- Subset boundary reached (B1 / B2 / B3 / B4 / all)
- Structured-output sample for each
records …command added - Approval-exempt-base test transcript
sourcefield appearance in a sample audit entry- Confirmation that
config/bases.yamlwas not modified (sha256 before/after)
And update the folder README pointer to the Round B evidence.
S177 Sprint 1 Round B addendum — binding when Round B is authorized.