feat/go-task-slice-v1 #27

Merged
clawdie merged 13 commits from feat/go-task-slice-v1 into main 2026-09-21 01:26:33 +02:00
Owner
No description provided.
Move the canonical zot RPC JSONL captures out of colibri-glasspane into
testdata/contracts/task-slice/v1/zot/ and add the language-neutral
expectations that Rust and Go must both replay:

- normalized-events.json: (event, state) fold plus accumulated usage per
  stream, with error stickiness across a trailing done;
- task-records.json: queued -> claimed -> started -> done|failed lifecycle
  and terminal record per decision case;
- README.md: projection rules, provenance, consumers.

Existing Glasspane tests read the relocated canonical path so there is one
copy of each stream. The expectations are rewired onto those tests in the
next commit; this commit is contract capture only, no behavior change.

Glasspane suite: 57 passed, 0 failed.
Rewire the Glasspane event-fold tests and the daemon RPC-completion tests
onto the shared pack so the expectations live in one canonical place:

- glasspane: the fixture_* tests load normalized-events.json and assert
  every (source type, normalized event, state) step plus the accumulated
  usage and cumulative consistency, instead of re-deriving them inline;
- daemon: rpc_dispatched_task_{completes,fails} replay task-records.json —
  queued -> claimed -> started -> done|failed, the {agent_id}:{rpc_id}
  correlation, usage-delta billing, and a normalized terminal-record
  projection. Volatile ids/timestamps are asserted for presence and
  consistency, never by value. The provider/model route now comes from the
  fixture's agent block through a CLI-route AgentSpawnConfig.

No production behavior change; the fixtures drive both suites.

colibri-glasspane: 57 passed, 0 failed.
colibri-daemon (rpc_dispatched): 2 passed, 0 failed.
clippy -p colibri-glasspane -p colibri-daemon --all-targets: clean.
Add the root Go module (code.clawdie.si/clawdie/colibri) and the minimal
task/ledger/zot-RPC slice that replays the shared contract fixtures. This
is the smallest Go replacement slice, not a daemon takeover: no cmd/, no
socket, no scheduler, no provider credentials, temporary databases only.

- internal/zotrpc: zot NDJSON normalization (zot_event_type/apply_pi_event
  parity, sticky error), per-turn usage accumulation, and cumulative
  recording without double-counting.
- internal/ledger: task lifecycle over modernc.org/sqlite (pure Go, no
  CGO) with the same schema columns, guarded atomic claim, and
  set_task_cost semantics as crates/colibri-ledger.
- internal/task: the RPC-completion correlation ({agent_id}:{rpc_id} ->
  task, usage baseline at the ack, delta billed at done/error) mirroring
  stream_agent_stdout_to_glasspane/finalize_rpc_task.
- tests replay testdata/contracts/task-slice/v1/ exactly as the Rust
  suites do; parity is field-level, not SQLite byte identity.
- scripts/check-go.sh: focused gofmt + vet + CGO_ENABLED=0 go test gate,
  kept separate from ci-checks.sh until the runner's Go toolchain is
  pinned.

Driver proof (FreeBSD 15.1, go1.26.8, modernc.org/sqlite v1.36.3):
- CGO_ENABLED=0 go test ./...    ok (internal/task, internal/zotrpc)
- go vet ./...                   clean
- go build ./...                 freebsd/amd64 ok
- GOOS=linux GOARCH=amd64/arm64  ok
- file (test binary)             statically linked
- a Go-written DB re-read with the FreeBSD sqlite3 CLI shows the same
  tasks schema and the expected terminal row.
A `response` ack with success:false closes the task before the turn runs, so
it must bill zero new-turn usage. The failure path passed a zero baseline to
finalize, which measures the delta as (pane accumulator - 0) — on a
persistent pane that already ran a turn, that re-bills every prior turn's
usage and cost.

Snapshot the pane accumulator at the acknowledgement and use it for both
branches, so a rejected ack bills an empty delta.

Regression: TestAckFailureAfterPriorUsageBillsZeroDelta runs a successful
turn, then a second correlated task whose ack is rejected, and asserts
failed + zero input/output/cache/cost, correlation consumed, and the prior
task row unchanged. With the fix reverted it fails on the re-billed values
(1042/17/256, cost 0.000151).

The ack-failure stream joins the shared pack (both languages validate its
normalization) and is covered by a Glasspane fixture test.

Known mirror: the Rust daemon has the same latent defect in its ack-failure
branch (socket.rs `finalize_rpc_task(..., &PaneUsage::default(), false)`),
reproduced here with a temporary probe. It is not changed in this commit,
which stays inside the Go slice + contract-capture scope.

FreeBSD 15.1: gofmt/vet clean; CGO_ENABLED=0 go test ok; -race ok;
freebsd/amd64 and linux/arm64 builds ok; colibri-glasspane 58 passed;
colibri-daemon rpc_dispatched 2 passed; clippy clean; cargo fmt --check clean.
A `response` ack with success:false closes the task before the turn runs, so
it must bill zero new-turn usage. The failure arm passed a default baseline
to finalize_rpc_task, which measures the delta as (pane accumulator - 0); on a
persistent pane that already ran a turn, that re-bills every prior turn's
usage and cost.

This is a billing correctness fix, not contract-only test capture. Snapshot
the pane accumulator before branching and reuse it for both arms.

Evidence: with the fix reverted the new shared case fails with
`input_tokens left: 1042, right: 0` (same for output/cache_read/cost).

Contract: add `failed-ack-after-prior-usage` to task-records.json with an
optional `prior` block, replayed by both languages. The `prior` turn is what
makes the case meaningful: it asserts the delta that turn billed rather than
the pane total, since on a fresh pane the defect is invisible. The prior row
must be unchanged after the case stream runs. This is the task-level parity
case; the `ack-failure` stream in normalized-events.json remains the separate
normalization axis.

The Go-side regression (TestAckFailureAfterPriorUsageBillsZeroDelta) is kept.

Gates: cargo fmt --check clean; cargo clippy --workspace --all-targets
-D warnings clean; daemon rpc_ 9 passed; colibri-glasspane 58 passed;
gofmt/vet clean; CGO_ENABLED=0 go test ok; -race ok; freebsd/amd64 and
linux/arm64 Go builds ok.
Go checks and focused Rust contract replays pass on FreeBSD.
docs only
Go build, version, cache, and temporary-store boundaries are explicit.
zot reports reasoning tokens separately from output — a distinct billed
count for reasoning models such as DeepSeek-R1. Both the Rust PaneUsage
and the Go Usage projection dropped the `reasoning` field, so the token
ledger undercounted by the full reasoning amount even though `cost_usd`
stayed correct.

An aborted turn (`turn_end{stop:"aborted"}` with no standalone error
frame) was also finalized as success: both terminal readers keyed on
`done`/`error` only. Map abort to error in the normalized event fold and
close the task as failed with its partial usage billed, so the trailing
bare `done` cannot flip it back to success.

- Go: Usage/ledger/task slice gain reasoning_tokens; Normalized() maps
  stop:aborted -> error; Consume() finalizes aborted turns as failed.
- Rust: PaneUsage/Task/TaskCost/schema/set_task_cost/row_to_task gain
  reasoning_tokens; zot_event_type maps stop:aborted -> error; the RPC
  completion reader finalizes aborted turns as failed.
- Fixtures: reasoning_tokens expectations across normalized-events.json
  and task-records.json; new zot-abort.jsonl stream + abort case.

Linux proof: check-go.sh green, cargo fmt/clippy clean, glasspane (58),
ledger (31), daemon lib (197) all pass.
Formatting-only: rustfmt 1.95 wants the let-chain guard on one line.
No behavior or scope change.
Move reasoning_tokens to the end of the Go tasks CREATE TABLE so the
column order matches the Rust migration (reasoning_tokens is the last
additive column). Replay the normalized-events pack through an explicit
id-to-fixture map that panics on an unknown stream, covering zot-abort.
test(contracts): replay every task-records case via a case-id fixture map
Some checks failed
CI / python-tests (pull_request) Successful in 1s
format-check / prettier (pull_request) Successful in 4s
leak-guard / guard (pull_request) Successful in 1s
ci-gate / gate (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / markdown (pull_request) Has been cancelled
CI / port (pull_request) Has been cancelled
CI / agent-jail-pkgs (pull_request) Has been cancelled
92960c444e
Add an id-to-fixture map for task-records cases that panics on unknown
case ids, and an exhaustive test iterating every pack case entry through
it. Correct the stream_fixture comment to claim only the pack-to-fixture
direction it actually enforces.
Merge pull request 'feat/task-slice-reasoning-abort' (#26) from feat/task-slice-reasoning-abort into feat/go-task-slice-v1
Some checks failed
CI / python-tests (pull_request) Successful in 1s
format-check / prettier (pull_request) Successful in 4s
leak-guard / guard (pull_request) Successful in 1s
ci-gate / gate (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / markdown (pull_request) Has been cancelled
CI / port (pull_request) Has been cancelled
CI / agent-jail-pkgs (pull_request) Has been cancelled
2f4c1c6b05
Reviewed-on: #26
clawdie deleted branch feat/go-task-slice-v1 2026-09-21 01:26:33 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
clawdie/colibri!27
No description provided.