Skip to content

lint: stop W2 switching itself off, and see an asset name nobody declared - #10

Merged
inhuman merged 1 commit into
mainfrom
lint-w2-and-assets
Aug 6, 2026
Merged

lint: stop W2 switching itself off, and see an asset name nobody declared#10
inhuman merged 1 commit into
mainfrom
lint-w2-and-assets

Conversation

@inhuman

@inhuman inhuman commented Aug 6, 2026

Copy link
Copy Markdown
Owner

What

Two linter reports from an outside review, both reproduced by a failing test
before anything was changed. The format did not changeEngineVersion
stays 2.2.4, no skill file is affected.

1. W2 switched itself off offline

The rule makes two checks and only one needs the installation:

check needs
the program uses a server the skill did not declare the skill alone
a declared server is not registered Facts.ServerNames

Interleaved, the registry was asked for on the first declared server — which
in any healthy skill comes first — and a missing one returned out of the whole
walk. So without a registry the rule did not degrade partially; it turned itself
off, and the undeclared server further down was never reached.

That is what let a live catalogue report "0 errors" while carrying two:
steps called a server their skill had not declared, the radius was never handed
over, and 21 turns answered "no such pod" about live pods.

Now two passes — the skill against itself, then the skill against the
installation — which makes the dependency structural instead of a comment. The
skip still fires (a report must distinguish "part of a rule did not run" from
"the rule did not run"), but only when pass two had something to check.

The patch that came with the report would have introduced false findings. It
handed the loop an empty registry and continued, so on the second declared
server !known[srv] is true and every existing server is reported as "not
registered" — a report full of findings about live servers, which is worse than
the silence it replaces. A test pins that now.

The order of the steps in the fixture is the test: put the undeclared server
first and it passes on the broken code too.

2. W19 / W20 — asset names

A reference to an asset the skill does not declare was caught by nothing.

The engine expands an unknown asset to an empty string by contract — a
marker left in an instruction would be read by the model as part of it — and
that contract is right. The price nobody was paying: a typo is indistinguishable
from an empty asset, and what fails is whatever stood next to it. The call is
rejected by the server for a missing argument, and the error names the
argument rather than the substitution a floor above.

  • W19 (error): every {{asset:name}} and every {from: "asset:name"} names
    a declared asset. Both forms, because both are used — one puts content in the
    text for the model, the other passes it into an argument past the context. The
    finding lists the declared names, since a typo is recognised by comparison.
  • W20 (warn): an asset declared and never referenced. Clutter rather than a
    failure, and worth having because assets outlive the steps that used them.

Both are purely static — declarations and references live in one document, no
registry of anything involved.

One thing the report did not mention: workflowAssets returned early when the
asset map was empty, so a skill with no assets that still referenced one
would have been missed. The check now runs before that exit.

Also documented

Rule.Needs read as "the rule cannot run without this", while for W2 it is one
of two checks. It now says so on the type, and the partially-dependent rules say
which half needs what — including W12, which the report rightly notes is
entirely dependent on a live tool listing and does not run offline at all. A
CI report of "0 warnings" was giving false confidence about it.

Testing

  • go vet ./... clean
  • go test ./... -count=1 all green
  • go test ./... -race all green
  • Both example modules vet and test green
  • Every finding reproduced first; the new fixture is registered and the
    catalogue/README guards updated (27 → 29 rules everywhere)

Format & API compatibility

  • The format did not change; EngineVersion stays 2.2.4 and CHANGELOG.md
    (which documents the format) is untouched
  • Go API unchanged
  • Behaviour change for linter users: skills that were reported clean may
    now show W2 findings offline, and W19/W20 are new — that is the point of
    the change

🤖 Generated with Claude Code

…ared

Two reports, both reproduced by a failing test before anything was changed.

**W2 halted on the first declared server.** The rule makes two checks and only
one needs the installation: "the program uses a server the skill did not
declare" needs the skill alone, "a declared server is not registered" needs the
registry. Interleaved, the registry was asked for on the first DECLARED server —
which in any healthy skill comes first — and a missing one returned out of the
whole walk. So offline the rule did not degrade, it turned itself off.

That is what let a live catalogue report "0 errors" while carrying two: steps
called a server their skill had not declared, the radius was never handed over,
and 21 turns answered "no such pod" about live pods.

Now two passes, which also makes the dependency structural rather than a comment:
pass one is the skill against itself, pass two is the skill against the
installation. The skip still fires — "part of a rule did not run" and "the rule
did not run" are different facts and a report has to keep them apart — but only
when there was something for pass two to check.

The patch that came with the report handed the loop an empty registry and
carried on, which turns every declared server into "not registered". A test now
pins that too: without a registry, no invented findings.

The order of the steps in the fixture IS the test — put the undeclared server
first and it passes on the broken code.

**W19 and W20: asset names.** A reference to an asset the skill does not declare
was caught by nothing. The engine expands an unknown asset to an empty string by
contract — a marker left in an instruction would be read by the model as part of
it — and that contract is right. The price nobody was paying is that a typo is
indistinguishable from an empty asset, and what fails is whatever stood next to
it: the call is rejected for a MISSING ARGUMENT, and the error names the
argument rather than the substitution a floor above.

Both reference forms are checked, because both are used: `{{asset:name}}` puts
content in the text for the model, `{from: "asset:name"}` passes it into an
argument past the context. The finding lists the declared names — a typo is
recognised by comparison.

W20 is the other side: an asset declared and never referenced. A warning, not an
error — it costs nothing at run time — and worth having because assets outlive
the steps that used them.

Both are purely static: declarations and references are in one document.

**Also documented.** `Rule.Needs` was read as "the rule cannot run without
this", while for W2 it is one of two checks. It now says so, and the two
partial rules say which half needs what — including W12, which the report
rightly notes is ENTIRELY dependent on a live tool listing and does not run
offline at all. A CI report of "0 warnings" gave false confidence about it.

The format did not change: EngineVersion stays 2.2.4.
@inhuman
inhuman merged commit 3f4a558 into main Aug 6, 2026
1 check passed
@inhuman
inhuman deleted the lint-w2-and-assets branch August 6, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant