Add Extended-only direct, credential-proxy, and platform-proxy modes while keeping Standard behavior and external-credential product details isolated behind source-neutral runtime capabilities. Fail closed across helper execution, proxy routing, streams, managed file transfers, and unsupported event flows. Harden system configuration, edition updates, and release publication. Cover Standard compatibility and Extended integration with contract tests, repository-local three-mode E2E, native trust checks, and CI and release gates.
lint/
Source-level static checks that guard lark-cli conventions golangci-lint
cannot express. Each lint domain is a sibling Go package under lint/;
the top-level lint/main.go aggregates results and emits a single
exit code.
lint/ is its own Go module so its golang.org/x/tools/go/packages
dependency does not leak into the shipped lark-cli binary's module
graph.
Layout
lint/
├── go.mod # module github.com/larksuite/cli/lint
├── go.sum
├── main.go # package main — dispatches to every registered domain
├── lintapi/ # shared types every domain returns
│ └── violation.go # Violation, Action, ActionReject / ActionLabel / ActionWarning
├── errscontract/ # first domain: typed-error contract guards
├── scan.go # ScanRepoWithOptions(root, opts) ← public entry
├── runner.go
├── typecheck.go
├── violation.go # local type aliases to lintapi
├── rule_problem_embed.go
├── rule_no_registrar.go
├── rule_adhoc_subtype.go
├── rule_declared_subtype.go
├── rule_subtype_classifier.go
├── rule_typed_error_completeness.go
└── *_test.go
├── domaincontract/ # resolver ownership + approved public hostname policy
├── scan.go # ScanRepoWithOptions(root, opts) ← public entry
├── unapproved.go # Go AST/type-aware hostname extraction
├── policy.go # exact public/fixture allowlist validation
├── diff.go # added-line attribution
└── *_test.go
└── flagcontract/ # framework ownership for flag aliases
├── scan.go # rejects local name normalizers
└── scan_test.go
Flag alias contract (flagcontract)
flagcontract keeps exact flag-name synonyms on the shared framework path. It
rejects production calls to SetNormalizeFunc outside internal/flagalias.
Exact synonyms belong in the canonical common.Flag.Aliases; legacy inputs
with a different value grammar or meaning remain real hidden flags and
normalize into canonical state inside the business-owned Shortcut.Normalize
execution stage.
Endpoint domain contract (domaincontract)
domaincontract contains two complementary Go source guards.
The resolver-ownership guard rejects:
- string literals containing a resolver-owned host FQDN
(
{open,accounts,mcp,applink}.{feishu.cn,larksuite.com}), and - direct references to the SDK base-URL globals (
FeishuBaseUrl/LarkBaseUrl) selected off an import of the SDK root package, which pick a host without going through the resolver. Unrelated identifiers sharing the name are not flagged.
Host literals are permitted only inside the resolver's ResolveEndpoints
function body (internal/core/types.go) and in this rule's own host list
(lint/domaincontract/scan.go); a helper elsewhere in the resolver file
returning a hardcoded host is still rejected. Comments and _test.go files
are not scanned. Literals are unquoted before matching (escape sequences
cannot hide a host) and match case-insensitively, and dot-imports of the SDK
root package are rejected outright (they would hide the globals from this
parse-level guard). The forbidden-host list is bound to the resolver source by
TestForbiddenHostsMatchResolver, so adding a resolver domain without updating
the guard fails the lint module's tests.
The approved-domain guard parses every Git-tracked Go file in full. In CI, unapproved-host findings are limited to values whose expressions intersect an added line; policy validation and unused-entry checks remain repository-wide. It rejects an exact hostname unless it is present in one of:
internal/qualitygate/config/allowlists/public-domains.txt, for production and test code; orinternal/qualitygate/config/allowlists/fixture-domains.txt, only for*_test.go, the repository-roottests/, and anytestdata/(neverskills/).
RFC 2606 example/test names are accepted independently of those lists. This
includes the reserved .test, .example, .invalid, and .localhost
namespaces and the exact names example.com, example.net, and example.org;
they are safe placeholders rather than supported public endpoints.
High-confidence evidence is deliberately limited to static string expressions
assigned to host, hostname, or domain semantic names (including common
case/plural forms and collections), plus static strings whose entire value is
an absolute http, https, ws, or wss URL. It supports Go literals,
escapes, compile-time concatenation, constant references, grouped declarations,
multi-value assignments, and multiline expressions. Bare domain-shaped strings
without hostname semantics are not blocked.
Sequence values are scanned individually. For a hostname-semantic map, a key or
value is evidence only when it is the sole hostname-shaped side of that entry;
ambiguous string-to-string entries are not guessed. Struct fields use Go type
information so known non-network Host / Domain fields do not become hostname
evidence merely because an enum or command category contains a dot.
Allowlist matching is lowercase and exact: there are no wildcard, suffix, DNS,
or public-suffix exceptions. Entries must be sorted and unique, use ASCII
hostnames, and have a current in-scope use. See
internal/qualitygate/config/README.md for admission and approval rules.
This is not a general outbound-URL or cross-language data-flow analyzer. It does not inspect non-Go assets or dynamically constructed values.
To add or change a resolver-owned Feishu/Lark endpoint, edit the resolver rather than hardcoding the host elsewhere.
Running
# PR-scoped scan from the repo root (one level above lint/)
go run -C lint . --changed-from <base-revision> ..
# Full inventory (also reports historical unapproved hostnames)
go run -C lint . ..
-C lint switches Go's working directory to lint/; the .. argument
is the repo root to scan (relative to lint/).
CI: .github/workflows/ci.yml step Run source-contract lint guards (lintcheck).
Exit codes follow lint/main.go:
| Code | Meaning |
|---|---|
| 0 | no REJECT diagnostics (LABEL / WARNING are advisory) |
| 1 | one or more REJECT diagnostics |
| 2 | a domain's ScanRepo returned an error |
Adding a new lint domain
-
Create a sibling package:
lint/<domain>/. Pick a name that reads like a category, not a list of rules (errscontract/covers many error-contract rules;flagnaming/would cover many flag-related rules). -
Inside the new package, expose one public entry:
package <domain> import "github.com/larksuite/cli/lint/lintapi" type ScanOptions struct { ChangedFrom string } // ScanRepoWithOptions walks root and returns every violation produced // by this domain's checks. Domains MUST return []lintapi.Violation so // the top-level dispatcher can aggregate uniformly. func ScanRepoWithOptions(root string, opts ScanOptions) ([]lintapi.Violation, error) { ... } -
Per-rule files are named
rule_<name>.gowith siblingrule_<name>_test.go. Each rule function returns[]lintapi.Violation.runner.go(orscan.go) composes the rules. -
Register the domain in
lint/main.go:var scanners = []scanner{ {name: "errscontract", fn: errscontract.ScanRepoWithOptions}, {name: "<domain>", fn: func(root string, opts errscontract.ScanOptions) ([]lintapi.Violation, error) { return <domain>.ScanRepoWithOptions(root, <domain>.ScanOptions{ ChangedFrom: opts.ChangedFrom, }) }}, } -
Verify locally:
go test -C lint ./... # all domains' tests go run -C lint . .. # full scan against the repo -
Document the rules. If they enforce a contract that already has a spec (e.g.
errs/ERROR_CONTRACT.md), add the lint entry to that contract's "CI guards" table. Otherwise create a short spec alongside the package.
Rule severity conventions (lintapi.Action)
| Action | Effect | When to use |
|---|---|---|
ActionReject |
exit 1, fails CI | a contract violation that must be fixed before merge |
ActionLabel |
stderr only; CI can grep for [needs-taxonomy-decision] and label the PR |
governance signal that asks a human to choose (e.g. ad_hoc_* subtype needs a taxonomy decision) |
ActionWarning |
stderr only | advisory hint surfaced to reviewers (typed scope unavailable, fallback to AST-only, etc.) — never gates merges |
Only ActionReject contributes to a nonzero exit code; ActionLabel
and ActionWarning are reviewer signal only.