deps: bump modernc.org/sqlite from 1.56.0 to 1.57.0 - #11
Open
dependabot[bot] wants to merge 1 commit into
Open
Conversation
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.56.0 to 1.57.0. - [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md) - [Commits](https://gitlab.com/cznic/sqlite/compare/v1.56.0...v1.57.0) --- updated-dependencies: - dependency-name: modernc.org/sqlite dependency-version: 1.57.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Contributor
Author
LabelsThe following labels could not be found: Please fix the above issues or remove invalid values from |
treeol
added a commit
that referenced
this pull request
Aug 26, 2026
* docs(card148): add wakild foundation design doc, Mashura-gated plan, D10 inventory
Card #148 (https://trello.com/c/Ba4YYGXM) planning artifacts on
feature/wakild-daemon:
- docs/design/wakild-foundation.md — the wakild daemon foundation design doc,
verbatim from the user (command/event split, Connect API, tenancy from day
one, SQLite+kvr storage split, P0-P5 phases).
- docs/cards/card-148-wakild-impl-plan.md — FINAL implementation plan, gated
by Mashura (3 panels). Key decisions: P0 is daemon-shaped (non-blocking
SubmitInput), two event classes, store-pluggable sequencer, tenancy from P0,
sync->async approval shim in P0, Service split, P1 replay = projection not
event sourcing.
- docs/cards/card-148-d10-inventory.md — P0 chunk 1: full inventory of every
direct *agent.App access from internal/tui and cmd/wakil (~130 accesses,
7 output channels, 27 sendEvent sites, 1 channel-bearing message).
No code changes in this commit.
* feat(core/event): add transport-free domain event model (card #148 P0 chunk 2)
First code of P0. internal/core/event is the domain event vocabulary every
client will consume; it lives under internal/core so that core never imports
api/gen or internal/server (foundation doc §2.1).
Implements plan decisions D2/D4/D6:
- Two event classes (durable replayable vs ephemeral live-only), derived from
Kind; MessageCommitted is the durable counterpart of MessageDelta.
- Tenancy from day one: TenantID/SessionID on every envelope, embedded
tnt_local/usr_local principals; no empty tenant passes.
- Events are data-only: single envelope + typed payload, no channels/callbacks.
Kind/payload pairing enforced against an exact reflect.Type registry (not
name strings, so same-named types from other packages or proto can't match).
- Draft vs committed lifecycle: ValidateDraft (Seq==0) vs ValidateCommitted
(durable Seq>=1, ephemeral Seq==0), so the appender has a clean pre/post
contract before the store exists.
- Typed prefixed IDs (tnt_/usr_/wsp_/ses_/trn_/tcl_/apr_/sub_) with Validate
methods, called from the envelope AND payloads; checkID panics on unknown
kind (programmer error) instead of silently passing.
Mashura (3 panels) reviewed this chunk; folded in: draft/committed split,
ephemeral Seq==0 enforcement, reflect.Type registry, typed-nil pointer
rejection, payload ID/enum validation, checkID unknown-key guard, registry
completeness test.
Verified: go test + go test -race pass, go build ./... clean, go vet clean,
gofmt clean.
* feat(core): add session service contracts + typed UUIDv7 id generation (card #148 P0 chunk 3)
Introduces the transport-free core contract surface (plan D3/D4/D7) that
chunks 4-8 and P1/P2 build on, plus prefixed UUIDv7 id generation.
internal/core (package core, service.go):
- Three behavior-first interfaces (D7): SessionService (commands),
EventReader (Subscribe/ListEvents), SessionReader (queries/snapshot).
Bootstrap (config, proxy.Client, executor, sinks) stays OFF the interfaces.
- Principal identity model (D4): typed tenant/user + role + scopes +
auth_method; EmbeddedPrincipal() returns a fresh value (no mutable global).
- Session state machine with an explicit transition table + CanTransitionTo.
- Store contracts (D3): EventAppender (durable-append + sequence assignment,
atomic, commit->notify point; rejects ephemeral drafts) and EventLog
(cursor-addressable durable read). Sequencer is NOT exposed as a seam --
assignment folds into Append so contiguous sequences can't be violated.
- ApprovalOutcome enum (deny/allow_once/allow_reads_once) instead of a
representable-ambiguous bool pair; decision carries session correlation.
- SubmitInput contract pinned: FIFO enqueue (bounded), TurnAck = acceptance
not completion, error-session re-drive via SubmitInput, non-blocking.
- Subscription lifecycle contract (handoff, dedup-by-seq, Next->io.EOF after
Close, backpressure) and a fuller error vocabulary (state transition,
approval not-found/already-resolved, subscription closed).
internal/core/id:
- Generator producing event-prefixed UUIDv7 ids (tnt_/usr_/wsp_/ses_/trn_/
tcl_/apr_/sub_); NewFromReader for deterministic tests; package-level
helpers on crypto/rand. Promotes github.com/google/uuid v1.6.0 to a direct
dependency.
Verified: go build ./... + go vet clean; go test -race ./internal/core/...
green; full go test -count=1 ./... green across all packages; gofmt clean on
tracked files (CI gate). Mashura review (3 panels) folded in: Sequencer seam
removed, ephemeral-append rejection, approval enum, state-transition table,
EmbeddedPrincipal as function, consistent explicit principal args, submit
enqueue-vs-busy resolution, subscription lifecycle contract, not-found vs
not-authorized resolution, honest SessionSnapshot framing. Known seams noted
in docs, not implemented: tenant-isolation is service-layer (plan D4/P1 note),
pagination/retention, idempotency enforcement (RequestID reserved), P1
session-creation fields (additive).
Refs: https://trello.com/c/Ba4YYGXM
* feat(core): add in-memory session host (card #148 P0 chunk 4)
Implements deliverable 3 of the P0 plan and all three service interfaces
(SessionService, EventReader, SessionReader) over the D3 store contracts.
- One executor goroutine per session with a bounded FIFO input queue;
SubmitInput is genuinely non-blocking and returns TurnAck{TurnID}.
- Turn finalization is a single lock-protected linearization point
(finishTurn), so a close or interrupt racing a turn's return can never be
lost or park the session in a stuck state.
- Interrupt/CloseSession cancel via an internal context and emit
TurnCompleted{cancelled} / SessionClosed — never a silent abort.
- Durable event log (MemLog) with contiguous, atomic append-as-one-step
sequencing; the executor is the single commit->notify point.
- Subscribe registers-before-replays, stages replay history in an unbounded
seq-ordered pending queue, and merges concurrent live commits without
order inversion or loss. Slow subscribers are disconnected with
ErrSubscriptionGap (never block the executor; durables never silently lost).
- Turn output is committed as MessageCommitted; acked-but-never-run inputs are
always explicitly abandoned with TurnCompleted{cancelled} (never silent).
- Crash-recovery stub RecoverRunning: recovered sessions enter error state
with a SessionError{daemon_restart} marker.
- Tenant isolation (no existence leak) and role gating on every method.
Verified: build/vet clean; go test -race -count=5 on sessionhost green;
full go test -count=1 ./... green across all 27 packages; gofmt clean.
Mashura review (card #148 chunk 4): two panels converged on blocking defects
(finalization race, replay/live reordering + lossy catch-up, discarded turn
output, subscription detach leak); all folded in.
Trello: https://trello.com/c/Ba4YYGXM
* docs(card148): add Mashura-reviewed chunk-5 plan (event-emission seam + agent adapter)
Card #148 P0, workflow step 4: plan for chunk 5 (deliverables 4 + 6).
Reviewed by three panels (gpt-5.6-sol, claude-fable-5, glm-5.2); feedback
folded in: serialized append->notify (emitMu), turn fencing + late-emission
contract, internal_error classification, ctx-aware approval confirmer with
full outcome fidelity, TurnInput.UserID resolver identity, authoritative
message text = TurnOutcome.Text, tool/subagent durable events deferred.
* feat(core): add turn-scoped event emitter + agent-loop adapter (card #148 P0 chunk 5)
Implements plan deliverables 4 + 6: the command/event boundary's outbound
half. A turn can now emit intermediate domain events (not just its flat
text/error result), and a real *agent.App turn drives an in-memory host
session end-to-end through SessionService/EventReader only.
sessionhost:
- TurnInput.Emit: turn-scoped Emitter (durable Emit + ephemeral Notify),
concurrent-safe, fenced at finalization.
- s.emitMu serializes durable append->notify across ALL producers (executor
terminals + worker-emitted events) so subscribers observe exact increasing
Seq order.
- Fence check runs INSIDE emitMu against finishTurn's terminal emit, so a
turn-emitted durable event can never append after its TurnCompleted
(terminal ordering).
- Host-owned kind allowlist; ErrEmitFailed + ErrInternal classify emit/store
failures as SessionError{internal_error}, not backend_failure.
- TurnInput.UserID carries the submitter principal for audit identity.
internal/wiring (new):
- HostTurnFunc: installs agent Out/OnReasoning/Confirm callbacks, runs the
SendOutcome -> WaitForAsyncCompletion -> Resume loop, restores callbacks
panic-safe. Bound to ONE host session (single-App, single-session rejected
loudly). MessageDelta/ReasoningDelta emissions; authoritative text returns
TurnOutcome.Text.
- hostConfirmer (D5 shim): ApprovalRequested -> ApprovalResolved with full
approve/decline/allow-reads fidelity; resolver runs in a goroutine raced
against ctx cancellation (stuck resolver cannot hang Interrupt/Close);
emit failures latch into the turn error (no orphaned/duplicate approvals).
Docs: MessageDelta presentation-streaming caveat; ApprovalResolved.Resolver
now populated in P0; SubmitInput.ReadAction doc corrected; plan fence
wording corrected (linearizes before terminal append).
Reviewed by three panels (gpt-5.6-sol, claude-fable-5, glm-5.2): fold-ins
from both plan review and implementation review are applied.
Verified: go build/vet clean; go test -race full suite green (28 pkg);
go list structural gate clean (internal/core imports no bubbletea/server/
agent/tui/api-gen; wiring is the sole bridge). Trello: card #148.
Ref: https://trello.com/c/Ba4YYGXM
* docs(card148): chunk 6 plan — interim TUI→App control seam (deliverable 5, step 1/2)
* feat(card148): chunk 6 — interim TUI→App control seam (deliverable 5, step 1/2)
Introduce Control (user/session commands) and StateApply (round-trip runtime
results) interfaces in internal/agent, implemented by *App, and route all 20
TUI mutation sites through them. Fix ResumeSessionMsg + AppendSystemMessage to
take convMu (pre-existing lock bypass). Add a heuristic structural guard test
(narrowed: field writes rooted at m.app + seam-method calls through m.app +
enumerated pass-*App set) plus a negative test proving the guard is not
vacuous, and fake-Control/StateApply routing tests proving the seam is used.
D12 + deliverable-5 completion remain red (TUI still holds *agent.App; the
turn-driving re-route is the next chunk).
* docs(card148): chunk 7 plan — headless turn-driving re-route (Mashura-gated)
* feat(card148): chunk 7 — headless single-task re-route through session host (deliverable 5 step 2, exit gate #2 partial)
* docs(card148): chunk 7b plan — TUI re-route off *agent.App (Mashura-gated v2, gate #1)
* feat(card148): chunk 7b1 — agent-free facade contract + appOwners release path (Mashura-gated)
7b1: server-side foundations for closing Gate #1 (remove *agent.App from
internal/tui). No TUI cut yet — Gate #1 stays red; the TUI keeps driving
*agent.App until 7b3.
sessionclient (NEW package, internal/core/sessionclient):
- Agent-free facade interface + neutral DTO inventory (D26)
- Imports only event, proxy, core — never internal/agent
- go list -deps verified: zero internal/agent in transitive graph
- DTOs: Consent, ContextLimit, Backend, OpID, SessionSummary, SessionScope,
ApprovalChoice, ApprovalRequest, ClientSnapshot, WorkflowSnapshot,
CommandResult, RotateRequest, RepoStateMutator, CompletionSource
- CommandResult replaces agent.HandleTUICommand's (handled, quit, cmd)
return — no agent.Cmd/Msg leak; Validate() rejects contradictory states
- ClientSnapshot: immutable version-stamped view-model (D26), not live getters
- Full Facade interface: SessionService + EventReader + TUI-specific ops +
client-initiated mutations (D26 grounding #11) + side questions (D29) +
slash-command dispatch (D23) + session listing + lifecycle
wiring/hostturn.go — appOwners release path (7b1):
- appOwners map: struct{} → *hostTurn (tracks the owner)
- HostTurnHandle: bundles TurnFunc + Release() + App()
- NewHostTurnHandle: factory entry point returning the handle
- HostTurnFunc: kept as thin wrapper (headless backward compat)
- Release(): idempotent, rejects active turns (ErrTurnActive)
- run(): atomically sets turnActive; rejects second concurrent turn and
released-before-start (defense-in-depth + concurrency fix from Mashura review)
Tests:
- sessionclient: structural agent-free guard (go list -deps), ContextLimit
parity, CommandResult.Validate
- wiring/factory_test.go: claim/release/reclaim lifecycle, idempotent release,
active-turn rejection, released-turn rejection, concurrent claim/release
- All green under -race (29 packages)
Mashura review op-12 (gpt-5.6-sol) findings addressed:
- Critical: run() now rejects a second concurrent turn atomically
- doc.go: 'event and proxy only' → 'event, proxy, and core'
- TestPackageIsAgentFree: real go list -deps guard (not a placebo)
- CommandResult.Validate + RotateRequest.Validate added
- Rotation rationale clarified: fresh App pointer; Release is cleanup signal
* feat(card148): chunk 7b2 — session-scoped emitter + async approval + new event kinds (Mashura-gated)
Event package (D24/D28/D29):
- 10 new event kinds: user_message_committed (durable, replay truth),
conversation_compacted, workflow_turn_started, workflow_final_review,
async_job_started/completed, side_question_completed (all durable),
tok_rate, async_job_progress, side_question_progress, learn_nudge
(all ephemeral)
- OpID type with op_ prefix, validation, constructor
- TurnCompleted gains Warn + WorkflowWillContinue fields (D28)
- All new payloads with Validate() methods
- Registry + completeness + validation tests updated
Session host (D24/D25):
- SessionEmitter interface: session-scoped emitter fenced at session
close (not turn close) — legal for detached work after turn completion
- sessionEmitter concrete impl: rejects host-reserved, turn-scoped,
and ephemeral kinds on Emit; accepts ephemeral on Notify
- turnScopedKinds allowlist: approvals, tool calls, subagent events
rejected by session emitter to preserve terminal turn ordering
- UserMessageCommitted emitted from executor goroutine (handleInput)
not SubmitInput — guarantees FIFO ordering invariant
- UserMessageCommitted in hostReservedKinds (host-owned)
- ParkApproval hook on TurnInput: parks session in awaiting_approval,
blocks turn goroutine on decision channel raced with ctx
- Real RespondToApproval: validates pending approval, resolves via
buffered channel, idempotent same-outcome duplicates
- Cancel-during-approval: ctx cancellation → forced decline before
emitter fence
- Resolver identity: ApprovalResolved.Resolver records who actually
answered (principal.UserID), not just the turn submitter
Wiring adapter (D25):
- WithAsyncApproval() option: TUI uses async park+resolve path;
headless keeps sync inline resolver (parity unchanged)
- app.OnTokRate wired to SessionEmit.Notify(KindTokRate)
- app.EventSink wired to session-scoped surface (projection TODO 7b3)
- ApprovalResolved emit failure now fails closed (return false, not
just log) — safety invariant
Tests (14 new, 2 updated):
- Session emitter: legal-after-turn, fenced-at-close, rejects-host-reserved,
rejects-ephemeral-emit, rejects-turn-scoped-kinds, notify-accepts-ephemeral
- Async approval: round-trip, cancel-during-approval, not-found,
already-resolved, wrong-id, concurrent-park-and-resolve (race)
- Headless sync parity: unchanged
- Event: OpID validation, new payload validation, updated kind class test
- Updated TestSubscribeReplayOverlap for UserMessageCommitted event
Pre-existing flaky test TestCloseSessionEmitsSessionClosedAndIsIdempotent
confirmed flaky before and after changes (state-before-event race in test).
Mashura review (op-14, gpt-5.6-sol): 5 critical findings addressed
(host-reserved UserMessageCommitted, executor-goroutine ordering,
resolver identity, fail-closed audit, turn-scoped kind rejection);
D28 workflow emission and D29 EventSink projection deferred to 7b3
per plan scope.
* fix(card148): 7b2 prerequisite fixes — detached callback ownership, approval resolver identity, SetAllowReads ordering, event projection scaffold (Mashura-gated)
Three 7b2 bugs identified by Mashura review (op-17, 3 panels) fixed:
1. Detached event delivery broken (hostturn.go): hostTurn.run saved/restored
app.EventSink and app.OnTokRate per-turn. Between turns, the sink reverted
to main.go's globalProg.Send — detached async jobs/side questions lost the
session-scoped emitter. Fix: session-scoped callbacks (EventSink, OnTokRate)
are now installed permanently on the first turn and NOT restored. Only
turn-scoped callbacks (Out, Confirm, OnReasoning) are restored per-turn.
The hostTurn struct gains a sessionEmit field that persists across turns.
2. Approval resolver identity (hostturn.go): forced decline on cancel/interrupt
used in.UserID (submitter) instead of a system principal. D25 says
cancellation records the system/interrupt principal. Fix: add
event.SystemUserID and use it for forced declines.
3. SetAllowReads before durable emit (hostturn.go): app.SetAllowReads(true)
was called before ApprovalResolved was durably emitted. If the append
failed, consent was mutated but the turn failed. Fix: move consent mutation
AFTER successful durable emit.
4. Event projection scaffold (projection.go): the app.EventSink was a no-op
(TODO(7b3)). Added projectAgentEvent with the full mapping structure
(skeleton — projection implementations land in 7b3 m2). This is the legal
emit path for all agent message types through the session-scoped emitter.
TestCallbackRestore updated: OnTokRate/EventSink are now session-scoped
(permanent, not restored). Only Out/Confirm/OnReasoning are turn-scoped.
Refs: card-148-chunk7b-plan.md D24/D25, Mashura op-17 (gpt-5.6-sol,
claude-fable-5, glm-5.2)
* feat(card148): 7b3 m1 — neutral format package, ConversationManager interface, snapshot fixes, async command mechanism, mapping matrix
m1 (Contracts & Lifecycle):
- internal/core/format: extracted ShortID, Truncate, DerefStr, Indent,
Yellow, StrPtr, TranscriptSize from internal/agent so agent-free
packages (sessionclient, wiring, TUI) can use them without importing
internal/agent. Includes agent-free structural guard test.
- ClientSnapshot: added OutputMode field (was missing), added slice
immutability documentation and test.
- CommandResult: added OpID field for async commands (/handoff,
/remember, /recall) that need event-based completion notification.
- ConversationManager interface: agent-free, sits above facade, handles
/new, /resume, /handoff rotation. Documents detached-job cancel policy.
- mapping_matrix.go: complete command/message mapping matrix documenting
every agent.Msg type → event.Kind or CommandResult or ClientSnapshot
field, plus slash-command → CommandResult mapping.
- Structural tests: OpID validation, OutputMode field, slice cloning,
ConversationManager interface shape.
All tests pass: go build ./..., go vet, agent, TUI, cmd/wakil, core.
* feat(card148): 7b3 m2 — complete agent-message→domain-event projection
m2 (Host Capabilities & Projection):
- Complete projectAgentEvent in internal/wiring/projection.go:
- SubagentStartMsg → KindSubagentSpawned (durable)
- SubagentActiveMsg → KindSubagentProgress (ephemeral, [active] marker)
- SubagentChunkMsg → KindSubagentProgress (ephemeral)
- SubagentFinishedMsg → KindSubagentProgress (ephemeral, [finished:status])
- SubagentDoneMsg → KindSubagentCompleted (durable, status from Err)
- AsyncJobStartMsg → KindAsyncJobStarted (durable)
- AsyncJobChunkMsg → KindAsyncJobProgress (ephemeral)
- AsyncJobDoneMsg → KindAsyncJobCompleted (durable, status from Err)
- SideQuestionChunkMsg → KindSideQuestionProgress (ephemeral)
- SideQuestionDoneMsg → KindSideQuestionCompleted (durable, status from Err)
- AgentDoneMsg.LearnNudge → KindLearnNudge (ephemeral, if non-empty)
- ToolStartMsg/ToolResultMsg: dropped (turn-scoped path handles these)
- SysNoteMsg/CompactedMsg/BackendCtxLimitMsg/ModelListUpdatedMsg/
MCPReconnectedMsg/TokRateMsg: dropped (snapshot fields or client-local)
- ID mapping helpers: subagentIDFromChatID, opIDFromString, toolCallIDFromString
(deterministic prefix-stripping: proxy ID body reused with domain prefix)
- 33 projection tests covering every message type, nil/closed emitter,
ID helpers, error status derivation, dropped messages, unknown types.
All tests pass: go build, go vet, wiring package.
* feat(card148): 7b3 m3 — wiring facade, ConversationManager, event pump
m3 (Wiring Implementation & Event Pump):
- wiringFacade: implements sessionclient.Facade by bridging *agent.App +
*sessionhost.Host. Delegates SessionService/EventReader to host, constructs
ClientSnapshot from App fields, routes mutations to App methods.
- conversationManager: implements sessionclient.ConversationManager. Creates
fresh *agent.App for each conversation, wires to host, handles /new, /resume,
/handoff. Detached-job cancel policy on close.
- EventPump: goroutine driving EventSubscription.Next, delivers events to TUI
callback. Handles subscription gap recovery (resubscribe from lastSeq),
pump cancellation, rotation drain via Done() channel.
- ClientSnapshot.Costs changed to *proxy.CostTracker (CostTracker has a mutex,
cannot be copied by value).
- interpretAgentMsg: translates agent.Msg from HandleTUICommand into
CommandResult fields (Notice, Rotate, Submit, Compacted).
- Conversion helpers: toClientContextLimit, toAgentContextLimit, toClientBackends,
toClientWorkflow.
- Tests: event pump delivery, idempotent stop, ctx cancel, interface satisfaction.
All tests pass: go build, go vet, core, wiring, tui, cmd/wakil.
* fix(card148): 7b3 m3 — Mashura review fixes (gap recovery, SaveRepoState, event pump ownership)
Fixes from Mashura op-20 review:
1. EventPump gap recovery: use errors.Is instead of == (wrapped errors)
2. EventPump lastSeq: accept initialSeq in constructor so gap recovery starts
from the subscription's cursor, not zero (prevents full-history replay)
3. EventPump EOF: use errors.Is(err, io.EOF) for terminal error
4. SaveRepoState: initialize mutator from current repo state before callback
so unset fields preserve existing values (no accidental zeroing)
5. wiringFacade: own the EventPump (not just subscription); Close stops the
pump and drains it, not just the subscription
All tests pass: go build, go vet, wiring package.
* feat(card148): 7b3 m4a — workflow continuation via host enqueue hook, session notes, learn-nudge parity
Mashura review panel (3 panels unanimous) chose Option A: the adapter runs
HandleWorkflowTransition after a successful turn and enqueues the continuation
through a host-provided TurnInput.EnqueueInput closure — the TUI is passive.
- sessionhost: TurnInput.EnqueueInput hook; enqueueTurn (SubmitInput-equivalent
acceptance semantics); finishTurn sets TurnCompleted.WorkflowWillContinue
when a completed turn has queued work following it
- wiring/hostturn: post-turn HandleWorkflowTransition + enqueue + durable
workflow_turn_started audit marker; TakeLearnNudge parity (agent accessor
replicating the old RunTurn nudge computation) delivered as ephemeral
learn_nudge
- event: KindSessionNote (ephemeral) + SessionNote payload — in-turn progress
notes (wfProgNote, handoff progress, policy notices) projected instead of
dropped
- projection: SysNoteMsg → ephemeral session_note
- tests: workflow continuation (2 turns + audit marker + WorkflowWillContinue),
no-workflow control, session-note projection
* feat(card148): 7b3 m4a — side-question registry, SetWorkflow conversion, snapshot versioning
- facade: unique domain OpIDs for side questions (id.NewOpID generator added
to core/id) with a facade-side OpID→CancelFunc registry; CancelSideQuestion
looks up + cancels + removes; Close cancels all registered side questions
(detached-job policy). Replaces the constant 'op_sq' pseudo-ID and no-op
cancel stub.
- facade: SetWorkflow converts WorkflowSnapshot → workflow.WorkflowState
(phase-name→enum mapping, workflowPhaseFromName); nil still clears.
- facade: snapshot revision counter — every facade-mediated mutation bumps
Version so clients detect staleness; Snapshot.Title from Session.Label.
- tests: unique OpIDs + registry lifecycle, SetWorkflow round-trip through
Snapshot, version increments across mutators
* feat(card148): 7b3 m4a — real HandoffConversation via RunHandoffPipeline, hashed workspace IDs
- agent: RunHandoffPipeline exported seam over performHandoff steps 1–4
(validation, old-session save, recency-split summary generation,
session-history indexing with fallback chain, durable handoff record);
HandoffResult carries what the wiring layer needs (payload, continuation
prompt, note, chat IDs)
- wiring: HandoffConversation now runs the real pipeline — saves old session,
generates summary, creates new conversation seeded with the pinned handoff
context (untrusted-delimiter framing), clears pending images, and enqueues
the continuation turn via host SubmitInput when proceeding (host-enqueues
policy; auto-start failure degrades to a startup note, not a failed rotation)
- wiring: workspaceIDFromConfig derives wsp_<sha256[:8]> of the effective
workdir — stable per workspace, valid ID grammar (the raw-path stub failed
validation for empty workdirs)
- tests: handoff seeding (pinned context, no image leak, new ChatID), empty-
conversation guard
* feat(card148): 7b3 m4a — subagent event enrichment, InfoSnapshot DTO, SessionSummary.Turns
- event payloads: SubagentSpawned carries Backend/Model/ToolNames;
SubagentCompleted carries Err/CostUSD/FilesChanged/Grounding labels/
CtxSize/HardMaxBytes/UsedBackend; SubagentProgress carries structured
early-finished fields (Finished/FinishedStatus/FinishedCostUSD/
FinishedFilesN) instead of a '[finished:status]' text marker;
AsyncJobCompleted carries Err. Client tab/info-panel parity with the old
Subagent*Msg/AsyncJobDoneMsg fields.
- projection: maps the new fields through.
- sessionclient: InfoSnapshot DTO (narrow, immutable, defensive copies) —
endpoint/identity, model+backend selection, prompt/config bits, context
gauge, transcript stats, workflow label, MCP servers, SearXNG tools,
grounding entries, costs. Facade.Info() interface method; SessionSummary.
Turns() mirrors agent.SessionTurns for the resume picker.
- wiring: Info() implementation (mashuraPanelLabel moved from the TUI so the
label is computed wiring-side); endpoints for completion.
- tests: Info snapshot content + copy semantics.
* fix(card148): 7b3 m4a — DispatchCommand handoff deferral + learn-text mapping
- DispatchCommand intercepts /handoff BEFORE agent.HandleTUICommand: arg
validation + quick-fail emptiness guards run at dispatch (fast), and the
result carries Rotate{Type:handoff, Proceed} WITHOUT executing the
summarizer pipeline — HandoffConversation runs it exactly once. Fixes the
double-pipeline (dispatch executing performHandoff AND the manager running
RunHandoffPipeline) and the event-loop blocking (120s summarizer) from the
synchronous cmd() call.
- Documented calling contract: slow commands (/handoff /remember /recall
/compact) execute synchronously inside DispatchCommand; the caller (TUI)
wraps the call in a worker goroutine — the AdaptCmd pattern.
- LearnTurnMsg → Submit 'learn this for next time' (the literal the old TUI's
LearnTurnMsg handler submitted via RunTurn), NOT '/learn' (infinite
redispatch). WFFinalReviewMsg → Submit 'continue' with rationale (the
adapter re-runs HandleFinalReview at turn end in verify state).
- tests: handoff deferral (no pipeline execution, proceed/stop/usage
variants), empty quick-fail, learn literal.
* feat(card148): 7b3 m4b-prep — tool-call events on turn emitter, facade-owned event pump, BootstrapTUI
- hostturn: turnEmit field — the EventSink closure routes ToolStartMsg/
ToolResultMsg (turn-scoped kinds; the session emitter rejects them by
design) to the live turn's emitter, stamped with the turn ID; everything
else projects on the session surface. turnEmit set/cleared per turn.
- projection: ToolStartMsg → tool_call_started (ArgDigest = primary arg),
ToolResultMsg → tool_call_completed (Result preview) — previously dropped
with a wrong assumption ('the host emits them' — tools run inside the
agent loop; nothing emitted them). The TUI running-tool status line now
has a live wiring path.
- facade: Subscribe starts the facade-owned event pump (deliver callback =
tea.Program.Send); StartEventPump begins delivery when the caller is
ready. Facade interface updated accordingly.
- wiring: BootstrapTUI — manager + first conversation (fresh or --resume),
subscription armed, cleanup closure; the m4c main.go entry point.
- tests: tool events through a real turn (ordering: start < done < turn
completed; TurnID stamped), projection unit tests, BootstrapTUI fresh +
missing-resume.
* feat(card148): 7b3 m4b-prep — wiring-side fixes for the TUI cut (Mashura op-32 review)
Wiring-side blockers and review findings fixed before the TUI cutover:
- WithAsyncApproval enabled on manager-built conversations (B1): the
sync confirmer with nil resolver declined every approval — an
interactive TUI could never approve a tool. E2E test proves the park/
respond/complete cycle.
- hostTurn EventSink closure bugs (op-32): captured the FIRST turn's
TurnID forever (later turns' tool events stamped wrong) and read
turnEmit without the mutex (data race). Both fields now written/read
under ht.mu as a consistent pair.
- interpretAgentMsg gaps (B2): BatchMsg recursion (/backend, /model —
note + ctx-limit/model-list side effects now applied facade-side per
D24 query-state), MCPReconnectedMsg applies rebuilt tools,
ClipboardImageRequest sentinel → CommandResult.ClipboardImage,
OpenResumePickerMsg → ResumePicker (was a bogus Rotate).
- /new, /reset, /resume intercepted in DispatchCommand (B3): the agent
path mutated the OLD App (NewConversation + finalizeSessionHistory).
Rotation now classifies only; the manager owns finalize-on-rotation
(FinalizeSessionHistory exported) and old-App freshness.
- Facade.Close: CloseSession first (cancels in-flight turn → unblocks a
PARKED approval — was a permanent goroutine leak on rotation), drain
the pump (bounded), retry Release while the turn winds down.
- bumpVersion AFTER mutation (was before — new version + stale data
window defeated the staleness check).
- BootstrapTUI subscribes at the durable HEAD, not seq 0 (replayed
ApprovalRequested would pop dead confirm gates; replay duplicates
hydrated snapshot state).
- InfoSnapshot: InfoPanelOpen + OracleLabel 'no key' env fallback moved
wiring-side.
- ConversationManager.NewConversation gains a current-Facade param
(rotation context for finalize).
- TUI: tui_msgs.go + session_view.go migrated off agent format utils to
internal/core/format (first two of nine files; stage 1 of the cut).
* feat(card148): 7b3 m4b stage 2 — TUI read paths through facade Snapshot/Info
Model gains facade/manager/principal fields (wiring path), with the App
kept for the legacy path until stage 3 (one read source per file:
snapshot()/info() accessors return ok=false when facade is nil and
callers fall back).
Migrated files (wiring path reads facade; legacy path unchanged):
- info_panel.go: infoMainSegments/infoSubSegments/infoToolsSegments/
infoGroundingSegments/costSegments/billedSegment/toggleInfoPanel —
every App-internal read (Exec.Describe/Cwd, Cfg.Image/Oracle/SearXng,
Client.BaseURL/ChatID/Grounding, MCP.Servers, Workflow.SidebarLabel,
Costs) now comes from InfoSnapshot on the wiring path. OracleLabel
'no key' fallback + InfoPanelOpen are wiring-side (committed earlier).
- tui_view.go: ctxSegment (limit/usage/transcript stats) and
headerStatusInput (workflow label, backends, model/submodel, consent,
rawTools) read Info()/Snapshot()/facade.Consent().
- complete.go: compSources() model method (backends/models/endpoints/
mentionBase via snapshot+Info); fetchSessionShortIDs via
facade.ListSessions; computeCompletion/computeSlashCompletion take an
injected session fetcher (stays a pure function).
- resume_picker.go: SessionSummary DTO, facade.ListSessions reload,
Turns() for row rendering; Enter routes through beginRotation on the
wiring path (rotation scaffolding: rotationRequest/rotationMsg/
beginRotation — build-new-first, old facade closed after replacement
exists, per the op-32 review); legacy path keeps agent resume.
- NewTUIModelWithFacade: wiring-path constructor (nil-App base model,
hydrates from snapshot.Conv + Info().InfoPanelOpen).
tui_msgs.go + session_view.go were already migrated in stage 1 (a80666e).
* feat(card148): 7b3 m4b stage 3 — wiring-path runtime cut (events, approval gate, send, rotation)
The TUI now runs end-to-end on the wiring path when facade-backed:
- tui_events.go: handleEventMsg switch on event.Kind — the D2 mapping.
TurnStarted (state machine for host-initiated turns), MessageDelta/
ReasoningDelta (stream accumulation, reasoning collapse), ToolCall-
Started/Completed (status-line indicators; ArgDigest=command),
ApprovalRequested/Resolved (async gate), TurnCompleted (finishWiring-
Turn: flush, classification by Outcome, deferred /auto grants, queue
flush gated on Outcome+WorkflowWillContinue, chime, clearWiringTurn-
State incl. pendApproval safety clear), SessionError, Subagent*
Spawned/Progress(Finished)/Completed, AsyncJob*, SideQuestion*,
SessionNote, LearnNudge, Workflow* (passive audit notes), Compacted,
UserMessage/MessageCommitted (replay truth — no live handling).
Session guard: cached m.sessionID drops stale-pump events.
- tui_event_tabs.go: tab lifecycle (spawn/complete/job) keyed by domain
IDs (sub_*/op_*), extracted as methods.
- Confirm gate: pendApproval{approvalID} + y/a/n/esc/ctrl+c → facade.
RespondToApproval (non-blocking buffered chan); ctrl+c also Interrupt;
already-resolved races tolerated.
- Send path: wiring branch submits via facade.SubmitInput (no RunTurn/
startTurn); optimistic streaming state confirmed by TurnStarted;
image chips reconcile against snapshot.PendingImages; mentions via
Info().MentionBase. flushQueuedPrompt mirrors it.
- Slash dispatch: /-prefixed input → facade.DispatchCommand in a Cmd
goroutine → commandResultMsg applied on the event loop (Notice/
Submit/Rotate→beginRotation/ResumePicker/ClipboardImage/Compacted).
Plain text no longer swallowed (slash-prefix guard).
- Rotation: beginRotation Cmd (manager op → close OLD facade after the
replacement exists) → applyRotation swaps refs, rebuilds view state,
THEN subscribes at the durable head + starts the pump (after the swap
— events before it would hit the session guard). SetProgramSend
installs tea.Program.Send without a package global in main.
- cancelTurn → facade.Interrupt; startSideQuestion → facade; Init
startup note via local startupNoteMsg; mid-turn /auto consent via
facade.
- E2E tests (fake facade): turn lifecycle, approval gate incl. ctrl+c,
session guard, send path, queue-flush-vs-workflow-continuation.
* feat(card148): 7b3 m4c — main.go bootstrap reroute through the session host
cmd/wakil/main.go now runs the TUI on the wiring path (Gate #1, TUI
half): wiring.BootstrapTUI builds the ConversationManager + first
conversation (fresh or --resume), runs the TUI startup steps, and
subscribes the event stream; tui.NewTUIModelWithFacade carries the
facade/manager/principal; tui.SetProgramSend installs prog.Send and
rt.StartEventPump begins delivery. globalProg and the
app.EventSink=globalProg.Send line are gone — the pump is the only
runtime event path.
BootstrapTUI gains BootstrapTUIOpts carrying what main.go did inline:
--attach-image (pending images), RestoreRepoState (fresh conversations
only + ctx re-resolve, same caveat about literal restored strings),
counsel mode/max defaults, and staging/memory startup-note composition.
--resume/--resume-id resolution (workspace-scoped most-recent for bare
--resume) moved into main.go's bootstrap prelude; the manager restores
the transcript.
main.go keeps exactly two agent imports: PrintSessions (--list-sessions
short-circuit) and ShortID (diag log path) + LoadSessionScoped for bare
--resume resolution — all three move behind wiring/agent wrappers in
m4d's guard pass.
* feat(card148): 7b3 m4d — hard cut complete: TUI production code drops internal/agent (Gate #1)
The legacy App-backed path is deleted; the facade+event path is the only
runtime path. Local TUI messages (dotTickMsg, armTickMsg, subTabCloseMsg,
copiedMsg, clipboardImageMsg) moved from the deleted handleAgentMsg
switch into handleEventMsg's local-message section.
Removed: tui_agent_msgs.go (the 30-case agent-msg switch), adapter.go
(AdaptCmd — no agent.Cmds remain), tuiModel's app/control/apply/pendConf
fields, NewTUIModel(app) (newBaseModel + NewTUIModelWithFacade only),
startTurn (submit path owns the state flip), legacy branches in
info_panel/tui_view/complete/resume_picker (Snapshot/Info are the only
read sources), startSideQuestion/cancelTurn legacy paths, the legacy
converters.
Tests (~30 files) migrated to the event switch: evt(kind,payload,sid)
feeds, fakeFacade-backed models, domain-ID tab identities (sub_*/op_*),
rotationMsg for rotation semantics, TurnCompleted outcomes for the
AgentDoneMsg cases. Test helpers: fakeFacade (extended: consent mutators,
SetInfoPanelOpen, StartSideQuestion), newWiringModel, rotatedFake,
wiringTestInfo. Deleted: adapter_test, control_routing_test (seam
deleted), control_seam_test (seam deleted). Agent-package behavior tests
(cost recording, ctx-limit resolution, ProgWriter) keep their agent
imports — they test agent functions and are exempt from the guard.
Guard: TestNoAgentImport asserts go list -deps of internal/tui contains
no internal/agent package — Gate #1's TUI half is now compiler+test
enforced. main.go's residual agent uses (PrintSessions, ShortID,
LoadSessionScoped for bare --resume) are the remaining cmd-side gap.
Full suite + -race green: internal/... cmd/...
* fix(card148): 7b3 m4 — subscribe first-boot event stream after program construction
BootstrapTUI was called with deliver=nil (prog.Send does not exist at
construction time), and Subscribe is gated on deliver != nil — so on first
boot the facade never subscribed, StartEventPump was a no-op (no pump), and
turn events never reached the TUI. The host still ran every submitted turn
(billing the request), but MessageDelta/TurnCompleted were undelivered: the
optimistic streaming state never cleared and the answer never rendered
(the 'stuck streaming' live-test finding).
Fix: TUIRuntime.SubscribeLive subscribes at the durable head once the
tea.Program exists (mirroring the rotation path's lazy subscribe);
main.go binds prog.Send as the deliver callback between SetProgramSend
and StartEventPump. Regression test TestBootstrapTUISubscribeLive proves
turn events flow through a nil-deliver bootstrap + manual subscribe.
* feat(card148): 7b3 m4e — main.go drops internal/agent via wiring session wrappers (Gate #1 cmd half)
main.go was the last production file importing internal/agent. Three thin
package-level wrappers in internal/wiring/sessions.go (PrintSessions,
ResolveRecentSession, ShortID) now carry those call sites:
- --list-sessions and the session-log ShortID calls delegate verbatim
- bare --resume resolves through ResolveRecentSession, fixing a
variable-shadowing bug from the half-applied rewire where the resolved
id was silently clobbered by the outer empty id
- the cmd guard is tightened: internal/agent is banned in ALL non-test
cmd/wakil files including main.go; internal/tui stays main.go-only
- guard proven non-vacuous (synthetic agent import in main.go fails the
test); wrapper tests pin the scoped/all/error contracts main.go relies on
go build, go vet, and tests for cmd/wakil + internal/wiring all green.
* test(card148): P0 exit-gate certification tests — concurrent seq, dual-subscriber order, replay projection
Closes impl-plan §3 gates 4, 5 and 9:
- Gate 4: TestExitGateConcurrentSeqUniqueAndIncreasing — 8 concurrent
SubmitInput producers + 8 detached workers emitting through the session
emitter inside a turn; durable log must be unique + strictly increasing.
Backpressure rejections (queue full) are counted, not failed.
- Gate 5: TestExitGateTwoSubscribersSameOrder — two subscribers from
cursor 0 see the identical durable order, equal to ListEvents order.
- Gate 9 (D9): TestExitGateReplayReconstructsProjection — replay from
ListEvents(0) reconstructs the user+assistant transcript and approval
terminal state identically to a live subscriber (6-entry transcript,
approval resolved "approved").
Also deflakes TestCloseSessionEmitsSessionClosedAndIsIdempotent: it
previously read the log right after observing state=closed, racing the
documented P0 window where state flips before SessionClosed is appended
(host.go package doc). The test now also waits for the event itself.
Gate 3 coverage mapped (no new tests needed): all six enumerated paths
have existing host/integration tests.
All green under -race (full sessionhost suite, -count=1, plus targeted
stability loop).
Ref: trello.com/c/Ba4YYGXM (card #148, P0 exit gate)
* feat(card148): 7c — headless --plan re-routed through the session host (Gate #2 fully green)
`wakil run --plan` no longer drives agent.App directly. The legacy workflow
loop (workflow_legacy.go, deleted) is replaced by host-driven turns with an
after-turn resolver in the adapter:
- New HostTurnFunc option WithPlanAutoAdvance: the resolver applies the legacy
headless auto-advance policy when HandleWorkflowTransition pauses the
workflow — present→implement, review force-skip (+ legacy warning record),
implement step advance, resolver-owned final review on no-marker crossing.
- Exactly-one-of invariant (Mashura op-34/35): every completed plan turn
yields one of {terminal WorkflowOutcome event, one queued continuation,
error}; enqueue rejection is terminal in plan mode (never silently idle).
- Decline control latch on hostTurn (last-wins, legacy parity): a declined
approval — tool OR oracle confirm, including one latched DURING the
transition — terminates the workflow before another turn is enqueued.
Cancellation declines (reason "cancelled") are excluded.
- New durable KindWorkflowOutcome (+payload/validation) and ephemeral
KindWorkflowWarning; KindWorkflowFinalReview is finally emitted (plan mode
only — TUI event stream unchanged).
- runPlanTask/runPlanSession/consumeWorkflowEvents in wiring: first submit is
"continue" (byte parity), terminal records byte-identical to legacy
(pass/declined/gaps/verify_failed/backend_failure+resume_id), no tokens
record (legacy parity), consumer never reads app state.
- Fail-closed fixes from review: NewApprovalID failure now records
emitErr (internal error) instead of a silent decline.
Tests: 6 new plan-host integration tests (full chain→gaps, backend failure
with resume_id, mid-workflow decline, verify_failed, PASS with live oracle
reviewing, decline reason capture) + the 115 existing cmd/wakil workflow
tests now exercising the host path (oracle default/no-oracle, confirmer
policy). All green under -race; full build + vet clean.
Plan: docs/cards/card-148-chunk7c-plan.md (v2, Mashura-gated: op-34 plan
review with 3 panels, op-35 implementation review; all blockers folded in).
Ref: trello.com/c/Ba4YYGXM (card #148, P0 — Gate #2 closed)
* feat(card148): P1a-P1e — SQLite event log + store-backed sequencer (D3)
P1a — Event payload codec (internal/core/event/codec.go):
- MarshalPayload/UnmarshalPayload using payloadTypes registry for type dispatch
- JSON encoding ("json-v1") as P1 interim; P2 will add proto
- UnmarshalPayload returns VALUE (not pointer) matching MemLog representation
- Rejects: nil, typed-nil, type mismatch, NaN/Inf, malformed JSON, invalid
decoded payload
- 30-kind round-trip test + 7 edge-case rejection tests
P1b — Migrations (internal/store/migrations/):
- 001_init.sql: sessions + events tables with composite FK
(tenant_id, session_id) → sessions(tenant_id, id), CHECK constraints
(seq > 0, encoding IN json-v1, last_seq >= 0), UNIQUE(tenant_id, id)
- migrate.go: forward-only, per-migration transactions, version tracked in
schema_migrations (owned by runner, not SQL file), idempotent
- 6 migration tests: fresh DB, idempotent, constraints, reopen, load, missing dir
P1c — SQLiteStore (internal/core/sessionhost/sqlstore/):
- Implements sessionhost.Store (EventAppender + EventLog — NO interface change)
- SessionCreated append: atomic session row + event (seq=1) in one transaction
- Non-SessionCreated append: tenant-qualified UPDATE + INSERT in one tx;
unknown session → ErrSessionNotFound; tenant mismatch → ErrSessionNotFound
(no existence leak)
- Read: cursor-exclusive, ORDER BY seq ASC, limit<=0 omits LIMIT clause,
eager payload decoding via UnmarshalPayload, encoding dispatch
- LastSeq: 0 for nonexistent session
- modernc.org/sqlite, SetMaxOpenConns(1), WAL, foreign_keys=ON, busy_timeout=5000
- 15 unit tests including: concurrent seq uniqueness (-race), reopen durability,
PRAGMA verification after reopen, pointer payload, duplicate SessionCreated
P1d — Shared store contract harness (internal/core/sessionhost/storetest/):
- RunContract: 8 subtests (append, ascending seq, LastSeq, concurrent, cursor,
ephemeral rejection, replay reconstruction) run identically against both
MemLog and SQLiteStore
- MemLog contract test + SQLiteStore contract test (with reopen durability)
- exit_gate_test.go UNCHANGED (still uses MemLog)
P1e — Cross-tenant isolation test (internal/core/sessionhost/cross_tenant_test.go):
- Verifies host-level authz: tenant B → all of ListEvents, GetSession,
Subscribe, SessionSnapshot, SubmitInput, CloseSession, Interrupt,
RespondToApproval return ErrSessionNotFound (not ErrNotAuthorized, not empty
— per core/service.go "no existence leak" contract)
- ListSessions does not leak cross-tenant sessions
- Runs against MemLog (tests P0 host authz, not store-level behavior)
Plan: docs/cards/card-148-p1-chunk-plan.md (v2.1, 2 Mashūra review rounds:
op-40 → 11 blocking findings → v2; op-41 → 5 remaining → v2.1 addendum)
Verification: go build ./... ✅, go vet ./... ✅,
go test -race ./... 31/31 packages ok ✅,
exit_gate_test.go unchanged ✅
Refs: Trello card Ba4YYGXM
* feat(card148): P1f — wire SQLiteStore into daemon composition (TUI + headless)
Production now uses SQLiteStore instead of MemLog for the session-host event
log (card #148 D3). Both TUI and headless paths open a workspace-keyed
SQLite database at <wakil-data>/sessionhost/<short-key>/sessionhost.db.
Wiring:
- agent.SessionHostDBPath: workspace-keyed DB path (same pattern as
MemoryDBPath/SessionHistoryDBPath)
- conversation_manager.go: NewConversationManager opens SQLiteStore at init,
injects via WithStore for every newConversation. Best-effort: falls back
to MemLog on open failure (logged to stderr).
- headless.go: headlessStoreOpts() helper opens SQLiteStore for both
runSingleTask and runPlanSession paths. Same best-effort fallback.
Tests (sqlstore_restart_test.go):
- TestHostRestartRecovery: create session + turn → close host+store → reopen
→ verify events persist (SessionCreated, UserMessageCommitted, TurnStarted,
MessageCommitted, TurnCompleted, SessionClosed). Second host creates a
new session in the same store; independent seq counters, old session
unchanged.
- TestHostWithSQLiteStore_SubmitAndConsume: full turn cycle through host
with SQLiteStore; verify MessageCommitted persisted with correct text.
Verification: go build/vet ✅, go test -race ./... 31/31 ok ✅
Refs: Trello card Ba4YYGXM
* feat(card148): P2a — proto schema + buf toolchain (wakil.v1alpha1)
- api/proto/wakil/v1alpha1/: 5 proto files (event, session, event_service,
session_service, system) defining the P2 wire contract
- Event oneof: all 33 event kinds (field numbers 10-40, reserved 41-50)
- Services: SessionService (8 RPCs incl DeleteSession), EventService (3 RPCs
incl GetSessionSnapshot), SystemService (GetServerInfo, Health)
- buf.yaml (lint: STANDARD + PACKAGE_DIRECTORY_MATCH + PACKAGE_VERSION_SUFFIX,
except RPC_REQUEST_RESPONSE_UNIQUE + RPC_RESPONSE_STANDARD_NAME for shared
Session type), buf.gen.yaml (local protoc-gen-go + protoc-gen-connect-go)
- api/gen/ committed (reproducible: buf generate && git diff --exit-code clean)
- go.mod: +connectrpc.com/connect v1.20.0, +google.golang.org/protobuf v1.36.12
- P2 chunk plan at docs/cards/card-148-p2-chunk-plan.md (Mashura-reviewed,
revised per 3-panel feedback: D5 already implemented, TurnFunc transport-
free, principal server-side, fail-closed store, SessionSnapshot RPC added)
buf lint: pass | buf generate: no drift | go build ./...: pass | go vet: pass | go test -race ./...: 33/33 ok
* feat(card148): P2b — DeleteSession on SessionService + SessionSnapshot wire path
- DeleteSession added to core.SessionService interface (service.go:70 says P2)
- Host.DeleteSession: soft-delete — closes active session, marks deleted,
excludes from GetSession/ListSessions; events remain for audit
- lookup() returns ErrSessionNotFound for deleted sessions (all methods reject)
- Double-delete = ErrSessionNotFound; cross-tenant = ErrSessionNotFound;
viewer = ErrNotAuthorized
- SessionSnapshot already on SessionReader — no core change needed,
proto GetSessionSnapshot RPC maps directly
- 5 new tests: excludes from queries, rejects operations, closes active,
cross-tenant, viewer-not-allowed
- Full -race suite: 33/33 ok
* feat(card148): P2c — Connect server adapter (core↔proto bridge)
- internal/server/connect/: 7 files bridging proto wire contract to core
- event_conv.go: 33-kind core.Event ↔ proto Event converter (all payloads
round-trip verified)
- session_handler.go: SessionService 8 RPCs (Create/Get/List/Delete/Submit/
Approval/Interrupt/Close)
- event_handler.go: EventService 3 RPCs (StreamEvents server-streaming,
ListEvents, GetSessionSnapshot)
- system_handler.go: SystemService (GetServerInfo, Health)
- errors.go: 10 core sentinels → Connect codes (NotFound, FailedPrecondition,
ResourceExhausted, PermissionDenied, etc.)
- principal.go: server-side EmbeddedPrincipal (no client-supplied identity)
- server.go: Server bundling all 3 handlers, http.Handler mount
- converter_test.go: 33-kind round-trip test (all pass)
proto: added MessageCommitted (field 41) to oneof (was missing from initial
schema). buf lint + generate: clean. go build + vet: pass.
* feat(card148): P2d — wakild binary + Unix-socket transport
Daemon binary that serves the Connect API over a Unix socket:
- cmd/wakild/main.go: flag parsing (--socket, --ephemeral,
--shutdown-timeout), config loading, workspace ID derivation,
serve-until-signal lifecycle.
- cmd/wakild/server.go: fail-closed SQLiteStore open (unless --ephemeral),
executor + App + HostTurnHandle with WithAsyncApproval, sessionhost.New
with store, Connect server, Unix-socket listener (0600 permissions,
stale-socket detection/unlink, in-use refusal, parent dir 0700),
graceful shutdown (http.Server.Shutdown → host.Close → resource cleanup).
- cmd/wakild/signal.go: SIGTERM/SIGINT → context cancellation.
- 16 tests: socket lifecycle (stale/in-use/permissions/parent-dir/
connect-accept), flag parsing, Health RPC + GetServerInfo RPC over
real Unix socket (ephemeral + non-ephemeral), nil-safe shutdown.
P2d simplification: one App drives one session (HostTurnFunc's
single-App binding). The daemon serves one active session at a time.
Per-session factory (multiple Apps/hosts) is a P2e concern.
* feat(card148): P2e — remote client + TUI --daemon mode
Remote client architecture (internal/remote/):
- dialer.go: Unix-socket HTTP client for Connect-go service clients
(Session, Event, System). Verifies socket exists and is connectable
before building clients. Disables keepalive pooling for clean
reconnects across daemon restarts.
- pump.go: RemoteEventPump consumes the StreamEvents server-stream RPC,
converts proto→domain events via protoconv, deduplicates durable events
by seq, and reconnects from lastSeq+1 on stream break. Stop cancels
the stream context; Done channel for rotation drain.
- facade.go: RemoteFacade implements sessionclient.Facade by calling
Connect RPCs for all SessionService + EventReader surfaces. TUI-
specific surfaces (Snapshot, Consent, Info) are limited to what the
daemon exposes — the conversation is projected from events, not read
from agent.App. Slash-command dispatch handles /quit, /new, /resume
client-side; the rest passes through as regular input.
- manager.go: RemoteConversationManager implements
sessionclient.ConversationManager (New/Resume/Handoff/Close).
- bootstrap.go: BootstrapRemote mirrors wiring.BootstrapTUI — dials
daemon, checks health, creates/resumes first conversation, subscribes
event stream. Returns TUIRuntime (Facade, Manager, Principal).
Shared proto conversion (internal/protoconv/):
- event_conv.go + payload.go: 32-kind proto↔domain event oneof conversion
extracted from internal/server/connect/event_conv.go. Used by both
the server adapter (domain→proto for RPC responses) and the remote
client (proto→domain for inbound events). Avoids duplication and
drift. SessionToProto/SessionFromProto handle the Session message.
- internal/server/connect/event_conv.go refactored to delegate to
protoconv (432 lines → 14).
Config + main.go wiring:
- --daemon flag: selects remote daemon mode (TUI dials wakild over
Unix socket instead of embedding the agent loop).
- --socket flag: overrides the default socket path
(/wakild.sock or ~/.local/share/wakil/wakild.sock).
- cmd/wakil/daemon_mode.go: RunDaemonMode wires the remote bootstrap
into the TUI — mirrors main.go's embedded path but uses remote.Bootstrap
Remote instead of wiring.BootstrapTUI.
Tests: 6 tests pass with -race — dialer (socket exists, missing, non-
socket), default socket path, event round-trip conversion, pump dedup
logic, facade slash-command dispatch. Full suite passes (all 29
internal/ packages green).
* feat(card148): P2f — integration tests + buf breaking CI
End-to-end daemon↔client integration tests (cmd/wakild/integration_e2e_test.go):
- 9 tests exercising the full wire path over Unix socket: CreateSession,
SubmitInput + ListEvents (verifies 7-event sequence), SessionSnapshot,
CloseSession, Interrupt (verifies cancelled outcome), ResumeSession,
DeleteSession, StreamEvents (real-time pump delivery), NewConversation
(rotation). All pass with -race.
buf breaking CI (.github/workflows/ci.yml):
- New proto-breaking job: buf lint + buf breaking against base branch.
Uses bufbuild/buf-action@v1, fetch-depth: 0 for full git history.
PRs compare against origin/master; pushes compare against HEAD~1.
buf.yaml already configured with WIRE_JSON + WIRE breaking rules.
Fix: merge daemon_mode.go into main.go (P2e regression).
- daemon_mode.go imported internal/tui, violating the headless seam test
(TestHeadlessNoAgentImport: internal/tui is main.go-only). Moved
RunDaemonMode → runDaemonMode into main.go, deleted daemon_mode.go.
* fix(card148): client workspace ID must be hashed, not raw path
runDaemonMode was passing cfg.WorkDir directly as the workspace ID
(e.g. "/home/valon/coding/wakil") instead of the hashed form
("wsp_<16-hex>") that the daemon expects. CreateSession rejected it
with invalid_argument because the raw path doesn't match the
WorkspaceID.Validate() format.
Fix: export wiring.WorkspaceIDFromConfig (was unexported) and use it
in both runDaemonMode (client) and cmd/wakild/main.go (daemon),
removing the duplicate copy. This ensures both sides derive the same
"wsp_"-prefixed SHA-256 hash of the effective workdir.
* feat(card148): P3 — read-only web UI
Add a static web console served by wakild via --http-addr flag.
The browser speaks the same Connect API (HTTP/JSON) as the TUI:
- web/embed.go: //go:embed for static assets
- web/index.html: SPA shell (sessions list + live viewer)
- web/app.js: vanilla JS RPC client (protojson camelCase, oneof
payload access, 500ms polling for live events, renders all 32
event kinds incl. tool calls and subagent tree)
- web/styles.css: Tokyo Night dark theme
Daemon changes:
- cmd/wakild/main.go: --http-addr flag (TCP, empty = disabled)
- cmd/wakild/server.go: dual-listener (Unix socket + optional TCP),
webHandler combining Connect RPC + static files
- internal/server/connect/server.go: HandlerWithStatic method
Tests (6): static files, GetServerInfo, Health, ListSessions,
SessionSnapshot (with tool-call verification = exit gate),
ListEvents cursor pagination.
Exit gate: a running session is live-trackable in the browser,
including tool-calls and subagent tree.
go test -race ./... 36/36 packages green.
buf lint + buf breaking clean. gofmt + go vet clean.
* feat(card148): P4a — auth + tenancy schema (migration 002)
New migration 002_auth_tenancy.sql creates the control-plane tables for
auth and multi-tenancy per design doc §4.3:
- tenants (id, slug, display_name, status)
- users (id, email, display_name, auth_subject, password_hash, status)
with partial UNIQUE index on auth_subject (non-NULL values only)
- memberships (tenant_id, user_id, role) with composite PK
- api_tokens (id, tenant_id, user_id, name, token_hash UNIQUE, scopes,
expires_at, last_used_at, revoked_at) — composite FK to memberships
- join_tokens (id, tenant_id, user_id nullable, role, token_hash UNIQUE,
created_by, expires_at, used_at) — composite FK on (tenant_id, created_by)
to memberships; nullable user_id for create-on-exchange tokens
Indexes on all FK columns. Explicit ON DELETE clauses (CASCADE/SET NULL/
RESTRICT). Default tenant (tnt_local), user (usr_local), and owner
membership seeded via plain INSERT (tables are new within this migration).
sessions.tenant_id stays app-layer validated (accepted gap: SQLite cannot
add FKs to existing tables without recreation).
Tests (11 total, all pass with -race):
- Updated: TestApplyFreshDB (checks all 8 tables, version >= 2),
TestApplyIdempotent (dynamic count), TestApplyReopen (pragmas on reopen),
TestLoadMigrations (checks versions 1+2)
- New: TestApplyAuthTenancyTables (bootstrap + foreign_key_check),
TestAuthTenancyConstraints (CHECK on role/status/expires_at),
TestAuthTenancyFKs (all FK paths incl. composite FKs),
TestAuthTenancyUnique (email/slug/token_hash/auth_subject),
TestUpgradeFromV1 (data preservation across reopen + bootstrap)
Mashūra-reviewed: added UNIQUE on token_hash, composite FK on
join_tokens.created_by, partial unique on auth_subject, ON DELETE clauses,
FK indexes, fixed bootstrap to plain INSERT, expanded test coverage.
Verified: go build, go test -race, go vet, gofmt, buf lint, buf breaking —
all clean.
Trello: https://trello.com/c/Ba4YYGXM/148
* feat(card148): P4b — SO_PEERCRED local auth + fail-closed TCP
P4b implements Unix-socket peer-credential authentication (SO_PEERCRED) and
closes the TCP authentication bypass that existed since P3.
## What changed
### SO_PEERCRED + principal resolution
- New internal/auth/peercred package: platform-conditional extraction of
Unix-socket peer credentials. Linux uses GetsockoptUcred (SO_PEERCRED);
other platforms return unsupported (fail-closed).
- New internal/auth package: PrincipalResolver interface + LocalResolver
that maps the daemon owner UID (os.Geteuid) to the seeded local owner
principal (tnt_local/usr_local/owner/AuthLocal). Rejects all other UIDs
including root. Fail-closed: no credentials -> ErrUnauthenticated.
- http.Server.ConnContext hook captures peer creds at connection-accept
time and stores them in the request context. The resolver reads them
per-request.
- All 11 Connect handler methods (8 SessionHandler + 3 EventHandler) now
call resolvePrincipal(ctx, resolver) instead of the old localPrincipal()
stub. SystemHandler (Health/GetServerInfo) remains intentionally
unauthenticated — it exposes no session or tenant data.
### TCP security hole closed
- TCP listener now serves ONLY static files (webStaticHandler). Connect
RPC handlers are NOT mounted on TCP. Before P4b, the TCP path served
Connect RPC with EmbeddedPrincipal (owner) — an authentication bypass.
- HandlerWithStatic (Connect + static on one mux) deleted — it was a
loaded footgun that could reopen the hole.
- serve() fixed: TCP server errors are logged, not returned to the
daemon main loop (only Unix-socket errors stop the daemon).
### Store tenant predicates
- EventLog interface unchanged (Read/LastSeq have no tenant_id parameter).
The service-layer lookup() is the tenant isolation gate, as documented
in the EventLog contract. Cross-tenant tests (cross_tenant_test.go)
verify all read/write paths reject cross-tenant access with
ErrSessionNotFound. Mashura confirmed this is the correct stance;
adding SQL predicates without a tenant input would be security theater.
### Error handling
- resolvePrincipal distinguishes auth.ErrUnauthenticated (CodeUnauthenticated)
from other resolver errors (CodeInternal via mapError fallthrough).
This prepares the seam for P4c DB-backed resolvers.
- errUnauthenticated added to errors.go map.
- Nil resolver guard: NewServer panics at construction, not at first request.
- ConnContext logs peercred extraction failures (fail-closed but visible).
## Mashura review
- Plan reviewed by 3 panels (gpt-5.6-sol, claude-fable-5, glm-5.2).
- Implementation reviewed by 3 panels.
- Key feedback folded in:
* TCP security hole (critical blocker) — Connect removed from TCP
* os.Geteuid() not os.Getuid() (real vs effective UID)
* resolvePrincipal error distinction (auth vs internal)
* HandlerWithStatic deletion (loaded footgun)
* Stale comment fixes (listenUnix, tcpSrv field, serve(), role-refresh)
* Embedded resolver kept in production package with explicit test-only
doc (Go internal package visibility requires this for cmd/wakild tests)
## Tests
- internal/auth/peercred: Unix-socket + TCP extraction tests
- internal/auth: LocalResolver tests (owner UID, wrong UID, no creds, root)
- cmd/wakild/p4b_test: TCP does NOT serve Connect RPC, static files still
served, Unix socket RPC works
- cmd/wakild/p4b_integration_test: real SO_PEERCRED path (success on Linux,
UID mismatch rejection, no-credentials rejection over TCP)
- Existing cross-tenant tests (cross_tenant_test.go) verify service-layer
tenant isolation for all read/write paths
- Web integration tests updated: RPCs now go through Unix socket, TCP
serves only static files
Verified: go build ./..., go test -race (cmd/wakild, server/connect, auth,
sessionhost), go vet, gofmt — all clean.
Refs: Trello card #148 (https://trello.com/c/Ba4YYGXM/148)
* P4c: join token system + session cookies
Implements the join token onboarding flow and browser session cookies
for the wakild daemon. Admin (local owner via SO_PEERCRED) issues
one-time-use join tokens; clients exchange them for opaque server-side
session cookies stored in SQLite.
New packages:
- internal/auth/tokenstore: DB queries for join_tokens + web_sessions
- internal/auth/jointoken: token generation (256-bit CSPRNG, jnt_ prefix),
issuance with role-based authorization, atomic one-transaction exchange
- internal/auth/tokenresolver: WebSessionResolver (cookie -> principal),
distinguishes ErrCredentialAbsent (try next) from ErrInvalidCredential
(hard fail, no fallthrough)
New proto:
- auth.proto + auth_service.proto: AuthService with CreateJoinToken,
ListJoinTokens, RevokeJoinToken, ExchangeJoinToken (public),
WhoAmI, Logout. No Login/Refresh (OIDC/P4e scope).
Migration 003:
- web_sessions table (opaque token, SHA-256 hashed, sliding + absolute
expiry, FK to memberships ON DELETE CASCADE)
- join_tokens recreation: fix ON DELETE SET NULL -> CASCADE (security:
SET NULL converts bound tokens to create-user-on-exchange tokens),
add revoked_at column
Security (Mashura-reviewed, 3 panels):
- 256-bit CSPRNG token entropy, SHA-256 hash at rest
- One-time-use via conditional UPDATE (used_at IS NULL AND expires_at > now
AND revoked_at IS NULL) in a single transaction
- auth_subject NEVER from exchange request (OIDC only, P4e)
- Only owners can issue owner-role tokens
- Cookie: HttpOnly, SameSite=Strict, Path=/
- Origin validation middleware (CSRF defense-in-depth)
- Generic errors (no token enumeration: expired vs used vs revoked)
- Server-side sessions: immediate revocation, current role read from
memberships at resolve time (not cached in session row)
Daemon wiring:
- TCP server now mounts Conne…
Contributor
Author
|
Dependabot tried to update this pull request, but something went wrong. We're looking into it, but in the meantime you can retry the update by commenting |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps modernc.org/sqlite from 1.56.0 to 1.57.0.
Changelog
Sourced from modernc.org/sqlite's changelog.
... (truncated)
Commits
6e86ac4doc.go, CHANGELOG.md: promote freebsd/386, freebsd/arm and netbsd/amd6447d0960Merge branch 'driver-registration' into 'master'9ed2aadCHANGELOG.md: document the per-Driver registration methods20e2e17sqlite: let a caller-constructed Driver register its own functions, collation...15039fdall_test: make TestConnectionHook survive -count>1224fef6all_test: drop a trailing space gofmt flags50ee6ddvendor_libs: handle a deduplicated libsqlite3/libsqlite_vec checkout15ca503licensing: ship the sqlite-vec MIT notice, normalize the license names69cd3caGOVERNANCE.md: add Ian Chechin as maintainer198be3call_test: tolerate a cgo-less toolchain in the recursive -race checkDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)