feat(tasks): cite-before-claim gate on completion

Completing a task that declared affected_paths but saw no matching write this
session is now blocked ("marked done without doing it"), escapable with force.
TaskUpdateTool reads the session's writes through a SessionWrites port whose
adapter folds SessionContext (writes = recorded receipt.affectedEntities), and
matches them against the task's affected_paths globs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 21:31:08 +00:00
parent 30321e08a5
commit 6b5a758081
6 changed files with 101 additions and 1 deletions
@@ -63,6 +63,7 @@ import com.correx.core.toolintent.rules.NetworkHostRule
import com.correx.core.toolintent.rules.PathContainmentRule
import com.correx.core.toolintent.rules.ReadBeforeWriteRule
import com.correx.core.toolintent.rules.ReferenceExistsRule
import com.correx.core.toolintent.rules.WriteScopeRule
import com.correx.apps.server.freestyle.FreestyleDriver
import com.correx.apps.server.inference.summarizeWithInference
import com.correx.core.kernel.orchestration.JournalCompactionService
@@ -239,6 +240,7 @@ fun main() {
taskArtifactResolver,
taskSessionResolver,
com.correx.apps.server.tasks.EventStoreSessionFactRecorder(eventStore),
com.correx.apps.server.tasks.EventStoreSessionWrites(eventStore),
)
val toolRegistry = InfrastructureModule.createToolRegistry(
buildToolConfig(
@@ -266,6 +268,7 @@ fun main() {
PathContainmentRule(),
ReadBeforeWriteRule(),
ReferenceExistsRule(),
WriteScopeRule(),
ManifestContainmentRule(),
ExecInterpreterRule(toolsConfig.interpreterExecutables.toSet()),
NetworkHostRule(
@@ -0,0 +1,21 @@
package com.correx.apps.server.tasks
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.SessionId
import com.correx.core.toolintent.SessionContextProjection
import com.correx.infrastructure.tools.task.SessionWrites
/**
* Adapter for the [SessionWrites] port: folds a session's events through
* [SessionContextProjection] and returns its writes (the recorded receipt.affectedEntities).
* Powers the cite-before-claim gate. Replay-safe (reads the log, never live state).
*/
class EventStoreSessionWrites(private val eventStore: EventStore) : SessionWrites {
override fun pathsWritten(sessionId: SessionId): Set<String> {
val projection = SessionContextProjection(sessionId)
return eventStore.read(sessionId)
.fold(projection.initial()) { acc, event -> projection.apply(acc, event) }
.writes
}
}