mavgpud.service, the Maven GPU supervisor, is an enabled systemd user unit that
binds *:8080 and restarts on kill. The correx server wanted the same port, so
qa-stack died with BindException and the QA stack never came up. Worse,
qa-stack's --stop ran `fuser -k 8080/tcp`, which killed mavgpud rather than a
correx server; systemd then restarted it straight into the port it had just
freed, so it won the race every time.
Moves the default to 8090 in the four places that have to agree: ServerConfig,
ConfigLoader's fallback, the CLI's DEFAULT_PORT, and the TUI's -port flag. A
mismatch between any two of them is a client that cannot find its own server.
The machine-local halves are not in this diff and were applied on disk:
`~/.config/correx/config.toml` pinned `port = 8080` explicitly, which overrides
the code default, and `scripts/` is gitignored so qa-stack.sh's five references
(including the --stop kill, now aimed at 8090) live only on this box.
Verified live: the server binds 8090 and answers /health while mavgpud keeps
8080. Left open in #695: mavgpud also spawns a llama-server on :10000, which is
qa-stack's router port, and qa-stack pkills that pattern.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch's uncommitted WIP, committed together (entangled at file level).
Distinct pieces of work:
Freestyle QA fixes (this session):
- FileEditTool: pre-validate replace anchor in validateRequest — reject a
missing/ambiguous target BEFORE the approval gate, mirroring read/write's
file-not-found / read-before-write pre-checks. Shared not-found/ambiguous
messages between validate and execute so they can't drift.
- PlanGrounder: add `scanned` flag; when no RepoMapComputedEvent was recorded,
repoMapPaths is "unknown" not "empty workspace" — skip scope grounding
(which proves a path ABSENT) so real paths (apps/server/**) aren't falsely
rejected. Build-manifest check still runs.
- FreestyleDriver: wire scanned=(repoMap!=null); on plan rejection emit a
session-terminal WorkflowFailedEvent so a rejected run reads FAILED, not the
COMPLETED-lie (last verdict was the planning-phase WorkflowCompleted).
- ServerModule: resolve project-memory workspace root from the session's bound
workspace (sessionWorkspaceRoot) instead of boot-static pm.repoRoot(), fixing
the workspace-binding divergence (correx vs empty scratch dir). Retire tracked
in Vikunja #266.
- LaunchRegistrationRaceTest: join registered jobs before asserting launchCount
— computeIfAbsent returns the Job immediately but the fire-and-forget launch
body lagged awaitAll (the 49-vs-50 flake).
ACR concept-compiler experiment (pre-existing WIP on this branch):
- ExecutionPlanCompiler/Model/PlanLinter, #264 needs-seam (sessionArtifacts),
LSP diagnostics subsystem (LspDiagnosticEvents/Runner/Lsp4j), BootWorkspace,
config surface, workflow prompts/schemas, orchestrator advance-don't-rerun.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds an MCP host layer so external MCP servers (AST/LSP code-intelligence,
package resolvers, etc.) can be mounted at startup and surface their tools/list
as first-class Correx tools named mcp__<server>__<tool>. Each MCP tool is a
Tool+ToolExecutor, so it rides the normal ToolExecutor path and inherits tier
gating, receipts, and event-recorded execution (#5) with zero special-casing;
replay reads the recorded receipt rather than re-calling the server (#8/#9).
- McpProtocol/McpStdioClient/McpTool/McpMounter in infrastructure:tools
(stdio JSON-RPC 2.0, tools/* slice only). Capabilities empty; safety via
default T2 tier since external side effects are opaque.
- [[mcp]] config (id/command/env/tier) + array-of-tables parsing.
- Main wires mounted servers into extraTools (main + per-workspace paths) with
a shutdown hook; a server that fails to start is logged and skipped.
- Tests: in-process fake transport (handshake/list/call/error + tool mapping),
[[mcp]] parser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GenerationConfig only carried temperature/top_p/max_tokens/stop/seed. Added nullable
topK/minP/repeatPenalty, serialized to the llama.cpp and OpenAI-compat request bodies
via @EncodeDefault(NEVER) so an unset knob is omitted (the model keeps its own default)
and behavior is unchanged unless the operator opts in.
Surfaced as a new [sampling] config section feeding the default stage GenerationConfig
(the main agentic loop) through TomlWorkflowLoader + ExecutionPlanCompiler; the former
hardcoded temperature=0.7/topP=1.0 stage defaults now come from config. Talkie
chat/narration keep their own generation settings.
Vikunja #46 (task 76) — sampling half.
The kernel's ReAct-loop tuning constants (max tool rounds, read/rejection-loop
nudge thresholds, feedback issue cap, repo-map top-k/files-per-dir, docs catalog
cap, clarification-round cap, review-block confidence/retry cap, default
refinement, recovery/intent route budgets) were hardcoded in SessionOrchestrator/
DefaultSessionOrchestrator. Relocated to a new OrchestrationTuning value threaded
through the orchestrator constructor, mapped from CorrexConfig.orchestration in
Main.kt, parsed in ConfigLoader, written by CorrexConfigWriter. Defaults equal the
former constants so an absent [orchestration] section reproduces prior behavior.
Startup-load (not hot-reload); pure output-truncation caps left as constants.
Vikunja #46 (task 76).
Threads reasoning_content across inference calls and journal renders, filters
markdown noise out of L3 repo-knowledge retrieval, adds PlanLinter H3 checks,
and tightens filesystem tool output/dir-listing behavior surfaced by prior
live QA (see project_readloop_campaign memory).
Bundles three operator-reliability guardrails (Vikunja #28/#29/#30) plus the
in-flight branch WIP they were built on top of (reasoning_content capture,
operator/project profile editor, write-jail workspaceRoot fix) — the tree is
interdependent (SessionOrchestrator references reasoningArtifactId from the WIP)
and does not compile as separable subsets, so it lands as one commit.
Guardrails:
- #28 mid-stage steering: ClientMessage.SteerSession -> GlobalStreamHandler ->
orchestrator.submitSteering, reusing SteeringNoteAddedEvent + existing context
fold (advisory, non-authoritative; invariants #3/#7). Closes the gap where
steering typed off an approval gate was silently dropped.
- #29 shell-in-file guardrail: ShellInFileContentRule (core:toolintent) blocks a
file_write whose content is a bare shell command (e.g. "mkdir -p ..."); FileWriteTool
description now advertises auto-mkdir of parent dirs. Basename-allowlist so the
extensionless case is caught; scripts/Makefiles/multiline exempt.
- #30 pt1 capability-gap detector: deterministic CapabilityGapDetector maps stage
intent -> implied ToolCapability, compares to granted tools, emits advisory
CapabilityGapDetectedEvent in FreestyleDriver.lockAndRun. Recorded, never fails
the gate and never auto-grants (invariants #3/#4/#5). Reflection rung is pt2.
Verified: ./gradlew check green (whole tree).
The 'router' name was misleading — it's the always-on conversational front-end
(CHAT triage + STEERING into a running workflow), not a routing layer, and it's
distinct from InferenceRouter. Rename core:router -> core:talkie, package
com.correx.core.router -> com.correx.core.talkie, and RouterFacade/Config/State/
Repository/ContextBuilder/Projector/Reducer/Response -> Talkie*. Config section
[router] -> [talkie] (legacy [router] still read as fallback).
Persisted wire formats are preserved: ChatTurnRole.ROUTER and the
RouterNarrationEvent @SerialName("RouterNarration") stay, so existing event
logs still replay. RouterNarrationEvent the class is now TalkieNarrationEvent.
They are shared infra (the orchestrator uses them for workflow repo-knowledge,
not just the router chat layer), so [router.embedder]/[router.l3] was misleading.
Loader/writer use the new names; legacy [router.*] still read as a fallback.
Also notes the STEERING LLM-reformulation cost as a ponytail follow-up.
Found while live-QAing freestyle_planning on a 12B local model:
- list_dir tool: recursive, .gitignore-aware listing so weak models stop
flooding context with `ls -R` over node_modules/build/dist. Wired into
the fileRead toggle + advertised to the planner (architect_freestyle).
- ContextClassifier: assistantToolCall turns are STRUCTURED, so the token
pruner never shreds the model's own tool-call history — that was causing
amnesia loops (re-issuing calls it had already made).
- Retire instruction-doc LLMLingua pruning (DOC_SOURCE_TYPES emptied): it
fused load-bearing procedural text into unparseable soup. The static
block stays small by dropping CLAUDE.md at the loader instead.
- AgentInstructionsLoader: load only AGENTS.md, not CLAUDE.md — the latter
targets the outer assistant and polluted the agent's stage context.
- DefaultSessionReducer: WorkflowFailed flips session status to FAILED (was
stuck ACTIVE forever, so clients/approval loops never saw a terminal).
- ShellTool: run shell command lines (cd/&&/pipes) via `sh -c`; unrunnable
program is recoverable instead of an uncaught IOException killing the
stage; malformed argv (non-string/collapsed-array) rejected with guidance.
- llmlingua sidecar: cap force_tokens to max_force_token (big docs blew the
assert and 500'd, so doc pruning silently failed open).
Tests added/updated across all of the above.
Move embedder construction ahead of the engines so the context pack builder ranks
freeform turns by query relevance (query-conditioned selection). Default
compression_level 4 (format + static cache + token pruning + age-tiering) for a 12b
on a 16k window.
Enable autonomous QA through a remote OpenAI-compatible provider (NVIDIA NIM)
and harden the tool/approval path so unattended multi-stage runs complete.
- inference: add openai_compat provider (Bearer chat-completions for NIM/OpenAI),
dispatched by provider type "nim"/"openai"; key via api_key/api_key_env.
- server: bind configured [server] host/port instead of a hardcoded 8080;
POST /sessions accepts an optional `intent` (WS parity) for intent-driven workflows.
- kernel: thread the bound operator profile's approval_mode into per-tool gating so
auto/yolo enable unattended approval (engine still consulted; policy/plane-2 BLOCK
stays terminal); on a recoverable tool failure feed the tool's arg-schema back into
context so the model self-corrects instead of repeating a malformed call.
- tools: split deletion out of file_write into a separate, explicitly-named file_delete
tool — a model can no longer delete a file by getting a write-mode parameter wrong.
- server: add GET /metrics/tool-reliability — per-model tool-call validity from the
event log (measurement groundwork for capability-aware routing).
- docs: update AGENTS.md across kernel, tools, server, inference.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root Child DOX Index assembled, plus a per-module AGENTS.md across the tree
(core/*, infrastructure/*, apps/*, testing/*, and docs/examples/frontend/etc),
each following the DOX section shape: Purpose, Ownership, Local Contracts,
Work Guidance, Verification, Child DOX Index.
On session start, discover CLAUDE.md and AGENTS.md at the bound workspace root and inject
their (concatenated, header-labeled) contents as an L0 system context entry for every stage —
mirroring the .correx/project.toml ProjectProfile path end to end. Recorded as
AgentInstructionsBoundEvent (invariant #9) so replay reads the recorded fact, not the live
file; folded into SessionState.boundAgentInstructions and rendered by buildAgentInstructionsEntry
right after the project profile. AgentInstructionsLoader reads root-only, both files if present.
Bound via ServerModule.bindAgentInstructions alongside bindProjectProfile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Promote an auto-captured idea from the cross-session board into the curated per-repo
profile at <workspaceRoot>/.correx/project.toml as a `conventions` entry. New
IdeaPromotedEvent tombstones the idea off the board (IdeaReader treats it like a
discard) and records the promotion as a fact; the server PromoteIdea handler loads
the profile, appends the idea text (deduped via ProjectProfile.withConvention), and
writes it back. Adds ProjectProfileWriter (escape-aware TOML serialize/write) and
makes SimpleToml's reader unescape \\ and \" symmetrically with the writers
(fixes a pre-existing read/write asymmetry surfaced by the round-trip test).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- EventStoreLatencyProbe: times a side-effect-free lastGlobalSequence read,
wired into HealthMonitor; HealthConfig.eventStoreWarnLatencyMs (BACKLOG A §4)
- LlamaServerHealthProbe: liveness + tokens/sec degradation trend, injectable
client (HttpLlamaLivenessClient over Ktor); deterministic unit tests
- llama probe left unregistered (TODO) — HttpClient not in monitor scope yet
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Third HealthProbe (after disk + llama): times a cheap lastGlobalSequence()
read as an event-store responsiveness heartbeat — DEGRADED on throw or when
latency exceeds eventStoreLatencyWarnMs (default 500). Read-only by design
(no probe writes into the source-of-truth log; Invariant #1). Plugs into the
existing HealthMonitor; no new events. Observability spec §4.
Also wraps a long line in LlamaServerHealthProbeTest to clear a detekt
MaxLineLength finding introduced in aaf896d.
Second HealthProbe (after disk-watermark): liveness via GET /health on the
configured llama base URL + tokens/sec degradation watch derived from recorded
InferenceCompletedEvents (windowed average vs configurable threshold, opt-in via
llamaTpsWarnBelow). Plugs into the existing HealthMonitor edge-detection loop;
no new events. Observability spec §4.
[tools.research].enabled now defaults true, matching shell/file tools. Safe because
web_fetch is T2 — nothing leaves the machine without operator approval regardless of
the flag; the flag only controls whether the tools are registered. web_search (T1) hits
only the configured SearXNG endpoint.
Makes the research feature runnable end-to-end, off by default.
- config: [tools.research] (enabled, searxng_url, max_results, max_fetch_bytes).
- registration: web_search/web_fetch are built into BOTH the default and per-workspace
tool registries when research.enabled, sharing one HTTP client threaded from Main
(none built on the static path). Egress stays harness-enforced: web_fetch is T2
(operator-approved) and the existing NetworkHostRule still applies.
- workflow: examples/workflows/research.toml — decompose → gather → report, with the
three artifact schemas and prompts. Fan-out (search per sub-question, fetch per source)
runs as repeated tool calls inside the gather stage (Correx has no parallel agents);
per-source synthesis into the dossier is the compression step, so the report stage
consumes summaries, never raw pages. ResearchWorkflowTest validates the graph contract.
To run: set [tools.research].enabled, register the 3 [[artifacts]], copy research.toml +
prompts + schemas into the workflows dir, start SearXNG. Launch like any workflow (the
T2 fetch approval surfaces as an approval card; the report opens in the artifact viewer).
Follow-ups (noted, not blocking): batch fetch-approval at the source-list level (§3),
a dedicated SourceFetched/LowQualityExtraction event (quality + content hash are already
in tool-result metadata), dynamic per-session egress allowlist, and the web approval client (§6).
Health states are events. A HealthMonitor polls registered HealthProbes on an
interval and appends HealthDegraded/HealthRestored only on a status transition
(hysteresis), so health is observable, replayable, and renderable like every
other fact in the log. The probe reads nondeterministic environment state and
records it at observe-time (Invariant #9); HealthProjection rebuilds current
health by replay, never re-probing (Invariant #8). Monitor is live-only, started
in ServerModule.start alongside narration; seeded from the projection so a
restart does not re-emit a degraded the log already records.
First concrete probe: DiskWatermarkProbe — event-log + CAS size and growth rate,
warning before the 2am surprise. Read surface mirrors `correx stats`:
GET /health/checks replays the system session and `correx health` renders it.
Health events ride the reserved __system__ session, filtered from operator
session listings.
LLAMA_SERVER (liveness + tokens/sec trend) and EVENT_STORE (append/fsync latency)
probes plug into the same HealthProbe framework as follow-ups.
Every TUI config-editor save regenerates the TOML through
CorrexConfigWriter, whose [router.narration] section never wrote
model_id — one save and narration silently fell back to the main
model, queueing every narration behind slow 9B inferences (live QA:
approval narrations arrived minutes late, after the workflow had
already failed). The writer now persists it and the field is editable
in the TUI (blank clears it).
#2: approval pauses narrate from ApprovalRequestedEvent (carries
tool/tier/preview/stage directly) instead of OrchestrationPaused +
a pending-approvals map the next event had not yet populated — the
narrator always lost that race and produced generic text.
#3: NarrationTrigger now carries the triggering event's stageId; the
narration status line and RouterNarrationEvent.stageId use it instead
of the router projection's currentStageId, which terminal events have
already cleared ('Stage: none').
#9: ExecutionPlanLocked -> plan.locked and ArtifactValidated ->
artifact.validated mapped to the TUI (Go: feed lines, plan shows the
compiled stage chain); ArtifactValidating explicitly dropped.
#10: blank router turn guard — a length-finish with empty text now
emits an explanatory turn naming the max_tokens budget instead of a
silent blank; blank steering responses no longer emit steering notes.
#12: resumeAbandoned only auto-resumes sessions whose last event is
within orchestration.resume_abandoned_max_age_minutes (default 24h,
0 disables); staler sessions stay parked for POST /resume. Knob is
TUI-editable and persisted.
The line-based parser treated 'conventions = [' as the complete value,
binding conventions to listOf("[") and dropping every entry on the
following lines. Accumulate value lines until the top-level bracket
closes (quote-aware), skipping comments inside the array.
[router.narration] model_id selects which provider handles narration turns
instead of generic capability routing — lets a small/fast model own narration
while the main model keeps CHAT/STEERING.
- NarrationSettings.modelId parsed from TOML, threaded via RouterConfig
.narrationModelId into DefaultRouterFacade.narrate (3-arg route)
- DefaultInferenceRouter: exact-id match against healthy providers, with a
post-select health check; any miss logs WARN and falls back to capability
routing (never fails the narration turn)
- onUserInput unchanged — only narrate uses the pinned model (tested)
Two operator features over the existing WebSocket protocol, plus a tui-go nav fix.
Config editor (g / palette "config"):
- core:config gains a thin registry stack: OrchestrationKnobs ([orchestration]
block), CorrexConfigWriter (round-trip TOML serializer; parseToml(write(c))==c),
ConfigHolder (AtomicReference live config), and EditableConfig (allowlist of
editable fields + patch applier; security fields are absent so they can never
be patched).
- ConfigService validates -> persists (atomic temp+move) -> swaps the holder ->
rebuilds config-derived services. Live-apply is scoped to safe seams: stage
timeout, compaction threshold (now a supplier), router knobs (routerFacade
rebuild), and personalization/project toggles all take effect on the next
session/turn; in-flight sessions keep the config they started with.
server.host/port are persisted + badged "restart required", not hot-swapped.
- Protocol: GetConfig / UpdateConfig / ConfigSnapshot; TUI overlay with
type-aware editing (bool toggle, enum cycle, inline int/string edit) and save.
Artifact viewer (v / palette "artifacts"):
- Server assembles a session's artifacts from the event log and resolves bytes
from the CAS store (StreamQueries.listArtifacts) — a replay-neutral snapshot
read. TUI overlay lists artifacts and shows scrollable content.
tui-go fix: workflow failure no longer clears selectedID (which yanked the user
to the list); listNav lands on the first session when nothing is selected
instead of wrapping to a random one.
Config is not event-sourced (it lives in TOML); editing stays outside the log.
The router/narration model behaviour was hardcoded (Main built RouterConfig()
with defaults). Surface it under the [router] section so it's tunable without a
rebuild:
[router] conversation_keep_last, retrieval_k, token_budget
[router.generation] temperature, top_p, max_tokens (chat/steering)
[router.narration] temperature, top_p, max_tokens, max_per_run
ConfigLoader parses the new sections (adds asDouble); Main maps the config-layer
RouterConfig onto the domain RouterConfig and threads narration.max_per_run into
ServerModule. All values default to the previous constants, so behaviour is
unchanged when the sections are absent. Documents the block in sample-config.toml
and adds parser tests for present/absent cases.
Resolve a client-supplied workspace dir safely: canonicalize via toRealPath,
reject privileged locations, clamp to [tools] allowed_workspace_roots (permissive
when unset, logged), fall back to the boot default on any rejection. PathJail made
non-internal so the server can reuse its symlink-safe containment. (Axis 2 Phase A, task 4.)
Users declare artifact kinds in [[artifacts]] (id + schema_path to a JSON-schema file + llm_emitted); ConfigArtifactKind registers them at startup via createWorkflowLoader(extraKinds). New JsonSchemaValidator validates any non-built-in kind generically against its declared deriveJsonSchema(), so an LLM-emitted custom kind is checked against its shape, never trusted. Removed the dead payloadSerializer from the ArtifactKind contract. Schema source is path-to-file (not inline TOML — the hand-rolled parser can't nest).
[[models]] config (ModelConfig/ModelsSettings) parsed by ConfigLoader;
InfrastructureModule.createModelManager + modelConfigToDescriptor; Main.kt
managed boot path spawns the default llama-server when [[models]] is present
(static [[providers]] path preserved when absent) and kills it on shutdown.
Plan: docs/plans/2026-05-31-model-lifecycle-management.md (slice 1 of 5).
Implements the full vertical slice for invariant #9 tool-call assessment:
- core:toolintent — new module with ToolCallRule seam, ToolCallAssessor,
WorldProbe/FileSystemWorldProbe, WorkspacePolicy, PathContainmentRule,
and RiskMapping (assessment → RiskSummary / AssessedIssue)
- core:tools — ToolCallAssessmentRecord + ToolInvocationRecord.assessment
field; DefaultToolReducer handles ToolCallAssessedEvent (replay proof)
- core:config — ToolsConfig gains workspaceRoot + privilegedLocations;
ConfigLoader parses both; DEFAULT_PRIVILEGED_LOCATIONS built-in
- core:kernel — OrchestratorEngines gains toolCallAssessor/workspacePolicy/
worldProbe fields; SessionOrchestrator.dispatchToolCalls runs
runPlane2Assessment before the tier gate: BLOCK → hard-reject without
executing; PROMPT_USER → elevates tier into approval path with plane2Risk
in the ApprovalRequestedEvent
- apps/server — constructs PathContainmentRule + ToolCallAssessor +
WorkspacePolicy from config and wires them into OrchestratorEngines
Assessment is recorded as ToolCallAssessedEvent (environment observed once,
facts stored, replay reads events — invariant #9). Assessor and WorldProbe
are only invoked on the live orchestrator path, never in replay.
Remove the wall-clock approval timeout that auto-rejected pending approvals.
The timeout modeled machine latency, but approvals are human latency
(unbounded); it also created an intent/outcome divergence where a human
approving at the same instant the timeout fired was silently overridden by
the auto-reject. Approvals now block until the operator decides; the
resolved-request dedup still guards against double-submit.
Add ServerMessage.ApprovalResolved and map ApprovalDecisionResolvedEvent to
it in DomainEventMapper so clients are notified when any approval is resolved
(by anyone) — letting a second connected client clear its prompt. This is the
event-sourced replacement for the removed timeout notification path.
Drop the now-dead ApprovalConfig (timeout_ms) and its loader/test/sample-config
references. ApprovalStatus.TIMED_OUT is retained for replay of historical events.
Adds [router.embedder] and [router.l3] sections to CorrexConfig with
backend selectors. Ships LlamaCppEmbedder that hits llama.cpp's
/embedding endpoint (handles OpenAI-compatible, simple, and array
response shapes; validates dimension). InfrastructureModule gains
createEmbedderFromConfig and createL3MemoryStoreFromConfig that
dispatch on backend value.
Defaults preserve current behavior (noop embedder + in-memory L3).
Switching to "llamacpp" / "turbovec" is a config-only change — no code
edits required. For turbovec backend, the bundled python sidecar
script is extracted from classpath to ~/.cache/correx/ on first use.
Adds ProviderConfig + providers list and per-tool enabled flags to
CorrexConfig, and rewires apps/server/Main to build the provider list
from config instead of the hardcoded buildLlamaProvider() factory. Env
vars (CORREX_MODEL_ID, CORREX_MODEL_PATH, CORREX_LLAMA_URL) remain a
fallback only when no providers are declared in config.
Completes slice A of the config layer alongside commit 0834c70 which
already shipped the TOML parser upgrade and sample config — those
parser changes referenced these types but were committed prematurely
in isolation; this commit makes HEAD buildable.
- Add support for nested table headers ([tools.shell], [tools.file_read], etc.)
- Add support for inline tables (capabilities = { General = 1.0, Coding = 0.7 })
- Add support for JSON-style arrays (shell_allowed_executables = ["bash", "sh"])
- Maintain backward compatibility with old CSV formats (deprecated, logged as warnings)
- Refactor buildConfig() to handle new value types (Map, List, Boolean, Number)
- Update docs/sample-config.toml to use clean TOML shapes
- Add comprehensive tests for inline tables, nested sections, JSON arrays, and fallback parsing
- Remove ugly workarounds: capabilities no longer a CSV string, tool flags now in proper nested sections
Deletes 21 Module.kt scaffolding objects that were never wired into any DI
registry. Removes unused imports across 8 production and 3 test files.
Restores StageExecutor, CyclePolicyResolver, PolicyValidation, and
ReplayContractTest — these are deferred features, not dead code.