{"record":{"id":"a2ca25e125303625","repo":"larksuite/cli","slug":"l3-meta-access-tokens-must-not-be-empty","errorCode":null,"errorMessage":"L3: _meta.access_tokens must not be empty","messagePattern":"L3: _meta\\.access_tokens must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/schema/lint.go","lineNumber":101,"sourceCode":"\t// ---- L3: cross-field self-consistency ----\n\tdangerExpected := env.Meta.Risk == core.RiskWrite || env.Meta.Risk == core.RiskHighRiskWrite\n\tif env.Meta.Danger != dangerExpected {\n\t\terrs = append(errs, fmt.Errorf(\"L3: _meta.danger=%v inconsistent with risk=%q\", env.Meta.Danger, env.Meta.Risk))\n\t}\n\n\t// `yes` lives at inputSchema.properties.yes (sibling of params/data),\n\t// injected only for risk == RiskHighRiskWrite.\n\thasYes := false\n\tif env.InputSchema != nil && env.InputSchema.Properties != nil {\n\t\t_, hasYes = env.InputSchema.Properties.Map[\"yes\"]\n\t}\n\twantYes := env.Meta.Risk == core.RiskHighRiskWrite\n\tif hasYes != wantYes {\n\t\terrs = append(errs, fmt.Errorf(\"L3: inputSchema `yes` property=%v inconsistent with risk=%q\", hasYes, env.Meta.Risk))\n\t}\n\n\tif len(env.Meta.AccessTokens) == 0 {\n\t\terrs = append(errs, errors.New(\"L3: _meta.access_tokens must not be empty\"))\n\t}\n\tfor _, t := range env.Meta.AccessTokens {\n\t\tif !validAccessTokens[t] {\n\t\t\terrs = append(errs, fmt.Errorf(\"L3: _meta.access_tokens contains invalid value %q (allowed: user, bot)\", t))\n\t\t}\n\t}\n\n\treturn errs\n}\n\n// walkForL2 recursively applies per-field L2 checks (format:binary on\n// non-string; minimum>=maximum) plus the sub-object required-exists invariant.\n// Required only matters on object-typed Properties (e.g. the params / data\n// wrappers); leaf scalars ignore it.\nfunc walkForL2(props *OrderedProps, errs *[]error) {\n\tif props == nil {\n\t\treturn\n\t}","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/schema/lint.go#L83-L119","documentation":"The L3 policy lint requires every envelope to declare at least one access token in Meta.AccessTokens, because the runtime must know whether the command runs as 'user' or 'bot' identity. An empty list means the envelope's identity policy was never specified, so the lint rejects it; values outside the allowed set {user, bot} are also rejected by the following loop.","triggerScenarios":"Linting an envelope with Meta present but AccessTokens empty (len == 0), e.g. &Meta{EnvelopeVersion: \"1.0\", Risk: ...} without tokens.","commonSituations":"Authors fill in risk level but forget identity policy; meta structs generated from configs where the access_tokens key was missing; plugins copied from examples that predate the access-token requirement.","solutions":["Add at least one valid access token to Meta.AccessTokens: \"user\", \"bot\", or both as appropriate for the command's identity needs.","Align tokens with the declared Risk: high-risk writes typically need an explicit token decision and a matching `yes` confirmation property (the adjacent L3 check).","Re-run the envelope lint suite to confirm the policy block passes."],"exampleFix":"// before\nMeta: &Meta{EnvelopeVersion: \"1.0\", Risk: core.RiskSafeRead}\n// after\nMeta: &Meta{EnvelopeVersion: \"1.0\", Risk: core.RiskSafeRead, AccessTokens: []string{\"user\"}}","handlingStrategy":"validation","validationCode":"func validateAccessTokens(m *Meta) error {\n    if len(m.AccessTokens) == 0 {\n        return errors.New(\"meta.access_tokens must include at least one of: user, bot\")\n    }\n    return nil\n}","typeGuard":"if env.Meta != nil && len(env.Meta.AccessTokens) == 0 {\n    // handle: identity policy missing\n}","tryCatchPattern":"if err := lintEnvelope(env); err != nil {\n    // detect \"access_tokens must not be empty\" and add tokens\n}","preventionTips":["Decide user vs bot identity for every command when declaring Risk.","Keep AccessTokens and Risk in the same meta template so one is never forgotten.","Only use the allowed values user and bot; anything else trips the adjacent validity loop."],"tags":["go","schema","policy","access-control","envelope"],"backgroundTag":"missing-required-argument","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}