@sjawhar/pi-legion-envoy 5.15.2 → 5.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/envoy.js +27 -6
- package/dist/legion.js +28 -7
- package/dist/skills/dispatch/SKILL.md +30 -4
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -30063,6 +30063,7 @@ var ISSUE_STATUSES = [
|
|
|
30063
30063
|
function isIssueStatus(value) {
|
|
30064
30064
|
return ISSUE_STATUSES.includes(value);
|
|
30065
30065
|
}
|
|
30066
|
+
var ISSUE_ROUTE_STATUSES = ["live", "no_holder", "unknown"];
|
|
30066
30067
|
var DOC_EDIT_OPS = [
|
|
30067
30068
|
"replace",
|
|
30068
30069
|
"delete",
|
|
@@ -30369,8 +30370,8 @@ var dispatchToolSpecs = [
|
|
|
30369
30370
|
},
|
|
30370
30371
|
{
|
|
30371
30372
|
name: "dispatch_issues",
|
|
30372
|
-
example: { project: "AGENTC",
|
|
30373
|
-
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels,
|
|
30373
|
+
example: { project: "AGENTC", route_status: "no_holder" },
|
|
30374
|
+
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, open-ask count, and route with whether it reaches anyone, " + "so you can see backlog shape without opening every issue. Optionally filter by status, parent, " + "label, priority, route status, or how recently it changed; priority takes one or more of 0-3 " + "(P0-P3) and null for an issue with no priority, so an owner's P0/P1 audit is priority [0, 1]. " + 'route_status "no_holder" lists every open issue whose route names a role nobody holds or a ' + "session that is not running at the moment of the read, whatever its priority. A restarting " + "session is absent for minutes, so an issue is unowned only when a read ten minutes later agrees. " + "Do not use it to search by keyword or phrase; dispatch_search remains the keyword surface. " + "Rows are capped at limit (default 50, max 250), applied to the response here, not by the server.",
|
|
30374
30375
|
arguments: (z2) => ({
|
|
30375
30376
|
project: z2.string().describe("Project key to list issues from."),
|
|
30376
30377
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
@@ -30378,6 +30379,7 @@ var dispatchToolSpecs = [
|
|
|
30378
30379
|
label: z2.string().describe("Optional label filter.").optional(),
|
|
30379
30380
|
priority: z2.array(z2.number({ int: true, min: 0, max: 3 }).nullable(), { min: 1, max: 5 }).describe("Optional priority filter: one or more of 0 (P0, highest) through 3 (P3, lowest), and null " + "for an issue with no priority; an issue matching any listed value is returned.").optional(),
|
|
30380
30381
|
updated_since: z2.string().describe("Optional RFC3339 timestamp; only issues updated at or after it.").optional(),
|
|
30382
|
+
route_status: z2.enum(ISSUE_ROUTE_STATUSES).describe("Optional: only open issues whose route is in this state. live: a running session holds " + "the role or is the routed session. no_holder: nobody running holds the role, or the " + "session is not running, right now. unknown: the Envoy listener did not answer.").optional(),
|
|
30381
30383
|
limit: z2.number({ int: true, min: 1, max: 250 }).describe("Maximum rows, 1-250; default 50.").optional()
|
|
30382
30384
|
})
|
|
30383
30385
|
},
|
|
@@ -33398,6 +33400,20 @@ async function liveSessionTitles(client, needed) {
|
|
|
33398
33400
|
function holdsSession(claim) {
|
|
33399
33401
|
return claim?.actor.kind === "session";
|
|
33400
33402
|
}
|
|
33403
|
+
function routeHeldBySession(issue2) {
|
|
33404
|
+
return issue2.route_status === "live" && issue2.route?.startsWith("role:") === true;
|
|
33405
|
+
}
|
|
33406
|
+
function routeText(issue2, titles) {
|
|
33407
|
+
if (issue2.route === null)
|
|
33408
|
+
return "none";
|
|
33409
|
+
const holder = issue2.route_holder ?? null;
|
|
33410
|
+
const reach = {
|
|
33411
|
+
live: routeHeldBySession(issue2) && holder !== null ? ` (held by ${titles?.get(holder) ?? holder})` : "",
|
|
33412
|
+
no_holder: issue2.route.startsWith("role:") ? " (nobody holds it right now)" : " (that session is not running right now)",
|
|
33413
|
+
unknown: " (the Envoy listener did not answer, so whether it reaches anyone is unknown)"
|
|
33414
|
+
};
|
|
33415
|
+
return issue2.route + (issue2.route_status == null ? "" : reach[issue2.route_status]);
|
|
33416
|
+
}
|
|
33401
33417
|
function issueSummary(issue2, events, references, graph, titles) {
|
|
33402
33418
|
const asks = issue2.open_asks;
|
|
33403
33419
|
const spec = issue2.artifacts?.find((artifact) => artifact.primary);
|
|
@@ -33419,7 +33435,7 @@ function issueSummary(issue2, events, references, graph, titles) {
|
|
|
33419
33435
|
...issue2.priority === null ? [] : [`Priority: P${issue2.priority}`],
|
|
33420
33436
|
`Labels: ${issue2.labels.length === 0 ? "none" : issue2.labels.join(", ")}`,
|
|
33421
33437
|
componentsLine(issue2.components),
|
|
33422
|
-
`Route: ${issue2
|
|
33438
|
+
`Route: ${routeText(issue2, titles)}`,
|
|
33423
33439
|
...specApproval === undefined ? [] : [`Spec ${specApproval.replace(/^Approval/, "approval")}`],
|
|
33424
33440
|
"Open asks:",
|
|
33425
33441
|
...asks.length === 0 ? ["- none"] : asks.map((ask) => `- ${ask.id}: ${ask.question}`),
|
|
@@ -33931,6 +33947,7 @@ async function executeDispatchTool(input) {
|
|
|
33931
33947
|
const label = optionalString(args, "label");
|
|
33932
33948
|
const priority = optionalPriorityFilter(args, "priority");
|
|
33933
33949
|
const updatedSince = optionalString(args, "updated_since");
|
|
33950
|
+
const routeStatus = optionalString(args, "route_status");
|
|
33934
33951
|
const limit = Math.min(Math.max(optionalNumber(args, "limit") ?? 50, 1), 250);
|
|
33935
33952
|
const issues = await client.listIssues({
|
|
33936
33953
|
project,
|
|
@@ -33938,7 +33955,8 @@ async function executeDispatchTool(input) {
|
|
|
33938
33955
|
...parent === undefined ? {} : { parent },
|
|
33939
33956
|
...label === undefined ? {} : { label },
|
|
33940
33957
|
...priority === undefined ? {} : { priority },
|
|
33941
|
-
...updatedSince === undefined ? {} : { updated_since: updatedSince }
|
|
33958
|
+
...updatedSince === undefined ? {} : { updated_since: updatedSince },
|
|
33959
|
+
...routeStatus === undefined ? {} : { route_status: routeStatus }
|
|
33942
33960
|
});
|
|
33943
33961
|
const rows = issues.slice(0, limit).map((row) => ({
|
|
33944
33962
|
key: row.key,
|
|
@@ -33949,13 +33967,16 @@ async function executeDispatchTool(input) {
|
|
|
33949
33967
|
labels: row.labels ?? [],
|
|
33950
33968
|
open_asks: row.open_asks,
|
|
33951
33969
|
claim: row.claim ?? null,
|
|
33970
|
+
route: row.route ?? null,
|
|
33971
|
+
route_status: row.route_status ?? null,
|
|
33972
|
+
route_holder: row.route_holder ?? null,
|
|
33952
33973
|
updated_at: row.updated_at
|
|
33953
33974
|
}));
|
|
33954
33975
|
const titles = await liveSessionTitles(client, rows.some((row) => holdsSession(row.claim)));
|
|
33955
33976
|
return {
|
|
33956
33977
|
text: rows.length === 0 ? `No issues in ${project}.` : [
|
|
33957
33978
|
`${rows.length} ${rows.length === 1 ? "issue" : "issues"} in ${project}` + (issues.length > rows.length ? ` (showing ${rows.length} of ${issues.length})` : ""),
|
|
33958
|
-
...rows.map((row) => `${row.key} [${row.status}]${row.priority === null ? "" : ` P${row.priority}`} ${row.title}` + (row.open_asks === 0 ? "" : ` \xB7 ${row.open_asks} open ${row.open_asks === 1 ? "ask" : "asks"}`) + (row.claim === null ? "" : ` \xB7 claimed by ${claimText(row.claim, titles)}`))
|
|
33979
|
+
...rows.map((row) => `${row.key} [${row.status}]${row.priority === null ? "" : ` P${row.priority}`} ${row.title}` + (row.open_asks === 0 ? "" : ` \xB7 ${row.open_asks} open ${row.open_asks === 1 ? "ask" : "asks"}`) + (row.claim === null ? "" : ` \xB7 claimed by ${claimText(row.claim, titles)}`) + (row.route === null || row.route_status === "live" || row.route_status === null ? "" : ` \xB7 route ${routeText(row)}`))
|
|
33959
33980
|
].join(`
|
|
33960
33981
|
`),
|
|
33961
33982
|
details: { issues: rows }
|
|
@@ -34391,7 +34412,7 @@ ${trailer.join(`
|
|
|
34391
34412
|
const [references, graph, titles] = await Promise.all([
|
|
34392
34413
|
referencesPromise,
|
|
34393
34414
|
graphSections(client, dispatchIssueRef(read.issue.key)),
|
|
34394
|
-
liveSessionTitles(client, holdsSession(read.issue.claim))
|
|
34415
|
+
liveSessionTitles(client, holdsSession(read.issue.claim) || routeHeldBySession(read.issue))
|
|
34395
34416
|
]);
|
|
34396
34417
|
return {
|
|
34397
34418
|
text: issueSummary(read.issue, read.events, references, graph, titles),
|
package/dist/legion.js
CHANGED
|
@@ -30232,6 +30232,7 @@ var ISSUE_STATUSES = [
|
|
|
30232
30232
|
function isIssueStatus(value) {
|
|
30233
30233
|
return ISSUE_STATUSES.includes(value);
|
|
30234
30234
|
}
|
|
30235
|
+
var ISSUE_ROUTE_STATUSES = ["live", "no_holder", "unknown"];
|
|
30235
30236
|
var DOC_EDIT_OPS = [
|
|
30236
30237
|
"replace",
|
|
30237
30238
|
"delete",
|
|
@@ -30538,8 +30539,8 @@ var dispatchToolSpecs = [
|
|
|
30538
30539
|
},
|
|
30539
30540
|
{
|
|
30540
30541
|
name: "dispatch_issues",
|
|
30541
|
-
example: { project: "AGENTC",
|
|
30542
|
-
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels,
|
|
30542
|
+
example: { project: "AGENTC", route_status: "no_holder" },
|
|
30543
|
+
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, open-ask count, and route with whether it reaches anyone, " + "so you can see backlog shape without opening every issue. Optionally filter by status, parent, " + "label, priority, route status, or how recently it changed; priority takes one or more of 0-3 " + "(P0-P3) and null for an issue with no priority, so an owner's P0/P1 audit is priority [0, 1]. " + 'route_status "no_holder" lists every open issue whose route names a role nobody holds or a ' + "session that is not running at the moment of the read, whatever its priority. A restarting " + "session is absent for minutes, so an issue is unowned only when a read ten minutes later agrees. " + "Do not use it to search by keyword or phrase; dispatch_search remains the keyword surface. " + "Rows are capped at limit (default 50, max 250), applied to the response here, not by the server.",
|
|
30543
30544
|
arguments: (z2) => ({
|
|
30544
30545
|
project: z2.string().describe("Project key to list issues from."),
|
|
30545
30546
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
@@ -30547,6 +30548,7 @@ var dispatchToolSpecs = [
|
|
|
30547
30548
|
label: z2.string().describe("Optional label filter.").optional(),
|
|
30548
30549
|
priority: z2.array(z2.number({ int: true, min: 0, max: 3 }).nullable(), { min: 1, max: 5 }).describe("Optional priority filter: one or more of 0 (P0, highest) through 3 (P3, lowest), and null " + "for an issue with no priority; an issue matching any listed value is returned.").optional(),
|
|
30549
30550
|
updated_since: z2.string().describe("Optional RFC3339 timestamp; only issues updated at or after it.").optional(),
|
|
30551
|
+
route_status: z2.enum(ISSUE_ROUTE_STATUSES).describe("Optional: only open issues whose route is in this state. live: a running session holds " + "the role or is the routed session. no_holder: nobody running holds the role, or the " + "session is not running, right now. unknown: the Envoy listener did not answer.").optional(),
|
|
30550
30552
|
limit: z2.number({ int: true, min: 1, max: 250 }).describe("Maximum rows, 1-250; default 50.").optional()
|
|
30551
30553
|
})
|
|
30552
30554
|
},
|
|
@@ -32337,6 +32339,20 @@ async function liveSessionTitles(client, needed) {
|
|
|
32337
32339
|
function holdsSession(claim) {
|
|
32338
32340
|
return claim?.actor.kind === "session";
|
|
32339
32341
|
}
|
|
32342
|
+
function routeHeldBySession(issue2) {
|
|
32343
|
+
return issue2.route_status === "live" && issue2.route?.startsWith("role:") === true;
|
|
32344
|
+
}
|
|
32345
|
+
function routeText(issue2, titles) {
|
|
32346
|
+
if (issue2.route === null)
|
|
32347
|
+
return "none";
|
|
32348
|
+
const holder = issue2.route_holder ?? null;
|
|
32349
|
+
const reach = {
|
|
32350
|
+
live: routeHeldBySession(issue2) && holder !== null ? ` (held by ${titles?.get(holder) ?? holder})` : "",
|
|
32351
|
+
no_holder: issue2.route.startsWith("role:") ? " (nobody holds it right now)" : " (that session is not running right now)",
|
|
32352
|
+
unknown: " (the Envoy listener did not answer, so whether it reaches anyone is unknown)"
|
|
32353
|
+
};
|
|
32354
|
+
return issue2.route + (issue2.route_status == null ? "" : reach[issue2.route_status]);
|
|
32355
|
+
}
|
|
32340
32356
|
function issueSummary(issue2, events, references, graph, titles) {
|
|
32341
32357
|
const asks = issue2.open_asks;
|
|
32342
32358
|
const spec = issue2.artifacts?.find((artifact) => artifact.primary);
|
|
@@ -32358,7 +32374,7 @@ function issueSummary(issue2, events, references, graph, titles) {
|
|
|
32358
32374
|
...issue2.priority === null ? [] : [`Priority: P${issue2.priority}`],
|
|
32359
32375
|
`Labels: ${issue2.labels.length === 0 ? "none" : issue2.labels.join(", ")}`,
|
|
32360
32376
|
componentsLine(issue2.components),
|
|
32361
|
-
`Route: ${issue2
|
|
32377
|
+
`Route: ${routeText(issue2, titles)}`,
|
|
32362
32378
|
...specApproval === undefined ? [] : [`Spec ${specApproval.replace(/^Approval/, "approval")}`],
|
|
32363
32379
|
"Open asks:",
|
|
32364
32380
|
...asks.length === 0 ? ["- none"] : asks.map((ask) => `- ${ask.id}: ${ask.question}`),
|
|
@@ -32870,6 +32886,7 @@ async function executeDispatchTool(input) {
|
|
|
32870
32886
|
const label = optionalString(args, "label");
|
|
32871
32887
|
const priority = optionalPriorityFilter(args, "priority");
|
|
32872
32888
|
const updatedSince = optionalString(args, "updated_since");
|
|
32889
|
+
const routeStatus = optionalString(args, "route_status");
|
|
32873
32890
|
const limit = Math.min(Math.max(optionalNumber(args, "limit") ?? 50, 1), 250);
|
|
32874
32891
|
const issues = await client.listIssues({
|
|
32875
32892
|
project,
|
|
@@ -32877,7 +32894,8 @@ async function executeDispatchTool(input) {
|
|
|
32877
32894
|
...parent === undefined ? {} : { parent },
|
|
32878
32895
|
...label === undefined ? {} : { label },
|
|
32879
32896
|
...priority === undefined ? {} : { priority },
|
|
32880
|
-
...updatedSince === undefined ? {} : { updated_since: updatedSince }
|
|
32897
|
+
...updatedSince === undefined ? {} : { updated_since: updatedSince },
|
|
32898
|
+
...routeStatus === undefined ? {} : { route_status: routeStatus }
|
|
32881
32899
|
});
|
|
32882
32900
|
const rows = issues.slice(0, limit).map((row) => ({
|
|
32883
32901
|
key: row.key,
|
|
@@ -32888,13 +32906,16 @@ async function executeDispatchTool(input) {
|
|
|
32888
32906
|
labels: row.labels ?? [],
|
|
32889
32907
|
open_asks: row.open_asks,
|
|
32890
32908
|
claim: row.claim ?? null,
|
|
32909
|
+
route: row.route ?? null,
|
|
32910
|
+
route_status: row.route_status ?? null,
|
|
32911
|
+
route_holder: row.route_holder ?? null,
|
|
32891
32912
|
updated_at: row.updated_at
|
|
32892
32913
|
}));
|
|
32893
32914
|
const titles = await liveSessionTitles(client, rows.some((row) => holdsSession(row.claim)));
|
|
32894
32915
|
return {
|
|
32895
32916
|
text: rows.length === 0 ? `No issues in ${project}.` : [
|
|
32896
32917
|
`${rows.length} ${rows.length === 1 ? "issue" : "issues"} in ${project}` + (issues.length > rows.length ? ` (showing ${rows.length} of ${issues.length})` : ""),
|
|
32897
|
-
...rows.map((row) => `${row.key} [${row.status}]${row.priority === null ? "" : ` P${row.priority}`} ${row.title}` + (row.open_asks === 0 ? "" : ` \xB7 ${row.open_asks} open ${row.open_asks === 1 ? "ask" : "asks"}`) + (row.claim === null ? "" : ` \xB7 claimed by ${claimText(row.claim, titles)}`))
|
|
32918
|
+
...rows.map((row) => `${row.key} [${row.status}]${row.priority === null ? "" : ` P${row.priority}`} ${row.title}` + (row.open_asks === 0 ? "" : ` \xB7 ${row.open_asks} open ${row.open_asks === 1 ? "ask" : "asks"}`) + (row.claim === null ? "" : ` \xB7 claimed by ${claimText(row.claim, titles)}`) + (row.route === null || row.route_status === "live" || row.route_status === null ? "" : ` \xB7 route ${routeText(row)}`))
|
|
32898
32919
|
].join(`
|
|
32899
32920
|
`),
|
|
32900
32921
|
details: { issues: rows }
|
|
@@ -33330,7 +33351,7 @@ ${trailer.join(`
|
|
|
33330
33351
|
const [references, graph, titles] = await Promise.all([
|
|
33331
33352
|
referencesPromise,
|
|
33332
33353
|
graphSections(client, dispatchIssueRef(read.issue.key)),
|
|
33333
|
-
liveSessionTitles(client, holdsSession(read.issue.claim))
|
|
33354
|
+
liveSessionTitles(client, holdsSession(read.issue.claim) || routeHeldBySession(read.issue))
|
|
33334
33355
|
]);
|
|
33335
33356
|
return {
|
|
33336
33357
|
text: issueSummary(read.issue, read.events, references, graph, titles),
|
|
@@ -33394,7 +33415,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
33394
33415
|
// package.json
|
|
33395
33416
|
var package_default = {
|
|
33396
33417
|
name: "@sjawhar/pi-legion-envoy",
|
|
33397
|
-
version: "5.
|
|
33418
|
+
version: "5.16.1",
|
|
33398
33419
|
type: "module",
|
|
33399
33420
|
omp: {
|
|
33400
33421
|
extensions: [
|
|
@@ -286,13 +286,14 @@ distinction from the named issue, citing it (`dispatch://KEY`), for whoever read
|
|
|
286
286
|
|
|
287
287
|
To see the shape of a project rather than find a phrase, list its issues:
|
|
288
288
|
```ts
|
|
289
|
-
dispatch_issues({ project, status?, parent?, label?, priority?, updated_since?, limit? })
|
|
289
|
+
dispatch_issues({ project, status?, parent?, label?, priority?, route_status?, updated_since?, limit? })
|
|
290
290
|
```
|
|
291
291
|
Each row carries the issue key, title, status, priority, parent, labels, its open-ask count, and
|
|
292
292
|
when it last changed — a roadmap or backlog pass without opening every issue. Filter with `status`
|
|
293
293
|
(a lifecycle status), `parent` (one issue's children), `label`, `priority` (a list of `0`–`3`, with
|
|
294
|
-
`null` for an issue with no priority: `[0, 1]` is every P0 and P1),
|
|
295
|
-
timestamp, for "what moved this week"). `limit` caps the rows at 50 by
|
|
294
|
+
`null` for an issue with no priority: `[0, 1]` is every P0 and P1), `route_status` (below), or
|
|
295
|
+
`updated_since` (an RFC3339 timestamp, for "what moved this week"). `limit` caps the rows at 50 by
|
|
296
|
+
default and 250 at most.
|
|
296
297
|
|
|
297
298
|
This is not search: it matches no text. Use `dispatch_search` for a keyword or phrase, and
|
|
298
299
|
`dispatch_issues` when you want every issue in a project and its current state.
|
|
@@ -311,7 +312,13 @@ and is not re-staffed. A todo with a finished spec reads as queued work that nob
|
|
|
311
312
|
(LEGION-173 sat in todo for two weeks with a complete spec; AGENTC-1010's v4 plan sat in backlog
|
|
312
313
|
with nobody building it).
|
|
313
314
|
|
|
314
|
-
|
|
315
|
+
A close that says the defect cannot happen cites the code that makes it impossible. An issue
|
|
316
|
+
closed because a rewrite forecloses it names the file and line in the rewrite that does so; a
|
|
317
|
+
close that cannot name one is not foreclosed, it is unread. The cheapest way for a rewrite to reach
|
|
318
|
+
parity is to port the code, defect included: LEGION-211's bare `git worktree prune`, filed against
|
|
319
|
+
the TypeScript daemon, had been ported into the Go coordinator and was live in production.
|
|
320
|
+
|
|
321
|
+
The audit finds four shapes:
|
|
315
322
|
|
|
316
323
|
- **Unstaffed work.** A plan or measurement exists, and no one is building it.
|
|
317
324
|
- **Unrecorded delivery.** An issue not yet in `testing` or `done`, claimed or not, has a merged PR
|
|
@@ -321,6 +328,25 @@ The audit finds three shapes:
|
|
|
321
328
|
sits in backlog (OPS-132, done by hand on every migration merge). It leaves no plan and no PR to
|
|
322
329
|
find; the tell is your own messages. Doing something by hand more than once means an issue is
|
|
323
330
|
wearing the wrong status.
|
|
331
|
+
- **Unreachable route.** An open issue whose route names a role nobody holds, or a session that is
|
|
332
|
+
not running, reaches nobody, whatever its priority, and the priority filter above never finds
|
|
333
|
+
it. List it on its own:
|
|
334
|
+
```ts
|
|
335
|
+
dispatch_issues({ project, route_status: "no_holder", limit: 250 })
|
|
336
|
+
```
|
|
337
|
+
Each row reads `route role:sre (nobody holds it right now)` or `route session:<id> (that session
|
|
338
|
+
is not running right now)`. That is one read of the listener, and one read is a restart gap as
|
|
339
|
+
often as a vacancy: an agent box that restarts or resumes keeps the session id, but the session
|
|
340
|
+
is absent from the listener for minutes, and its role with it. On 2026-09-27, 58 of 63 session
|
|
341
|
+
routes one read showed as unreachable pointed at a single session that was moving between boxes.
|
|
342
|
+
So a route is unowned only when it is `no_holder` on two reads at least ten minutes apart: list
|
|
343
|
+
again after ten minutes and act on the issues both lists name. Confirm with the second
|
|
344
|
+
`dispatch_issues` read, not `envoy_role_get`: a role lookup releases the claim of a holder whose
|
|
345
|
+
session is absent from the registry as it answers. Then staff the role, re-route the
|
|
346
|
+
issue to a live holder, or clear the route and assign it (AGENTC-1065, a P2 production listener
|
|
347
|
+
503, sat routed to an unheld `role:sre` with no assignee). `route_status: "unknown"` means the
|
|
348
|
+
listener did not answer, so a route could not be judged; a `no_holder` filter refuses rather
|
|
349
|
+
than answer an empty list then.
|
|
324
350
|
|
|
325
351
|
Run the audit as a step of a coordinator's loop, at each checkpoint, not as a habit: these shapes
|
|
326
352
|
are found by running the check, not by noticing them.
|