Wire the display-only ArchitectContradictionChecker (eae0a0c) live: ServerModule.start()
subscribes to the architect stage's `design` ArtifactCreatedEvent, resolves the decision
text (prefers the design schema's `approach` field; falls back to the capped artifact
text), runs checker.check(...), and appends the non-null PossibleContradictionFlaggedEvent.
Live-only and never halts a stage (runCatching). Checker built in Main.kt gated on
project.enabled (same L3 "project:" namespace ProjectMemoryService.persist populates) and
threaded into ServerModule as a nullable param. Replaces the standing TODO(wiring) in Main.kt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
f107ff5 (idea-board -> profile promotion) moves to RETRO. Plan-comparison view marked
BLOCKED: no plan-candidate event exists server-side (only workflow.proposed), so there
is no wire shape to render or unit-verify until a multi-candidate planner lands.
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>
ab7e4be (egress activation) and 4b0024f (batch source-list approval) move to RETRO;
§D is now fully landed except the standalone web-approval client (needs network/SearXNG).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
POST /sessions/{id}/approve-sources extracts distinct hosts from a source list
(SourceListHosts.extract) and emits one EgressHostsGrantedEvent for the session, so
the live per-session egress allowlist auto-clears subsequent web_fetches to those
hosts — approve the list once instead of per-URL.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ToolCallAssessmentInput gains sessionEgressHosts; SessionOrchestrator resolves it at
the assessment build site by folding EgressHostsGrantedEvents through
EgressAllowlistProjection. NetworkHostRule now enforces session-granted hosts (union
with static), replacing the emptySet() TODO — the allowlist built in 027ff1f is live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PlanStage gains a `writes` field (planner-declared workspace-relative paths/globs);
PlanDerivedManifest.deriveAllowedWrites + combine union it with the static baseline.
ExecutionPlanCompiler wires the union into StageConfig.writeManifest, enforced by the
existing ManifestContainmentRule via the same glob matcher (no parallel matcher).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- pending approvals are a navigable queue (PendingQueue/PendingIdx; ↑↓/jk, count
shown), no modal stacking; resolve removes the specific gate by request id
- y/n/e keys (approve/reject/steer); T0–T2 act on one key, T3+ requires arm+confirm
("press y again"), any other key disarms; reject/steer/auto never gated
- snapshot restore now rehydrates the full pending list, not just the first
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Table-driven render assertions (37 cases over 42 protocol Type* constants), driven
through applyServer and asserted ANSI-stripped on stable defining text. Coverage
guard parses every Type* from protocol.go and fails if a kind is neither covered
nor explicitly classified non-rendering. Pins width/theme/timestamp; no behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
R opens a navigable overlay of recent sessions (GET /sessions: short id, status,
workflow/stage, relative age); enter resumes via POST /sessions/{id}/resume, which
replays onto the existing global /stream (no WS re-dial). Fetch/resume errors surface
in the overlay, never panic. stdlib-only (ws.Client.HTTPBase derives the base URL).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
renderMarkdown wraps charmbracelet/glamour (dark style, per-width memoized) and is
hooked into the router chat-turn render path; falls back to plain content on any
glamour error so the TUI never crashes or loses text. Other roles/UI untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`go 1.26` is not a fetchable/released toolchain, so released Go cannot build the
module ("toolchain not available"). 1.24.2 is the minimum the deps require
(charmbracelet/x/ansi needs >= 1.24.2) and is a real toolchain; the directive is a
floor, so newer Go still works. Bump if a specific newer release is intended.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pause narrations are tagged with a pauseKey; a per-session pending-pause set gates
the lane worker so a resolved/resumed pause is skipped instead of blending with the
current status. ApprovalDecisionResolved drops the specific request's pause;
OrchestrationResumed clears all session pauses. Replay-safe (no recorded events change).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- StaticFinding/StaticFindingSeverity + StaticFindingsRecordedEvent (registered + test)
- StaticAnalysisRunner over an injectable CommandRunner; parses compiler/ktlint/detekt
finding formats (lenient) into StaticFindings
- excludeStaticFindingsFromReview pure filter, live-wired into SessionOrchestrator
context assembly so static-tool findings are kept out of the reviewer's context
- static_check pipeline stage left as a documented TODO (needs an event-emitting
deterministic stage-type seam that doesn't exist yet)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- EgressHostsGrantedEvent (registered + serialization test): additive per-session
egress grants (e.g. an approved research source list)
- EgressAllowlist pure union helper + EgressAllowlistProjection (folds grants per
session); NetworkHostRule delegates to the union, preserving exact/suffix semantics
- rule not yet fed the session set live (ToolCallAssessmentInput has no sessionId);
precise TODO(wiring) left. batch fetch-approval skipped (needs approval-flow surgery)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- promote research fetch quality + content_sha256 from ToolExecutionCompletedEvent
metadata to first-class SourceFetchedEvent / LowQualityExtractionEvent (registered
+ serialization test); existing metadata write kept intact (additive)
- emitted from SandboxedToolExecutor on research-fetch tool completion, guarded on
the url/content_sha256/quality markers; reuses the extractor's LOW_QUALITY marker
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- PossibleContradictionFlaggedEvent + RelatedDecision (registered + serialization test)
- ArchitectContradictionChecker: L3 similarity retrieval over prior decisions,
threshold-gated, fake-testable; non-blocking (surfaces candidates only)
- live wiring left as documented TODO (needs an architect-decision emit hook +
in-session decision embedding into L3)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- StageCheckpointPassed/FailedEvent (registered + serialization test): per-stage
reconciliation of produced artifacts vs the locked plan's produces-slots
- StageCheckpointReconciler (pure) + tests
- emitStageCheckpoint wired in after verifyProduces, runs regardless of its
pass/fail (verifyProduces still owns the halt); gated on an ExecutionPlanLocked
in the session log so non-plan workflows are unaffected
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- BriefEcho model + BriefEchoMismatchEvent (registered + serialization test)
- BriefEchoExtractor: parses the planner's brief_echo block and the original
brief's referenced-files/symbols/acceptance-criteria dimensions
- BriefEchoComparator: conservative diff — a dropped acceptance criterion or an
invented file path halts; dropping a file/symbol alone does not
- checkBriefEcho gate chained into the post-stage flow after groundBriefReferences;
opt-in via stageConfig.metadata["briefEcho"], fails the stage (retryable) on
divergence with re-grounding feedback
- prompt/workflow activation (planner.md emitting brief_echo; role_pipeline
metadata) left as live-QA follow-up
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Standalone SceneValidator(content) -> ValidationReport: header well-formedness,
ExtResource/SubResource id resolution, node parent-path integrity. Not wired into
any pipeline (validation layer only; scene editing is a later epic). (BACKLOG I)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Conservative type-declaration pattern (class/interface/struct/enum/record);
no method capture to avoid false positives. (BACKLOG I)
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>
- repomap: tag prefix match requires trailing ':' so /repo can't match /repo2
(L3RepoKnowledgeRetriever filter + ProjectMemoryService write; the existing
existsByTurnIdPrefix check already included the delimiter)
- WorkspaceStateProbe fingerprint joins entries with newline to match docstring
- regression test for /repo vs /repo2 non-collision
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Synthesize the five 2026-06-13 design specs and session memory into a
single outstanding-work list. Header rules: resolved items move to
RETRO.md; every finished feature gets a live-QA plan before it counts as
resolved. Add docs/qa/TEMPLATE.md as the observable-evidence QA form.
OverlayIdeas (opened with I, global/cross-session) lists the idea board in the
artifact-viewer shell: ↑↓ to move, x/d to remove (sends DiscardIdea, optimistic
drop), esc to close. Pull-based via ListIdeas -> idea.list.
ListIdeas -> idea.list (StreamQueries folds the board from the log, replay-neutral)
and DiscardIdea -> append IdeaDiscardedEvent landed in the capturing session, then
reply with the refreshed board. IdeaDto on the wire.
The rubber-duck path emits a fenced {"ideas":[…]} block; Ideas.parse strips it
from the chat turn and onUserInput records an IdeaCapturedEvent each (CHAT only).
IdeaReader folds eventStore.allEvents() into the active cross-session board, and
DefaultRouterContextBuilder injects a capped "## Ideas" entry — router-only, so
workflow stages never see it. Extended SYSTEM_PROMPT option (d) to capture ideas.
IdeaCapturedEvent(ideaId, sessionId, text, timestampMs) + IdeaDiscardedEvent
(ideaId, sessionId) tombstone, registered in eventModule. The cross-session idea
board is rebuilt from these via eventStore.allEvents(); discard hides without
deleting (invariant #1).
Render a workflow.proposed frame as a single-select picker with a manual-answer
slot: ↑↓/jk to choose, enter to launch, e for custom, esc to peek away. Picking
a candidate sends StartSession (and focuses the launched session); the custom
slot continues the conversation via ChatInput. Reuses the modal helpers from the
clarification view.
Map WorkflowProposedEvent to a workflow.proposed ServerMessage carrying the
candidates + original request. No new ClientMessage: a pick reuses StartSession
(launch the chosen workflow seeded with the request) and the manual-answer slot
reuses ChatInput.
The triage system prompt now appends a fenced json propose_workflows block;
WorkflowProposals.parse strips it from the visible chat turn and onUserInput
records a WorkflowProposedEvent (CHAT only). Candidate ids are validated against
the registered workflows (invariant #7) so an invented id never reaches the
operator. Bumped a token-budget test for the larger prompt.
ProposedWorkflow + WorkflowProposedEvent(sessionId, proposalId, prompt,
candidates, originalRequest), registered in the polymorphic eventModule.
Records the router's triage suggestion of candidate workflows as a
non-authoritative fact the operator acts on.
Replace the inline "Q:"-prefixed-summary convention with a structured
`questions` array of {prompt, options?, multiSelect?, header?} objects that
rides in the analysis artifact (allowed by additionalProperties:true, skipped
by JsonSchemaValidator). The kernel parses it to drive the producer-exit
clarification loop; the TUI renders it as an interactive form.
Render stage clarification questions as an AskUserQuestion-style form:
header chip, prompt, selectable option chips, and a free-text custom slot.
Arrow/hjkl nav, space to select, e for custom, enter to submit. Submit
guards on all-answered and sends ClarificationResponse; the parked stage
re-runs with the answers in context.
Surfaces the clarification loop over the wire: ServerMessage.ClarificationRequired
(clarification.required, carrying the structured questions) is mapped from
ClarificationRequestedEvent; ClientMessage.ClarificationResponse carries the
operator's answers and is routed to orchestrator.submitClarification on both
the global and session streams. Reuses the core ClarificationQuestion/Answer
types as the wire shape. Round-trip tests for both directions.
When a stage produces an LLM artifact carrying a non-empty questions
array, the orchestrator parks the run, records ClarificationRequested,
awaits the operator's answers (submitClarification seam), records
ClarificationAnswered, injects the answered Q&A as an L2 USER entry, and
re-runs the same stage so the questions are resolved by the role that
asked them. Runs on a dedicated pendingClarifications map and the
enterStage success-branch loop, so a clarification round never consumes
the failure-retry budget; capped at three rounds. Integration test covers
the happy path and the cap.
Adds the vocabulary for a stage raising open questions to the operator:
ClarificationQuestion/ClarificationAnswer value types, the
ClarificationRequested/ClarificationAnswered events (registered in the
event module), a ClarificationRequestId, and ClarificationQuestions.parse
— a lenient, fence-tolerant reader that pulls an optional questions array
out of an LLM artifact (shared by orchestrator and server).
When given a directory path, file_read now returns its entries (sorted,
sub-directories suffixed '/') instead of failing. The tool description
and path-param schema advertise this so the model discovers it rather
than giving up after a missing-file probe. Generalises file_read into a
'read this path' primitive; the existing PathJail/Plane-2 containment
already covers directories.
The schema declared sources as an array of objects, which correx's flat
JsonSchema model can't represent (JsonSchemaProperty has no nested
properties). The strict config loader rejected it at startup, silently
disabling the source_dossier artifact kind. Flatten sources to an array
of strings (URL-prefixed per-source notes), update the gather prompt to
match, and add ResearchSchemaLoadTest to guard every shipped research
schema against the strict loader.
[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).
The research feature is "no new architecture" (spec §1) — composition of existing
mechanisms. The only genuinely new code is the two web tools + the HTML→markdown
extractor (spec marks them "(new)"), and those belong with ShellTool/FileReadTool
in :infrastructure:tools, not in a separate module.
Moves extract/ + web/ from the short-lived :infrastructure:research module into
:infrastructure:tools (packages com.correx.infrastructure.tools.{extract,web}); adds
jsoup + ktor-client there; removes the module from settings.gradle. No behaviour change
— 44 tests green.
Searches the self-hosted SearXNG JSON API: local-first, no API keys, no third-party
telemetry. Hits only the configured endpoint, so unlike web_fetch there is no
arbitrary-host egress to police — T1. Returns the top-N results as a readable list
(title / url / snippet) with the result URLs in metadata so the fetch stage can act on
them and the operator can approve the source list rather than each URL.
Tested with Ktor MockEngine (query encoding, result parsing/cap, empty + error paths).
Completes the §3 "two tools" pair; with slice-1 extraction this is the deterministic
+ network-tool foundation of the research workflow.