diff --git a/docs/architecture/rfcs/ledger/typescript-control-plane-migration-v0/2026-11-02-global-manager-reads.md b/docs/architecture/rfcs/ledger/typescript-control-plane-migration-v0/2026-11-02-global-manager-reads.md new file mode 100644 index 0000000000..8274b67e4c --- /dev/null +++ b/docs/architecture/rfcs/ledger/typescript-control-plane-migration-v0/2026-11-02-global-manager-reads.md @@ -0,0 +1,78 @@ +# Global summary and gate read transactions — 2026-11-02 + +The shipped `global-summary` and `global-gates` CLI commands now call +`presentation.global_summary.project` and `presentation.global_gates.project` +in `presentation/global_manager_read.ts`. These are complete read projections: +lane selection and ordering, gate questions and routing, exact-target precedence +over scope-derived blocked references, visible work verification, progress +filtering, pre-limit totals, grouping, actions and envelope composition belong +to TypeScript. Decision relations and projection-envelope sealing compose in +process through their existing owners. No standalone leaf RPC is added. + +## Boundary and crossings + +Frozen synthetic characterization covers complete JSON and Markdown bytes and +source-read order, including empty/unhealthy sources, mixed lanes, duplicate +Goals, unavailable quota, Agent mismatch, early termination, range and limit +normalization, exact/scope references, redaction and stale/unreadable/incomplete +upstream envelopes. It passed on the unchanged Python implementation before +production edits. Its expected outputs remain unchanged. + +Instrumented projection crossings change from **1 → 1** for summary and +**R + 1 → 1** for gates, where R counts nonempty relation evaluations. The +mixed and early-stop gate cases measure **2 → 1**; empty, unhealthy and scoped +cases without relation evaluation measure **1 → 1**. These counts exclude the +unchanged status/quota source providers and their own internal requests. + +Python retains source acquisition and quota-failure adaptation. Gate eligibility +and user-gate classification remain a single Python source-side predicate: +`_gate_source_facts` uses the shared `open_user_gate_todo_items` owner, which has +other Python callers. The incremental scan must decide its early stop before +further host reads. It preserves deduplication and the original stop point, +attaching the first formal gate row and eligibility only to gate samples. TS +strictly decodes these facts and composes owner selection, blocked references +and the complete gate projection; it does not repeat classification or +eligibility. This avoids per-candidate bridge chatter and reads beyond the limit. + +The replaced Python lane/gate construction, relation calls, progress filtering, +ordering, counting, grouping, action/result assembly and envelope composition +are deleted. Python retains text redaction and timestamp compatibility codecs, +existing error rendering and Markdown rendering for the CLI and presentation +smoke. `SOURCE_SURFACES` remains source-acquisition metadata under its existing +registered context and is passed to the typed projection. No persisted format, +permission or state mutation changes. + +## Exit and qualification + +Remove the Python facade, codecs and both bridge methods when native callers +obtain these source facts through TS-owned status/quota acquisition and consume +the complete projections directly, and when the CLI and presentation smoke no +longer import the Python Markdown renderers. The source predicate can move only +when user-gate classification has a TS owner and source acquisition moves to TS; +retire its Python implementation then. Remove the frozen migration fixtures when that last Python caller contract is retired; retain +independent semantic tests. + +The existing CLI surface is preserved: `summary-all` is unregistered on both +base and candidate; `global-summary` is the supported summary command. Real CLI +JSON and Markdown readbacks match byte-for-byte against an archived base on a +synthetic canonical runtime with the same clock. This is source-checkout +qualification, not wheel/sdist or installed-host qualification. No latency, +complete-cost or long-horizon benefit is claimed. + +Focused Python characterization and existing summary tests pass (31), as do +38 TS owner/envelope/relation/dispatch tests, typechecking, the vocabulary gate +(45/45 independent twins), maintainability, interface budgets and Markdown +presentation smoke. File and SQLite integrated canary behavior passes; an +initial concurrent run exceeded the unchanged 60-second aggregate budget, +while a rerun passed (18.15s File, 13.44s SQLite). The broader source-checkout +Python selection has 168 passes and two failures, both reproduced on the +archived base: the quota CLI scheduler-envelope test lacks required decision +capture arguments, and the steward endpoint vocabulary test expects the old +endpoint set. No gate or expected fixture was changed to resolve these failures. + +After the source-predicate repair, the unchanged characterization and summary +selection passes all 31 tests; the global-manager and handler selection passes +22 TS tests, including malformed-fact rejection and source-eligibility adoption. +Typechecking, ruff, vocabulary drift, maintainability, hot-path budget, Markdown +primitives and all six CLI parity comparisons pass. Product changes against the +baseline total +321/-629 lines (net -308). Fixtures and gates remain unchanged. diff --git a/docs/architecture/rfcs/ledger/typescript-control-plane-migration-v0/2026-11-02-global-manager-reads.zh-CN.md b/docs/architecture/rfcs/ledger/typescript-control-plane-migration-v0/2026-11-02-global-manager-reads.zh-CN.md new file mode 100644 index 0000000000..d872e371cd --- /dev/null +++ b/docs/architecture/rfcs/ledger/typescript-control-plane-migration-v0/2026-11-02-global-manager-reads.zh-CN.md @@ -0,0 +1,60 @@ +# 全局摘要与门禁读取事务 — 2026-11-02 + +已交付的 `global-summary` 和 `global-gates` CLI 命令现调用 +`presentation/global_manager_read.ts` 中的 +`presentation.global_summary.project` 与 `presentation.global_gates.project`。 +这是完整读取投影:lane 选择和排序、门禁问题与路由、精确目标优先于作用域派生 +阻塞引用、可见工作校验、进展过滤、截断前总数、分组、动作以及 envelope 组合均由 +TypeScript 所有。决策关系与投影 envelope 封装通过已有 owner 在进程内组合, +没有新增独立叶级 RPC。 + +## 边界与跨运行时调用 + +冻结的合成刻画覆盖完整 JSON 与 Markdown 字节和源读取顺序,包括空或不健康源、 +混合 lane、重复 Goal、quota 不可用、Agent 不匹配、提前终止、时间范围和 limit +规范化、精确/作用域引用、脱敏,以及过期、不可读、覆盖不完整的上游 envelope。 +它在生产代码修改前已通过原有 Python 实现;预期输出保持不变。 + +仪表测得投影跨运行时调用:摘要 **1 → 1**,门禁 **R + 1 → 1**,R 为非空关系 +求值次数。混合与提前终止门禁用例为 **2 → 1**;不需关系求值的空源、不健康源和 +限定 Agent 用例为 **1 → 1**。这些计数不含保持不变的 status/quota 源 provider +及其内部请求。 + +Python 保留源采集与 quota 失败适配。门禁资格与用户门禁分类保留为单一 Python +源侧谓词:`_gate_source_facts` 复用共享的 `open_user_gate_todo_items` owner, +它还有其他 Python 调用方。增量扫描必须在进一步读取 host 之前决定提前停止。 +扫描保留去重和原有停止位置,仅向门禁样本附加首个正式门禁行与资格布尔值。 +TS 严格解码这些事实,组合 owner 选择、阻塞引用与完整门禁投影,不重复分类 +或资格判断。这避免逐候选桥接调用及越过 limit 的额外读取。 + +原有 Python lane/门禁构建、关系调用、进展过滤、排序、计数、分组、动作/结果 +组装以及 envelope 组合已删除。Python 保留文本脱敏和时间戳兼容 codec、已有错误 +渲染以及 CLI 和展示 smoke 使用的 Markdown 渲染。`SOURCE_SURFACES` 仍是原有 +注册上下文中的源采集元数据,传给 typed 投影。持久化格式、权限和状态变更不变。 + +## 退出与资格边界 + +当原生调用方通过 TS 所有的 status/quota 采集获取这些源事实并直接消费完整投影, +且 CLI 和展示 smoke 不再导入 Python Markdown 渲染器时,删除 Python facade、 +codec 和两个桥接方法。只有当用户门禁分类拥有 TS owner 且源采集迁移到 TS 时, +才能迁移源侧谓词;届时删除其 Python 实现。最后一个 Python 调用合同退役时 +删除冻结迁移 fixture,保留独立语义测试。 + +保持现有 CLI 表面:base 与 candidate 均未注册 `summary-all`;受支持的摘要命令为 +`global-summary`。在相同时钟的合成 canonical runtime 上,真实 CLI 的 JSON 与 +Markdown 读取结果与归档 base 逐字节一致。这是源码 checkout 资格,不是 wheel/ +sdist 或已安装主机资格。不声称延迟、完整成本或长程效果收益。 + +聚焦 Python 刻画与已有摘要测试通过(31 项),38 项 TS owner/envelope/关系/分发 +测试、类型检查、词汇门禁(独立 twins 为 45/45)、可维护性、接口预算以及 +Markdown 展示 smoke 也通过。File 与 SQLite 集成 canary 行为通过;首次并发运行 +超过未修改的 60 秒总预算,重跑通过(File 18.15 秒,SQLite 13.44 秒)。较广的 +源码 Python 测试选择为 168 项通过、2 项失败,两项均在归档 base 复现:quota CLI +scheduler-envelope 测试缺少必需的决策捕获参数,steward 端点词汇测试仍预期旧端点 +集合。未通过修改门禁或 fixture 预期来处理这些失败。 + +源侧谓词修复后,未修改的刻画与摘要测试选择全部通过(31 项);全局管理读取与 +handler 测试选择通过 22 项 TS 测试,包括拒绝畸形事实和采用源侧资格。 +类型检查、ruff、词汇漂移、可维护性、热路径预算、Markdown 基础组件及全部六项 +CLI 一致性比较通过。产品代码相对基线共增加 321 行、删除 629 行(净减 308 行)。 +fixture 与门禁保持不变。 diff --git a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md index b7988256d0..d2fd4bf976 100644 --- a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md +++ b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md @@ -2836,3 +2836,4 @@ with shared write/read status-phase checks. See the [2026-10-02 checkpoint](ledg - Summary-display final-head qualification and preserved tail cohorts: [2026-10-27 follow-up](ledger/typescript-control-plane-migration-v0/2026-10-27-todo-summary-qualification.md). - Quota summary composition in one `todo.quota_planning.project` crossing with source references: [2026-11-02 cutover](ledger/typescript-control-plane-migration-v0/2026-11-02-quota-summary-transaction.md). +- Complete global summary and gate read transactions: [2026-11-02 checkpoint](ledger/typescript-control-plane-migration-v0/2026-11-02-global-manager-reads.md). diff --git a/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md b/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md index abc69a3925..f8fb45a01a 100644 --- a/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md +++ b/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md @@ -2101,3 +2101,4 @@ SQLite 中位数略升,收益不能外推为全面加速。这只资格化本 - 摘要展示最终 head 验收与历史尾延迟批次:[2026-10-27 后续记录](ledger/typescript-control-plane-migration-v0/2026-10-27-todo-summary-qualification.zh-CN.md)。 - 配额摘要经来源引用在一次 `todo.quota_planning.project` 跨运行时调用内完成组合:[2026-11-02 切换记录](ledger/typescript-control-plane-migration-v0/2026-11-02-quota-summary-transaction.zh-CN.md)。 +- 完整全局摘要与门禁读取事务:[2026-11-02 检查点](ledger/typescript-control-plane-migration-v0/2026-11-02-global-manager-reads.zh-CN.md)。 diff --git a/loopx/control_plane/effect_runtime_handlers.ts b/loopx/control_plane/effect_runtime_handlers.ts index 125e4caeef..4c829f95f6 100644 --- a/loopx/control_plane/effect_runtime_handlers.ts +++ b/loopx/control_plane/effect_runtime_handlers.ts @@ -469,6 +469,8 @@ export function createEffectRuntimeHandlers( ["quota.monitor_poll.commit", lazyHandler(() => import("./quota/monitor_poll_commit.ts"), ({evaluateQuotaMonitorPollCommit}) => evaluateQuotaMonitorPollCommit)], ["presentation.decision_notice.project", lazyHandler(() => import("./presentation/decision_notice.ts"), ({projectDecisionNotice}) => projectDecisionNotice)], ["presentation.decision_notice.validate_references", lazyHandler(() => import("./presentation/decision_notice.ts"), ({validateDecisionNoticeReferences}) => validateDecisionNoticeReferences)], + ["presentation.global_summary.project", lazyHandler(() => import("./presentation/global_manager_read.ts"), ({projectGlobalSummary}) => projectGlobalSummary)], + ["presentation.global_gates.project", lazyHandler(() => import("./presentation/global_manager_read.ts"), ({projectGlobalGates}) => projectGlobalGates)], ["presentation.goal_attention.project", lazyHandler(() => import("./presentation/goal_attention.ts"), ({projectGoalAttention}) => projectGoalAttention)], ["presentation.goal_sources.project", lazyHandler(() => import("./presentation/goal_source_links.ts"), ({projectGoalSourceLinks}) => projectGoalSourceLinks)], ["presentation.owner_attention.plan", lazyHandler(() => import("./presentation/owner_attention.ts"), ({planOwnerAttention}) => planOwnerAttention)], diff --git a/loopx/control_plane/presentation/global_manager_read.ts b/loopx/control_plane/presentation/global_manager_read.ts new file mode 100644 index 0000000000..d05ce877d2 --- /dev/null +++ b/loopx/control_plane/presentation/global_manager_read.ts @@ -0,0 +1,217 @@ +/** Complete global read projections; host adapters retain incremental source IO. */ +import type {JsonObject} from "../effect_program.ts"; +import {requireJsonObject, requireInteger, requireNonEmptyString, requireStringArray} from "../runtime_decode.ts"; +import {sealProjectionEnvelope} from "../projection_envelope.ts"; +import {todoGateRelation} from "../todos/decision_scope.ts"; +import {object, id, readScope, readScopes, pythonFieldText} from "../todos/read_codec.ts"; +import {authorityUnicodeCompare} from "../coordination/authority_store_codec.ts"; +const COMMAND = "/loopx-global-summary"; +const GLOBAL_GATES_COMMAND = "/loopx-global-gates"; +const SCHEMA_VERSION = "global_manager_command_response_v0"; +const GLOBAL_GATES_SOURCE_SURFACES = ["status attention queue", "quota should-run summaries", "active-state todo projections", "compact run-history projection consumed by status"]; +const LANE_PRIORITY: Record = {"eligible": 0, "normal_run": 0, "run": 0, "operator_gate": 1, "focus_wait": 1, "waiting": 2, "throttled": 3, "paused": 4, "quota_unavailable": 5}; +const list = (value: unknown): unknown[] => Array.isArray(value) ? value : []; +const rows = (value: unknown): JsonObject[] => list(value).filter(value => value !== null && typeof value === "object" && !Array.isArray(value)).map(object); +const truth = (value: unknown): boolean => Array.isArray(value) ? value.length > 0 + : value !== null && typeof value === "object" ? Object.keys(value).length > 0 : Boolean(value); +const or = (...values: unknown[]): unknown => values.find(truth) ?? values.at(-1); +const str = (value: unknown): string => pythonFieldText({value: value ?? ""}, "value"); +const field = (row: JsonObject, key: string): string => str(or(row[key], "")); +const display = (...fields: [JsonObject, string][]): string => { + const selected = fields.find(([row, key]) => truth(row[key])); + return selected ? String(object(selected[0]._display)[selected[1]] ?? "") : ""; +}; +const firstTodo = (quota: JsonObject): JsonObject | null => { + const lane = object(quota.agent_lane_next_action); + if (Object.keys(lane).length) return lane; + const summary = object(quota.agent_todo_summary); + for (const key of ["first_open_items", "items", "open_items"]) { + const item = rows(summary[key])[0]; + if (item) return item; + } + return null; +}; +function laneFrom(item: JsonObject, quota: JsonObject): JsonObject { + const lane = object(quota.agent_lane_next_action), todo = firstTodo(quota); + const asset = object(item.project_asset); + const result: JsonObject = { + goal_id: str(or(item.goal_id, quota.goal_id, "")), + status: str(or(quota.state, item.status, "unknown")), + waiting_on: str(or(item.waiting_on, quota.waiting_on, "")), + agent_id: truth(or(lane.agent_id, object(quota.agent_identity).agent_id, object(asset.owner).agent_id)) + ? str(or(lane.agent_id, object(quota.agent_identity).agent_id, object(asset.owner).agent_id)) : null, + top_todo_id: todo?.todo_id ?? null, + last_event_id: object(quota.rollout_event).event_id ?? null, + next_safe_action: display([quota, "recommended_action"], [item, "recommended_action"], [asset, "next_action"]), + }; + for (const key of Object.keys(result)) if (result[key] === null || result[key] === "") delete result[key]; + return result; +} +const waitAction = "Wait for the projected user/controller decision before running the blocked path."; +function summaryGate(item: JsonObject, quota: JsonObject): JsonObject | null { + const channel = object(object(quota.interaction_contract).user_channel), summary = object(quota.user_todo_summary); + if (!truth(channel.action_required) && Number(or(summary.open_count, summary.count, 0)) <= 0) return null; + let first: JsonObject = {}; + for (const key of ["first_open_items", "items", "open_items"]) { + first = rows(summary[key])[0] ?? {}; + if (Object.keys(first).length) break; + } + const goal = str(or(item.goal_id, quota.goal_id, "")); + return {gate_id: str(or(quota.gate_id, `${goal}:user-gate`)), goal_id: goal, owner: "user", + blocks: truth(first.todo_id) ? [first.todo_id] : [goal], + question: display([channel, "question"], [quota, "operator_question"], [quota, "gate_prompt"], [first, "text"], [item, "recommended_action"]), + next_safe_action: waitAction}; +} +function candidates(quota: JsonObject): JsonObject[] { + const summary = object(quota.agent_todo_summary), seen = new Set(), result: JsonObject[] = []; + const values = [quota.selected_todo, quota.agent_lane_next_action, + ...["first_open_items", "first_executable_items", "items", "open_items", "backlog_items", "executable_backlog_items"].flatMap(key => list(summary[key]))]; + for (const item of rows(values)) { + const key = JSON.stringify([item.todo_id ?? null, item.index ?? null, item.text ?? null]); + if (!seen.has(key)) {seen.add(key); result.push(item);} + } + return result; +} +function blockedRefs(gate: JsonObject | null, quota: JsonObject, goal: string): string[] { + if (!gate) return [goal]; + const exact = field(gate, "unblocks_todo_id").trim(); + if (exact) return [exact]; + const facts = (row: JsonObject): JsonObject => ({todo_id: id(row.todo_id), unblocks_todo_id: id(row.unblocks_todo_id), + decision_scope: readScope(row.decision_scope), required_decision_scopes: readScopes(row.required_decision_scopes)}); + for (const item of candidates(quota)) { + const relation = todoGateRelation(facts(gate), facts(item)); + if (relation && ["gate_targets_todo", "gate_covers_action"].includes(String(relation.state))) { + const todo = field(item, "todo_id").trim(); + if (todo) return [todo]; + } + } + return [goal]; +} +function globalGate(item: JsonObject, quota: JsonObject, facts: GateSourceFacts): JsonObject | null { + if (!facts.eligible) return null; + const channel = object(object(quota.interaction_contract).user_channel), formal = facts.formal_gate; + const action = channel.action_required === true, waiting = field(item, "waiting_on").trim(); + const owner = formal || action ? "user" : "controller", goal = str(or(item.goal_id, quota.goal_id, "")).trim(); + return {gate_id: str(or(quota.gate_id, formal?.todo_id, `${goal}:${owner}-gate`)), goal_id: goal, owner, + waiting_on: waiting || owner, blocks: blockedRefs(formal, quota, goal), + question: display([channel, "question"], [item, "operator_question"], [formal ?? {}, "text"], [formal ?? {}, "title"], [quota, "recommended_action"], [item, "recommended_action"]), + next_safe_action: waitAction}; +} +function envelope(input: JsonObject, projection: string, evaluated: number, unavailable: number, shown: number, available: number): JsonObject { + const status = object(input.status), registry = object(status.global_registry), upstream = object(status.projection_envelope); + const expected = truth(registry.available) ? Number(or(registry.global_goal_count, 0)) : null; + const excluded = truth(registry.available) ? Number(or(registry.current_registry_excluded_goal_count, 0)) : 0; + const refs = truth(registry.available) ? list(registry.current_registry_excluded_goal_ids).map(str) : []; + const global = rows(upstream.sources).find(row => row.source_id === "global_registry") ?? {}; + const source = (source_id: string, read_status: string, last_read_at: unknown, item_count: unknown = null, unreadable_count = 0): JsonObject => + ({source_id, read_status, last_read_at, required: true, source_updated_at: null, item_count, missing_count: 0, unreadable_count}); + return sealProjectionEnvelope({schema_version: "loopx_projection_envelope_seal_request_v0", projection, observed_at: input.observed_at, + sources: [source("global_registry", str(or(global.read_status, truth(registry.available) ? "read" : "not_read")), + or(global.last_read_at, truth(registry.available) ? input.quota_read_at : null) ?? null, expected), + source("goal_quota", input.quota_read_at ? "read" : "not_read", input.quota_read_at, evaluated, unavailable), + ...(Object.keys(upstream).length ? [] : [source("status", "not_read", null)])], + coverage: {scope: "global", expected_count: expected, + included_count: expected !== null ? Math.max(0, expected - excluded) : Number(or(object(upstream.coverage).included_count, 0)), + omitted: excluded ? [{reason: "outside_current_registry", count: excluded, refs: refs.slice(0, 8)}] : [], shown_count: shown, available_count: available}, + upstream: Object.keys(upstream).length ? [upstream] : []}); +} +const queueCount = (status: JsonObject): number => new Set(rows(object(status.attention_queue).items).map(row => field(row, "goal_id").trim()).filter(Boolean)).size; +type GateSourceFacts = {formal_gate: JsonObject | null; eligible: boolean}; +function decodeGateSourceFacts(value: unknown): GateSourceFacts { + const facts = requireJsonObject(value, "gate_source_facts"); + if (typeof facts.eligible !== "boolean") throw new Error("gate_source_facts.eligible must be a boolean"); + return {eligible: facts.eligible, formal_gate: facts.formal_gate === null ? null + : requireJsonObject(facts.formal_gate, "gate_source_facts.formal_gate")}; +} +function decode(input: JsonObject): {status: JsonObject; samples: {item: JsonObject; quota: JsonObject; gate_source_facts: unknown}[]; limit: number} { + if (input.schema_version !== "global_manager_read_request_v0") throw new Error("Unsupported global manager read schema"); + const status = requireJsonObject(input.status, "status"), limit = requireInteger(input.limit, "limit"); + requireNonEmptyString(input.observed_at, "observed_at"); + requireNonEmptyString(input.generated_at, "generated_at"); + requireJsonObject(input.boundary, "boundary"); + if (!Array.isArray(input.samples)) throw new Error("samples must be an array"); + const samples = input.samples.map(value => { + const row = requireJsonObject(value, "sample"); + return {item: requireJsonObject(row.item, "item"), quota: requireJsonObject(row.quota, "quota"), gate_source_facts: row.gate_source_facts}; + }); + return {status, limit, samples}; +} +const action = (action_id: string, kind: string, preview: string): JsonObject => + ({action_id, kind, requires_user_approval: false, requires_maintainer_authority: false, preview}); +const omission = "Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted."; +export function projectGlobalSummary(input: JsonObject): JsonObject { + const {status, samples, limit} = decode(input); + requireNonEmptyString(input.time_range, "time_range"); + const surfaces = requireStringArray(input.source_surfaces, "source_surfaces"); + if (input.since !== null) requireInteger(input.since, "since"); + const lanes: JsonObject[] = [], gates: JsonObject[] = [], todos: JsonObject[] = [], states: Record = {}; + for (const {item, quota} of samples) { + const goal = field(item, "goal_id").trim(), lane = laneFrom(item, quota), gate = summaryGate(item, quota), todo = firstTodo(quota); + lanes.push(lane); states[String(lane.status ?? "unknown")] = (states[String(lane.status ?? "unknown")] ?? 0) + 1; + if (gate) gates.push(gate); + if (todo) todos.push({todo_id: todo.todo_id ?? null, goal_id: goal, role: or(todo.role, "agent"), claimed_by: todo.claimed_by ?? null, + state: or(todo.status, "open"), priority: todo.priority ?? null, title: display([todo, "title"], [todo, "text"]), next_safe_action: display([quota, "recommended_action"])}); + } + lanes.sort((a, b) => (LANE_PRIORITY[String(a.status)] ?? 4) - (LANE_PRIORITY[String(b.status)] ?? 4) || authorityUnicodeCompare(field(a, "goal_id"), field(b, "goal_id"))); + const sealed = envelope(input, "global_summary", samples.length, samples.filter(({quota}) => quota.state === "quota_unavailable").length, + Math.min(lanes.length, limit), queueCount(status)); + const risks = rows(object(status.global_registry).findings).map(row => ({kind: str(or(row.kind, "status_warning")), severity: str(or(row.severity, "info")), + goal_id: row.goal_id ?? null, evidence_refs: ["status_contract"], next_safe_action: display([row, "recommended_action"], [row, "message"])})); + const progress: JsonObject[] = []; + for (const row of rows(object(status.run_history).recent_runs)) { + if (input.since !== null && row._parsed_at !== null && row._parsed_at !== undefined) { + if (row._naive_time) throw new Error("can't compare offset-naive and offset-aware datetimes"); + if (Number(row._parsed_at) < Number(input.since)) continue; + } + progress.push({goal_id: row.goal_id ?? null, generated_at: row.generated_at ?? null, + classification: object(row._display).classification ?? "", recommended_action: row._progress_action ?? ""}); + if (progress.length >= limit) break; + } + const runnable = lanes.filter(row => ["eligible", "normal_run", "run"].includes(String(row.status))); + const waiting = (row: JsonObject) => ["controller", "user", "external"].includes(field(row, "waiting_on")); + const slice = (values: T[]): T[] => values.slice(0, limit < 0 ? Math.max(0, values.length + limit) : limit); + return {ok: true, schema_version: SCHEMA_VERSION, + request: {schema_version: "global_manager_command_request_v0", command: COMMAND, legacy_aliases: ["/loop-global-summary"], cli_command: "loopx global-summary", + time_range: input.time_range, include: ["progress", "gates", "todos", "risks", "next_actions"], privacy_mode: "public_safe_summary", dry_run: true}, + generated_at: input.generated_at, + summary: {headline: `${progress.length} recent progress items, ${gates.length} open user/controller gates, ${todos.length} runnable or visible todos.`, + progress_count: progress.length, open_gate_count: gates.length, runnable_todo_count: runnable.length, waiting_lane_count: lanes.filter(waiting).length, + risk_count: risks.length, source_surfaces: surfaces, quota_states: states}, + groups: {user_gates: slice(gates), runnable_agent_work: slice(runnable), + waiting_lanes: slice(lanes.filter(row => waiting(row) || ["waiting", "focus_wait", "operator_gate"].includes(String(row.status)))), + health_risks: slice(risks), recent_progress: progress}, + lanes: slice(lanes), gates: slice(gates), todos: slice(todos), risks: slice(risks), recent_progress: progress, + actions: [action("act_read_goal_detail", "read_more", "Run `loopx review-packet --goal-id ` or `loopx quota should-run --goal-id ` for one lane."), + action("act_ask_user_gate", "ask_user", "Surface the projected gate question when open_gate_count is non-zero.")], + omissions: [omission, "Status health findings are summarized without filesystem paths."], projection_envelope: sealed, boundary: input.boundary}; +} +export function projectGlobalGates(input: JsonObject): JsonObject { + const {status, samples, limit} = decode(input); + const gateSamples = samples.map(sample => ({...sample, facts: decodeGateSourceFacts(sample.gate_source_facts)})); + const request = {schema_version: "global_manager_command_request_v0", command: GLOBAL_GATES_COMMAND, legacy_aliases: ["/loop-global-gates"], + cli_command: "loopx global-gates", include: ["gates", "blocked_work", "next_questions"], privacy_mode: "public_safe_summary", dry_run: true}; + if (status.ok !== true) return {ok: false, schema_version: SCHEMA_VERSION, request, generated_at: input.generated_at, + error: "Global status source unavailable.", omissions: ["Raw/private failure details and local paths were intentionally omitted."], boundary: input.boundary, + projection_envelope: envelope(input, "global_gates", 0, 0, 0, 0)}; + const gates: JsonObject[] = [], lanes: JsonObject[] = []; + const agent = input.agent_id == null ? null : requireNonEmptyString(input.agent_id, "agent_id"); + for (const {item, quota, facts} of gateSamples) { + if (agent && (quota.ok === false || field(object(quota.agent_identity), "agent_id") !== agent)) continue; + const gate = globalGate(item, quota, facts); + if (!gate) continue; + gates.push(gate); + const lane = laneFrom(item, quota), goal = field(item, "goal_id").trim(); + const verified = list(gate.blocks).map(str).filter(ref => ref !== goal); + if (!verified.includes(field(lane, "top_todo_id"))) delete lane.top_todo_id; + lanes.push(lane); + if (gates.length >= limit) break; + } + const sealed = envelope(input, "global_gates", samples.length, samples.filter(({quota}) => quota.state === "quota_unavailable").length, samples.length, queueCount(status)); + return {ok: true, schema_version: SCHEMA_VERSION, request, generated_at: input.generated_at, + summary: {headline: `${gates.length} open user/controller ${gates.length === 1 ? "gate requires" : "gates require"} a decision.`, + open_gate_count: gates.length, blocked_lane_count: lanes.length, source_surfaces: GLOBAL_GATES_SOURCE_SURFACES}, + groups: {user_gates: gates.filter(row => row.owner === "user"), controller_gates: gates.filter(row => row.owner === "controller"), blocked_work: lanes}, + gates, lanes, actions: gates.length ? [action("act_read_gate_detail", "read_more", "Run `loopx quota should-run --goal-id ` for one blocked lane."), + action("act_ask_gate_owner", "ask_user", "Surface the projected gate question without changing state.")] : [], + omissions: [omission, "Recent progress and unrelated risks are outside this focused command."], projection_envelope: sealed, boundary: input.boundary}; +} diff --git a/loopx/summary_all.py b/loopx/summary_all.py index c4261e9321..1b2aad80f5 100644 --- a/loopx/summary_all.py +++ b/loopx/summary_all.py @@ -4,13 +4,11 @@ from pathlib import Path from typing import Any +from .control_plane.effect_runtime import effect_runtime_result from .control_plane.projection_envelope_facts import ( render_projection_envelope_markdown, - seal_projection_envelope, - source_fact, ) from .control_plane.runtime.time import now_utc, now_utc_iso -from .control_plane.todos.decision_scope import todo_gate_relations from .control_plane.todos.user_gate import open_user_gate_todo_items from .presentation.markdown import as_dict as _as_dict from .presentation.markdown import as_list as _as_list @@ -37,24 +35,6 @@ "run history index summaries", ] -GLOBAL_GATES_SOURCE_SURFACES = [ - "status attention queue", - "quota should-run summaries", - "active-state todo projections", - "compact run-history projection consumed by status", -] - -LANE_PRIORITY = { - "eligible": 0, - "normal_run": 0, - "run": 0, - "operator_gate": 1, - "focus_wait": 1, - "waiting": 2, - "throttled": 3, - "paused": 4, - "quota_unavailable": 5, -} def _now_iso() -> str: return now_utc_iso() @@ -89,32 +69,6 @@ def _redact_text(value: object, *, limit: int = 260) -> str: ) -def _first_open_todo(quota_payload: dict[str, Any]) -> dict[str, Any] | None: - lane = _as_dict(quota_payload.get("agent_lane_next_action")) - if lane: - return lane - summary = _as_dict(quota_payload.get("agent_todo_summary")) - for key in ("first_open_items", "items", "open_items"): - for item in _as_list(summary.get(key)): - if isinstance(item, dict): - return item - return None - - -def _goal_agent_id(item: dict[str, Any], quota_payload: dict[str, Any]) -> str | None: - lane = _as_dict(quota_payload.get("agent_lane_next_action")) - if lane.get("agent_id"): - return str(lane.get("agent_id")) - agent_identity = _as_dict(quota_payload.get("agent_identity")) - if agent_identity.get("agent_id"): - return str(agent_identity.get("agent_id")) - project_asset = _as_dict(item.get("project_asset")) - owner = _as_dict(project_asset.get("owner")) - if owner.get("agent_id"): - return str(owner.get("agent_id")) - return None - - def _build_goal_quota( status_payload: dict[str, Any], *, @@ -133,141 +87,6 @@ def _build_goal_quota( } -def _quota_unavailable(quota_payload: dict[str, Any]) -> bool: - return quota_payload.get("state") == "quota_unavailable" - - -def _global_projection_envelope( - projection: str, - status_payload: dict[str, Any], - *, - quota_read_at: str | None, - quota_evaluated: int, - quota_unavailable: int, - shown_count: int, - available_count: int, -) -> dict[str, Any]: - """A global read is complete only when it covers every global-registry goal.""" - global_registry = _as_dict(status_payload.get("global_registry")) - status_envelope = _as_dict(status_payload.get("projection_envelope")) - status_coverage = _as_dict(status_envelope.get("coverage")) - excluded = 0 - refs: list[str] = [] - if global_registry.get("available"): - expected = int(global_registry.get("global_goal_count") or 0) - excluded = int(global_registry.get("current_registry_excluded_goal_count") or 0) - refs = [str(ref) for ref in _as_list(global_registry.get("current_registry_excluded_goal_ids"))] - else: - expected = None # Local membership cannot prove global membership. - global_source = next( - (row for row in _as_list(status_envelope.get("sources")) - if isinstance(row, dict) and row.get("source_id") == "global_registry"), - {}, - ) - return seal_projection_envelope( - projection=projection, - observed_at=_now_iso(), - sources=[ - source_fact( - "global_registry", - read_status=str(global_source.get("read_status") or ( - "read" if global_registry.get("available") else "not_read" - )), - last_read_at=global_source.get("last_read_at") or ( - quota_read_at if global_registry.get("available") else None - ), - item_count=expected, - ), - source_fact( - "goal_quota", - read_status="read" if quota_read_at else "not_read", - last_read_at=quota_read_at, - item_count=quota_evaluated, - unreadable_count=quota_unavailable, - ), - *([] if status_envelope else [source_fact("status", read_status="not_read")]), - ], - coverage={ - "scope": "global", - "expected_count": expected, - "included_count": (max(0, expected - excluded) if expected is not None - else int(status_coverage.get("included_count") or 0)), - "omitted": ( - [{"reason": "outside_current_registry", "count": excluded, "refs": refs[:8]}] - if excluded - else [] - ), - "shown_count": shown_count, - "available_count": available_count, - }, - upstream=[status_envelope] if status_envelope else [], - ) - - -def _lane_from_item( - item: dict[str, Any], - *, - quota_payload: dict[str, Any], -) -> dict[str, Any]: - todo = _first_open_todo(quota_payload) - rollout_event = _as_dict(quota_payload.get("rollout_event")) - goal_id = str(item.get("goal_id") or quota_payload.get("goal_id") or "") - status = str(quota_payload.get("state") or item.get("status") or "unknown") - waiting_on = str(item.get("waiting_on") or quota_payload.get("waiting_on") or "") - next_action = ( - quota_payload.get("recommended_action") - or item.get("recommended_action") - or _as_dict(item.get("project_asset")).get("next_action") - ) - lane: dict[str, Any] = { - "goal_id": goal_id, - "status": status, - "waiting_on": waiting_on, - "agent_id": _goal_agent_id(item, quota_payload), - "top_todo_id": todo.get("todo_id") if isinstance(todo, dict) else None, - "last_event_id": rollout_event.get("event_id"), - "next_safe_action": _redact_text(next_action), - } - return {key: value for key, value in lane.items() if value not in (None, "")} - - -def _gate_from_item( - item: dict[str, Any], - *, - quota_payload: dict[str, Any], -) -> dict[str, Any] | None: - user_channel = _as_dict(_as_dict(quota_payload.get("interaction_contract")).get("user_channel")) - user_summary = _as_dict(quota_payload.get("user_todo_summary")) - open_count = int(user_summary.get("open_count") or user_summary.get("count") or 0) - if not user_channel.get("action_required") and open_count <= 0: - return None - goal_id = str(item.get("goal_id") or quota_payload.get("goal_id") or "") - first_item: dict[str, Any] | None = None - for key in ("first_open_items", "items", "open_items"): - for candidate in _as_list(user_summary.get(key)): - if isinstance(candidate, dict): - first_item = candidate - break - if first_item: - break - question = ( - user_channel.get("question") - or quota_payload.get("operator_question") - or quota_payload.get("gate_prompt") - or (first_item or {}).get("text") - or item.get("recommended_action") - ) - blocked_todo = (first_item or {}).get("todo_id") - return { - "gate_id": str(quota_payload.get("gate_id") or f"{goal_id}:user-gate"), - "goal_id": goal_id, - "owner": "user", - "blocks": [blocked_todo] if blocked_todo else [goal_id], - "question": _redact_text(question), - "next_safe_action": "Wait for the projected user/controller decision before running the blocked path.", - } - - def _quota_matches_agent_scope( quota_payload: dict[str, Any], *, @@ -281,169 +100,6 @@ def _quota_matches_agent_scope( return str(identity.get("agent_id") or "") == agent_id -def _agent_todo_candidates(quota_payload: dict[str, Any]) -> list[dict[str, Any]]: - candidates: list[dict[str, Any]] = [] - seen: set[tuple[object, object, object]] = set() - - def add_candidate(value: object) -> None: - if not isinstance(value, dict): - return - item_key = (value.get("todo_id"), value.get("index"), value.get("text")) - if item_key in seen: - return - seen.add(item_key) - candidates.append(value) - - add_candidate(quota_payload.get("selected_todo")) - add_candidate(quota_payload.get("agent_lane_next_action")) - summary = _as_dict(quota_payload.get("agent_todo_summary")) - for key in ( - "first_open_items", - "first_executable_items", - "items", - "open_items", - "backlog_items", - "executable_backlog_items", - ): - for item in _as_list(summary.get(key)): - add_candidate(item) - return candidates - - -def _blocked_refs_for_gate( - gate_item: dict[str, Any] | None, - *, - quota_payload: dict[str, Any], - goal_id: str, -) -> list[str]: - if not gate_item: - return [goal_id] - exact_todo_id = str(gate_item.get("unblocks_todo_id") or "").strip() - if exact_todo_id: - return [exact_todo_id] - candidates = _agent_todo_candidates(quota_payload) - for agent_item, relation in zip(candidates, todo_gate_relations([gate_item], candidates)[0], strict=True): - if not relation or relation.get("state") not in { - "gate_targets_todo", - "gate_covers_action", - }: - continue - todo_id = str(agent_item.get("todo_id") or "").strip() - if todo_id: - return [todo_id] - return [goal_id] - - -def _global_gate_from_item( - item: dict[str, Any], - *, - quota_payload: dict[str, Any], - agent_id: str | None, -) -> dict[str, Any] | None: - interaction = _as_dict(quota_payload.get("interaction_contract")) - user_channel = _as_dict(interaction.get("user_channel")) - user_summary = _as_dict(quota_payload.get("user_todo_summary")) - formal_gates = open_user_gate_todo_items(user_summary) - formal_gate = formal_gates[0] if formal_gates else None - action_required = user_channel.get("action_required") is True - waiting_on = str(item.get("waiting_on") or "").strip() - controller_routed = waiting_on in {"controller", "user_or_controller"} and ( - not agent_id or quota_payload.get("state") == "operator_gate" - ) - if not action_required and not formal_gate and not controller_routed: - return None - - owner = "user" if formal_gate or action_required else "controller" - goal_id = str(item.get("goal_id") or quota_payload.get("goal_id") or "").strip() - question = ( - user_channel.get("question") - or item.get("operator_question") - or (formal_gate or {}).get("text") - or (formal_gate or {}).get("title") - or quota_payload.get("recommended_action") - or item.get("recommended_action") - ) - gate_id = ( - quota_payload.get("gate_id") - or (formal_gate or {}).get("todo_id") - or f"{goal_id}:{owner}-gate" - ) - return { - "gate_id": str(gate_id), - "goal_id": goal_id, - "owner": owner, - "waiting_on": waiting_on or owner, - "blocks": _blocked_refs_for_gate( - formal_gate, - quota_payload=quota_payload, - goal_id=goal_id, - ), - "question": _redact_text(question), - "next_safe_action": ( - "Wait for the projected user/controller decision before " - "running the blocked path." - ), - } - - -def _collect_global_gate_state( - status_payload: dict[str, Any], - *, - agent_id: str | None, - limit: int, -) -> dict[str, Any]: - queue = _as_dict(status_payload.get("attention_queue")) - queue_items = [ - item for item in _as_list(queue.get("items")) if isinstance(item, dict) - ] - gates: list[dict[str, Any]] = [] - lanes: list[dict[str, Any]] = [] - seen_goal_ids: set[str] = set() - quota_evaluated = quota_unavailable = 0 - for item in queue_items: - goal_id = str(item.get("goal_id") or "").strip() - if not goal_id or goal_id in seen_goal_ids: - continue - seen_goal_ids.add(goal_id) - quota_payload = _build_goal_quota( - status_payload, - goal_id=goal_id, - agent_id=agent_id, - ) - quota_evaluated += 1 - quota_unavailable += int(_quota_unavailable(quota_payload)) - if not _quota_matches_agent_scope(quota_payload, agent_id=agent_id): - continue - gate = _global_gate_from_item( - item, - quota_payload=quota_payload, - agent_id=agent_id, - ) - if not gate: - continue - gates.append(gate) - lane = _lane_from_item(item, quota_payload=quota_payload) - verified_todo_ids = { - str(blocked_ref) - for blocked_ref in _as_list(gate.get("blocks")) - if str(blocked_ref) != goal_id - } - if str(lane.get("top_todo_id") or "") not in verified_todo_ids: - lane.pop("top_todo_id", None) - lanes.append(lane) - if len(gates) >= limit: - break - unique_goal_count = len({str(item.get("goal_id") or "").strip() for item in queue_items} - {""}) - return { - "gates": gates, - "lanes": lanes, - "quota_evaluated": quota_evaluated, - "quota_unavailable": quota_unavailable, - "scanned_goal_count": len(seen_goal_ids), - "queue_goal_count": unique_goal_count, - } - - def _global_gates_request() -> dict[str, Any]: return { "schema_version": "global_manager_command_request_v0", @@ -456,99 +112,6 @@ def _global_gates_request() -> dict[str, Any]: } -def build_global_gates( - *, - registry_path: Path, - runtime_root_override: str | None, - agent_id: str | None, - limit: int, -) -> dict[str, Any]: - normalized_limit = max(1, limit) - status_payload = collect_status( - registry_path=registry_path, - runtime_root_override=runtime_root_override, - limit=normalized_limit, - ) - if status_payload.get("ok") is not True: - payload = build_global_gates_error("Global status source unavailable.") - payload["projection_envelope"] = _global_projection_envelope( - "global_gates", status_payload, quota_read_at=None, - quota_evaluated=0, quota_unavailable=0, shown_count=0, available_count=0, - ) - return payload - quota_read_at = _now_iso() - state = _collect_global_gate_state( - status_payload, - agent_id=agent_id, - limit=normalized_limit, - ) - envelope = _global_projection_envelope( - "global_gates", - status_payload, - quota_read_at=quota_read_at, - quota_evaluated=state["quota_evaluated"], - quota_unavailable=state["quota_unavailable"], - shown_count=state["scanned_goal_count"], - available_count=state["queue_goal_count"], - ) - gates = state["gates"] - lanes = state["lanes"] - gate_count = len(gates) - noun = "gate requires" if gate_count == 1 else "gates require" - actions = [] - if gates: - actions = [ - { - "action_id": "act_read_gate_detail", - "kind": "read_more", - "requires_user_approval": False, - "requires_maintainer_authority": False, - "preview": ( - "Run `loopx quota should-run --goal-id ` for one " - "blocked lane." - ), - }, - { - "action_id": "act_ask_gate_owner", - "kind": "ask_user", - "requires_user_approval": False, - "requires_maintainer_authority": False, - "preview": "Surface the projected gate question without changing state.", - }, - ] - return { - "ok": True, - "schema_version": SCHEMA_VERSION, - "request": _global_gates_request(), - "generated_at": _now_iso(), - "summary": { - "headline": f"{gate_count} open user/controller {noun} a decision.", - "open_gate_count": gate_count, - "blocked_lane_count": len(lanes), - "source_surfaces": GLOBAL_GATES_SOURCE_SURFACES, - }, - "groups": { - "user_gates": [gate for gate in gates if gate.get("owner") == "user"], - "controller_gates": [ - gate for gate in gates if gate.get("owner") == "controller" - ], - "blocked_work": lanes, - }, - "gates": gates, - "lanes": lanes, - "actions": actions, - "omissions": [ - ( - "Raw logs, raw transcripts, connector payloads, credential values, " - "local paths, and private source bodies were intentionally omitted." - ), - "Recent progress and unrelated risks are outside this focused command.", - ], - "projection_envelope": envelope, - "boundary": BOUNDARY, - } - - def build_global_gates_error(error: object) -> dict[str, Any]: return { "ok": False, @@ -602,197 +165,6 @@ def render_global_gates_markdown(payload: dict[str, Any]) -> str: return "\n".join(lines) -def _todo_from_quota(goal_id: str, quota_payload: dict[str, Any]) -> dict[str, Any] | None: - todo = _first_open_todo(quota_payload) - if not isinstance(todo, dict): - return None - return { - "todo_id": todo.get("todo_id"), - "goal_id": goal_id, - "role": todo.get("role") or "agent", - "claimed_by": todo.get("claimed_by"), - "state": todo.get("status") or "open", - "priority": todo.get("priority"), - "title": _redact_text(todo.get("title") or todo.get("text")), - "next_safe_action": _redact_text(quota_payload.get("recommended_action")), - } - - -def _risk_from_finding(finding: dict[str, Any]) -> dict[str, Any]: - return { - "kind": str(finding.get("kind") or "status_warning"), - "severity": str(finding.get("severity") or "info"), - "goal_id": finding.get("goal_id"), - "evidence_refs": ["status_contract"], - "next_safe_action": _redact_text(finding.get("recommended_action") or finding.get("message")), - } - - -def _recent_progress(history_payload: dict[str, Any], *, since: datetime | None, limit: int) -> list[dict[str, Any]]: - progress: list[dict[str, Any]] = [] - for run in _as_list(history_payload.get("runs")): - if not isinstance(run, dict): - continue - generated_at = _parse_datetime(run.get("generated_at")) - if since and generated_at and generated_at < since: - continue - progress.append( - { - "goal_id": run.get("goal_id"), - "generated_at": run.get("generated_at"), - "classification": _redact_text(run.get("classification"), limit=120), - "recommended_action": _redact_text(run.get("recommended_action"), limit=220), - } - ) - if len(progress) >= limit: - break - return progress - - -def _lane_sort_key(lane: dict[str, Any]) -> tuple[int, str]: - status = str(lane.get("status") or "unknown") - return (LANE_PRIORITY.get(status, 4), str(lane.get("goal_id") or "")) - - -def build_summary_all( - *, - registry_path: Path, - runtime_root_override: str | None, - agent_id: str | None, - time_range: str, - limit: int, -) -> dict[str, Any]: - normalized_range, since = _parse_time_range(time_range) - recent_progress_limit = max(limit * 2, 5) - status_payload = collect_status( - registry_path=registry_path, - runtime_root_override=runtime_root_override, - limit=max(limit, 1), - recent_run_limit=recent_progress_limit, - ) - - queue = _as_dict(status_payload.get("attention_queue")) - queue_items = [item for item in _as_list(queue.get("items")) if isinstance(item, dict)] - lanes: list[dict[str, Any]] = [] - gates: list[dict[str, Any]] = [] - todos: list[dict[str, Any]] = [] - quota_states: dict[str, int] = {} - seen_goal_ids: set[str] = set() - quota_unavailable = 0 - quota_read_at = _now_iso() - - for item in queue_items[: max(limit * 4, 40)]: - goal_id = str(item.get("goal_id") or "").strip() - if not goal_id or goal_id in seen_goal_ids: - continue - seen_goal_ids.add(goal_id) - quota_payload = _build_goal_quota(status_payload, goal_id=goal_id, agent_id=agent_id) - quota_unavailable += int(_quota_unavailable(quota_payload)) - lane = _lane_from_item(item, quota_payload=quota_payload) - if lane: - lanes.append(lane) - quota_states[str(lane.get("status") or "unknown")] = quota_states.get(str(lane.get("status") or "unknown"), 0) + 1 - gate = _gate_from_item(item, quota_payload=quota_payload) - if gate: - gates.append(gate) - todo = _todo_from_quota(goal_id, quota_payload) - if todo: - todos.append(todo) - lanes.sort(key=_lane_sort_key) - envelope = _global_projection_envelope( - "global_summary", - status_payload, - quota_read_at=quota_read_at, - quota_evaluated=len(seen_goal_ids), - quota_unavailable=quota_unavailable, - shown_count=min(len(lanes), limit), - available_count=len({str(item.get("goal_id") or "").strip() for item in queue_items} - {""}), - ) - - global_registry = _as_dict(status_payload.get("global_registry")) - risks = [_risk_from_finding(item) for item in _as_list(global_registry.get("findings")) if isinstance(item, dict)] - run_history = _as_dict(status_payload.get("run_history")) - recent_progress = _recent_progress( - {"runs": _as_list(run_history.get("recent_runs"))}, - since=since, - limit=limit, - ) - - runnable_count = sum(1 for lane in lanes if lane.get("status") in {"eligible", "run", "normal_run"}) - waiting_count = sum(1 for lane in lanes if str(lane.get("waiting_on") or "") in {"controller", "user", "external"}) - headline = ( - f"{len(recent_progress)} recent progress items, {len(gates)} open user/controller gates, " - f"{len(todos)} runnable or visible todos." - ) - payload: dict[str, Any] = { - "ok": True, - "schema_version": SCHEMA_VERSION, - "request": { - "schema_version": "global_manager_command_request_v0", - "command": COMMAND, - "legacy_aliases": ["/loop-global-summary"], - "cli_command": "loopx global-summary", - "time_range": normalized_range, - "include": ["progress", "gates", "todos", "risks", "next_actions"], - "privacy_mode": "public_safe_summary", - "dry_run": True, - }, - "generated_at": _now_iso(), - "summary": { - "headline": headline, - "progress_count": len(recent_progress), - "open_gate_count": len(gates), - "runnable_todo_count": runnable_count, - "waiting_lane_count": waiting_count, - "risk_count": len(risks), - "source_surfaces": SOURCE_SURFACES, - "quota_states": quota_states, - }, - "groups": { - "user_gates": gates[:limit], - "runnable_agent_work": [ - lane for lane in lanes if lane.get("status") in {"eligible", "normal_run", "run"} - ][:limit], - "waiting_lanes": [ - lane - for lane in lanes - if str(lane.get("waiting_on") or "") in {"controller", "user", "external"} - or lane.get("status") in {"waiting", "focus_wait", "operator_gate"} - ][:limit], - "health_risks": risks[:limit], - "recent_progress": recent_progress, - }, - "lanes": lanes[:limit], - "gates": gates[:limit], - "todos": todos[:limit], - "risks": risks[:limit], - "recent_progress": recent_progress, - "actions": [ - { - "action_id": "act_read_goal_detail", - "kind": "read_more", - "requires_user_approval": False, - "requires_maintainer_authority": False, - "preview": "Run `loopx review-packet --goal-id ` or `loopx quota should-run --goal-id ` for one lane.", - }, - { - "action_id": "act_ask_user_gate", - "kind": "ask_user", - "requires_user_approval": False, - "requires_maintainer_authority": False, - "preview": "Surface the projected gate question when open_gate_count is non-zero.", - }, - ], - "omissions": [ - "Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.", - "Status health findings are summarized without filesystem paths.", - ], - "projection_envelope": envelope, - "boundary": BOUNDARY, - } - return payload - - def render_summary_all_markdown(payload: dict[str, Any]) -> str: if not payload.get("ok"): return "# LoopX Global Summary\n\n- ok: `False`\n- error: " + _redact_text(payload.get("error")) @@ -836,3 +208,104 @@ def render_summary_all_markdown(payload: dict[str, Any]) -> str: lines.extend(["", "## Boundary"]) lines.append("- raw/private material omitted; local paths are not recorded.") return "\n".join(lines) + + +def _read_facts(value: Any, *, progress: bool = False, history: bool = False) -> Any: + """Preserve Python text and timestamp spellings, without selecting results.""" + if isinstance(value, list): + return [_read_facts(item, progress=progress, history=history) for item in value] + if not isinstance(value, dict): + return value + result = {key: _read_facts(item, progress=history and key == "recent_runs", history=history) + for key, item in value.items()} + text_keys = {"question", "operator_question", "gate_prompt", "text", "title", + "recommended_action", "next_action", "message", "classification"} + display = { + key: _redact_text(item, limit=120 if key == "classification" else 260) + for key, item in value.items() if key in text_keys + } + if display: + result["_display"] = display + if progress: + parsed = _parse_datetime(value.get("generated_at")) + result["_parsed_at"] = int(parsed.timestamp() * 1_000_000) if parsed else None + result["_naive_time"] = bool(parsed and parsed.tzinfo is None) + result["_progress_action"] = _redact_text(value.get("recommended_action"), limit=220) + return result + + +def _gate_source_facts(item: dict[str, Any], quota: dict[str, Any], + agent_id: str | None) -> dict[str, Any]: + formal_gates = open_user_gate_todo_items(_as_dict(quota.get("user_todo_summary"))) + formal_gate = formal_gates[0] if formal_gates else None + channel = _as_dict(_as_dict(quota.get("interaction_contract")).get("user_channel")) + routed = str(item.get("waiting_on") or "").strip() in {"controller", "user_or_controller"} + return { + "formal_gate": formal_gate, + "eligible": (channel.get("action_required") is True or formal_gate is not None + or routed and (not agent_id or quota.get("state") == "operator_gate")), + } + + +def _read_samples(status: dict[str, Any], *, agent_id: str | None, + limit: int, gates_only: bool) -> list[dict[str, Any]]: + queue = [item for item in _as_list(_as_dict(status.get("attention_queue")).get("items")) + if isinstance(item, dict)] + samples = [] + seen = set() + found = 0 + for item in queue if gates_only else queue[:max(limit * 4, 40)]: + goal_id = str(item.get("goal_id") or "").strip() + if not goal_id or goal_id in seen: + continue + seen.add(goal_id) + quota = _build_goal_quota(status, goal_id=goal_id, agent_id=agent_id) + sample = {"item": _read_facts(item), "quota": _read_facts(quota)} + facts = _gate_source_facts(item, quota, agent_id) if gates_only else None + if facts is not None: + sample["gate_source_facts"] = _read_facts(facts) + samples.append(sample) + if not gates_only or not _quota_matches_agent_scope(quota, agent_id=agent_id): + continue + if facts is not None and facts["eligible"]: + found += 1 + if found >= limit: + break + return samples + + +def build_summary_all(*, registry_path: Path, runtime_root_override: str | None, + agent_id: str | None, time_range: str, limit: int) -> dict[str, Any]: + normalized_range, since = _parse_time_range(time_range) + status = collect_status(registry_path=registry_path, + runtime_root_override=runtime_root_override, + limit=max(limit, 1), recent_run_limit=max(limit * 2, 5)) + read_at = _now_iso() + samples = _read_samples(status, agent_id=agent_id, limit=limit, gates_only=False) + return effect_runtime_result("presentation.global_summary.project", { + "schema_version": "global_manager_read_request_v0", "status": _read_facts(status, history=True), + "samples": samples, "agent_id": agent_id, "limit": limit, + "source_surfaces": SOURCE_SURFACES, "time_range": normalized_range, + "since": int(since.timestamp() * 1_000_000) if since else None, + "quota_read_at": read_at, "observed_at": _now_iso(), "generated_at": _now_iso(), + "boundary": BOUNDARY, + }) + + +def build_global_gates(*, registry_path: Path, runtime_root_override: str | None, + agent_id: str | None, limit: int) -> dict[str, Any]: + normalized_limit = max(1, limit) + status = collect_status(registry_path=registry_path, + runtime_root_override=runtime_root_override, limit=normalized_limit) + healthy = status.get("ok") is True + # The unhealthy path previously generated the error before sealing its envelope. + error_at = _now_iso() if not healthy else None + read_at = _now_iso() if healthy else None + samples = (_read_samples(status, agent_id=agent_id, limit=normalized_limit, gates_only=True) + if healthy else []) + return effect_runtime_result("presentation.global_gates.project", { + "schema_version": "global_manager_read_request_v0", "status": _read_facts(status), + "samples": samples, "agent_id": agent_id, "limit": normalized_limit, + "quota_read_at": read_at, "observed_at": _now_iso(), + "generated_at": error_at or _now_iso(), "boundary": BOUNDARY, + }) diff --git a/tests/control_plane_ts/global_manager_read.test.ts b/tests/control_plane_ts/global_manager_read.test.ts new file mode 100644 index 0000000000..f8a592350f --- /dev/null +++ b/tests/control_plane_ts/global_manager_read.test.ts @@ -0,0 +1,59 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import {projectGlobalSummary, projectGlobalGates} from "../../loopx/control_plane/presentation/global_manager_read.ts"; +import {createEffectRuntimeHandlers, dispatchEffectRuntimeMethod} from "../../loopx/control_plane/effect_runtime_handlers.ts"; +import type {JsonObject} from "../../loopx/control_plane/effect_program.ts"; + +const request = (): JsonObject => ({schema_version: "global_manager_read_request_v0", status: {ok: true}, samples: [], + observed_at: "2026-11-02T12:00:00+00:00", generated_at: "2026-11-02T12:00:00+00:00", + quota_read_at: "2026-11-02T12:00:00+00:00", boundary: {}, limit: 8, since: null, source_surfaces: [], time_range: "24h"}); + +for (const project of [projectGlobalSummary, projectGlobalGates]) { + test(`${project.name} rejects malformed boundary facts`, () => { + for (const patch of [{schema_version: "unknown"}, {status: []}, {samples: {}}, {samples: [null]}, + {samples: [{item: {}, quota: []}]}, {limit: "8"}, {observed_at: null}, {boundary: null}]) { + assert.throws(() => project({...request(), ...patch})); + } + }); +} + +test("exact target wins over scope and unrelated visible work is removed", () => { + const gate = {task_class: "user_gate", todo_id: "todo_gate", unblocks_todo_id: "todo_exact", + decision_scope: {kind: "production", granularity: "project", scope_key: "deploy"}}; + const payload = projectGlobalGates({...request(), samples: [{gate_source_facts: {formal_gate: gate, eligible: true}, item: {goal_id: "synthetic"}, quota: { + user_todo_summary: {gate_open_items: [gate]}, + agent_lane_next_action: {todo_id: "todo_other", required_decision_scopes: [gate.decision_scope]}, + }}]}); + assert.deepEqual((payload.gates as JsonObject[])[0].blocks, ["todo_exact"]); + assert.equal(Object.hasOwn((payload.lanes as JsonObject[])[0], "top_todo_id"), false); +}); + +test("both transaction handlers dispatch and replay without state changes", async () => { + const handlers = createEffectRuntimeHandlers({fingerprint: "synthetic", requestShutdown: () => undefined}); + for (const method of ["presentation.global_summary.project", "presentation.global_gates.project"]) { + const input = request(), before = structuredClone(input); + const first = await dispatchEffectRuntimeMethod(handlers, method, input); + assert.deepEqual(await dispatchEffectRuntimeMethod(handlers, method, input), first); + assert.deepEqual(input, before); + assert.equal((first.projection_envelope as JsonObject).complete, false); + } +}); + +test("gates reject malformed source facts before filtering samples", () => { + for (const facts of [undefined, null, [], {}, {eligible: "true", formal_gate: null}, + {eligible: true}, {eligible: true, formal_gate: []}]) { + assert.throws(() => projectGlobalGates({...request(), samples: [ + {item: {}, quota: {}, gate_source_facts: facts}, + ]})); + } +}); + +test("gate projection consumes source eligibility without reclassifying quota", () => { + const quota = {interaction_contract: {user_channel: {action_required: true}}}; + const samples = [{item: {goal_id: "synthetic"}, quota, + gate_source_facts: {eligible: false, formal_gate: null}}]; + assert.deepEqual(projectGlobalGates({...request(), samples}).gates, []); + samples[0].quota = {}; + samples[0].gate_source_facts.eligible = true; + assert.equal((projectGlobalGates({...request(), samples}).gates as JsonObject[])[0].owner, "controller"); +}); diff --git a/tests/fixtures/summary_all/relations.json b/tests/fixtures/summary_all/relations.json new file mode 100644 index 0000000000..2fef37c7bf --- /dev/null +++ b/tests/fixtures/summary_all/relations.json @@ -0,0 +1,103 @@ +{ + "scope-and-envelope": { + "summary": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-summary\",\n \"legacy_aliases\": [\n \"/loop-global-summary\"\n ],\n \"cli_command\": \"loopx global-summary\",\n \"time_range\": \"24h\",\n \"include\": [\n \"progress\",\n \"gates\",\n \"todos\",\n \"risks\",\n \"next_actions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"2 recent progress items, 2 open user/controller gates, 6 runnable or visible todos.\",\n \"progress_count\": 2,\n \"open_gate_count\": 2,\n \"runnable_todo_count\": 3,\n \"waiting_lane_count\": 6,\n \"risk_count\": 1,\n \"source_surfaces\": [\n \"global registry compact status\",\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"run history index summaries\"\n ],\n \"quota_states\": {\n \"eligible\": 1,\n \"quota_unavailable\": 1,\n \"operator_gate\": 1,\n \"waiting\": 1,\n \"paused\": 1,\n \"run\": 1,\n \"normal_run\": 1\n }\n },\n \"groups\": {\n \"user_gates\": [\n {\n \"gate_id\": \"goal-1:user-gate\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-1\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-2:user-gate\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-2\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"runnable_agent_work\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-5\",\n \"status\": \"normal_run\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-5\",\n \"last_event_id\": \"event-5\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"waiting_lanes\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-5\",\n \"status\": \"normal_run\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-5\",\n \"last_event_id\": \"event-5\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-1\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"top_todo_id\": \"todo_synthetic_work\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-3\",\n \"status\": \"paused\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-3\",\n \"last_event_id\": \"event-3\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"health_risks\": [\n {\n \"kind\": \"synthetic_warning\",\n \"severity\": \"info\",\n \"goal_id\": null,\n \"evidence_refs\": [\n \"status_contract\"\n ],\n \"next_safe_action\": \"Review synthetic warning.\"\n }\n ],\n \"recent_progress\": [\n {\n \"goal_id\": \"goal-0\",\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"classification\": \"progress\",\n \"recommended_action\": \"Continue.\"\n },\n {\n \"goal_id\": \"unknown-time\",\n \"generated_at\": \"invalid\",\n \"classification\": \"visible\",\n \"recommended_action\": \"\"\n }\n ]\n },\n \"lanes\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-5\",\n \"status\": \"normal_run\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-5\",\n \"last_event_id\": \"event-5\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-1\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"top_todo_id\": \"todo_synthetic_work\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-3\",\n \"status\": \"paused\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-3\",\n \"last_event_id\": \"event-3\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"unavailable\",\n \"status\": \"quota_unavailable\"\n }\n ],\n \"gates\": [\n {\n \"gate_id\": \"goal-1:user-gate\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-1\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-2:user-gate\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-2\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"todos\": [\n {\n \"todo_id\": \"work-0\",\n \"goal_id\": \"goal-0\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-1\",\n \"goal_id\": \"goal-1\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"todo_synthetic_work\",\n \"goal_id\": \"goal-2\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-3\",\n \"goal_id\": \"goal-3\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-4\",\n \"goal_id\": \"goal-4\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-5\",\n \"goal_id\": \"goal-5\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"risks\": [\n {\n \"kind\": \"synthetic_warning\",\n \"severity\": \"info\",\n \"goal_id\": null,\n \"evidence_refs\": [\n \"status_contract\"\n ],\n \"next_safe_action\": \"Review synthetic warning.\"\n }\n ],\n \"recent_progress\": [\n {\n \"goal_id\": \"goal-0\",\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"classification\": \"progress\",\n \"recommended_action\": \"Continue.\"\n },\n {\n \"goal_id\": \"unknown-time\",\n \"generated_at\": \"invalid\",\n \"classification\": \"visible\",\n \"recommended_action\": \"\"\n }\n ],\n \"actions\": [\n {\n \"action_id\": \"act_read_goal_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx review-packet --goal-id ` or `loopx quota should-run --goal-id ` for one lane.\"\n },\n {\n \"action_id\": \"act_ask_user_gate\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question when open_gate_count is non-zero.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Status health findings are summarized without filesystem paths.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_summary\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"stale_sources\",\n \"unreadable_sources\"\n ],\n \"alert_source_ids\": [\n \"global_registry\",\n \"goal_quota\",\n \"status/global_registry\",\n \"status/synthetic_unreadable\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-10-01T00:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 2808000,\n \"status\": \"stale\",\n \"item_count\": 9,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"stale\"\n ]\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 7,\n \"missing_count\": 0,\n \"unreadable_count\": 1,\n \"alert\": true,\n \"alert_reasons\": [\n \"partially_unreadable\"\n ]\n },\n {\n \"source_id\": \"global_registry\",\n \"via\": \"status\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-10-01T00:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 2808000,\n \"status\": \"stale\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"stale\"\n ]\n },\n {\n \"source_id\": \"synthetic_unreadable\",\n \"via\": \"status\",\n \"required\": true,\n \"read_status\": \"unreadable\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"unreadable\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"unreadable\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": 9,\n \"included_count\": 7,\n \"omitted\": [\n {\n \"reason\": \"outside_current_registry\",\n \"count\": 2,\n \"refs\": [\n \"outside-a\",\n \"outside-b\"\n ]\n }\n ],\n \"shown_count\": 7,\n \"available_count\": 7,\n \"truncated\": false,\n \"complete\": false\n },\n \"upstream\": [\n {\n \"projection\": \"status\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"complete\": false\n }\n ]\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Summary\n\n- command: `/loopx-global-summary`\n- time_range: `24h`\n- headline: 2 recent progress items, 2 open user/controller gates, 6 runnable or visible todos.\n- counts: progress=`2`, gates=`2`, todos=`3`, risks=`1`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`1/4` coverage=`7/9` scope=`global`\n- 🔴 projection alerts: `incomplete_coverage,stale_sources,unreadable_sources` sources=`global_registry,goal_quota,status/global_registry,status/synthetic_unreadable` omitted=`outside_current_registry=2`; state this before treating the projection as current or whole\n\n## Lanes\n- `goal-0` status=`eligible` waiting_on=`user` todo=`work-0`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-4` status=`run` waiting_on=`controller` todo=`work-4`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-5` status=`normal_run` waiting_on=`user` todo=`work-5`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-1` status=`operator_gate` waiting_on=`user` todo=`work-1`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-2` status=`waiting` waiting_on=`user` todo=`todo_synthetic_work`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-3` status=`paused` waiting_on=`controller` todo=`work-3`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `unavailable` status=`quota_unavailable` waiting_on=`None` todo=`None`: None\n\n## Gates\n- `goal-1` owner=`user`: Approve synthetic work?\n- `goal-2` owner=`user`: Approve synthetic work?\n\n## Recent Progress\n- `2026-11-02T12:00:00+00:00` `goal-0` `progress`: Continue.\n- `invalid` `unknown-time` `visible`: \n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 8, + 16 + ], + [ + "quota", + "goal-0", + null + ], + [ + "quota", + "unavailable", + null + ], + [ + "quota", + "goal-1", + null + ], + [ + "quota", + "goal-2", + null + ], + [ + "quota", + "goal-3", + null + ], + [ + "quota", + "goal-4", + null + ], + [ + "quota", + "goal-5", + null + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + }, + "gates": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-gates\",\n \"legacy_aliases\": [\n \"/loop-global-gates\"\n ],\n \"cli_command\": \"loopx global-gates\",\n \"include\": [\n \"gates\",\n \"blocked_work\",\n \"next_questions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"4 open user/controller gates require a decision.\",\n \"open_gate_count\": 4,\n \"blocked_lane_count\": 4,\n \"source_surfaces\": [\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"compact run-history projection consumed by status\"\n ]\n },\n \"groups\": {\n \"user_gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"gate-2\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"todo_synthetic_work\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"controller_gates\": [\n {\n \"gate_id\": \"goal-3:controller-gate\",\n \"goal_id\": \"goal-3\",\n \"owner\": \"controller\",\n \"waiting_on\": \"controller\",\n \"blocks\": [\n \"goal-3\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-4:controller-gate\",\n \"goal_id\": \"goal-4\",\n \"owner\": \"controller\",\n \"waiting_on\": \"controller\",\n \"blocks\": [\n \"goal-4\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"blocked_work\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"top_todo_id\": \"todo_synthetic_work\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-3\",\n \"status\": \"paused\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-3\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ]\n },\n \"gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"gate-2\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"todo_synthetic_work\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-3:controller-gate\",\n \"goal_id\": \"goal-3\",\n \"owner\": \"controller\",\n \"waiting_on\": \"controller\",\n \"blocks\": [\n \"goal-3\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-4:controller-gate\",\n \"goal_id\": \"goal-4\",\n \"owner\": \"controller\",\n \"waiting_on\": \"controller\",\n \"blocks\": [\n \"goal-4\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"lanes\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"top_todo_id\": \"todo_synthetic_work\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-3\",\n \"status\": \"paused\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-3\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"actions\": [\n {\n \"action_id\": \"act_read_gate_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx quota should-run --goal-id ` for one blocked lane.\"\n },\n {\n \"action_id\": \"act_ask_gate_owner\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question without changing state.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Recent progress and unrelated risks are outside this focused command.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_gates\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"stale_sources\",\n \"unreadable_sources\"\n ],\n \"alert_source_ids\": [\n \"global_registry\",\n \"goal_quota\",\n \"status/global_registry\",\n \"status/synthetic_unreadable\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-10-01T00:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 2808000,\n \"status\": \"stale\",\n \"item_count\": 9,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"stale\"\n ]\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 7,\n \"missing_count\": 0,\n \"unreadable_count\": 1,\n \"alert\": true,\n \"alert_reasons\": [\n \"partially_unreadable\"\n ]\n },\n {\n \"source_id\": \"global_registry\",\n \"via\": \"status\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-10-01T00:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 2808000,\n \"status\": \"stale\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"stale\"\n ]\n },\n {\n \"source_id\": \"synthetic_unreadable\",\n \"via\": \"status\",\n \"required\": true,\n \"read_status\": \"unreadable\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"unreadable\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"unreadable\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": 9,\n \"included_count\": 7,\n \"omitted\": [\n {\n \"reason\": \"outside_current_registry\",\n \"count\": 2,\n \"refs\": [\n \"outside-a\",\n \"outside-b\"\n ]\n }\n ],\n \"shown_count\": 7,\n \"available_count\": 7,\n \"truncated\": false,\n \"complete\": false\n },\n \"upstream\": [\n {\n \"projection\": \"status\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"complete\": false\n }\n ]\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Gates\n\n- command: `/loopx-global-gates`\n- open_gate_count: `4`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`1/4` coverage=`7/9` scope=`global`\n- 🔴 projection alerts: `incomplete_coverage,stale_sources,unreadable_sources` sources=`global_registry,goal_quota,status/global_registry,status/synthetic_unreadable` omitted=`outside_current_registry=2`; state this before treating the projection as current or whole\n\n## Gates\n- `goal-1` owner=`user` waiting_on=`user` blocks=`exact-target`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n- `goal-2` owner=`user` waiting_on=`user` blocks=`todo_synthetic_work`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n- `goal-3` owner=`controller` waiting_on=`controller` blocks=`goal-3`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n- `goal-4` owner=`controller` waiting_on=`controller` blocks=`goal-4`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 8, + null + ], + [ + "quota", + "goal-0", + null + ], + [ + "quota", + "unavailable", + null + ], + [ + "quota", + "goal-1", + null + ], + [ + "quota", + "goal-2", + null + ], + [ + "quota", + "goal-3", + null + ], + [ + "quota", + "goal-4", + null + ], + [ + "quota", + "goal-5", + null + ] + ], + "crossings": [ + "todo.decision_scope.evaluate", + "projection.envelope.seal" + ] + } + } +} diff --git a/tests/fixtures/summary_all/transactions.json b/tests/fixtures/summary_all/transactions.json new file mode 100644 index 0000000000..1097112b93 --- /dev/null +++ b/tests/fixtures/summary_all/transactions.json @@ -0,0 +1,508 @@ +{ + "empty": { + "summary": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-summary\",\n \"legacy_aliases\": [\n \"/loop-global-summary\"\n ],\n \"cli_command\": \"loopx global-summary\",\n \"time_range\": \"24h\",\n \"include\": [\n \"progress\",\n \"gates\",\n \"todos\",\n \"risks\",\n \"next_actions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"0 recent progress items, 0 open user/controller gates, 0 runnable or visible todos.\",\n \"progress_count\": 0,\n \"open_gate_count\": 0,\n \"runnable_todo_count\": 0,\n \"waiting_lane_count\": 0,\n \"risk_count\": 0,\n \"source_surfaces\": [\n \"global registry compact status\",\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"run history index summaries\"\n ],\n \"quota_states\": {}\n },\n \"groups\": {\n \"user_gates\": [],\n \"runnable_agent_work\": [],\n \"waiting_lanes\": [],\n \"health_risks\": [],\n \"recent_progress\": []\n },\n \"lanes\": [],\n \"gates\": [],\n \"todos\": [],\n \"risks\": [],\n \"recent_progress\": [],\n \"actions\": [\n {\n \"action_id\": \"act_read_goal_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx review-packet --goal-id ` or `loopx quota should-run --goal-id ` for one lane.\"\n },\n {\n \"action_id\": \"act_ask_user_gate\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question when open_gate_count is non-zero.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Status health findings are summarized without filesystem paths.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_summary\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\"\n ],\n \"alert_source_ids\": [\n \"global_registry\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 0,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": null,\n \"included_count\": 0,\n \"omitted\": [],\n \"shown_count\": 0,\n \"available_count\": 0,\n \"truncated\": false,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Summary\n\n- command: `/loopx-global-summary`\n- time_range: `24h`\n- headline: 0 recent progress items, 0 open user/controller gates, 0 runnable or visible todos.\n- counts: progress=`0`, gates=`0`, todos=`0`, risks=`0`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`1/3` coverage=`0/None` scope=`global`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources` sources=`global_registry,status` omitted=`-`; state this before treating the projection as current or whole\n\n## Lanes\n\n## Gates\n- none\n\n## Recent Progress\n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 8, + 16 + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + }, + "gates": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-gates\",\n \"legacy_aliases\": [\n \"/loop-global-gates\"\n ],\n \"cli_command\": \"loopx global-gates\",\n \"include\": [\n \"gates\",\n \"blocked_work\",\n \"next_questions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"0 open user/controller gates require a decision.\",\n \"open_gate_count\": 0,\n \"blocked_lane_count\": 0,\n \"source_surfaces\": [\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"compact run-history projection consumed by status\"\n ]\n },\n \"groups\": {\n \"user_gates\": [],\n \"controller_gates\": [],\n \"blocked_work\": []\n },\n \"gates\": [],\n \"lanes\": [],\n \"actions\": [],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Recent progress and unrelated risks are outside this focused command.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_gates\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\"\n ],\n \"alert_source_ids\": [\n \"global_registry\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 0,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": null,\n \"included_count\": 0,\n \"omitted\": [],\n \"shown_count\": 0,\n \"available_count\": 0,\n \"truncated\": false,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Gates\n\n- command: `/loopx-global-gates`\n- open_gate_count: `0`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`1/3` coverage=`0/None` scope=`global`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources` sources=`global_registry,status` omitted=`-`; state this before treating the projection as current or whole\n\n## Gates\n- none\n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 8, + null + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + } + }, + "unhealthy": { + "summary": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-summary\",\n \"legacy_aliases\": [\n \"/loop-global-summary\"\n ],\n \"cli_command\": \"loopx global-summary\",\n \"time_range\": \"24h\",\n \"include\": [\n \"progress\",\n \"gates\",\n \"todos\",\n \"risks\",\n \"next_actions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"0 recent progress items, 0 open user/controller gates, 0 runnable or visible todos.\",\n \"progress_count\": 0,\n \"open_gate_count\": 0,\n \"runnable_todo_count\": 0,\n \"waiting_lane_count\": 0,\n \"risk_count\": 0,\n \"source_surfaces\": [\n \"global registry compact status\",\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"run history index summaries\"\n ],\n \"quota_states\": {}\n },\n \"groups\": {\n \"user_gates\": [],\n \"runnable_agent_work\": [],\n \"waiting_lanes\": [],\n \"health_risks\": [],\n \"recent_progress\": []\n },\n \"lanes\": [],\n \"gates\": [],\n \"todos\": [],\n \"risks\": [],\n \"recent_progress\": [],\n \"actions\": [\n {\n \"action_id\": \"act_read_goal_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx review-packet --goal-id ` or `loopx quota should-run --goal-id ` for one lane.\"\n },\n {\n \"action_id\": \"act_ask_user_gate\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question when open_gate_count is non-zero.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Status health findings are summarized without filesystem paths.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_summary\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\"\n ],\n \"alert_source_ids\": [\n \"global_registry\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 0,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": null,\n \"included_count\": 0,\n \"omitted\": [],\n \"shown_count\": 0,\n \"available_count\": 0,\n \"truncated\": false,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Summary\n\n- command: `/loopx-global-summary`\n- time_range: `24h`\n- headline: 0 recent progress items, 0 open user/controller gates, 0 runnable or visible todos.\n- counts: progress=`0`, gates=`0`, todos=`0`, risks=`0`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`1/3` coverage=`0/None` scope=`global`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources` sources=`global_registry,status` omitted=`-`; state this before treating the projection as current or whole\n\n## Lanes\n\n## Gates\n- none\n\n## Recent Progress\n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 1, + 5 + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + }, + "gates": { + "json": "{\n \"ok\": false,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-gates\",\n \"legacy_aliases\": [\n \"/loop-global-gates\"\n ],\n \"cli_command\": \"loopx global-gates\",\n \"include\": [\n \"gates\",\n \"blocked_work\",\n \"next_questions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"error\": \"Global status source unavailable.\",\n \"omissions\": [\n \"Raw/private failure details and local paths were intentionally omitted.\"\n ],\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n },\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_gates\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\"\n ],\n \"alert_source_ids\": [\n \"global_registry\",\n \"goal_quota\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": 0,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": null,\n \"included_count\": 0,\n \"omitted\": [],\n \"shown_count\": 0,\n \"available_count\": 0,\n \"truncated\": false,\n \"complete\": false\n },\n \"upstream\": []\n }\n}", + "markdown": "# LoopX Global Gates\n\n- ok: `False`\n- error: Global status source unavailable.\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`0/3` coverage=`0/None` scope=`global`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources` sources=`global_registry,goal_quota,status` omitted=`-`; state this before treating the projection as current or whole", + "reads": [ + [ + "status", + 1, + null + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + } + }, + "mixed": { + "summary": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-summary\",\n \"legacy_aliases\": [\n \"/loop-global-summary\"\n ],\n \"cli_command\": \"loopx global-summary\",\n \"time_range\": \"24h\",\n \"include\": [\n \"progress\",\n \"gates\",\n \"todos\",\n \"risks\",\n \"next_actions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"2 recent progress items, 2 open user/controller gates, 6 runnable or visible todos.\",\n \"progress_count\": 2,\n \"open_gate_count\": 2,\n \"runnable_todo_count\": 3,\n \"waiting_lane_count\": 6,\n \"risk_count\": 1,\n \"source_surfaces\": [\n \"global registry compact status\",\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"run history index summaries\"\n ],\n \"quota_states\": {\n \"eligible\": 1,\n \"quota_unavailable\": 1,\n \"operator_gate\": 1,\n \"waiting\": 1,\n \"paused\": 1,\n \"run\": 1,\n \"normal_run\": 1\n }\n },\n \"groups\": {\n \"user_gates\": [\n {\n \"gate_id\": \"goal-1:user-gate\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-1\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-2:user-gate\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-2\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"runnable_agent_work\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-5\",\n \"status\": \"normal_run\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-5\",\n \"last_event_id\": \"event-5\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"waiting_lanes\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-5\",\n \"status\": \"normal_run\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-5\",\n \"last_event_id\": \"event-5\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-1\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"top_todo_id\": \"work-2\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-3\",\n \"status\": \"paused\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-3\",\n \"last_event_id\": \"event-3\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"health_risks\": [\n {\n \"kind\": \"synthetic_warning\",\n \"severity\": \"info\",\n \"goal_id\": null,\n \"evidence_refs\": [\n \"status_contract\"\n ],\n \"next_safe_action\": \"Review synthetic warning.\"\n }\n ],\n \"recent_progress\": [\n {\n \"goal_id\": \"goal-0\",\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"classification\": \"progress\",\n \"recommended_action\": \"Continue.\"\n },\n {\n \"goal_id\": \"unknown-time\",\n \"generated_at\": \"invalid\",\n \"classification\": \"visible\",\n \"recommended_action\": \"\"\n }\n ]\n },\n \"lanes\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-5\",\n \"status\": \"normal_run\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-5\",\n \"last_event_id\": \"event-5\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-1\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"top_todo_id\": \"work-2\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-3\",\n \"status\": \"paused\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-3\",\n \"last_event_id\": \"event-3\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"unavailable\",\n \"status\": \"quota_unavailable\"\n }\n ],\n \"gates\": [\n {\n \"gate_id\": \"goal-1:user-gate\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-1\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-2:user-gate\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-2\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"todos\": [\n {\n \"todo_id\": \"work-0\",\n \"goal_id\": \"goal-0\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-1\",\n \"goal_id\": \"goal-1\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-2\",\n \"goal_id\": \"goal-2\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-3\",\n \"goal_id\": \"goal-3\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-4\",\n \"goal_id\": \"goal-4\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-5\",\n \"goal_id\": \"goal-5\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"risks\": [\n {\n \"kind\": \"synthetic_warning\",\n \"severity\": \"info\",\n \"goal_id\": null,\n \"evidence_refs\": [\n \"status_contract\"\n ],\n \"next_safe_action\": \"Review synthetic warning.\"\n }\n ],\n \"recent_progress\": [\n {\n \"goal_id\": \"goal-0\",\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"classification\": \"progress\",\n \"recommended_action\": \"Continue.\"\n },\n {\n \"goal_id\": \"unknown-time\",\n \"generated_at\": \"invalid\",\n \"classification\": \"visible\",\n \"recommended_action\": \"\"\n }\n ],\n \"actions\": [\n {\n \"action_id\": \"act_read_goal_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx review-packet --goal-id ` or `loopx quota should-run --goal-id ` for one lane.\"\n },\n {\n \"action_id\": \"act_ask_user_gate\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question when open_gate_count is non-zero.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Status health findings are summarized without filesystem paths.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_summary\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\",\n \"unreadable_sources\"\n ],\n \"alert_source_ids\": [\n \"goal_quota\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 9,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 7,\n \"missing_count\": 0,\n \"unreadable_count\": 1,\n \"alert\": true,\n \"alert_reasons\": [\n \"partially_unreadable\"\n ]\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": 9,\n \"included_count\": 7,\n \"omitted\": [\n {\n \"reason\": \"outside_current_registry\",\n \"count\": 2,\n \"refs\": [\n \"outside-a\",\n \"outside-b\"\n ]\n }\n ],\n \"shown_count\": 7,\n \"available_count\": 7,\n \"truncated\": false,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Summary\n\n- command: `/loopx-global-summary`\n- time_range: `24h`\n- headline: 2 recent progress items, 2 open user/controller gates, 6 runnable or visible todos.\n- counts: progress=`2`, gates=`2`, todos=`3`, risks=`1`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`2/3` coverage=`7/9` scope=`global`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources,unreadable_sources` sources=`goal_quota,status` omitted=`outside_current_registry=2`; state this before treating the projection as current or whole\n\n## Lanes\n- `goal-0` status=`eligible` waiting_on=`user` todo=`work-0`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-4` status=`run` waiting_on=`controller` todo=`work-4`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-5` status=`normal_run` waiting_on=`user` todo=`work-5`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-1` status=`operator_gate` waiting_on=`user` todo=`work-1`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-2` status=`waiting` waiting_on=`user` todo=`work-2`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-3` status=`paused` waiting_on=`controller` todo=`work-3`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `unavailable` status=`quota_unavailable` waiting_on=`None` todo=`None`: None\n\n## Gates\n- `goal-1` owner=`user`: Approve synthetic work?\n- `goal-2` owner=`user`: Approve synthetic work?\n\n## Recent Progress\n- `2026-11-02T12:00:00+00:00` `goal-0` `progress`: Continue.\n- `invalid` `unknown-time` `visible`: \n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 8, + 16 + ], + [ + "quota", + "goal-0", + null + ], + [ + "quota", + "unavailable", + null + ], + [ + "quota", + "goal-1", + null + ], + [ + "quota", + "goal-2", + null + ], + [ + "quota", + "goal-3", + null + ], + [ + "quota", + "goal-4", + null + ], + [ + "quota", + "goal-5", + null + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + }, + "gates": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-gates\",\n \"legacy_aliases\": [\n \"/loop-global-gates\"\n ],\n \"cli_command\": \"loopx global-gates\",\n \"include\": [\n \"gates\",\n \"blocked_work\",\n \"next_questions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"4 open user/controller gates require a decision.\",\n \"open_gate_count\": 4,\n \"blocked_lane_count\": 4,\n \"source_surfaces\": [\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"compact run-history projection consumed by status\"\n ]\n },\n \"groups\": {\n \"user_gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"gate-2\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"goal-2\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"controller_gates\": [\n {\n \"gate_id\": \"goal-3:controller-gate\",\n \"goal_id\": \"goal-3\",\n \"owner\": \"controller\",\n \"waiting_on\": \"controller\",\n \"blocks\": [\n \"goal-3\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-4:controller-gate\",\n \"goal_id\": \"goal-4\",\n \"owner\": \"controller\",\n \"waiting_on\": \"controller\",\n \"blocks\": [\n \"goal-4\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"blocked_work\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-3\",\n \"status\": \"paused\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-3\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ]\n },\n \"gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"gate-2\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"goal-2\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-3:controller-gate\",\n \"goal_id\": \"goal-3\",\n \"owner\": \"controller\",\n \"waiting_on\": \"controller\",\n \"blocks\": [\n \"goal-3\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-4:controller-gate\",\n \"goal_id\": \"goal-4\",\n \"owner\": \"controller\",\n \"waiting_on\": \"controller\",\n \"blocks\": [\n \"goal-4\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"lanes\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-3\",\n \"status\": \"paused\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-3\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"actions\": [\n {\n \"action_id\": \"act_read_gate_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx quota should-run --goal-id ` for one blocked lane.\"\n },\n {\n \"action_id\": \"act_ask_gate_owner\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question without changing state.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Recent progress and unrelated risks are outside this focused command.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_gates\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\",\n \"unreadable_sources\"\n ],\n \"alert_source_ids\": [\n \"goal_quota\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 9,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 7,\n \"missing_count\": 0,\n \"unreadable_count\": 1,\n \"alert\": true,\n \"alert_reasons\": [\n \"partially_unreadable\"\n ]\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": 9,\n \"included_count\": 7,\n \"omitted\": [\n {\n \"reason\": \"outside_current_registry\",\n \"count\": 2,\n \"refs\": [\n \"outside-a\",\n \"outside-b\"\n ]\n }\n ],\n \"shown_count\": 7,\n \"available_count\": 7,\n \"truncated\": false,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Gates\n\n- command: `/loopx-global-gates`\n- open_gate_count: `4`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`2/3` coverage=`7/9` scope=`global`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources,unreadable_sources` sources=`goal_quota,status` omitted=`outside_current_registry=2`; state this before treating the projection as current or whole\n\n## Gates\n- `goal-1` owner=`user` waiting_on=`user` blocks=`exact-target`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n- `goal-2` owner=`user` waiting_on=`user` blocks=`goal-2`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n- `goal-3` owner=`controller` waiting_on=`controller` blocks=`goal-3`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n- `goal-4` owner=`controller` waiting_on=`controller` blocks=`goal-4`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 8, + null + ], + [ + "quota", + "goal-0", + null + ], + [ + "quota", + "unavailable", + null + ], + [ + "quota", + "goal-1", + null + ], + [ + "quota", + "goal-2", + null + ], + [ + "quota", + "goal-3", + null + ], + [ + "quota", + "goal-4", + null + ], + [ + "quota", + "goal-5", + null + ] + ], + "crossings": [ + "todo.decision_scope.evaluate", + "projection.envelope.seal" + ] + } + }, + "early-stop": { + "summary": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-summary\",\n \"legacy_aliases\": [\n \"/loop-global-summary\"\n ],\n \"cli_command\": \"loopx global-summary\",\n \"time_range\": \"2d\",\n \"include\": [\n \"progress\",\n \"gates\",\n \"todos\",\n \"risks\",\n \"next_actions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"2 recent progress items, 2 open user/controller gates, 6 runnable or visible todos.\",\n \"progress_count\": 2,\n \"open_gate_count\": 2,\n \"runnable_todo_count\": 3,\n \"waiting_lane_count\": 6,\n \"risk_count\": 1,\n \"source_surfaces\": [\n \"global registry compact status\",\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"run history index summaries\"\n ],\n \"quota_states\": {\n \"eligible\": 1,\n \"quota_unavailable\": 1,\n \"operator_gate\": 1,\n \"waiting\": 1,\n \"paused\": 1,\n \"run\": 1,\n \"normal_run\": 1\n }\n },\n \"groups\": {\n \"user_gates\": [\n {\n \"gate_id\": \"goal-1:user-gate\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-1\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-2:user-gate\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-2\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"runnable_agent_work\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"waiting_lanes\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"health_risks\": [\n {\n \"kind\": \"synthetic_warning\",\n \"severity\": \"info\",\n \"goal_id\": null,\n \"evidence_refs\": [\n \"status_contract\"\n ],\n \"next_safe_action\": \"Review synthetic warning.\"\n }\n ],\n \"recent_progress\": [\n {\n \"goal_id\": \"goal-0\",\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"classification\": \"progress\",\n \"recommended_action\": \"Continue.\"\n },\n {\n \"goal_id\": \"unknown-time\",\n \"generated_at\": \"invalid\",\n \"classification\": \"visible\",\n \"recommended_action\": \"\"\n }\n ]\n },\n \"lanes\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"gates\": [\n {\n \"gate_id\": \"goal-1:user-gate\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-1\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-2:user-gate\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-2\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"todos\": [\n {\n \"todo_id\": \"work-0\",\n \"goal_id\": \"goal-0\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-1\",\n \"goal_id\": \"goal-1\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"risks\": [\n {\n \"kind\": \"synthetic_warning\",\n \"severity\": \"info\",\n \"goal_id\": null,\n \"evidence_refs\": [\n \"status_contract\"\n ],\n \"next_safe_action\": \"Review synthetic warning.\"\n }\n ],\n \"recent_progress\": [\n {\n \"goal_id\": \"goal-0\",\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"classification\": \"progress\",\n \"recommended_action\": \"Continue.\"\n },\n {\n \"goal_id\": \"unknown-time\",\n \"generated_at\": \"invalid\",\n \"classification\": \"visible\",\n \"recommended_action\": \"\"\n }\n ],\n \"actions\": [\n {\n \"action_id\": \"act_read_goal_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx review-packet --goal-id ` or `loopx quota should-run --goal-id ` for one lane.\"\n },\n {\n \"action_id\": \"act_ask_user_gate\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question when open_gate_count is non-zero.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Status health findings are summarized without filesystem paths.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_summary\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\",\n \"unreadable_sources\"\n ],\n \"alert_source_ids\": [\n \"goal_quota\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 9,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 7,\n \"missing_count\": 0,\n \"unreadable_count\": 1,\n \"alert\": true,\n \"alert_reasons\": [\n \"partially_unreadable\"\n ]\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": 9,\n \"included_count\": 7,\n \"omitted\": [\n {\n \"reason\": \"outside_current_registry\",\n \"count\": 2,\n \"refs\": [\n \"outside-a\",\n \"outside-b\"\n ]\n }\n ],\n \"shown_count\": 2,\n \"available_count\": 7,\n \"truncated\": true,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Summary\n\n- command: `/loopx-global-summary`\n- time_range: `2d`\n- headline: 2 recent progress items, 2 open user/controller gates, 6 runnable or visible todos.\n- counts: progress=`2`, gates=`2`, todos=`3`, risks=`1`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`2/3` coverage=`7/9` scope=`global` shown=`2/7`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources,unreadable_sources` sources=`goal_quota,status` omitted=`outside_current_registry=2`; state this before treating the projection as current or whole\n\n## Lanes\n- `goal-0` status=`eligible` waiting_on=`user` todo=`work-0`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-4` status=`run` waiting_on=`controller` todo=`work-4`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n\n## Gates\n- `goal-1` owner=`user`: Approve synthetic work?\n- `goal-2` owner=`user`: Approve synthetic work?\n\n## Recent Progress\n- `2026-11-02T12:00:00+00:00` `goal-0` `progress`: Continue.\n- `invalid` `unknown-time` `visible`: \n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 2, + 5 + ], + [ + "quota", + "goal-0", + null + ], + [ + "quota", + "unavailable", + null + ], + [ + "quota", + "goal-1", + null + ], + [ + "quota", + "goal-2", + null + ], + [ + "quota", + "goal-3", + null + ], + [ + "quota", + "goal-4", + null + ], + [ + "quota", + "goal-5", + null + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + }, + "gates": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-gates\",\n \"legacy_aliases\": [\n \"/loop-global-gates\"\n ],\n \"cli_command\": \"loopx global-gates\",\n \"include\": [\n \"gates\",\n \"blocked_work\",\n \"next_questions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"2 open user/controller gates require a decision.\",\n \"open_gate_count\": 2,\n \"blocked_lane_count\": 2,\n \"source_surfaces\": [\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"compact run-history projection consumed by status\"\n ]\n },\n \"groups\": {\n \"user_gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"gate-2\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"goal-2\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"controller_gates\": [],\n \"blocked_work\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ]\n },\n \"gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"gate-2\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"goal-2\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"lanes\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"actions\": [\n {\n \"action_id\": \"act_read_gate_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx quota should-run --goal-id ` for one blocked lane.\"\n },\n {\n \"action_id\": \"act_ask_gate_owner\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question without changing state.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Recent progress and unrelated risks are outside this focused command.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_gates\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\",\n \"unreadable_sources\"\n ],\n \"alert_source_ids\": [\n \"goal_quota\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 9,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 4,\n \"missing_count\": 0,\n \"unreadable_count\": 1,\n \"alert\": true,\n \"alert_reasons\": [\n \"partially_unreadable\"\n ]\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": 9,\n \"included_count\": 7,\n \"omitted\": [\n {\n \"reason\": \"outside_current_registry\",\n \"count\": 2,\n \"refs\": [\n \"outside-a\",\n \"outside-b\"\n ]\n }\n ],\n \"shown_count\": 4,\n \"available_count\": 7,\n \"truncated\": true,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Gates\n\n- command: `/loopx-global-gates`\n- open_gate_count: `2`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`2/3` coverage=`7/9` scope=`global` shown=`4/7`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources,unreadable_sources` sources=`goal_quota,status` omitted=`outside_current_registry=2`; state this before treating the projection as current or whole\n\n## Gates\n- `goal-1` owner=`user` waiting_on=`user` blocks=`exact-target`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n- `goal-2` owner=`user` waiting_on=`user` blocks=`goal-2`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 2, + null + ], + [ + "quota", + "goal-0", + null + ], + [ + "quota", + "unavailable", + null + ], + [ + "quota", + "goal-1", + null + ], + [ + "quota", + "goal-2", + null + ] + ], + "crossings": [ + "todo.decision_scope.evaluate", + "projection.envelope.seal" + ] + } + }, + "agent": { + "summary": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-summary\",\n \"legacy_aliases\": [\n \"/loop-global-summary\"\n ],\n \"cli_command\": \"loopx global-summary\",\n \"time_range\": \"0h\",\n \"include\": [\n \"progress\",\n \"gates\",\n \"todos\",\n \"risks\",\n \"next_actions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"2 recent progress items, 2 open user/controller gates, 6 runnable or visible todos.\",\n \"progress_count\": 2,\n \"open_gate_count\": 2,\n \"runnable_todo_count\": 3,\n \"waiting_lane_count\": 6,\n \"risk_count\": 1,\n \"source_surfaces\": [\n \"global registry compact status\",\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"run history index summaries\"\n ],\n \"quota_states\": {\n \"eligible\": 1,\n \"quota_unavailable\": 1,\n \"operator_gate\": 1,\n \"waiting\": 1,\n \"paused\": 1,\n \"run\": 1,\n \"normal_run\": 1\n }\n },\n \"groups\": {\n \"user_gates\": [\n {\n \"gate_id\": \"goal-1:user-gate\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-1\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-2:user-gate\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-2\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"runnable_agent_work\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-5\",\n \"status\": \"normal_run\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-5\",\n \"last_event_id\": \"event-5\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"waiting_lanes\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-5\",\n \"status\": \"normal_run\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-5\",\n \"last_event_id\": \"event-5\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-1\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"top_todo_id\": \"work-2\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-3\",\n \"status\": \"paused\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-3\",\n \"last_event_id\": \"event-3\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"health_risks\": [\n {\n \"kind\": \"synthetic_warning\",\n \"severity\": \"info\",\n \"goal_id\": null,\n \"evidence_refs\": [\n \"status_contract\"\n ],\n \"next_safe_action\": \"Review synthetic warning.\"\n }\n ],\n \"recent_progress\": [\n {\n \"goal_id\": \"goal-0\",\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"classification\": \"progress\",\n \"recommended_action\": \"Continue.\"\n },\n {\n \"goal_id\": \"unknown-time\",\n \"generated_at\": \"invalid\",\n \"classification\": \"visible\",\n \"recommended_action\": \"\"\n }\n ]\n },\n \"lanes\": [\n {\n \"goal_id\": \"goal-0\",\n \"status\": \"eligible\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-0\",\n \"last_event_id\": \"event-0\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-4\",\n \"status\": \"run\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-4\",\n \"last_event_id\": \"event-4\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-5\",\n \"status\": \"normal_run\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-5\",\n \"last_event_id\": \"event-5\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-1\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-2\",\n \"status\": \"waiting\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-b\",\n \"top_todo_id\": \"work-2\",\n \"last_event_id\": \"event-2\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"goal-3\",\n \"status\": \"paused\",\n \"waiting_on\": \"controller\",\n \"agent_id\": \"agent-a\",\n \"top_todo_id\": \"work-3\",\n \"last_event_id\": \"event-3\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"goal_id\": \"unavailable\",\n \"status\": \"quota_unavailable\"\n }\n ],\n \"gates\": [\n {\n \"gate_id\": \"goal-1:user-gate\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-1\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n },\n {\n \"gate_id\": \"goal-2:user-gate\",\n \"goal_id\": \"goal-2\",\n \"owner\": \"user\",\n \"blocks\": [\n \"gate-2\"\n ],\n \"question\": \"Approve synthetic work?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"todos\": [\n {\n \"todo_id\": \"work-0\",\n \"goal_id\": \"goal-0\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-1\",\n \"goal_id\": \"goal-1\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-2\",\n \"goal_id\": \"goal-2\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-3\",\n \"goal_id\": \"goal-3\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-4\",\n \"goal_id\": \"goal-4\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n },\n {\n \"todo_id\": \"work-5\",\n \"goal_id\": \"goal-5\",\n \"role\": \"agent\",\n \"claimed_by\": null,\n \"state\": \"open\",\n \"priority\": null,\n \"title\": \"Synthetic work\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"risks\": [\n {\n \"kind\": \"synthetic_warning\",\n \"severity\": \"info\",\n \"goal_id\": null,\n \"evidence_refs\": [\n \"status_contract\"\n ],\n \"next_safe_action\": \"Review synthetic warning.\"\n }\n ],\n \"recent_progress\": [\n {\n \"goal_id\": \"goal-0\",\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"classification\": \"progress\",\n \"recommended_action\": \"Continue.\"\n },\n {\n \"goal_id\": \"unknown-time\",\n \"generated_at\": \"invalid\",\n \"classification\": \"visible\",\n \"recommended_action\": \"\"\n }\n ],\n \"actions\": [\n {\n \"action_id\": \"act_read_goal_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx review-packet --goal-id ` or `loopx quota should-run --goal-id ` for one lane.\"\n },\n {\n \"action_id\": \"act_ask_user_gate\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question when open_gate_count is non-zero.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Status health findings are summarized without filesystem paths.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_summary\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\",\n \"unreadable_sources\"\n ],\n \"alert_source_ids\": [\n \"goal_quota\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 9,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 7,\n \"missing_count\": 0,\n \"unreadable_count\": 1,\n \"alert\": true,\n \"alert_reasons\": [\n \"partially_unreadable\"\n ]\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": 9,\n \"included_count\": 7,\n \"omitted\": [\n {\n \"reason\": \"outside_current_registry\",\n \"count\": 2,\n \"refs\": [\n \"outside-a\",\n \"outside-b\"\n ]\n }\n ],\n \"shown_count\": 7,\n \"available_count\": 7,\n \"truncated\": false,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Summary\n\n- command: `/loopx-global-summary`\n- time_range: `0h`\n- headline: 2 recent progress items, 2 open user/controller gates, 6 runnable or visible todos.\n- counts: progress=`2`, gates=`2`, todos=`3`, risks=`1`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`2/3` coverage=`7/9` scope=`global`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources,unreadable_sources` sources=`goal_quota,status` omitted=`outside_current_registry=2`; state this before treating the projection as current or whole\n\n## Lanes\n- `goal-0` status=`eligible` waiting_on=`user` todo=`work-0`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-4` status=`run` waiting_on=`controller` todo=`work-4`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-5` status=`normal_run` waiting_on=`user` todo=`work-5`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-1` status=`operator_gate` waiting_on=`user` todo=`work-1`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-2` status=`waiting` waiting_on=`user` todo=`work-2`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `goal-3` status=`paused` waiting_on=`controller` todo=`work-3`: Read /loopx-global-summary /synthetic/example.json then review synthetic work.\n- `unavailable` status=`quota_unavailable` waiting_on=`None` todo=`None`: None\n\n## Gates\n- `goal-1` owner=`user`: Approve synthetic work?\n- `goal-2` owner=`user`: Approve synthetic work?\n\n## Recent Progress\n- `2026-11-02T12:00:00+00:00` `goal-0` `progress`: Continue.\n- `invalid` `unknown-time` `visible`: \n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 8, + 16 + ], + [ + "quota", + "goal-0", + "agent-a" + ], + [ + "quota", + "unavailable", + "agent-a" + ], + [ + "quota", + "goal-1", + "agent-a" + ], + [ + "quota", + "goal-2", + "agent-a" + ], + [ + "quota", + "goal-3", + "agent-a" + ], + [ + "quota", + "goal-4", + "agent-a" + ], + [ + "quota", + "goal-5", + "agent-a" + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + }, + "gates": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-gates\",\n \"legacy_aliases\": [\n \"/loop-global-gates\"\n ],\n \"cli_command\": \"loopx global-gates\",\n \"include\": [\n \"gates\",\n \"blocked_work\",\n \"next_questions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"1 open user/controller gate requires a decision.\",\n \"open_gate_count\": 1,\n \"blocked_lane_count\": 1,\n \"source_surfaces\": [\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"compact run-history projection consumed by status\"\n ]\n },\n \"groups\": {\n \"user_gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"controller_gates\": [],\n \"blocked_work\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ]\n },\n \"gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"lanes\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"actions\": [\n {\n \"action_id\": \"act_read_gate_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx quota should-run --goal-id ` for one blocked lane.\"\n },\n {\n \"action_id\": \"act_ask_gate_owner\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question without changing state.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Recent progress and unrelated risks are outside this focused command.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_gates\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\",\n \"unreadable_sources\"\n ],\n \"alert_source_ids\": [\n \"goal_quota\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 9,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 7,\n \"missing_count\": 0,\n \"unreadable_count\": 1,\n \"alert\": true,\n \"alert_reasons\": [\n \"partially_unreadable\"\n ]\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": 9,\n \"included_count\": 7,\n \"omitted\": [\n {\n \"reason\": \"outside_current_registry\",\n \"count\": 2,\n \"refs\": [\n \"outside-a\",\n \"outside-b\"\n ]\n }\n ],\n \"shown_count\": 7,\n \"available_count\": 7,\n \"truncated\": false,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Gates\n\n- command: `/loopx-global-gates`\n- open_gate_count: `1`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`2/3` coverage=`7/9` scope=`global`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources,unreadable_sources` sources=`goal_quota,status` omitted=`outside_current_registry=2`; state this before treating the projection as current or whole\n\n## Gates\n- `goal-1` owner=`user` waiting_on=`user` blocks=`exact-target`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 8, + null + ], + [ + "quota", + "goal-0", + "agent-a" + ], + [ + "quota", + "unavailable", + "agent-a" + ], + [ + "quota", + "goal-1", + "agent-a" + ], + [ + "quota", + "goal-2", + "agent-a" + ], + [ + "quota", + "goal-3", + "agent-a" + ], + [ + "quota", + "goal-4", + "agent-a" + ], + [ + "quota", + "goal-5", + "agent-a" + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + } + }, + "zero": { + "summary": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-summary\",\n \"legacy_aliases\": [\n \"/loop-global-summary\"\n ],\n \"cli_command\": \"loopx global-summary\",\n \"time_range\": \"24h\",\n \"include\": [\n \"progress\",\n \"gates\",\n \"todos\",\n \"risks\",\n \"next_actions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"1 recent progress items, 2 open user/controller gates, 6 runnable or visible todos.\",\n \"progress_count\": 1,\n \"open_gate_count\": 2,\n \"runnable_todo_count\": 3,\n \"waiting_lane_count\": 6,\n \"risk_count\": 1,\n \"source_surfaces\": [\n \"global registry compact status\",\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"run history index summaries\"\n ],\n \"quota_states\": {\n \"eligible\": 1,\n \"quota_unavailable\": 1,\n \"operator_gate\": 1,\n \"waiting\": 1,\n \"paused\": 1,\n \"run\": 1,\n \"normal_run\": 1\n }\n },\n \"groups\": {\n \"user_gates\": [],\n \"runnable_agent_work\": [],\n \"waiting_lanes\": [],\n \"health_risks\": [],\n \"recent_progress\": [\n {\n \"goal_id\": \"goal-0\",\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"classification\": \"progress\",\n \"recommended_action\": \"Continue.\"\n }\n ]\n },\n \"lanes\": [],\n \"gates\": [],\n \"todos\": [],\n \"risks\": [],\n \"recent_progress\": [\n {\n \"goal_id\": \"goal-0\",\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"classification\": \"progress\",\n \"recommended_action\": \"Continue.\"\n }\n ],\n \"actions\": [\n {\n \"action_id\": \"act_read_goal_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx review-packet --goal-id ` or `loopx quota should-run --goal-id ` for one lane.\"\n },\n {\n \"action_id\": \"act_ask_user_gate\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question when open_gate_count is non-zero.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Status health findings are summarized without filesystem paths.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_summary\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\",\n \"unreadable_sources\"\n ],\n \"alert_source_ids\": [\n \"goal_quota\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 9,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 7,\n \"missing_count\": 0,\n \"unreadable_count\": 1,\n \"alert\": true,\n \"alert_reasons\": [\n \"partially_unreadable\"\n ]\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": 9,\n \"included_count\": 7,\n \"omitted\": [\n {\n \"reason\": \"outside_current_registry\",\n \"count\": 2,\n \"refs\": [\n \"outside-a\",\n \"outside-b\"\n ]\n }\n ],\n \"shown_count\": 0,\n \"available_count\": 7,\n \"truncated\": true,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Summary\n\n- command: `/loopx-global-summary`\n- time_range: `24h`\n- headline: 1 recent progress items, 2 open user/controller gates, 6 runnable or visible todos.\n- counts: progress=`1`, gates=`2`, todos=`3`, risks=`1`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`2/3` coverage=`7/9` scope=`global` shown=`0/7`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources,unreadable_sources` sources=`goal_quota,status` omitted=`outside_current_registry=2`; state this before treating the projection as current or whole\n\n## Lanes\n\n## Gates\n- none\n\n## Recent Progress\n- `2026-11-02T12:00:00+00:00` `goal-0` `progress`: Continue.\n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 1, + 5 + ], + [ + "quota", + "goal-0", + null + ], + [ + "quota", + "unavailable", + null + ], + [ + "quota", + "goal-1", + null + ], + [ + "quota", + "goal-2", + null + ], + [ + "quota", + "goal-3", + null + ], + [ + "quota", + "goal-4", + null + ], + [ + "quota", + "goal-5", + null + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + }, + "gates": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-gates\",\n \"legacy_aliases\": [\n \"/loop-global-gates\"\n ],\n \"cli_command\": \"loopx global-gates\",\n \"include\": [\n \"gates\",\n \"blocked_work\",\n \"next_questions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"1 open user/controller gate requires a decision.\",\n \"open_gate_count\": 1,\n \"blocked_lane_count\": 1,\n \"source_surfaces\": [\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"compact run-history projection consumed by status\"\n ]\n },\n \"groups\": {\n \"user_gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"controller_gates\": [],\n \"blocked_work\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ]\n },\n \"gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"lanes\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"actions\": [\n {\n \"action_id\": \"act_read_gate_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx quota should-run --goal-id ` for one blocked lane.\"\n },\n {\n \"action_id\": \"act_ask_gate_owner\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question without changing state.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Recent progress and unrelated risks are outside this focused command.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_gates\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\",\n \"unreadable_sources\"\n ],\n \"alert_source_ids\": [\n \"goal_quota\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 9,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 3,\n \"missing_count\": 0,\n \"unreadable_count\": 1,\n \"alert\": true,\n \"alert_reasons\": [\n \"partially_unreadable\"\n ]\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": 9,\n \"included_count\": 7,\n \"omitted\": [\n {\n \"reason\": \"outside_current_registry\",\n \"count\": 2,\n \"refs\": [\n \"outside-a\",\n \"outside-b\"\n ]\n }\n ],\n \"shown_count\": 3,\n \"available_count\": 7,\n \"truncated\": true,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Gates\n\n- command: `/loopx-global-gates`\n- open_gate_count: `1`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`2/3` coverage=`7/9` scope=`global` shown=`3/7`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources,unreadable_sources` sources=`goal_quota,status` omitted=`outside_current_registry=2`; state this before treating the projection as current or whole\n\n## Gates\n- `goal-1` owner=`user` waiting_on=`user` blocks=`exact-target`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 1, + null + ], + [ + "quota", + "goal-0", + null + ], + [ + "quota", + "unavailable", + null + ], + [ + "quota", + "goal-1", + null + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + } + }, + "negative": { + "summary": { + "error": "coverage.shown_count must be non-negative", + "reads": [ + [ + "status", + 1, + 5 + ], + [ + "quota", + "goal-0", + null + ], + [ + "quota", + "unavailable", + null + ], + [ + "quota", + "goal-1", + null + ], + [ + "quota", + "goal-2", + null + ], + [ + "quota", + "goal-3", + null + ], + [ + "quota", + "goal-4", + null + ], + [ + "quota", + "goal-5", + null + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + }, + "gates": { + "json": "{\n \"ok\": true,\n \"schema_version\": \"global_manager_command_response_v0\",\n \"request\": {\n \"schema_version\": \"global_manager_command_request_v0\",\n \"command\": \"/loopx-global-gates\",\n \"legacy_aliases\": [\n \"/loop-global-gates\"\n ],\n \"cli_command\": \"loopx global-gates\",\n \"include\": [\n \"gates\",\n \"blocked_work\",\n \"next_questions\"\n ],\n \"privacy_mode\": \"public_safe_summary\",\n \"dry_run\": true\n },\n \"generated_at\": \"2026-11-02T12:00:00+00:00\",\n \"summary\": {\n \"headline\": \"1 open user/controller gate requires a decision.\",\n \"open_gate_count\": 1,\n \"blocked_lane_count\": 1,\n \"source_surfaces\": [\n \"status attention queue\",\n \"quota should-run summaries\",\n \"active-state todo projections\",\n \"compact run-history projection consumed by status\"\n ]\n },\n \"groups\": {\n \"user_gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"controller_gates\": [],\n \"blocked_work\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ]\n },\n \"gates\": [\n {\n \"gate_id\": \"gate-1\",\n \"goal_id\": \"goal-1\",\n \"owner\": \"user\",\n \"waiting_on\": \"user\",\n \"blocks\": [\n \"exact-target\"\n ],\n \"question\": \"Which synthetic route?\",\n \"next_safe_action\": \"Wait for the projected user/controller decision before running the blocked path.\"\n }\n ],\n \"lanes\": [\n {\n \"goal_id\": \"goal-1\",\n \"status\": \"operator_gate\",\n \"waiting_on\": \"user\",\n \"agent_id\": \"agent-a\",\n \"last_event_id\": \"event-1\",\n \"next_safe_action\": \"Read /loopx-global-summary /synthetic/example.json then review synthetic work.\"\n }\n ],\n \"actions\": [\n {\n \"action_id\": \"act_read_gate_detail\",\n \"kind\": \"read_more\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Run `loopx quota should-run --goal-id ` for one blocked lane.\"\n },\n {\n \"action_id\": \"act_ask_gate_owner\",\n \"kind\": \"ask_user\",\n \"requires_user_approval\": false,\n \"requires_maintainer_authority\": false,\n \"preview\": \"Surface the projected gate question without changing state.\"\n }\n ],\n \"omissions\": [\n \"Raw logs, raw transcripts, connector payloads, credential values, local paths, and private source bodies were intentionally omitted.\",\n \"Recent progress and unrelated risks are outside this focused command.\"\n ],\n \"projection_envelope\": {\n \"schema_version\": \"loopx_projection_envelope_v0\",\n \"projection\": \"global_gates\",\n \"observed_at\": \"2026-11-02T12:00:00+00:00\",\n \"served_at\": \"2026-11-02T12:00:00+00:00\",\n \"age_seconds\": 0,\n \"served_from_cache\": false,\n \"fresh\": false,\n \"complete\": false,\n \"alert\": true,\n \"alert_reasons\": [\n \"incomplete_coverage\",\n \"missing_required_sources\",\n \"unreadable_sources\"\n ],\n \"alert_source_ids\": [\n \"goal_quota\",\n \"status\"\n ],\n \"sources\": [\n {\n \"source_id\": \"global_registry\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 9,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": false,\n \"alert_reasons\": []\n },\n {\n \"source_id\": \"goal_quota\",\n \"required\": true,\n \"read_status\": \"read\",\n \"last_read_at\": \"2026-11-02T12:00:00+00:00\",\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": 0,\n \"status\": \"fresh\",\n \"item_count\": 3,\n \"missing_count\": 0,\n \"unreadable_count\": 1,\n \"alert\": true,\n \"alert_reasons\": [\n \"partially_unreadable\"\n ]\n },\n {\n \"source_id\": \"status\",\n \"required\": true,\n \"read_status\": \"not_read\",\n \"last_read_at\": null,\n \"source_updated_at\": null,\n \"window_seconds\": 300,\n \"staleness_seconds\": null,\n \"status\": \"not_read\",\n \"item_count\": null,\n \"missing_count\": 0,\n \"unreadable_count\": 0,\n \"alert\": true,\n \"alert_reasons\": [\n \"not_read\"\n ]\n }\n ],\n \"coverage\": {\n \"scope\": \"global\",\n \"expected_count\": 9,\n \"included_count\": 7,\n \"omitted\": [\n {\n \"reason\": \"outside_current_registry\",\n \"count\": 2,\n \"refs\": [\n \"outside-a\",\n \"outside-b\"\n ]\n }\n ],\n \"shown_count\": 3,\n \"available_count\": 7,\n \"truncated\": true,\n \"complete\": false\n },\n \"upstream\": []\n },\n \"boundary\": {\n \"raw_logs_recorded\": false,\n \"raw_transcripts_recorded\": false,\n \"raw_connector_payloads_recorded\": false,\n \"credential_values_recorded\": false,\n \"absolute_paths_recorded\": false,\n \"private_source_bodies_recorded\": false\n }\n}", + "markdown": "# LoopX Global Gates\n\n- command: `/loopx-global-gates`\n- open_gate_count: `1`\n- projection: 🔴 envelope=`loopx_projection_envelope_v0` observed_at=`2026-11-02T12:00:00+00:00` age=`0s` sources_fresh=`2/3` coverage=`7/9` scope=`global` shown=`3/7`\n- 🔴 projection alerts: `incomplete_coverage,missing_required_sources,unreadable_sources` sources=`goal_quota,status` omitted=`outside_current_registry=2`; state this before treating the projection as current or whole\n\n## Gates\n- `goal-1` owner=`user` waiting_on=`user` blocks=`exact-target`: Which synthetic route? Next: Wait for the projected user/controller decision before running the blocked path.\n\n## Boundary\n- raw/private material omitted; local paths are not recorded.", + "reads": [ + [ + "status", + 1, + null + ], + [ + "quota", + "goal-0", + null + ], + [ + "quota", + "unavailable", + null + ], + [ + "quota", + "goal-1", + null + ] + ], + "crossings": [ + "projection.envelope.seal" + ] + } + } +} diff --git a/tests/test_summary_all_transaction_characterization.py b/tests/test_summary_all_transaction_characterization.py new file mode 100644 index 0000000000..6a55e689ca --- /dev/null +++ b/tests/test_summary_all_transaction_characterization.py @@ -0,0 +1,132 @@ +"""Frozen migration evidence; remove when the Python CLI facade is retired.""" +from __future__ import annotations + +import copy +import json +from datetime import datetime +from pathlib import Path + +import pytest + +import loopx.summary_all as manager +import loopx.control_plane.projection_envelope_facts as envelopes +import loopx.control_plane.todos.decision_scope as scopes + +FIXTURE = Path(__file__).parent / "fixtures/summary_all/transactions.json" +NOW = "2026-11-02T12:00:00+00:00" + + +def scenarios(): + scope = {"kind": "approval", "granularity": "action", "scope_key": "synthetic.publish"} + quotas = {} + items = [] + for index, state in enumerate(("eligible", "operator_gate", "waiting", "paused", "run", "normal_run")): + goal = f"goal-{index}" + todo = {"todo_id": f"work-{index}", "text": "Synthetic work", "status": "open", + "required_decision_scopes": [scope]} + quota = {"ok": True, "goal_id": goal, "state": state, + "agent_identity": {"agent_id": "agent-a" if index != 2 else "agent-b"}, + "recommended_action": "Read /loopx-summary-all " + "/" + "synthetic/example.json then review synthetic work.", + "agent_lane_next_action": todo, "selected_todo": todo, + "rollout_event": {"event_id": f"event-{index}"}} + if index in (1, 2): + gate = {"todo_id": f"gate-{index}", "text": "Approve synthetic work?", + "task_class": "user_gate", "decision_scope": scope} + if index == 1: + gate["unblocks_todo_id"] = "exact-target" + quota["user_todo_summary"] = {"open_count": 1, "gate_open_items": [gate], "items": [gate]} + quotas[goal] = quota + items.append({"goal_id": goal, "waiting_on": "controller" if index in (3, 4) else "user", + "operator_question": "Which synthetic route?", "recommended_action": "Wait safely."}) + quotas["unavailable"] = None + items = [items[0], items[0], {}, {"goal_id": "unavailable"}, *items[1:]] + status = {"ok": True, "attention_queue": {"items": items}, + "global_registry": {"available": True, "global_goal_count": 9, + "current_registry_excluded_goal_count": 2, + "current_registry_excluded_goal_ids": ["outside-a", "outside-b"], + "findings": [{"kind": "synthetic_warning", "message": "Review synthetic warning."}]}, + "run_history": {"recent_runs": [ + {"goal_id": "old", "generated_at": "2026-10-01T00:00:00Z", "classification": "old"}, + {"goal_id": "goal-0", "generated_at": NOW, "classification": "progress", "recommended_action": "Continue."}, + {"goal_id": "unknown-time", "generated_at": "invalid", "classification": "visible"}]}} + cases = [dict(name="empty", status={"ok": True}, quotas={}, limit=8, time_range="24h", agent_id=None), + dict(name="unhealthy", status={"ok": False}, quotas={}, limit=1, time_range="bad", agent_id=None)] + for name, limit, period, agent in (("mixed", 8, "24h", None), ("early-stop", 2, "2d", None), + ("agent", 8, "0h", "agent-a"), ("zero", 0, "bad", None), + ("negative", -1, "48h", None)): + cases.append(dict(name=name, status=status, quotas=quotas, limit=limit, time_range=period, agent_id=agent)) + return cases + + +def relation_scenarios(): + case = copy.deepcopy(scenarios()[2]) + case["name"] = "scope-and-envelope" + case["fixture"] = "relations.json" + case["quotas"]["goal-2"]["user_todo_summary"]["gate_open_items"][0]["decision_scope"] = { + "kind": "production", "granularity": "project", "scope_key": "deploy"} + case["quotas"]["goal-2"]["agent_lane_next_action"]["required_decision_scopes"] = [ + {"kind": "production", "granularity": "action", "scope_key": "deploy"}] + case["quotas"]["goal-2"]["agent_lane_next_action"]["todo_id"] = "todo_synthetic_work" + case["quotas"]["goal-2"]["selected_todo"] = case["quotas"]["goal-2"]["agent_lane_next_action"] + case["status"]["projection_envelope"] = envelopes.seal_projection_envelope( + projection="status", observed_at=NOW, + sources=[envelopes.source_fact("global_registry", last_read_at="2026-10-01T00:00:00+00:00"), + envelopes.source_fact("synthetic_unreadable", read_status="unreadable")], + coverage={"scope": "global", "expected_count": 9, "included_count": 7}) + return [case] + + +def observe(case, operation, monkeypatch, tmp_path): + reads, crossings = [], [] + monkeypatch.setattr(manager, "_now_iso", lambda: NOW) + monkeypatch.setattr(manager, "now_utc", lambda: datetime.fromisoformat(NOW)) + def status(**kwargs): + reads.append(["status", kwargs["limit"], kwargs.get("recent_run_limit")]) + return copy.deepcopy(case["status"]) + def quota(_status, *, goal_id, agent_id): + reads.append(["quota", goal_id, agent_id]) + value = case["quotas"][goal_id] + if value is None: + raise OSError("Synthetic quota unavailable") + return copy.deepcopy(value) + monkeypatch.setattr(manager, "collect_status", status) + monkeypatch.setattr(manager, "build_quota_should_run", quota) + for module in (envelopes, scopes, manager): + if not hasattr(module, "effect_runtime_result"): + continue + original = module.effect_runtime_result + def instrument(operation, request, _original=original, **kwargs): + crossings.append(operation) + return _original(operation, request, **kwargs) + monkeypatch.setattr(module, "effect_runtime_result", instrument) + kwargs = dict(registry_path=tmp_path / "registry.json", runtime_root_override=None, + agent_id=case["agent_id"], limit=case["limit"]) + try: + return project(operation, kwargs, case, reads, crossings) + except Exception as exc: + return {"error": str(exc), "reads": reads, "crossings": crossings} + + +def project(operation, kwargs, case, reads, crossings): + if operation == "summary": + payload = manager.build_summary_all(**kwargs, time_range=case["time_range"]) + markdown = manager.render_summary_all_markdown(payload) + else: + payload = manager.build_global_gates(**kwargs) + markdown = manager.render_global_gates_markdown(payload) + return {"json": json.dumps(payload, ensure_ascii=False, indent=2), "markdown": markdown, + "reads": reads, "crossings": crossings} + + +@pytest.mark.parametrize("case", scenarios() + relation_scenarios(), ids=lambda case: case["name"]) +@pytest.mark.parametrize("operation", ["summary", "gates"]) +def test_transaction_bytes_and_source_reads(case, operation, monkeypatch, tmp_path): + expected = json.loads(FIXTURE.with_name(case.get("fixture", FIXTURE.name)).read_text())[case["name"]][operation] + actual = observe(case, operation, monkeypatch, tmp_path) + for key in ("json", "markdown", "error"): + assert actual.get(key) == expected.get(key) + assert actual["reads"] == expected["reads"] + if hasattr(manager, "effect_runtime_result"): + assert actual["crossings"] == [f"presentation.global_{operation if operation == 'gates' else 'summary'}.project"] + else: + assert actual["crossings"] == expected["crossings"]