Skip to content

feat(tui): isolate nine Windows process interfaces#685

Merged
huangzesen merged 10 commits into
dev4/windows-tui-native-20260718from
dev4/windows-nine-independent-ports-20260718
Jul 19, 2026
Merged

feat(tui): isolate nine Windows process interfaces#685
huangzesen merged 10 commits into
dev4/windows-tui-native-20260718from
dev4/windows-nine-independent-ports-20260718

Conversation

@huangzesen

Copy link
Copy Markdown
Collaborator

Summary

This is the replacement for #684. It keeps the reviewed in-process Windows query commit and adds nine unrelated interfaces, each with its own Contract, Port, POSIX implementation, Windows implementation, minimal caller seam, and focused tests.

There is deliberately:

  • no coupling between the nine interfaces;
  • no cross-interface Port/type/status/policy/adapter reuse;
  • no shared helper or root interface graph;
  • no process-observation fallback where lock/manifest/heartbeat authority is required.

Nine independent interfaces

  1. DuplicateLaunchCheck — non-creating .agent.lock lease verdict before ordinary launch.
  2. SpawnProcess — typed process creation command.
  3. StopProcess — identity-verified stop command.
  4. TerminationObservation — observation-only exact-incarnation query.
  5. AgentActionState — read-only Running / Stopped / Unknown action enablement.
  6. AgentInventory — advisory inventory with typed query failure and explicit unknown/conflict.
  7. MigrationGuard — non-creating migration lease gate; Unknown blocks.
  8. AgentCounter — semantic count from registry/manifest/heartbeat evidence.
  9. TUIInstanceGuard — held singleton token for direct interactive TUI startup.

Review result

  • 9/9 independent semantic reviews: PASS
  • One real AgentInventory blocker was found: the human/ mailbox placeholder (admin: null) was initially surfaced as an unknown agent. The candidate was amended with the canonical exclusion plus a regression test, then independently re-reviewed: PASS.
  • An initial AgentActionState review incorrectly treated the interface's own Contract/Port/platform adapters as forbidden. The authoritative contract requires each interface to own those pieces; a corrected independent review found no blockers: PASS.

Consolidated validation

The nine reviewed commits were composed on the exact #684 head. The only textual conflict was in internal/process/launcher.go; resolution preserves DuplicateLaunchCheck as the independent pre-launch authority and SpawnProcess as the independent execution port.

Passed on the integrated head:

  • git diff --check
  • gofmt over every changed Go file
  • mechanical nine-package/no-cross-reference/no-root-graph scan
  • go test -count=1 ./...
  • go vet ./...
  • go build ./...
  • Linux amd64 full build
  • Windows amd64 and arm64 full build + vet
  • Windows amd64 and arm64 test compilation for all nine new packages and affected caller packages

PR lineage

No merge is requested as part of this handoff.

huangzesen and others added 10 commits July 18, 2026 15:00
Add internal/duplaunch, the DuplicateLaunchCheck policy interface: it
returns allow/block/unknown from a private, non-creating probe of the
kernel's .agent.lock lease (POSIX non-blocking flock; Windows
fail-immediately LockFileEx on the msvcrt-locked byte). Unknown blocks.

The LaunchAgent launch gate now consults this check instead of process
observation; ForceLaunchAgent still bypasses it for /refresh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New internal/spawnprocess owns exactly one authority: creating at most
one child from a typed Spec (executable/args/env/dir/stdio), returning a
Child identity or a typed Error (invalid_spec/not_found/start). Success
implies no readiness, ownership, or duplicate-launch safety; POSIX and
Windows creation live in private build-tag adapters (CONTRACT.md).

The one low-level launch seam (launchAgentUnsafe) now creates the child
via spawnprocess.Spawn; LaunchAgent/ForceLaunchAgent return
*spawnprocess.Child, with type-only follow-through in
waitForLaunchHeartbeat and headless RunSpawn (PID/Release).

Validation: go test ./internal/spawnprocess ./internal/process
./internal/headless (ok); go build ./... and go test -run '^$'
-exec=/usr/bin/true for windows/amd64 and windows/arm64 (ok); gofmt
clean on touched files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deliver one native stop request to an immediately identity-verified
target, with distinct stale-identity / access-denied / already-absent /
unsupported / unknown outcomes. POSIX verifies the kernel command name
then signals; Windows verifies image name and liveness and terminates on
the same held handle. Caller: the Windows stopTUIProcess stub no longer
fakes success.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Own Contract/Port with private POSIX (procfs/sysctl start-token) and
Windows (query+wait rights, creation-time token) adapters; observation
only — never signals, stops, or escalates, and Unknown/Error are never
converted into Exited. Minimal caller seam: stopTUIProcess's existing
bounded exit polls now observe the pinned incarnation instead of the
ad-hoc signal-0 probe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add internal/actionstate: a read-only policy that computes start/restart/
refresh enablement from its own heartbeat observation. Running, Stopped,
and Unknown stay distinct; malformed, unreadable, or future heartbeats
are Unknown and never enable an action. Absence proof is classified in
private POSIX/Windows adapter files. The /cpr seam now revives only on a
Stopped verdict and surfaces Unknown with its reason instead of treating
it as dead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Independent Query interface over one project .lingtai root: one record per
agent directory with explicit running/absent/unknown/conflict presence from
the directory's own manifest/status record checked against a private native
PID existence probe (kill(0) on POSIX, query-only OpenProcess on Windows).
Whole-query failure is a typed *QueryError, never a silent empty list.
Minimal caller seam: the list command's shared warnings section prints
unresolved (unknown/conflict) records and typed query failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MigrationGuard answers allow/block/unknown for one offline
migration/destructive action on an agent working directory, using only a
non-creating probe of the kernel's .agent.lock (POSIX flock; Windows
one-byte LockFileEx range probe). Unknown blocks, and the zero-value
verdict is Unknown so an uninitialized decision can never permit.

The m036 SQLite backfill migration now gates candidate discovery on this
verdict instead of process-table observation, and rechecks the lock
immediately before each rebuild since the user confirmation prompt can
stay open indefinitely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…evidence

Replace the ps/WMIC process-count startup counter with a self-contained
AgentCounter query (internal/agentcounter): registry.jsonl + .agent.json
+ .agent.heartbeat are the only authority, with explicit Known/Unknown
state and Issues so failure never passes silently as zero. Private
POSIX/Windows adapter files; minimal caller adapter in main.go.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TUIInstanceGuard: internal/tuiinstance acquires/releases the one TUI
singleton ownership token per project scope. Only Acquired permits the
protected interactive startup in runPreparedApp; Contended and Unknown
refuse distinctly. POSIX flock and Windows share-mode-0 handle locks are
private to this interface; no process scan, PID hint, record age, or
file-deletion bypass exists.

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