Example: read-only policy
A policy plugin that installs a Rule allowing only docs/* and
im/* read commands. Any write command produces a structured
command_denied envelope.
Build & run
cd extension/platform/examples/readonly-policy
go build -o readonly-cli .
./readonly-cli config policy show
# {
# "source": "plugin",
# "source_name": "readonly",
# "denied_paths": N,
# "rule": {
# "name": "agent-readonly",
# "allow": ["docs/**", "im/**"],
# "deny": [],
# "max_risk": "read",
# "identities": [],
# "allow_unannotated": false
# }
# }
./readonly-cli docs +update --doc-token X --content Y
# {"ok":false,"error":{
# "type":"command_denied",
# "detail":{
# "layer":"policy",
# "policy_source":"plugin:readonly",
# "rule_name":"agent-readonly",
# "reason_code":"write_not_allowed"
# }
# }}
./readonly-cli docs +fetch --doc-token X
# Normal read response (assuming credentials)
Key points
Restrict(&Rule{...}) is the only call needed — the Builder
flips Capabilities to Restricts=true, FailurePolicy=FailClosed
automatically. A policy plugin that silently fails to install
would erase the security boundary, so FailClosed is enforced.
MaxRisk: platform.RiskRead rejects any command annotated
write / high-risk-write.
AllowUnannotated is left default (false): unannotated commands
are denied with risk_not_annotated. Set it to true if you need
a gradual-adoption window for the lark-cli main tree.
Caveats
- A binary may have only one plugin calling
Restrict(). Two
policy plugins is a deliberate plugin_conflict configuration
error.
- This Rule shadows any
~/.lark-cli/policy.yml — plugin Rule
wins per the resolver precedence.