swarm-controller: replay a subagent's newest rows before its live tail
A subagent's terminal opened directly showed nothing until the subagent published again: the relay was a core subscription, though its rows sit in term-sub-<agent> for 24 hours. It now goes through the agent route's replay-then-tail, so it opens with the subagent's newest 100 rows and resumes after Last-Event-ID. - term_stream.rs: the replay takes a stream name and a filter subject instead of an agent, and `replay_or_live` (replay, else the live core subscription) is the one fn both routes call. When the filter is one of several subjects in the stream, a fresh reader's consumer starts at the oldest row and drops all but the newest REPLAY_ROWS it counts as pending, since the stream's newest sequences may belong to sibling subagents. A single-subject stream (term-main-<agent>) picks its start with replay_start exactly as before. Tests for both subagent cases. - subagent_term.rs: the relay calls replay_or_live on the subagent's subject in term-sub-<agent> and reads Last-Event-ID; module doc and OpenAPI text no longer say "live only". - main.rs: the handler test passes the new headers argument. - swarm-queue-client agent_term.rs: replay_consumer_config takes the filter subject instead of the agent. - swarm-queue-client subagent_term.rs: consumer_create_grant, the extended CONSUMER.CREATE form with a subagent subject as filter. - swarm-nats-auth policy.rs: the controller holds that grant; the agent grant's `$SWARM.term.*` filter is two tokens short of a subagent subject, and the test that refused it now admits it. - docs/swarm/ui.md, README.md: a subagent terminal opens with its newest rows. Fixes #5527
This commit is contained in:
parent
f65364759f
commit
d9c586ef94
8 changed files with 272 additions and 71 deletions
|
|
@ -135,17 +135,21 @@ pub async fn open_or_create(
|
|||
}
|
||||
}
|
||||
|
||||
/// The consumer that replays `agent`'s rows from `start_sequence` and then
|
||||
/// follows new ones, delivered to `deliver_subject` in stream order.
|
||||
/// The consumer that replays the rows on `filter_subject` from
|
||||
/// `start_sequence` and then follows new ones, delivered to `deliver_subject`
|
||||
/// in stream order. The filter is an agent's [`stream_subject`] or one
|
||||
/// subagent's [`crate::subagent_term::subject`].
|
||||
///
|
||||
/// Ephemeral and unacknowledged: the server drops it once nothing listens
|
||||
/// on `deliver_subject`, so a reader that goes away needs no delete grant.
|
||||
/// `name` must be set, because the server only takes the filter from the
|
||||
/// subject [`consumer_create_grant`] covers when the create names a consumer.
|
||||
/// subject [`consumer_create_grant`] (or
|
||||
/// [`crate::subagent_term::consumer_create_grant`]) covers when the create
|
||||
/// names a consumer.
|
||||
#[cfg(feature = "agent-term")]
|
||||
#[must_use]
|
||||
pub fn replay_consumer_config(
|
||||
agent: &str,
|
||||
filter_subject: String,
|
||||
name: String,
|
||||
deliver_subject: String,
|
||||
start_sequence: u64,
|
||||
|
|
@ -153,7 +157,7 @@ pub fn replay_consumer_config(
|
|||
async_nats::jetstream::consumer::push::Config {
|
||||
name: Some(name),
|
||||
deliver_subject,
|
||||
filter_subject: stream_subject(agent),
|
||||
filter_subject,
|
||||
deliver_policy: async_nats::jetstream::consumer::DeliverPolicy::ByStartSequence {
|
||||
start_sequence,
|
||||
},
|
||||
|
|
@ -269,7 +273,12 @@ mod tests {
|
|||
#[cfg(feature = "agent-term")]
|
||||
#[test]
|
||||
fn the_consumer_replays_before_it_follows() {
|
||||
let cfg = replay_consumer_config("iris", "r1".to_owned(), "_INBOX.x".to_owned(), 901);
|
||||
let cfg = replay_consumer_config(
|
||||
stream_subject("iris"),
|
||||
"r1".to_owned(),
|
||||
"_INBOX.x".to_owned(),
|
||||
901,
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.deliver_policy,
|
||||
async_nats::jetstream::consumer::DeliverPolicy::ByStartSequence {
|
||||
|
|
|
|||
Loading…
Reference in a new issue