feat: Mill Node Standard (goal 0030) — 8-item conformance checklist, 4 machine-checked - #14
Merged
Merged
Conversation
…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
enabled auto-merge (squash)
August 12, 2026 05:43
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
internal/domain/composition'sNodeTypes —.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.TestNodeTypesmachine-checks 4 of the 8 items against every registeredNodeType:ConfigField.Descriptionnon-empty, an explicitEffectclass (closedpureNodeTypesallow-list — the zero value silently meansClassNone/allow-with-no-guardrail-gate), ID prefixed by itsKind(closedidPrefixExceptionsallow-list, verified against the real registry),Outputnon-empty universally.list-lookup/list-searchhad no declaredEffect(fixed toClassRead),child-workflowhad no declaredEffect(fixed to an explicitClassNone, per ADR-0022's own already-decided-but-unwritten answer),decision-routehad noOutput.docs/SPEC.md§3.3 gets a newNode standardcapability-map row (LOCKED); goal 0030 archived;BACKLOG.mdupdated.Test plan
go vet ./...golangci-lint run(0 issues)go test ./...(all packages green, including the extendedTestNodeTypes)scripts/check-rules-frontmatter.sh/scripts/check-loc.shpre-commitsuite 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