Repository navigation
Nothing checks Starlark, and dead API calls survive for months #721
Description
Feature: #837 — lint provider. (Moved from QualityCoverage on 2026-09-05: lint.starlark is an embedded linter, and every lint files under the lint provider.)
Charter: docs/plans/lint-starlark.md
The observed failure
devlore-registry/packages/docker/ shipped 28 .star files. Read against the provider tree on
2026-08-26, the deploy path could not execute on any platform:
| Written in the package | Reality |
|---|---|
plan.package.install("docker-ce", "docker-ce-cli") |
No package namespace — it is pkg, and it takes a list, not varargs |
plan.verify(name, check=…, optional=True) |
No Verify method on any provider. All four verify.star files are entirely these calls |
plan.file.write(path=…, content=…) |
It is plan.file.write_text(destination_path=…, content=…, mode=…) |
plan.user.add_to_group(user, "docker") |
No user provider |
plan.download(url, dest) |
appnet.download(url) returns bytes; there is no dest form |
plan.notify(…) |
Does not exist |
Upgrade/install.star |
UpgradePhaseOrder is {prepare, upgrade, migrate, verify} — install is not an upgrade phase |
The registry's CI validates knowledge schemas and package structure. None of it reads a .star
file's contents, so all of the above passed continuously.
A syntax checker would not have helped
Every line above is syntactically valid Starlark. buildifier parses all of them without
complaint and always would have. The defects are resolution errors — wrong namespace, absent
method, wrong arity, wrong parameter name — and a general-purpose Starlark formatter knows nothing
about devlore's provider surface, so it structurally cannot see them.
This is the load-bearing point: the cheap check is available today and would have caught nothing.
The cost is low, because the machinery exists
go.starlark.netis already a direct dependency;go.starlark.net/syntaxis imported by ten files.cmd/star/provider/starindex/provider.goalready parses Starlark —syntax.FileOptions{},
walkingDefStmt,LoadStmt,AssignStmt. Its doc: "AST-based indexing of Starlark source files."- Resolution ground truth is generated:
action_names.gen.goenumerates every valid
<namespace>.<method>; theParameterNamestables in eachgen/provider.gen.goenumerate every
valid keyword argument. Reading these means the checker cannot drift from codegen — the same
disciplineKnowledge/extract.staralready applies to Go. - There is a home and a hole:
LintCopyright,LintGo,LintGoStyle,LintMarkdown,LintShell,
LintTools— and noLintStarlark, across 162.starfiles.
Rules worth considering beyond dead references
- Lifecycle verbs.
cmd/lore/lore/builder.go:31deniesplan.assemble,clear,load,run,
saveto phase scripts at runtime. A checker could reject them at author time. - Phase entry point. Every phase script must define
def <phase>(package, phase)matching its
filename and its action directory's phase order. - Lambdas — conditional on devlore-test writes the receipt after deleting the workspace it reads from #720. Whether this belongs in a linter depends on whether that fix lands.
What has to be discovered
- How much of
plan.<namespace>.<method>(…)resolution is tractable statically? Starlark is dynamic;
handling the literal call shape likely covers every real package script, but coverage should be
measured, not assumed. - Star extension (
LintStarlark, joiningmake checkviaLintAll), Go analyzer, orstar
subcommand? The extension family is the obvious home, butstarcurrently fails to load its own
extensions. - Does it run against
devlore-registrytoo? The motivating failure is in that repo, whose CI would
need the checker as a built artifact. - Adopt
buildifieralongside as a parse gate? Note-module-docstringand-unused-variableare
both wrong for devlore — the latter fires on the mandated(package, phase)signature of every
phase script ever written. Its formatter also rewriteskwarg=valuetokwarg = value, which no
file in the corpus uses and which is not configurable.
Exit criteria
- Running the checker over
devlore-registry/packages/docker/as it stood on 2026-08-26 reports
every row in the table above. That corpus is the regression fixture — worth preserving before the
stale files are deleted. - The checker reads the generated tables rather than a hand-maintained surface list, proven by adding
a provider method and observing the checker accept it with no edit. - Zero false positives across the 162
.starfiles indevlore-cli.
Epic: #452 · Feature: #340 — this is #340's Phase 2 row, star lint starlark, which its coverage table marks Not done.
Epic: #815 · Feature: #837
Plan and design documents
| Kind | Document |
|---|---|
| Plan | docs/plans/feature/721-lint-starlark.md |
The link is to develop and is dead until the branch merges, which is the form the process
requires: a link to the feature branch is correct only while the work is open and dead from the moment
it closes, which is when the issue is most likely to be read.
Activity
David-Noble-at-work commentedon Sep 5, 2026
Ruled 2026-09-05: the Starlark linter is a built-in, in Go, beside the code model — not a
LintStarlarkextension. That closes the charter's open question ("Star extension, Go analyzer, orstarsubcommand?") in favour of the built-in, which also removes the circularity the charter noted: a.starextension checking.starfiles would depend on the loader it is meant to protect.The extension family stays the home for the lints that shell out — shellcheck, buildifier — and
star lint starlarkcan still be the command name; what changes is that it is implemented in Go against the generated action tables, the way the goast analysis is.Interim, also ruled:
buildifiernow, rather than waiting for this. It is available, catches formatting and a handful of real defects (confusing names, unused variables), and is already gating noblefactor-ops#140's.starfiles in the PR script. It is not a substitute for resolution checking — every one of the registry's 28 broken files would pass it — and this issue remains the real fix.fix(ci): the PowerShell gate arrives with its sweep, 51 findings to z…
doc(plan): only three of the seven rows are live code, so the exit cr…
doc(plan): approved -- if it exists we check it, so Requirement 3 sta…
test(lint): the docker corpus is preserved as a fixture that proves i…
feat(lint): the resolver asks the registry, and finds three call shap…
feat(lint): the checker reports the corpus exactly, after two false-p…
feat(lint): lint.starlark ships, and star lint all can run a linter f…
feat(lint): star lint starlark resolves plan.* against the surface de…
doc: the header is configuration, the table describes file types, and…