The constraint walk descended into nested modules while the file walk skipped
them, and the two feed each other: constraints become the configurations the
file walk is measured against. A compound tag under lint/ therefore added
`-tags bar,foo` to this module's sweep — seven more `go list` runs selecting
nothing here, and a failing configuration list — over a file no walk ever
required to be selected. lint/ carries no custom tag today, so the divergence
was latent rather than broken.
One predicate now answers both walks, and a test pins it: a nested module is out
of scope, a plain package directory is not, and the module root itself stays in.
The internal/core split renamed the brand package and aliased the import as
brandpkg, and the sweep that rewrote `brand.` also rewrote the word ending three
sentences. One of them is user-visible: `apps --help` on Lark read "The "apps"
feature is not yet supported on the lark brandpkg." The error path a few lines
above was spelled without the trailing period and escaped the sweep, so the two
surfaces disagreed.
The brand-guard tests only exercised RunE, which --help bypasses, so nothing
covered the sentence. Pin it whole: a substring check would still pass on a
mangled tail.
The other two are comments in internal/auth.
The union executed one `go list` per registered tag, so a file constrained by
`foo && bar` was selected by neither `-tags foo` nor `-tags bar`. The coverage
test only asked whether each tag name appeared in the registry, which both did,
and the remedy it printed — "union the tag" — is what produced the gap: register
the two tags separately and the file lands in no graph while every check reports
covered. A probe file under events/ importing shortcuts/common, the exact shape
events-no-shortcuts forbids, passed the whole suite that way.
Derive the configurations instead. Every //go:build line is parsed with
go/build/constraint, and each distinct expression contributes a tag set that
satisfies it, so `foo && bar` yields `-tags bar,foo` without anyone registering
anything. Platform terms stay free variables: layeringBuildTargets already
varies GOOS and GOARCH, and -tags cannot set them. Cheapest set wins, which
keeps a platform-only constraint from adding a configuration it does not need.
Deriving the sets removes both hand-kept lists, including the exclusion map that
carved out the sidecar demo tags — those now get a configuration like everything
else.
The replacement invariant is a file-level one the tag registry could not state:
every Go file in this module must be compiled by at least one executed
configuration, asked of the toolchain rather than re-derived from the model that
produced the configurations. It immediately found a second blind spot with no
custom tag in it at all: internal/riskcontrol/osmodel_other.go is constrained
`!darwin && !windows && !linux`, and all seven release targets are one of those,
so no configuration has ever compiled it. That file is recorded as out of scope
with its reason checked — an entry naming a custom tag, or one a release target
does compile, now fails.
Nested modules are skipped, since `go list ./...` does not reach into lint/ and
the rules are written against this module's import paths.
Cost: the file-level check sweeps `go list` again because the rule graph merges
packages across configurations and keeps only imports, so it can no longer say
which configuration contributed which file. The package goes from roughly 16s to
roughly 60s.
Two conflicts, both in the calendar rich-image preview URL and both the same
shape: main fixed the default host while this branch renamed the brand type.
Took main's fix and this branch's package.
- shortcuts/calendar/description_rich_images.go: host stays the feishu.cn value
#2079 corrected it to, with the brandpkg.Brand signature.
- shortcuts/calendar/description_rich_images_test.go: the expected host fragment
follows the same fix; the table already carried brandpkg.Brand from the
automatic merge, so core.Brand* would not have compiled.
Consolidate the user-search identity note into SKILL.md, and clarify bot
handling across attendee flows: bots are virtual identities with no
free/busy semantics, no meeting-room seat, and no room preference, so
they must be excluded from +suggestion, +room-find, and the scheduling
free/busy check. Note in create/update that bots remain valid attendees.
The split left the layout table naming only the renamed config package, so
brand, workspace and identity — the three a caller reaches for most — had no
entry, and nothing said where the remaining two went. brand earns a row of its
own for a second reason: it sits at the repository root precisely so extension/
may import it.
The note under the table carries what a path table cannot. The five siblings do
not import each other, and that is the whole reason to ask for the narrow one:
a caller that only wants a config directory no longer compiles keychain, i18n
and validate along with it.
The internal/core split renamed or relocated every symbol these comments
name, and the sweep missed six call-outs. Two had gone self-contradictory:
internal/meta named the package internal/core while already qualifying the
type as identity.Identity, and authlog attributed its runtime-directory
indirection to a cycle through internal/core that the split removed.
Restate authlog's reason for keeping the indirection instead of promising a
follow-up: the factory-installed logger follows the detected workspace while
the Shared() fallback stays on the pre-workspace directory, so resolving the
directory inside the package would collapse that distinction.
The domaincontract rule's README still pointed host literals at
internal/core/types.go; the exemption moved to brand/brand.go with the
resolver.
Reframe the identity section around event ownership: use `--as user`
for the logged-in user's own events and `--as bot` for events the bot
creates or participates in, with matching `+agenda` examples.
The comment sent readers to a follow-up in the pull request description that
does not exist there. Keep the reason in the source, where it is already
complete, and add the evidence that made the decision: applying the validator
moved four packages' expectations from /var to /private/var, because it
resolves symlinks.
97e397cf classified every startup failure, including the one the config
resolver had already classified. An unconfigured CLI comes back as
not_configured carrying "run: lark-cli config init"; wrapping it in
invalid_config put the wrong subtype in front — ProblemOf reads the outermost —
and dropped the hint entirely. A caller would be told the config is broken when
it was never written.
Pass typed errors through untouched and reserve a fresh error for the case
where the resolver gave none, where internal/unknown is the honest answer
rather than a guess at invalid_config.
Flag rejections now name the flag through WithParam, so a caller learns which
one to fix without reading the sentence, and the tests assert subtype and
param through ProblemOf instead of matching prose.
The comment claimed the binary plus two words is the deepest command path in
this CLI. It is not: generated service commands go one level further, as
`drive file.comments create_v2` in the manifest tests shows, and the cap cuts
their last word. Calling the bound a measurement invites the next reader to
raise it for a command that does not fit — which would also admit the first
positional argument, where resource identifiers live.
State it as the privacy bound it is, and add the generated-command case to the
table so the trade-off is visible next to the cases it protects.
* feat(drive): add title+body joint search guidance and pagination rules for Top N results
- Add new blockquote explaining combined title+body search: use a single
--query with both keywords instead of splitting into two searches
- Add rule for Top N results: N is an output cap, not --page-size; scan
up to 3 pages filtering by title and summary_highlighted, read body
only for title-matched candidates, stop early at N confirmed results
- Add quick-reference table row for folder-scoped title+body search
- Update pagination strategy rule to cover the 3-page cap for joint
search in addition to the existing 5-page limit for other scenarios
* feat(drive): clarify Top N search output limit
* feat(drive): clarify search filters share one call
---------
Co-authored-by: yballul-bytedance <273011618+yballul-bytedance@users.noreply.github.com>
475f04a8 stopped the command line at the first flag and dropped the
"keep three words" rule with it, on the reasoning that the command path is what
the log needs. That reasoning missed positional arguments: `api <method> <path>`
takes the path as an argument, so a document token moved from truncated to
recorded in a file that is kept for a week.
Apply both limits. Stop at the first flag, so a sensitive flag ahead of the
subcommand cannot slip through, and keep at most three words, so a positional
identifier after the command path cannot either. Removing either one fails a
test: the flag boundary alone lets the document token through, the word cap
alone lets --token=... through.
Verified case by case that nothing reaches the log that the pre-475f04a8
behaviour withheld.
LARKSUITE_CLI_LOG_DIR is validated, LARKSUITE_CLI_CONFIG_DIR is not, and the
asymmetry looks like an oversight. It is not free to remove:
validate.SafeEnvDirPath resolves symlinks, so routing CONFIG_DIR through it
changes the directory the CLI reports on any host where the path crosses one.
Applying it moved four packages' expectations from /var to /private/var on
macOS. Whether config paths should be symlink-resolved is a decision about the
on-disk contract, not a local tidy-up, so say so where the next reader looks.
Also record why the stderr capture helper uses os while the file assertions use
vfs: os.Pipe and os.Stderr are process contracts with nothing for a substituted
filesystem to intercept.
Both demo entry points reported every startup failure with fmt.Errorf, so the
self-proxy rejection was indistinguishable from a missing key file except by
reading the sentence. Classify them instead: a poisoned environment and an
unreadable config are ConfigError, flag validation is ValidationError, local
key and log file work is InternalError, and listen or serve failures are
NetworkError. Causes are attached rather than folded into the message.
The self-proxy test asserts the type with errors.As and keeps the check that
the message names the variable, so renaming the variable still fails the test
while rewording the sentence no longer does.
bf56e903 asked the next author to change both ParseBrand implementations by
cross-referencing them. That is the weakest kind of guarantee: it holds until
someone adds a brand to the package they happened to open.
Assert it instead. The constants are read out of both sources rather than
listed again, so a brand added to one package is under test immediately, and
the two parsers are compared on every declared value plus the inputs that
exercise the normalisation — case, padding, an unknown brand, a near miss.
The file sits in neither parser's package so neither owns the contract. It does
not merge the two implementations: extension/credential ships as a standalone
SDK and may not import internal, which is the constraint this branch exists to
establish.
Two behaviours carried over from internal/keychain, both left as they were when
the package moved.
FormatAuthCmdline kept the first three arguments. That protected secrets only
while no sensitive flag appeared early: a global flag in front of the
subcommand put its value straight into a file that is world-readable to the
user, kept for seven days. Today's CLI cannot reach that state — the only
persistent flag is --profile and secrets arrive through --app-secret-stdin — so
this is about the shape, not a live leak. Drop everything from the first flag
onward instead. A denylist of sensitive names would need extending whenever one
is added; the command path is what the log is for, and it lives entirely in the
leading non-flag arguments. args[0] is reduced to its base name so an absolute
install path stays out too.
logDir swallowed the error when LARKSUITE_CLI_LOG_DIR failed validation and
wrote elsewhere while the caller kept watching the path they configured. Warn
instead. This fires only on a rejected override, not on every run, and logDir
resolves once per process.
Tests cover a flag ahead of the subcommand, the absolute-path case, and that a
usable override still prints nothing.
The build-tag list was checked against a literal, so the test only caught edits
to the list itself. A tag introduced anywhere else left its files out of every
graph and the rules went quiet on them, with nothing to notice. The platform
list does not have this hole: it is cross-checked against .goreleaser.yml.
Walk the tree for //go:build constraints and require every custom tag to be
either unioned or listed as excluded. GOOS, GOARCH and toolchain terms come
from `go tool dist list` so a new port cannot look like a custom tag.
Exclusions now carry a reason that is verified rather than asserted: the two
demo tags are skipped because their files sit outside every rule's FromPrefix,
and the test fails if a file carrying one ever lands inside one. A listed
exclusion nobody uses fails too, so the list cannot rot.
Removing extension's dependency on internal left the brand rule implemented
twice, once per Brand type. The two cannot share code: extension is published
as a standalone SDK and may not import internal, which is the constraint this
branch exists to establish. Cross-reference them so a third brand is added to
both rather than to whichever one the next author happens to open.
Also correct the timestamp wrapper's comment, which has claimed "HH:mm" since
before this branch while every implementation formatted seconds too.
The package documents one logger and one file handle per process, but SetShared
overwrote the current instance on every call and the factory can be built more
than once. A second construction opened a second file, left the first one open
with no way to close it, and moved later lines to whichever workspace directory
that construction resolved.
Only the first explicit install now takes effect. A lazily created fallback is
not an explicit install, so the first real one still replaces it — and closes
the file it had opened, which needs the handle to be retained rather than
handed to log.New and forgotten. The once-guarded init becomes a mutex so the
handle can be released safely; a closed logger drops writes instead of pointing
at a file nobody reads.
Tests cover two non-nil installs and the fallback handover, and both fail if
the guard is removed.
Two packages were listed in ExceptFrom, which makes the evaluator skip the
source package before it looks at any dependency. gitcred needs keychain and
vfs; manifest-export needs the cmd root. Exempting them wholesale also cleared
every other denied import, so a later gitcred -> internal/client or
manifest-export -> events would pass the gate and never reach the registry. A
probe confirmed both slip through unreported.
Add ExceptEdges, matched on the exact (from, denied) pair, and move these two
across. ExceptFrom stays for packages whose whole job is to sit on the boundary
the rule draws: the shortcuts/common runtime gate, the cmd assembly roots, the
wrapper-main demos. Contract tests feed each package its allowed imports plus
one denied import and assert exactly one violation, so the allowed edges carry
weight instead of being asserted trivially.
Replace the uniform 0.55em half-width coefficient with per-character-type
coefficients, add font-family awareness (sans/serif), bold multiplier,
letter-spacing support, and fix padding-aware line wrapping.
- Split half-width chars into uppercase (0.57), lowercase (0.51 sans / 0.53
serif), digits (0.58), and punctuation (0.50)
- Add classify_font_family() to apply slightly wider lowercase widths for
serif fonts (Georgia, Source Han Serif/思源宋体, Times, etc.)
- Add 5% width multiplier for bold text; detect <strong>/<b>/<i>/<em> tags
and span-level bold/italic attributes in addition to content attrs
- Fix estimate_text_line_count_for_text to subtract paddingLeft/paddingRight
from available width before computing wrap lines
- Add resolve_letter_spacing and wire letterSpacing through estimate_text_width
- Extract fontFamily/bold/italic/letterSpacing into element dict during parse
chartParsedValues is a server-injected roundtrip child tag under
chartField, not an attribute. Move it from ROUNDTRIP_SXSD_ATTRS to a
new ROUNDTRIP_SXSD_TAGS set and skip the tag (and its subtree) in the
SXSD tag whitelist check.