Skip to content

Split comenq into put, list, bump, bust, and del subcommands - #149

Draft
leynos wants to merge 7 commits into
user-hosted-executionfrom
comenq-subcommands
Draft

Split comenq into put, list, bump, bust, and del subcommands#149
leynos wants to merge 7 commits into
user-hosted-executionfrom
comenq-subcommands

Conversation

@leynos

@leynos leynos commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

This branch splits the comenq client into queue-management subcommands — put, list, bump, bust, and del — and rebuilds the daemon's queue so those operations are possible. put now prints the comment's deterministic eight-character identifier and an approximate ETA; list prints the pending schedule with identifiers, ETAs, and one-line sixty-character summaries; bump, bust, and del reorder or remove entries by identifier. The append-only yaque queue cannot reorder or delete entries, so the daemon now uses a bespoke persistent store, and the client-daemon socket exchange becomes a JSON request/response protocol. Random flutter is pre-calculated when a comment is enqueued, so the ETA reported at put time is the schedule the worker executes; cooldowns always run in full and flutter only ever lengthens the wait. A default put additionally waits one full cooldown (plus its flutter) from enqueue even when the queue is idle — each entry records a not_before floor that bump cannot circumvent — and put --now lifts the floor to post as soon as the queue allows.

Stacked on #148 (user-hosted-execution); the base branch of this pull request is user-hosted-execution and it should merge after #148. Note the daemon's on-disk queue format and wire protocol change together, so client and daemon must be upgraded in lockstep (the previous queue format's files are ignored; the store starts empty).

Review walkthrough

  • Start with src/protocol.rs for the wire contract: one tagged JSON Request per connection (op: put/list/bump/bust/del), answered by one Response (ok with optional entry/entries, or error).
  • Then crates/comenqd/src/store.rs, the heart of the change: one JSON file per pending comment under <queue_path>/entries (atomic write-then-rename), an explicit ordering key (bump = head minus one, bust = tail plus one), flutter sampled at enqueue, deterministic identifiers (first eight hex digits of a 64-bit FNV-1a hash over the request fields and enqueue second; an identical request within the same second is idempotent), a persisted last_post timestamp, and the ETA projection in schedule.
  • crates/comenqd/src/queue.rs bundles the store with the configuration and a change notifier, and dispatches protocol requests.
  • crates/comenqd/src/listener.rs and crates/comenqd/src/worker.rs: the listener executes requests and replies over the same connection; the worker recomputes the head's due time every iteration, so queue mutations take effect immediately, and API failures retain the entry for retry after a full cooldown. crates/comenqd/src/supervisor.rs sheds the queue-writer task and client channel entirely.
  • Client side: crates/comenq/src/lib.rs defines the subcommands with a global --socket/COMENQ_SOCKET override; crates/comenq/src/client.rs performs the full transaction and distinguishes daemon-reported errors from transport failures; crates/comenq/src/output.rs renders ETAs and the sixty-character one-line summaries.
  • Behavioural coverage: tests/features/queue.feature exercises put identifiers and ETAs, listing order with strictly increasing ETAs, summary truncation, bump/bust/del ordering, and unknown-identifier rejection, with steps in tests/steps/queue_steps.rs. The listener, worker, CLI, and client scenarios were rewritten for the protocol.
  • Documentation: docs/comenq-design.md (store, protocol, topology, ETA semantics), packaging/man/comenq.1, and the README. The vestigial client_channel_capacity configuration field is removed.

Validation

  • make lint (clippy with warnings denied plus the Whitaker Dylint suite): clean on every commit.
  • make test: 165 unit and integration tests passed; all nine cucumber feature files passed, including the new queue-management feature.
  • make markdownlint and make nixie: clean (all Mermaid diagrams in the refreshed design document validate).
  • Live smoke test: the rebuilt daemon and client are deployed under systemd --user; comenq list answers over the discovered socket.

Notes

  • The strengthened worker success scenario (asserting the posted entry leaves the queue) caught the old mock returning an unparseable empty body — the fixture now returns a structurally valid comment.
  • put is idempotent for an identical request within the same second by construction of the identifier; this is documented on QueueStore::put.
  • The immediate protocol field and the stored not_before floor both default off/zero, so entries persisted before the floor existed behave like immediate puts.

leynos added 5 commits July 19, 2026 17:24
Add a `protocol` module to `comenq-lib` with tagged `Request` and
`Response` enums covering the forthcoming queue operations: `put`,
`list`, `bump`, `bust`, and `del`. `PendingEntry` carries the
deterministic identifier, approximate ETA in seconds, target pull
request, and full comment body; display truncation is left to
consumers. Each connection will carry one JSON request and one JSON
reply. Derive `Clone` on `CommentRequest` so queue entries can embed
it.
Introduce `comenqd::store::QueueStore`, a filesystem-backed queue
holding one JSON file per pending comment under
`<queue_path>/entries`. Each entry carries:

- a deterministic eight-character identifier (64-bit FNV-1a over the
  request fields and enqueue time, first eight hex digits), stable
  across restarts;
- an explicit ordering key so entries can be moved to the head
  (`bump`), moved to the tail (`bust`), or removed (`del`); and
- a flutter sampled at enqueue time, fixing the entry's estimated
  posting time from the moment it is reported.

The Unix time of the most recent successful post persists in
`<queue_path>/last_post`, letting `schedule` project an estimated
posting time for every pending entry: the head is due one full
cooldown plus its own flutter after the last post (immediately when
nothing has been posted), and each successor follows its
predecessor's projected time by a further cooldown plus flutter.

Files are written to a temporary sibling and renamed into place so
entries are never observed half-written. The append-only `yaque`
queue cannot express reordering or deletion, so subsequent commits
move the daemon onto this store.
Replace the yaque-based pipeline (listener → mpsc channel → queue
writer → worker) with a `SharedQueue` that bundles the persistent
store, the daemon configuration, and a change notifier:

- The listener now parses protocol `Request`s, executes them against
  the shared queue, and writes the JSON `Response` back over the
  connection. Malformed requests receive an error reply instead of a
  silently dropped connection.
- The worker recomputes the head entry's due time on every
  iteration and sleeps until it falls due; queue mutations and
  shutdown interrupt the wait, so bump, bust, and del take effect
  immediately. Successful posts record `last_post`; API failures
  keep the entry and retry after a full cooldown.
- The supervisor sheds the queue-writer task, its respawn logic,
  and the client channel; it now supervises just the listener and
  worker over the shared queue.

Remove the `yaque` dependency and its metadata-file helper. Rewrite
the daemon integration tests and the listener and worker cucumber
steps against the protocol, strengthening the success scenario to
assert the posted entry leaves the queue — which also caught the
success mock returning an unparseable empty body.
Replace the single-purpose argument list with five subcommands:

- `put owner/repo <pr> <body>` enqueues a comment and prints its
  deterministic identifier with the daemon-reported approximate ETA
  (`Queued 1a2b3c4d for owner/repo#7 — posts in ~1h 01m`).
- `list` prints one line per pending comment: identifier, ETA,
  target pull request, and the body collapsed to a single line and
  truncated to sixty characters with an ellipsis.
- `bump <id>`, `bust <id>`, and `del <id>` move an entry to the
  head or tail of the queue, or remove it.

The `--socket` flag becomes global (still discoverable via
`$XDG_RUNTIME_DIR` and overridable with `COMENQ_SOCKET`). The client
now completes a full transaction: it writes one protocol request,
half-closes, reads the JSON reply, and surfaces daemon-reported
errors distinctly from transport failures. Rendering lives in a new
`output` module with unit-tested ETA and summary formatting.
Add `tests/features/queue.feature` with scenarios exercising the
daemon's protocol surface end to end: `put` replies carry an
eight-character identifier and an ETA, identifiers reappear in
listings, listings report strictly increasing ETAs in posting order,
long bodies collapse to a one-line sixty-character summary, `bump`
moves an entry to the head, `bust` to the tail, `del` removes it,
and unknown identifiers are rejected with a named error.

Refresh the documentation for the new architecture: the design
document now describes the bespoke `QueueStore` (entry format,
identifier scheme, ordering keys, atomic writes, `last_post`, ETA
projection), the request/response protocol, the two-task supervisor
topology, and the client subcommands; the `comenq` man page and
README document `put`, `list`, `bump`, `bust`, and `del`.

Remove the vestigial `client_channel_capacity` configuration field —
nothing has buffered client requests since the queue-writer task was
removed.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @leynos, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3b01ea88-bd72-4a52-9dc7-885b167b694c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch comenq-subcommands

Comment @coderabbitai help to get the list of available commands.

leynos added 2 commits July 19, 2026 18:20
A default `put` previously posted immediately when the queue was
idle. Each entry now records a `not_before` floor — its enqueue time
plus one full cooldown plus its own sampled flutter — and the
schedule takes the later of that floor and the chain-projected due
time, so even an idle queue paces a fresh comment and `bump` cannot
circumvent the floor.

`comenq put --now` (protocol field `immediate`) lifts the floor,
restoring the previous post-as-soon-as-possible behaviour. The
field defaults off on the wire and in the store, so entries
persisted before this change behave like immediate puts.

Group the store's put inputs in `PutOptions` (cooldown, flutter
ceiling, immediacy), cover the floor with store unit tests and new
queue scenarios, and document the behaviour in the design document,
man page, and README.
Bring in the stale-socket discovery fix: candidates are probed by
connecting in order rather than checking file existence. Adapt the
subcommand client's `transact` to connect through `connect_first`
over `Args::socket_candidates`, so every operation (put, list,
bump, bust, del) falls past a stale user socket to a healthy system
daemon.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant