Skip to content

feat: Mill Node Standard (goal 0030) — 8-item conformance checklist, 4 machine-checked - #14

Merged
alicoding merged 1 commit into
mainfrom
goal/0030-node-standard
Aug 12, 2026
Merged

feat: Mill Node Standard (goal 0030) — 8-item conformance checklist, 4 machine-checked#14
alicoding merged 1 commit into
mainfrom
goal/0030-node-standard

Conversation

@alicoding

Copy link
Copy Markdown
Owner

Summary

  • Adopts (researched, not invented) a node/plugin conformance standard for internal/domain/composition's NodeTypes — .claude/rules/node-standard.md, converged from n8n's community-node verification/UX/error-handling guidelines, Zapier's app publishing requirements, and the Raycast store checklist. Documents the 8-item checklist, the credential rule, three explicit rejections, and NodeType-versioning-as-latent.
  • TestNodeTypes machine-checks 4 of the 8 items against every registered NodeType: ConfigField.Description non-empty, an explicit Effect class (closed pureNodeTypes allow-list — the zero value silently means ClassNone/allow-with-no-guardrail-gate), ID prefixed by its Kind (closed idPrefixExceptions allow-list, verified against the real registry), Output non-empty universally.
  • Running the new checks found and fixed 3 real gaps: list-lookup/list-search had no declared Effect (fixed to ClassRead), child-workflow had no declared Effect (fixed to an explicit ClassNone, per ADR-0022's own already-decided-but-unwritten answer), decision-route had no Output.
  • docs/SPEC.md §3.3 gets a new Node standard capability-map row (LOCKED); goal 0030 archived; BACKLOG.md updated.

Test plan

  • go vet ./...
  • golangci-lint run (0 issues)
  • go test ./... (all packages green, including the extended TestNodeTypes)
  • scripts/check-rules-frontmatter.sh / scripts/check-loc.sh
  • Full lefthook pre-commit suite green (root-file-naming, rules-frontmatter, go-vet, golangci-lint, file-loc-limit, go-build-server, go-build-desktop, go-test)

🤖 Generated with Claude Code

https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft

…4 machine-checked

Adopts (not invents) a node/plugin conformance standard, researched
against n8n's community-node verification/UX/error-handling
guidelines, Zapier's app publishing requirements, and the Raycast
store checklist — .claude/rules/node-standard.md (paths-scoped to
internal/domain/composition/**). Documents the 8-item checklist (each
marked enforced-by-what), the credential rule (secrets only via an
existing credential-backed entity, never a raw ConfigField), three
explicit rejections (n8n publishing ceremony, CRUD completeness,
Raycast's no-keychain rule — contradicts Mill's deliberate go-keyring
design), and NodeType-level versioning named as real-but-latent, not
built speculatively.

TestNodeTypes (nodetypes_test.go) now machine-checks 4 of the 8 items
against every registered NodeType: ConfigField.Description non-empty;
an explicit Effect class via a closed pureNodeTypes allow-list (the
zero value silently resolves to ClassNone/allow-with-no-guardrail-gate
at run time — the standard's priority check); ID prefixed by its Kind
via a closed idPrefixExceptions allow-list, verified against the
actual registry rather than assumed; Output non-empty universally (no
kind exemption needed — every registered NodeType already had one
except the fix below).

Running the new checks against the existing registry surfaced and
fixed three real gaps, not just test additions:
- list-lookup and list-search had no declared Effect despite doing a
  real local List read — fixed to guardrail.ClassRead, the same
  classification capture-file already uses for a local filesystem
  read.
- child-workflow had no declared Effect either. ADR-0022 already
  states the correct answer ("Child workflows carry no class of their
  own (none)... gating the invocation too would double-charge") but
  it was never written into the source — fixed to an explicit
  Effect: guardrail.ClassNone.
- decision-route had no Output at all (the only NodeType missing
  one) — fixed with a short description of its pass-through behavior.

docs/SPEC.md §3.3's capability map gets a new Node standard row
(LOCKED). Goal 0030 archived with its Plan/Acceptance checked against
what shipped; BACKLOG.md updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft
@alicoding
alicoding enabled auto-merge (squash) August 12, 2026 05:43
@alicoding
alicoding merged commit 410b8f5 into main Aug 12, 2026
16 checks passed
@alicoding
alicoding deleted the goal/0030-node-standard branch August 12, 2026 05:48
alicoding added a commit that referenced this pull request Aug 15, 2026
…14)

Real empirical test, not assumed from the tool list: built a real
go build -tags mcp . desktop binary, launched it, connected a genuine
MCP client (the SDK's own client role), and exercised the built-in
server's 16 tools -- app_info, windows_list, window_control, dom_query,
call_bound_method, keyboard_press among them.

The specific open question from task #14's own scoping (raised when
this tool was first surfaced this session): does keyboard_press
trigger a real OS-level global hotkey golang.design/x/hotkey's
listener can see, or only a DOM-scoped KeyboardEvent? Answered
definitively: called call_bound_method to bind a real summon hotkey
(main.SettingsService.AssignSummonHotkey, an actual TCC/Accessibility-
gated OS registration -- succeeded, no error), minimised the window,
then sent keyboard_press with the identical combo. The press reached
and was handled by the real DOM (its own response returned the
genuine rendered page text), but a follow-up app_info call showed the
window's minimised/visible state unchanged -- proving keyboard_press
is DOM-scoped only, does not reach the OS-level listener.

Verdict recorded in .claude/skills/run-mill/SKILL.md (not adopted into
the standing workflow -- a one-off spike, not wired into CI/Lefthook):
this tool closes part of run-mill's own already-documented desktop-
only gap (window/tray state -- SetIcon'd tray from task #8, minimise/
show/focus -- is now agent-drivable via window_control/dom_query/
call_bound_method) but not hotkey-delivery verification specifically,
which still needs the real desktop app checked by hand, exactly as
SKILL.md already said before this spike.

Real bug hit and fixed along the way, not glossed over: the SDK's
call_bound_method tool takes a `name` argument (package.Service.Method),
not `method` as first guessed -- the tool's own error message
("missing required argument: name") caught this immediately rather
than silently no-op'ing.

No permanent code shipped -- the spike client and its throwaway Go
module were built in /tmp and discarded after use, matching this
task's own framing as investigation, not a feature. docs/SPEC.md §3.7
carries a short pointer to the SKILL.md verdict rather than duplicating
it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft
alicoding added a commit that referenced this pull request Aug 15, 2026
…4 machine-checked (#14)

Adopts (not invents) a node/plugin conformance standard, researched
against n8n's community-node verification/UX/error-handling
guidelines, Zapier's app publishing requirements, and the Raycast
store checklist — .claude/rules/node-standard.md (paths-scoped to
internal/domain/composition/**). Documents the 8-item checklist (each
marked enforced-by-what), the credential rule (secrets only via an
existing credential-backed entity, never a raw ConfigField), three
explicit rejections (n8n publishing ceremony, CRUD completeness,
Raycast's no-keychain rule — contradicts Mill's deliberate go-keyring
design), and NodeType-level versioning named as real-but-latent, not
built speculatively.

TestNodeTypes (nodetypes_test.go) now machine-checks 4 of the 8 items
against every registered NodeType: ConfigField.Description non-empty;
an explicit Effect class via a closed pureNodeTypes allow-list (the
zero value silently resolves to ClassNone/allow-with-no-guardrail-gate
at run time — the standard's priority check); ID prefixed by its Kind
via a closed idPrefixExceptions allow-list, verified against the
actual registry rather than assumed; Output non-empty universally (no
kind exemption needed — every registered NodeType already had one
except the fix below).

Running the new checks against the existing registry surfaced and
fixed three real gaps, not just test additions:
- list-lookup and list-search had no declared Effect despite doing a
  real local List read — fixed to guardrail.ClassRead, the same
  classification capture-file already uses for a local filesystem
  read.
- child-workflow had no declared Effect either. ADR-0022 already
  states the correct answer ("Child workflows carry no class of their
  own (none)... gating the invocation too would double-charge") but
  it was never written into the source — fixed to an explicit
  Effect: guardrail.ClassNone.
- decision-route had no Output at all (the only NodeType missing
  one) — fixed with a short description of its pass-through behavior.

docs/SPEC.md §3.3's capability map gets a new Node standard row
(LOCKED). Goal 0030 archived with its Plan/Acceptance checked against
what shipped; BACKLOG.md updated.


Claude-Session: https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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