Skip to content

Request lifecycle honesty: cancel_write, staleness tiers, badge fix (goal 0026) - #12

Merged
alicoding merged 3 commits into
mainfrom
goal/0026-lifecycle
Aug 12, 2026
Merged

Request lifecycle honesty: cancel_write, staleness tiers, badge fix (goal 0026)#12
alicoding merged 3 commits into
mainfrom
goal/0026-lifecycle

Conversation

@alicoding

Copy link
Copy Markdown
Owner

Summary

  • cancel_write MCP tool: the missing fourth verb (park/poll/resolve/WITHDRAW) — the requesting client withdraws its own still-pending write; cancelled is a distinct outcome from denied, ungated, at-most-once.
  • Age-tiered staleness presentation (fresh <15m as-is; older gets emphasis + "expires in Nh") across Review's pending rows, the MCPWriteApprovals banner, and the floating approval prompt — one shared shared/staleness.ts/StalenessBadge.tsx.
  • Requester-liveness heartbeat: check_write_status now records LastPolledAt, surfaced as a muted "requester last checked Nm ago" hint only past a 5-minute no-noise bar.
  • Real bug fixed, found live: ResolveMCPWrite/CancelMCPWrite/the expiry sweep never fired the pending-changed signal, so the sidebar badge could hold a phantom count against an empty queue. Root cause: main.go's RegisterEvent[MCPWriteRequest]("mcp-write-approval") binds that event to an exact Go type — an empty-struct{}{} payload silently failed Wails3's own type check and was dropped. Fixed by emitting a zero-value MCPWriteRequest.
  • Resolved MCP writes now durable in Review's own Recently-resolved section (previously only session-only Activity, gone on restart).
  • Activity's MCP-write rows are expandable (existing canExpand/result mechanism) with a jump-to-workflow WorkflowHoverPreview when the gated tool named an existing workflow.
  • Stuck-ENQUEUED runs (a real zombie run found in production data) get age emphasis + the existing Stop/CancelRun affordance in WorkflowRunsPanel and Activity's runs explorer.
  • CI: e2e job's shard matrix gains fail-fast: false after a real incident (PR 11, run 31557343422) where one shard's genuine failure cancelled two others mid-run before they reported their own verdict.
  • Item 4 (a session-side hygiene rule, not a Mill code change) intentionally left out of scope per the goal file itself.

Test plan

  • go vet / go vet -tags server — clean
  • go test . ./internal/... -race -cover — all packages pass
  • golangci-lint run — 0 issues
  • go build . (desktop) and CGO_ENABLED=0 go build -tags server . — both clean
  • scripts/check-loc.sh / scripts/check-rules-frontmatter.sh — clean
  • npm run lint / npm run boundaries / npx tsc --noEmit / npx vitest run (197 tests) — clean
  • Bindings regenerated, diff matches Go changes exactly
  • Full Playwright suite (165 tests) green — 2 tests confirmed pre-existing-flaky (reproduced independently on a clean stash of this branch's changes, unrelated to this goal), both pass on the suite's own configured retry
  • New Go tests: TestMCPWriteTools_CancelWrite_*, TestListRuns_EnqueuedRun_PresentationFieldsAndCancelPath (real DBOS ENQUEUED→CANCELLED path)
  • New e2e: mcp-write-cancel.spec.ts (3 tests: cancel lifecycle, phantom-badge deny repro, Activity jump-to-workflow), mcp-write-staleness.spec.ts (backdated-write age emphasis across all 3 surfaces)
  • New Vitest: staleness.test.ts (15), enqueuedStale.test.ts (4)

🤖 Generated with Claude Code

https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft

alicoding and others added 2 commits August 12, 2026 00:13
…e fix (goal 0026)

Delivers all seven Mill-side items: a cancel_write MCP tool (the
missing fourth verb alongside park/poll/resolve, ungated, a status
distinct from denied); age-tiered staleness presentation with
"expires in Nh" across Review, the MCPWriteApprovals banner, and the
floating approval prompt; a requester-liveness heartbeat
(LastPolledAt, shown only past a 5-minute no-noise bar); resolved MCP
writes now durable in Review's Recently-resolved; Activity's MCP-write
rows are expandable with a jump-to-workflow preview; and stuck-ENQUEUED
runs get age emphasis plus Stop in WorkflowRunsPanel and Activity's
runs explorer.

Fixes a real bug found live: ResolveMCPWrite/CancelMCPWrite/the expiry
sweep never fired the pending-changed signal at all, so the sidebar
badge could hold a phantom count against an empty queue — traced to
Wails3's own registered-event type check silently dropping an
empty-struct payload against a typed event registration.

Also: e2e's shard matrix gains fail-fast:false after a real incident
(PR 11) where one shard's real failure cancelled two others mid-run
before they could report their own verdict.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft
@alicoding
alicoding enabled auto-merge August 12, 2026 04:28
… self-cancel

Owner-mandated framework, researched to the converged standard and
found to need NAMING, not building: Scrum 2020's DoD (a formal
artifact commitment) + Kanban University's ready-for-delivery pull
criteria are the industry vocabulary for gates this repo already runs
— consolidated into ONE always-loaded rule
(.claude/rules/delivery-discipline.md) instead of four documents.
Estimation deliberately excluded (negotiates team capacity a
solo+agent loop doesn't have); peer review already settled by
ADR-0034. Build-health becomes a ritual, not a wonder: gh pr checks
--watch after opening a goal PR; gh run list -b main -L 1 at goal
pickup. Tech debt: same queue, same DoR/DoD, never a shadow register.

Plus the real config bug the research found: cancel-in-progress
applied to main's post-merge runs too, so two quick merges cancelled
the earlier one's verification (observed live: merges #1/#10) —
now conditional per GitHub's own documented pattern; PR runs still
supersede, main runs always complete.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYwojT8GdUbYSoggbvEFft
@alicoding
alicoding merged commit 3b94db8 into main Aug 12, 2026
16 checks passed
@alicoding
alicoding deleted the goal/0026-lifecycle branch August 12, 2026 04:43
alicoding added a commit that referenced this pull request Aug 15, 2026
…y (task #12)

Mill as MCP server for the first time -- §3.1 named three possible MCP
roles (server, client, host) back when only client was ever built
(§3.6, wrapping external tools as workflow nodes); this closes the
server role, already locked as fine ("fits §1.1 cleanly with zero
tension") since no LLM/agent loop runs inside Mill here -- an external
agent's own host connects and reads, the same shape as httpconnector
being an HTTP client. Does not touch or reopen the still-disputed
MCP-host question.

internal/adapters/mcpserving wraps modelcontextprotocol/go-sdk's server
role (the same SDK Mill already depends on for the client role) behind
a thin New()/Serve() pair -- new role, not a new dependency.
millmcpservice.go registers two URIs per entity type: a plain index
resource (mill://workflows, mill://requests, mill://lists,
mill://mcpservers) listing every current ID/Label/Description, and a
ResourceTemplate (mill://workflows/{id}, etc.) whose read returns the
full definition by calling the SAME Export* methods tasks #10/#11 just
built for the UI's own Export buttons -- one read-model, reused, not a
second one built for this.

Secrets stay excluded by the same construction Export* already
guarantees, verified independently through this new code path rather
than assumed inherited: a real test connects a genuine MCP client over
real HTTP, sets a real secret on a real HTTPRequest, reads it back
through mill://requests/{id}, and asserts the secret never appears in
the wire response.

Binds 127.0.0.1:8090 by default (MILL_MCP_ADDR overrides), loopback-only
deliberately -- a new unauthenticated local listener, same conservative-
default reasoning as the LAN-exposure question elsewhere in this repo.
Runs in both desktop and server-mode builds (no build tag); confirmed
the second listener doesn't conflict with server mode's existing :8080
via a real Playwright smoke run. A bind failure is logged, not fatal.

Explicitly NOT built here, named directly rather than silently
dropped: the write side (create/import via MCP Tools). §8's guardrail
policy is still OPEN, and programmatic writes are a materially
different risk than read-only exposure -- tracked as its own task
(#13), not folded into this one.

Verified: two new Go tests connect a genuine MCP client (the SDK's own
client role, not a mock, not a direct call into the handler functions)
over real HTTP -- resources/list returns the real registered set,
resources/read against a just-created real workflow returns its real
node data, an unknown ID returns a real resource-not-found error, and
the secret-exclusion test above. Full existing suite passes with
-race. go vet, golangci-lint (0 issues after fixing two real errcheck
findings on deferred session.Close() -- fixed using this repo's own
existing defer func() { _ = x.Close() }() convention, not suppressed),
ls_lint, check-loc.sh all clean. Documented in docs/SPEC.md §3.6.

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
Request lifecycle honesty: cancel_write, staleness tiers, badge fix (goal 0026)
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