diff --git a/contracts/OMN-20680.yaml b/contracts/OMN-20680.yaml new file mode 100644 index 0000000000..dbcd567641 --- /dev/null +++ b/contracts/OMN-20680.yaml @@ -0,0 +1,51 @@ +--- +schema_version: "1.0.0" +ticket_id: "OMN-20680" +title: "The linear-comments sweep and hourly tick decisions run as omnimarket compute nodes" +summary: "node_linear_comments_decision_compute ports the pure block of the linear-comments-hourly workflow script and node_hourly_tick_decision_compute holds the tick's probe and record decisions. Both are contract-declared COMPUTE nodes dispatched over the bus, with parity against captured outputs of the old code. Repointing callers and deleting the old script are separate changes." +is_seam_ticket: false +interface_change: false +interfaces_touched: [] +requirements: + - id: "req-linear-comments-decision-node" + statement: |- + The linear-comments sweep's deterministic decisions (author classification, draft-or-not, prompt, citation check, truncation, held classification, held tally, degraded flag, metrics row) run in node_linear_comments_decision_compute and match the old JS pure block. + acceptance: + - id: "AC1" + statement: |- + AC1: Every decision kind returns the old block's captured output for the same input -- falsifier: tests/nodes/node_linear_comments_decision_compute/test_parity_linear_comments_decision_compute.py against tests/fixtures/linear_comments_hourly_parity.json, which fails on any differing decision + - id: "AC2" + statement: |- + AC2: The node is dispatched through its contract on the RuntimeLocal in-memory bus and an invalid request is refused with no result -- falsifier: tests/nodes/node_linear_comments_decision_compute/test_golden_chain_linear_comments_decision_compute.py + - id: "req-hourly-tick-decision-node" + statement: |- + The hourly tick's probe and record decisions run in node_hourly_tick_decision_compute, with no clock and no file reads, and match the pre-conversion tick. + acceptance: + - id: "AC3" + statement: |- + AC3: probe then record over the bus returns the pre-conversion tick's window state, checkpoint line, sweep request, completion line, phase, degraded class and fire-result payload for every captured case -- falsifier: tests/nodes/node_hourly_tick_decision_compute/test_golden_chain_hourly_tick_decision_compute.py against tests/fixtures/hourly_tick_parity.json +evidence_requirements: + - kind: "tests" + description: "Parity, golden-chain and error-chain tests for both decision compute nodes." + command: "uv run pytest tests/nodes/node_linear_comments_decision_compute tests/nodes/node_hourly_tick_decision_compute -q -p no:randomly" +emergency_bypass: + enabled: false + justification: "" + follow_up_ticket_id: "" +dod_evidence: + - id: "dod-omn20680-linear-comments-decision" + description: "node_linear_comments_decision_compute matches the old JS pure block and runs over the bus." + source: "manual" + checks: + - check_type: "test_passes" + check_value: "uv run pytest tests/nodes/node_linear_comments_decision_compute/test_parity_linear_comments_decision_compute.py tests/nodes/node_linear_comments_decision_compute/test_golden_chain_linear_comments_decision_compute.py -q -p no:randomly" + cwd: "${OMNI_HOME}/omnimarket" + binds_ac: ["AC1", "AC2"] + - id: "dod-omn20680-hourly-tick-decision" + description: "node_hourly_tick_decision_compute matches the pre-conversion tick and runs probe then record over the bus." + source: "manual" + checks: + - check_type: "test_passes" + check_value: "uv run pytest tests/nodes/node_hourly_tick_decision_compute/test_golden_chain_hourly_tick_decision_compute.py -q -p no:randomly" + cwd: "${OMNI_HOME}/omnimarket" + binds_ac: ["AC3"] diff --git a/pyproject.toml b/pyproject.toml index 5147b88df4..6b3f3f3a75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -271,6 +271,8 @@ node_lab_fill_effect = "omnimarket.nodes.node_lab_fill_effect" node_delegation_split_compute = "omnimarket.nodes.node_delegation_split_compute" node_delegation_availability_report_compute = "omnimarket.nodes.node_delegation_availability_report_compute" node_delegation_size_band_compute = "omnimarket.nodes.node_delegation_size_band_compute" +node_linear_comments_decision_compute = "omnimarket.nodes.node_linear_comments_decision_compute" +node_hourly_tick_decision_compute = "omnimarket.nodes.node_hourly_tick_decision_compute" node_delegation_recombine_compute = "omnimarket.nodes.node_delegation_recombine_compute" node_projection_worktree_reconcile = "omnimarket.nodes.node_projection_worktree_reconcile" node_worktree_reconcile_compute = "omnimarket.nodes.node_worktree_reconcile_compute" diff --git a/src/omnimarket/nodes/node_hourly_tick_decision_compute/__init__.py b/src/omnimarket/nodes/node_hourly_tick_decision_compute/__init__.py new file mode 100644 index 0000000000..6f19925a99 --- /dev/null +++ b/src/omnimarket/nodes/node_hourly_tick_decision_compute/__init__.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +"""Hourly tick decision compute node (OMN-20680).""" + +from omnimarket.nodes.node_hourly_tick_decision_compute.handlers.handler_hourly_tick_decision import ( + HandlerHourlyTickDecision, +) + + +class NodeHourlyTickDecisionCompute(HandlerHourlyTickDecision): + """ONEX entrypoint for the tick's deterministic decisions.""" + + +__all__ = ["HandlerHourlyTickDecision", "NodeHourlyTickDecisionCompute"] diff --git a/src/omnimarket/nodes/node_hourly_tick_decision_compute/contract.yaml b/src/omnimarket/nodes/node_hourly_tick_decision_compute/contract.yaml new file mode 100644 index 0000000000..70c801166d --- /dev/null +++ b/src/omnimarket/nodes/node_hourly_tick_decision_compute/contract.yaml @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +name: node_hourly_tick_decision_compute +node_type: compute +runtime_profiles: [main] +contract_version: {major: 1, minor: 0, patch: 0} +node_version: "1.0.0" +description: >- + Definition-B deterministic decisions of the hourly tick (OMN-20680). probe turns the + checkpoint file, the sweep pointer and the ledger contents (read by the caller) into the + window state, the checkpoint line and the sweep request; record turns the state and the + sweep result into the completion line, the phase, the degraded class and the fire-result + payload. Reads no files, has no clock (the caller supplies now), mutates nothing. Every + refusal the retired tick made before a write is a refusal here, so a caller that gets one + has written nothing. Behaviour-preserving port of the retired tick, pinned by 60 captured + cases. +input_model: + name: ModelHourlyTickDecisionRequest + module: omnimarket.nodes.node_hourly_tick_decision_compute.models.model_hourly_tick_decision +output_model: + name: ModelHourlyTickDecisionResult + module: omnimarket.nodes.node_hourly_tick_decision_compute.models.model_hourly_tick_decision +handler: + module: omnimarket.nodes.node_hourly_tick_decision_compute.handlers.handler_hourly_tick_decision + class: HandlerHourlyTickDecision + input_model: omnimarket.nodes.node_hourly_tick_decision_compute.models.model_hourly_tick_decision.ModelHourlyTickDecisionRequest + output_model: omnimarket.nodes.node_hourly_tick_decision_compute.models.model_hourly_tick_decision.ModelHourlyTickDecisionResult +handler_routing: + routing_strategy: operation_match + default_handler: omnimarket.nodes.node_hourly_tick_decision_compute.handlers.handler_hourly_tick_decision:HandlerHourlyTickDecision + handlers: + - operation: decide_hourly_tick + handler: + name: HandlerHourlyTickDecision + module: omnimarket.nodes.node_hourly_tick_decision_compute.handlers.handler_hourly_tick_decision +descriptor: + node_archetype: compute + purity: pure + idempotent: true + timeout_ms: 1000 +inputs: + kind: {type: str, required: true, enum: [probe, record]} + now: {type: str, required: false, description: ISO-Z time of the tick, for probe.} + checkpoint_text: {type: str, required: false, description: Contents of the checkpoint file, for probe.} + sweep_pointer_text: {type: str, required: false, description: Contents of the sweep pointer file, for probe.} + ledger_text: {type: str, required: false, description: Contents of the rolling ledger, for probe.} + tailLines: {type: int, required: false} + facts: {type: str, required: false, description: Caller facts; take precedence over the derived facts.} + taskType: {type: str, required: false} + skipSweep: {type: bool, required: false} + state: {type: object, required: false, description: The state probe returned, for record.} + sweep: {type: object, required: false, description: The sweep result, for record.} + runId: {type: str, required: false} + fireId: {type: str, required: false} +outputs: + kind: {type: str} + state: {type: object} + checkpoint_line: {type: str} + facts_source: {type: str} + sweep_request: {type: object} + completion_line: {type: str} + phase: {type: str, enum: [prepared, completed, degraded]} + degraded: {type: bool} + class: {type: str} + result_payload: {type: object} +terminal_event: onex.evt.omnimarket.hourly-tick-decision-completed.v1 +runtime_dispatch: + command_topic: onex.cmd.omnimarket.hourly-tick-decision-requested.v1 +event_bus: + subscribe_topics: + - onex.cmd.omnimarket.hourly-tick-decision-requested.v1 + publish_topics: + - onex.evt.omnimarket.hourly-tick-decision-completed.v1 +externally_consumed_topics: + - onex.evt.omnimarket.hourly-tick-decision-completed.v1 +metadata: + transport_type: inmemory + description: Hourly tick decisions + author: OmniNode Team + created: "2026-10-08" + related_tickets: [OMN-20680] diff --git a/src/omnimarket/nodes/node_hourly_tick_decision_compute/handlers/__init__.py b/src/omnimarket/nodes/node_hourly_tick_decision_compute/handlers/__init__.py new file mode 100644 index 0000000000..64f06c497f --- /dev/null +++ b/src/omnimarket/nodes/node_hourly_tick_decision_compute/handlers/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT diff --git a/src/omnimarket/nodes/node_hourly_tick_decision_compute/handlers/handler_hourly_tick_decision.py b/src/omnimarket/nodes/node_hourly_tick_decision_compute/handlers/handler_hourly_tick_decision.py new file mode 100644 index 0000000000..2fdc3663eb --- /dev/null +++ b/src/omnimarket/nodes/node_hourly_tick_decision_compute/handlers/handler_hourly_tick_decision.py @@ -0,0 +1,215 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +"""Definition-B decisions of the hourly tick (OMN-20680). + +The retired tick mixed these decisions with host file reads and writes. Here the caller +reads the checkpoint file, the sweep pointer and the ledger and passes their contents; the +handler returns what to write. Formats are byte-identical to the retired tick (60 captured +cases in tests/fixtures/hourly_tick_parity.json). +""" + +from __future__ import annotations + +import re +from typing import Literal + +from omnibase_core.types import JsonType + +from omnimarket.nodes.node_hourly_tick_decision_compute.models.model_hourly_tick_decision import ( + EnumHourlyTickDecisionKind, + ModelHourlyTickDecisionRequest, + ModelHourlyTickDecisionResult, + ModelSweepRequest, + ModelSweepResult, + ModelTickState, +) + +HELD_CLASS_ORDER = ("floor_refused", "truncated", "unsourced", "transport", "timeout") +TS = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$") + + +def checkpoint_line(p: ModelTickState) -> str: + return ( + f"- {p.now} 60m checkpoint: ledger rows since {p.prev}: {p.rows}; " + f"TERMINAL {p.terminal} [{p.lanes}]; sweep since={p.sweep_since}" + ) + + +def tick_outcome(sweep: ModelSweepResult | None) -> dict[str, str | None]: + """Degraded when something needed a reply and nothing was accepted; ties follow HELD_CLASS_ORDER.""" + if sweep is None or not (sweep.needing and sweep.accepted == 0): + return {"phase": "completed", "class": None} + counts = sweep.held_by_class or {} + best = max(HELD_CLASS_ORDER, key=lambda c: counts.get(c, 0)) + return { + "phase": "degraded", + "class": best if counts.get(best, 0) > 0 else "unclassified", + } + + +def completion_line( + p: ModelTickState, sweep: ModelSweepResult | None, run_id: str +) -> str: + outcome = tick_outcome(sweep) + counts = ", ".join( + f"{field} {value if value is not None else 'n/a'}" + for field in ("collected", "needing", "accepted", "held") + for value in [getattr(sweep, field) if sweep is not None else None] + ) + phase = f"phase={outcome['phase']}" + ( + f" class={outcome['class']}" if outcome["class"] else "" + ) + drafts = sweep.linear_file if sweep is not None and sweep.linear_file else "none" + return ( + f"- {p.now} hourly-tick run={run_id or 'unrecorded'}: checkpoint rows {p.rows} / " + f"TERMINAL {p.terminal}; sweep since={p.sweep_since} {counts}, {phase}; drafts {drafts}; " + "NOT POSTED (per-message operator approval required)" + ) + + +def result_payload(fire_id: str, sweep: ModelSweepResult | None) -> dict[str, JsonType]: + return { + "fire_id": fire_id, + "workflow": "hourly-tick", + **tick_outcome(sweep), + **{ + field: getattr(sweep, field) if sweep is not None else None + for field in ("needing", "accepted", "held", "held_by_class") + }, + } + + +def facts_from_tail(tail: list[str], limit: int) -> str: + lines = [line for line in tail if line.strip()][-limit:] + if not lines: + raise ValueError("hourly-tick: no facts supplied and checkpoint tail is empty") + return "\n".join( + [ + "Ground-truth facts, derived from the most recent orchestration " + "checkpoint lines, oldest first.", + "Each line is a timestamped statement of what a lane reported " + "or what the operator ruled.", + "Cite only what appears below. Anything not here is not a fact you may state.", + "", + *[line if line.startswith("- ") else f"- {line}" for line in lines], + ] + ) + + +def probe_window( + *, + now: str, + checkpoint_text: str, + sweep_pointer_text: str, + ledger_text: str, + tail_lines: int, +) -> ModelTickState: + """Window after the previous checkpoint through now. The ledger row count is the positive control.""" + tail = checkpoint_text.splitlines() + checkpoints = [ + m.group(1) + for line in tail + if (m := re.match(r"^- ([0-9T:Z-]*) 60m checkpoint:", line)) + ] + if not checkpoints: + raise ValueError("hourly-tick: probe field PREV is not an ISO-Z timestamp") + prev = checkpoints[-1] + since = "".join(sweep_pointer_text.split()) + if not TS.fullmatch(since): + raise ValueError( + "hourly-tick: probe field SWEEP_SINCE is not an ISO-Z timestamp" + ) + ledger = ledger_text.splitlines() + timestamped = [ + line for line in ledger if len(line) >= 20 and TS.fullmatch(line[:20]) + ] + rows = [line for line in timestamped if prev < line[:20] <= now] + terminal = [line for line in rows if "| TERMINAL |" in line] + lanes: set[str] = set() + for line in terminal: + for field in line.split("|"): + if match := re.match(r"^lane=[A-Za-z0-9_.:-]+", field.strip()): + lanes.add(match.group()) + break + ledger_total = sum(bool(re.match(r"^\d{4}-\d{2}-\d{2}T", line)) for line in ledger) + if ledger_total <= 0: + raise ValueError( + "hourly-tick: ledger positive control failed: no timestamped rows" + ) + return ModelTickState( + now=now, + prev=prev, + sweep_since=since, + rows=len(rows), + terminal=len(terminal), + lanes=",".join(sorted(lanes)), + ledger_total=ledger_total, + tail=[line.strip() for line in tail[-tail_lines:] if line.strip()], + ) + + +class HandlerHourlyTickDecision: + """Stateless compute: probe the window, or record the sweep outcome.""" + + def handle( + self, request: ModelHourlyTickDecisionRequest + ) -> ModelHourlyTickDecisionResult: + if request.kind is EnumHourlyTickDecisionKind.PROBE: + assert request.now is not None + assert request.checkpoint_text is not None + assert request.sweep_pointer_text is not None + assert request.ledger_text is not None + state = probe_window( + now=request.now, + checkpoint_text=request.checkpoint_text, + sweep_pointer_text=request.sweep_pointer_text, + ledger_text=request.ledger_text, + tail_lines=request.tail_lines, + ) + facts = request.facts or facts_from_tail(state.tail, request.tail_lines) + facts_source = ( + "caller-supplied" + if request.facts + else f"derived from the last {request.tail_lines} checkpoint lines" + ) + else: + assert request.state is not None + state = request.state + facts = request.facts + facts_source = None + skipped = ( + request.skip_sweep and request.kind is EnumHourlyTickDecisionKind.PROBE + ) + probing = request.kind is EnumHourlyTickDecisionKind.PROBE + sweep_request = ( + ModelSweepRequest( + since=state.sweep_since, facts=facts, task_type=request.task_type + ) + if probing and not skipped + else None + ) + outcome = tick_outcome(request.sweep) + completing = not probing or skipped + phase: Literal["prepared", "completed", "degraded"] = "prepared" + if completing: + phase = "degraded" if outcome["phase"] == "degraded" else "completed" + return ModelHourlyTickDecisionResult( + kind=request.kind, + state=state, + checkpoint_line=checkpoint_line(state), + facts_source=facts_source, + sweep_request=sweep_request, + completion_line=( + completion_line(state, request.sweep, request.run_id or request.fire_id) + if completing + else None + ), + phase=phase, + degraded=phase == "degraded", + held_class=outcome["class"] if completing else None, + result_payload=( + result_payload(request.fire_id, request.sweep) + if completing and request.fire_id + else None + ), + ) diff --git a/src/omnimarket/nodes/node_hourly_tick_decision_compute/metadata.yaml b/src/omnimarket/nodes/node_hourly_tick_decision_compute/metadata.yaml new file mode 100644 index 0000000000..5775be6106 --- /dev/null +++ b/src/omnimarket/nodes/node_hourly_tick_decision_compute/metadata.yaml @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +name: node_hourly_tick_decision_compute +version: "1.0.0" +description: Deterministic decisions of the hourly tick (window state, checkpoint, completion, degraded class) +omnibase_core_compat: ">=0.47.28,<0.48.0" +entry_points: + onex.nodes: + node_hourly_tick_decision_compute: omnimarket.nodes.node_hourly_tick_decision_compute +capabilities: + standalone: true + full_runtime: true + requires_network: false + requires_repo: false + requires_secrets: false + side_effect_class: pure +dependencies: + - pydantic>=2.11.0 +authors: [OmniNode Team] +license: MIT +tags: [compute, hourly, tick, checkpoint] +node_role: compute +pack: "routing" +display_name: "Hourly Tick Decision Compute" diff --git a/src/omnimarket/nodes/node_hourly_tick_decision_compute/models/__init__.py b/src/omnimarket/nodes/node_hourly_tick_decision_compute/models/__init__.py new file mode 100644 index 0000000000..64f06c497f --- /dev/null +++ b/src/omnimarket/nodes/node_hourly_tick_decision_compute/models/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT diff --git a/src/omnimarket/nodes/node_hourly_tick_decision_compute/models/model_hourly_tick_decision.py b/src/omnimarket/nodes/node_hourly_tick_decision_compute/models/model_hourly_tick_decision.py new file mode 100644 index 0000000000..676e4ef408 --- /dev/null +++ b/src/omnimarket/nodes/node_hourly_tick_decision_compute/models/model_hourly_tick_decision.py @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +"""Typed request and result for the hourly tick decisions (OMN-20680).""" + +from __future__ import annotations + +from enum import StrEnum +from typing import Literal, Self + +from omnibase_core.types import JsonType +from pydantic import BaseModel, ConfigDict, Field, model_validator + +_TS = r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$" + + +class EnumHourlyTickDecisionKind(StrEnum): + PROBE = "probe" + RECORD = "record" + + +class ModelTickState(BaseModel): + """The checkpoint window and its counts; derived from file contents, never from the clock alone.""" + + model_config = ConfigDict(frozen=True, populate_by_name=True, extra="forbid") + + now: str = Field(pattern=_TS) + prev: str = Field(pattern=_TS) + sweep_since: str = Field(validation_alias="sweepSince", pattern=_TS) + rows: int = Field(ge=0) + terminal: int = Field(ge=0) + lanes: str + ledger_total: int = Field(validation_alias="ledgerTotal", gt=0) + tail: list[str] + + @model_validator(mode="after") + def validate_counts(self) -> Self: + if self.terminal > self.rows: + raise ValueError("hourly-tick: TERMINAL rows exceed window rows") + return self + + +class ModelSweepResult(BaseModel): + """What the Linear-comments sweep returned; every count is optional (a skipped sweep has none).""" + + model_config = ConfigDict(frozen=True, extra="allow") + __pydantic_extra__: dict[str, JsonType] = Field(init=False) + + collected: int | None = Field(default=None, ge=0) + needing: int | None = Field(default=None, ge=0) + accepted: int | None = Field(default=None, ge=0) + held: int | None = Field(default=None, ge=0) + held_by_class: dict[str, int] | None = None + linear_file: str | None = None + + +class ModelSweepRequest(BaseModel): + model_config = ConfigDict(frozen=True) + + since: str + facts: str + task_type: str = "document" + + +class ModelHourlyTickDecisionRequest(BaseModel): + """probe: file contents in, window state out. record: state and sweep in, completion out.""" + + model_config = ConfigDict(frozen=True, populate_by_name=True, extra="forbid") + + kind: EnumHourlyTickDecisionKind + now: str | None = Field(default=None, pattern=_TS) + checkpoint_text: str | None = None + sweep_pointer_text: str | None = None + ledger_text: str | None = None + tail_lines: int = Field(default=12, validation_alias="tailLines", gt=0) + facts: str = "" + task_type: str = Field(default="document", validation_alias="taskType") + skip_sweep: bool = Field(default=False, validation_alias="skipSweep") + state: ModelTickState | None = None + sweep: ModelSweepResult | None = None + run_id: str = Field(default="", validation_alias="runId") + fire_id: str = Field( + default="", + validation_alias="fireId", + pattern=r"^(?:[A-Za-z0-9][A-Za-z0-9._-]*)?$", + ) + + @model_validator(mode="after") + def _kind_needs_its_fields(self) -> Self: + probe_fields = ("now", "checkpoint_text", "sweep_pointer_text", "ledger_text") + if self.kind is EnumHourlyTickDecisionKind.PROBE: + missing = [f for f in probe_fields if getattr(self, f) is None] + if missing: + raise ValueError(f"hourly-tick: probe requires {', '.join(missing)}") + if self.state is not None or self.sweep is not None: + raise ValueError("hourly-tick: probe does not take state or sweep") + else: + if self.state is None: + raise ValueError("hourly-tick: record requires the prepared state") + given = [f for f in probe_fields if getattr(self, f) is not None] + if given: + raise ValueError( + f"hourly-tick: record does not take {', '.join(given)}" + ) + return self + + +class ModelHourlyTickDecisionResult(BaseModel): + model_config = ConfigDict(frozen=True, populate_by_name=True) + + kind: EnumHourlyTickDecisionKind + state: ModelTickState + checkpoint_line: str + facts_source: str | None = None + sweep_request: ModelSweepRequest | None = None + completion_line: str | None = None + phase: Literal["prepared", "completed", "degraded"] + degraded: bool = False + held_class: str | None = Field( + default=None, validation_alias="class", serialization_alias="class" + ) + result_payload: dict[str, JsonType] | None = None diff --git a/src/omnimarket/nodes/node_linear_comments_decision_compute/__init__.py b/src/omnimarket/nodes/node_linear_comments_decision_compute/__init__.py new file mode 100644 index 0000000000..a15e5c756d --- /dev/null +++ b/src/omnimarket/nodes/node_linear_comments_decision_compute/__init__.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +"""Hourly Linear-comments sweep decision compute node (OMN-20680).""" + +from omnimarket.nodes.node_linear_comments_decision_compute.handlers.handler_linear_comments_decision import ( + HandlerLinearCommentsDecision, +) + + +class NodeLinearCommentsDecisionCompute(HandlerLinearCommentsDecision): + """ONEX entrypoint for the sweep's deterministic decisions.""" + + +__all__ = ["HandlerLinearCommentsDecision", "NodeLinearCommentsDecisionCompute"] diff --git a/src/omnimarket/nodes/node_linear_comments_decision_compute/contract.yaml b/src/omnimarket/nodes/node_linear_comments_decision_compute/contract.yaml new file mode 100644 index 0000000000..542e9e7a33 --- /dev/null +++ b/src/omnimarket/nodes/node_linear_comments_decision_compute/contract.yaml @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +name: node_linear_comments_decision_compute +node_type: compute +runtime_profiles: [main] +contract_version: {major: 1, minor: 0, patch: 0} +node_version: "1.0.0" +description: >- + Definition-B deterministic decisions of the hourly Linear-comments sweep (OMN-20680): + which collected comments get a draft and why not (author classification, already + answered, needs_reply), the drafting prompt, the citation check on a draft, whether a + delegated response was cut off, the closed held-reason classifier, the held tally with + the degraded flag, and the per-call metrics row. Behaviour-preserving port of the pure + block of the retired linear-comments-hourly workflow. Reads no files, calls no model, + mutates no state; a refusal is a typed validation error naming the missing field. +input_model: + name: ModelLinearCommentsDecisionRequest + module: omnimarket.nodes.node_linear_comments_decision_compute.models.model_linear_comments_decision +output_model: + name: ModelLinearCommentsDecisionResult + module: omnimarket.nodes.node_linear_comments_decision_compute.models.model_linear_comments_decision +handler: + module: omnimarket.nodes.node_linear_comments_decision_compute.handlers.handler_linear_comments_decision + class: HandlerLinearCommentsDecision + input_model: omnimarket.nodes.node_linear_comments_decision_compute.models.model_linear_comments_decision.ModelLinearCommentsDecisionRequest + output_model: omnimarket.nodes.node_linear_comments_decision_compute.models.model_linear_comments_decision.ModelLinearCommentsDecisionResult +handler_routing: + routing_strategy: operation_match + default_handler: omnimarket.nodes.node_linear_comments_decision_compute.handlers.handler_linear_comments_decision:HandlerLinearCommentsDecision + handlers: + - operation: decide_linear_comments + handler: + name: HandlerLinearCommentsDecision + module: omnimarket.nodes.node_linear_comments_decision_compute.handlers.handler_linear_comments_decision +descriptor: + node_archetype: compute + purity: pure + idempotent: true + timeout_ms: 1000 +inputs: + kind: + type: str + required: true + enum: [decide_items, build_prompt, check_draft, detect_truncation, classify_held, tally, metrics_row] + items: {type: array, required: false, description: Collected comments, for decide_items.} + item: {type: object, required: false, description: One collected comment, for build_prompt and check_draft.} + facts: {type: str, required: false, description: Ground-truth facts table, for build_prompt and check_draft.} + draft: {type: object, required: false, description: The drafted reply, for check_draft.} + outcome: {type: object, required: false, description: Delegate finish_reason, truncated and response head and tail, for detect_truncation.} + held: {type: array, required: false, description: Held items, for classify_held and tally.} + needing_count: {type: int, required: false, description: Items that reached drafting, for tally.} + accepted_count: {type: int, required: false, description: Drafts accepted, for tally.} + receipt: {type: object, required: false, description: onex delegate receipt, for metrics_row.} +outputs: + status: {type: str, enum: [decided]} + kind: {type: str} + decisions: {type: array} + selected_comment_ids: {type: array} + prompt: {type: str} + citation: {type: object} + truncation: {type: object} + held_classes: {type: array} + held_by_class: {type: object} + degraded: {type: bool} + metrics: {type: object} +terminal_event: onex.evt.omnimarket.linear-comments-decision-completed.v1 +runtime_dispatch: + command_topic: onex.cmd.omnimarket.linear-comments-decision-requested.v1 +event_bus: + subscribe_topics: + - onex.cmd.omnimarket.linear-comments-decision-requested.v1 + publish_topics: + - onex.evt.omnimarket.linear-comments-decision-completed.v1 +externally_consumed_topics: + - onex.evt.omnimarket.linear-comments-decision-completed.v1 +metadata: + transport_type: inmemory + description: Hourly Linear-comments sweep decisions + author: OmniNode Team + created: "2026-10-08" + related_tickets: [OMN-20680] diff --git a/src/omnimarket/nodes/node_linear_comments_decision_compute/handlers/__init__.py b/src/omnimarket/nodes/node_linear_comments_decision_compute/handlers/__init__.py new file mode 100644 index 0000000000..64f06c497f --- /dev/null +++ b/src/omnimarket/nodes/node_linear_comments_decision_compute/handlers/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT diff --git a/src/omnimarket/nodes/node_linear_comments_decision_compute/handlers/handler_linear_comments_decision.py b/src/omnimarket/nodes/node_linear_comments_decision_compute/handlers/handler_linear_comments_decision.py new file mode 100644 index 0000000000..6b6d8f7571 --- /dev/null +++ b/src/omnimarket/nodes/node_linear_comments_decision_compute/handlers/handler_linear_comments_decision.py @@ -0,0 +1,503 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +"""Definition-B decisions of the hourly Linear-comments sweep (OMN-20680). + +A behaviour-preserving port of the pure block of the retired ``linear-comments-hourly`` +workflow script. tests/fixtures/linear_comments_hourly_parity.json holds the old block's +outputs over the same inputs. Regexes run ASCII-only (``re.ASCII``) because JavaScript's +``\\b`` and ``\\w`` are ASCII-only; the one divergence is ``\\s``, which is ASCII-only here +and Unicode-wide in JavaScript. + +Why the rules look the way they do (kept from the script): + +* A human collaborator whose ``author_role`` *denies* automation ("not automation") is a + human; negated clauses are stripped before markers are searched. +* A first-line ``actor: ()`` attribution marks an agent comment whatever the + account author is. +* Every refusal carries the reason the decision itself produced; nothing re-derives it. +* A draft that cites a comment id outside the source set never reaches the verifier. +* A response cut off by its token limit is never scored complete. +* Every held item lands in one member of the closed ``EnumHeldClass`` set. +""" + +from __future__ import annotations + +import json +import re +from collections.abc import Sequence + +from omnibase_core.types import JsonType + +from omnimarket.nodes.node_linear_comments_decision_compute.models.model_linear_comments_decision import ( + EnumAuthorKind, + EnumHeldClass, + EnumLinearCommentsDecisionKind, + ModelAttemptRow, + ModelCollectedItem, + ModelDelegateOutcome, + ModelDraftCitationCheck, + ModelHeldInput, + ModelItemDecision, + ModelLinearCommentsDecisionRequest, + ModelLinearCommentsDecisionResult, + ModelMetricsRow, + ModelReplyDraft, + ModelTruncationCheck, +) + +_FLAGS = re.IGNORECASE | re.ASCII + +AUTOMATION_WORDS = ( + "(?:bot|bots|app|apps|sweep|sweeps|sweeper|automation|automations|automated|" + "orchestrator|closer|autoclose|github-actions|onexbot|linear-closer|cron|webhook|" + "workflow|robot|integration)" +) +SELF_WORDS = ( + "(?:operator'?s own(?:\\s+\\w+){0,2}\\s+identity|under the operator'?s(?:\\s+own)? identity|" + "as the operator|the operator'?s own account|self[- ]authored|posted by the operator)" +) +HUMAN_WORDS = ( + "(?:human|person|people|collaborator|teammate|colleague|engineer|contributor|" + "co-?founder|candidate|real person)" +) +NEGATION = ( + "(?:not|no|never|non|neither|nor|isn'?t|aren'?t|wasn'?t|rather than|instead of|" + "as opposed to)" +) + +_KNOWN_KINDS = ("human", "automation", "operator_self", "unknown") +_ACTOR_LINE = re.compile(r"actor:\s*[^\n()]+\([^\n()]+\)\s*", _FLAGS) + +_UUID_ID = re.compile( + r"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b", _FLAGS +) +_COMMENT_ID_FIELD = re.compile( + r"\bcomment[_ ]?id\b[\"']?\s*[:=]\s*[\"']?([A-Za-z0-9][A-Za-z0-9_-]*)", _FLAGS +) +_COMMENT_WORD_ID = re.compile( + r"\bcomment(?:\s+id)?\s*[:#]?\s*[\"']?([A-Za-z0-9][A-Za-z0-9_-]{3,})", _FLAGS +) + +TRUNCATING_FINISH_REASONS = ("length", "max_tokens", "max_output_tokens", "token_limit") + +_FLOOR_FAILURE = re.compile( + r"\bproven floor\b|\bexit(?:ed|s)?(?:\s+code)?\s*[:=]?\s*3\b", _FLAGS +) +_TIMEOUT_FAILURE = re.compile( + r"ran about \d+\s*minutes? against a \d+\s*minute shell budget|" + r"\b(?:shell|delegation|execution) budget\b|\btimed?[ -]?out\b|" + r"\bpeer reconcile holds\b|\bbackground time limit\b", + _FLAGS, +) +_TRUNCATED_FAILURE = re.compile(r"cut off mid-sentence|\btruncat", _FLAGS) +_UNSOURCED_FAILURE = re.compile( + r"facts? table (?:does|do) ?n[o']t support|not supported by the facts|" + r"facts? (?:does|do) ?n[o']t (?:support|answer|contain)|\bunsupported\b|" + r"\binvented\b|\bfabricat|not in the facts", + _FLAGS, +) + + +def _js_json(value: JsonType | Sequence[str]) -> str: + """JSON.stringify: no spaces, non-ASCII kept.""" + return json.dumps(value, separators=(",", ":"), ensure_ascii=False) + + +def strip_negated_clauses(text: str, words: str) -> str: + """Remove " ... " within one clause so a denial is not a marker.""" + pattern = re.compile( + "\\b" + NEGATION + "\\b[^,;.()\\[\\]]{0,48}?\\b" + words + "\\b", _FLAGS + ) + return pattern.sub(" ", text) + + +def has_marker(text: str, words: str) -> bool: + return re.search("\\b" + words + "\\b", text, _FLAGS) is not None + + +def classify_author_role(role: str) -> str: + """Free-text author_role -> human | automation | operator_self | unknown.""" + raw = role.strip() + if not raw: + return "unknown" + residual = strip_negated_clauses( + strip_negated_clauses(raw, AUTOMATION_WORDS), SELF_WORDS + ) + if has_marker(residual, AUTOMATION_WORDS): + return "automation" + if re.search(SELF_WORDS, residual, _FLAGS): + return "operator_self" + if has_marker(residual, HUMAN_WORDS): + return "human" + return "unknown" + + +def has_actor_line(text: str) -> bool: + first = text.split("\n", 1)[0].strip() + return _ACTOR_LINE.fullmatch(first) is not None + + +def classify_author(item: ModelCollectedItem) -> str: + """An actor line beats the collector's judgement; operator_self folds into human.""" + if has_actor_line(item.text): + return "automation" + declared = item.author_kind.strip().lower() + kind = ( + declared + if declared in _KNOWN_KINDS and declared != "unknown" + else classify_author_role(item.author_role) + ) + return "human" if kind == "operator_self" else kind + + +def decide_item(item: ModelCollectedItem) -> ModelItemDecision: + """THE decision for one comment; it emits its own reason.""" + kind = EnumAuthorKind(classify_author(item)) + role = item.author_role or "(none recorded)" + + def decision(draft: bool, reason_class: str, reason: str) -> ModelItemDecision: + return ModelItemDecision( + ticket=item.ticket, + comment_id=item.comment_id, + comment_url=item.comment_url, + author_role=role, + author_kind=kind, + draft=draft, + reason_class=reason_class, + reason=reason, + ) + + if not item.text.strip(): + return decision( + False, "empty_text", "the collector recorded no comment text for this item" + ) + if kind is EnumAuthorKind.AUTOMATION: + reason = ( + f"agent-posted: {item.text.split(chr(10), 1)[0].strip()}" + if has_actor_line(item.text) + else f'automation-authored: the collector\'s author_role reads "{role}"' + ) + return decision(False, "automation", reason) + if item.already_answered_by.strip(): + return decision( + False, + "already_answered", + f"already answered in-thread by {item.already_answered_by}", + ) + if not item.needs_reply: + return decision( + False, + "no_reply_needed", + "the collector set needs_reply false for this comment", + ) + if kind is EnumAuthorKind.UNKNOWN: + return decision( + False, + "author_unclassified", + f'HELD for operator adjudication: author_role "{role}" names neither a human ' + "collaborator nor an automation, so this item was not classified either way", + ) + return decision(True, "", "") + + +def source_comment_ids(item: ModelCollectedItem) -> tuple[str, ...]: + cid = item.comment_id.strip() + return (cid,) if cid else () + + +def build_prompt(item: ModelCollectedItem, facts: str) -> str: + """The prompt handed to the delegated drafter: the item and the facts table, nothing else.""" + asks = list(item.asks) + asks_line = ( + f"Asks: {_js_json(asks)}." + if asks + else "Asks: the collector did not enumerate them. Read the comment above and answer " + "what it actually asks. If it asks nothing answerable, say so in " + "open_points_for_operator and leave proposed_reply empty rather than writing a " + "filler acknowledgement." + ) + return "\n".join( + [ + f"Draft the operator's reply comment for Linear {item.ticket} " + f"({item.ticket_title}), replying to comment {item.comment_id} by " + f"{item.author_role} at {item.posted_at}.", + "", + "SOURCE SET -- the only comment ids and comment texts you may cite. Cite no " + "comment id outside this set; every comment_id in your answer is the one below.", + f"comment id: {', '.join(source_comment_ids(item))}", + "comment text:", + "---", + item.text, + "---", + asks_line, + "", + "Ground-truth facts you may cite, and nothing else:", + facts, + "", + "Operator voice: plain, direct, first person, SHORT (3 to 10 sentences), no " + "bullets unless listing options, no em-dashes. Answer every ask with a verified " + "fact and cite the ticket, PR, run id or ledger line it comes from. Never invent " + "a fact, never use a placeholder, and never promise a date the facts do not " + "support. Where only the operator can decide, write a bracketed [OPERATOR: ...] " + "line naming the options.", + "", + "Never write a holding non-answer: no 'I'll look into it', no 'we're tracking " + "this', no 'more soon', no bare acknowledgement of receipt. If the facts above " + "do not answer an ask, that ask goes in open_points_for_operator and does not " + "appear in proposed_reply at all. A reply that answers nothing must come back " + "with proposed_reply empty.", + "", + "Do not echo the asker's own uncertainty phrasing back at them, and never write " + "a disclaimer about what you or they do not know. State what is true. When " + "something the asker wants is genuinely not in the facts above, do not hedge " + "about it in the reply text: leave it out of proposed_reply and put it in " + "open_points_for_operator instead.", + "", + "Your entire output must be one JSON object and nothing else. The first " + "character you emit is { and the last is }. No reasoning, no preamble, no " + "explanation, no code fence, no text after the object.", + "Keys: ticket, comment_id, their_summary, proposed_reply, facts_cited (array of " + "strings), open_points_for_operator (array of strings), confidence (one of " + "high, medium, low).", + "In their_summary, paraphrase what they asked in your own words. Never quote " + "their comment verbatim anywhere in the object.", + ] + ) + + +def _looks_like_an_id(token: str) -> bool: + if not re.search(r"\d", token): + return False + if re.match(r"\d{4}-\d{2}-\d{2}", token): + return False + return re.fullmatch(r"[A-Z]{2,}-\d+", token) is None + + +def extract_comment_ids(text: str) -> list[str]: + """Every comment-id-shaped token, in order of appearance, first spelling kept.""" + found: list[str] = [] + seen: set[str] = set() + + def add(token: str) -> None: + key = token.lower() + if key not in seen: + seen.add(key) + found.append(token) + + for match in _UUID_ID.finditer(text): + add(match.group(0)) + for match in _COMMENT_ID_FIELD.finditer(text): + add(match.group(1)) + for match in _COMMENT_WORD_ID.finditer(text): + if _looks_like_an_id(match.group(1)): + add(match.group(1)) + return found + + +def check_draft_cites_only_source_ids( + draft: ModelReplyDraft, item: ModelCollectedItem, facts: str +) -> ModelDraftCitationCheck: + """Refuse a draft citing an id outside the source set and facts, or answering another comment.""" + ids = source_comment_ids(item) + allowed = {x.lower() for x in ids} + haystack = (facts + "\n" + item.text).lower() + strings = [draft.their_summary, draft.proposed_reply, *draft.facts_cited] + strings += list(draft.open_points_for_operator) + foreign = [ + found + for found in extract_comment_ids("\n".join(strings)) + if found.lower() not in allowed and found.lower() not in haystack + ] + wanted = ids[0] if ids else "" + mismatch = ( + draft.comment_id if draft.comment_id is not None else "" + ).strip() != wanted + parts: list[str] = [] + if foreign: + parts.append( + f"the draft cites comment id(s) {', '.join(foreign)} that are in neither the " + "source set nor the facts table" + ) + if mismatch: + parts.append( + f"the draft's comment_id {_js_json(draft.comment_id)} is not the item's " + f"{_js_json(wanted)}" + ) + return ModelDraftCitationCheck( + ok=not parts, + reason="; ".join(parts), + foreign_ids=tuple(foreign), + comment_id_mismatch=mismatch, + ) + + +def _strip_fence_and_space(text: str) -> str: + text = re.sub(r"\s+\Z", "", text) + text = re.sub(r"```\s*\Z", "", text) + return re.sub(r"\s+\Z", "", text) + + +def detect_truncation(outcome: ModelDelegateOutcome) -> ModelTruncationCheck: + """From the receipt (truncated, finish_reason) plus an unclosed-JSON structural check.""" + signals: list[str] = [] + if outcome.truncated is True: + signals.append("receipt_truncated") + reason = (outcome.finish_reason or "").strip().lower() + if reason in TRUNCATING_FINISH_REASONS: + signals.append(f"finish_reason:{reason}") + head = re.sub( + r"\A\s*(?:```[a-z]*\s*)?", "", outcome.response_head or "", flags=_FLAGS + ) + tail = _strip_fence_and_space(outcome.response_tail or "") + if head[:1] == "{" and tail and tail[-1] != "}": + signals.append("unclosed_json") + return ModelTruncationCheck(truncated=bool(signals), signals=tuple(signals)) + + +def classify_held(held: ModelHeldInput) -> EnumHeldClass: + """Total: whatever arrives, one member of the closed set leaves.""" + if _FLOOR_FAILURE.search(held.reason): + return EnumHeldClass.FLOOR_REFUSED + if _TIMEOUT_FAILURE.search(held.reason): + return EnumHeldClass.TIMEOUT + if held.truncated or _TRUNCATED_FAILURE.search(held.reason): + return EnumHeldClass.TRUNCATED + if held.stage == "source_check" or _UNSOURCED_FAILURE.search(held.reason): + return EnumHeldClass.UNSOURCED + if held.stage == "verify" and held.reason.strip(): + return EnumHeldClass.UNSOURCED + return EnumHeldClass.TRANSPORT + + +def held_by_class(held: Sequence[ModelHeldInput]) -> dict[str, int]: + counts = {member.value: 0 for member in EnumHeldClass} + for entry in held: + declared = entry.reason_class + cls = declared if declared in counts else classify_held(entry).value + counts[cls] += 1 + return counts + + +def is_degraded(needing_count: int, accepted_count: int) -> bool: + """A window that had something to answer and accepted nothing.""" + return needing_count > 0 and accepted_count == 0 + + +def _or_none(*values: JsonType) -> JsonType: + """JavaScript ``a || b || null``: the first truthy value, else null.""" + for value in values: + if value: + return value + return None + + +def _dict(value: JsonType) -> dict[str, JsonType]: + return value if isinstance(value, dict) else {} + + +def metrics_row(receipt: dict[str, JsonType]) -> ModelMetricsRow: + """Which model answered. ``model_cloud_baseline`` is the premium counterfactual, never the answerer.""" + inner = _dict(receipt.get("receipt")) + result = _dict(inner.get("result")) + metrics = _dict(result.get("metrics")) + attempts = result.get("attempts") + tried = tuple( + ModelAttemptRow( + tier=_dict(a).get("tier"), + backend_id=_dict(a).get("backend_id"), + model_id=_dict(a).get("model_id"), + quality_score=_dict(a).get("quality_score"), + decision=_dict(a).get("acceptance_decision"), + cost_usd=_dict(a).get("cost_usd"), + ) + for a in (attempts if isinstance(attempts, list) else []) + ) + last = tried[-1] if tried else ModelAttemptRow() + attempts_count = result.get("attempts_count") + return ModelMetricsRow( + model=_or_none(result.get("model_name"), last.model_id), + backend_id=_or_none(receipt.get("backend_id"), last.backend_id), + tier=last.tier or None, + total_tokens=metrics.get("total_tokens"), + input_tokens=metrics.get("input_tokens"), + output_tokens=metrics.get("output_tokens"), + latency_ms=metrics.get("latency_ms"), + wall_ms=inner.get("duration_ms"), + cost_usd=metrics.get("cost_usd"), + cost_savings_usd=metrics.get("cost_savings_usd"), + premium_counterfactual_model=result.get("model_cloud_baseline") or None, + attempts=len(tried) if attempts_count is None else attempts_count, + escalations=result.get("escalation_count"), + accepted=last.decision == "accept", + tried=tried, + ) + + +class HandlerLinearCommentsDecision: + """Stateless compute: one request kind in, one typed answer out.""" + + def handle( + self, request: ModelLinearCommentsDecisionRequest + ) -> ModelLinearCommentsDecisionResult: + kind = request.kind + if ( + kind is EnumLinearCommentsDecisionKind.DECIDE_ITEMS + and request.items is not None + ): + decisions = tuple(decide_item(item) for item in request.items) + return ModelLinearCommentsDecisionResult( + kind=kind, + decisions=decisions, + selected_comment_ids=tuple(d.comment_id for d in decisions if d.draft), + ) + if ( + kind is EnumLinearCommentsDecisionKind.BUILD_PROMPT + and request.item is not None + ): + return ModelLinearCommentsDecisionResult( + kind=kind, prompt=build_prompt(request.item, request.facts or "") + ) + if ( + kind is EnumLinearCommentsDecisionKind.CHECK_DRAFT + and request.draft is not None + and request.item is not None + ): + return ModelLinearCommentsDecisionResult( + kind=kind, + citation=check_draft_cites_only_source_ids( + request.draft, request.item, request.facts or "" + ), + ) + if ( + kind is EnumLinearCommentsDecisionKind.DETECT_TRUNCATION + and request.outcome is not None + ): + return ModelLinearCommentsDecisionResult( + kind=kind, truncation=detect_truncation(request.outcome) + ) + if ( + kind is EnumLinearCommentsDecisionKind.CLASSIFY_HELD + and request.held is not None + ): + return ModelLinearCommentsDecisionResult( + kind=kind, held_classes=tuple(classify_held(h) for h in request.held) + ) + if ( + kind is EnumLinearCommentsDecisionKind.TALLY + and request.held is not None + and request.needing_count is not None + and request.accepted_count is not None + ): + return ModelLinearCommentsDecisionResult( + kind=kind, + held_by_class=held_by_class(request.held), + degraded=is_degraded(request.needing_count, request.accepted_count), + ) + if ( + kind is EnumLinearCommentsDecisionKind.METRICS_ROW + and request.receipt is not None + ): + return ModelLinearCommentsDecisionResult( + kind=kind, metrics=metrics_row(request.receipt) + ) + raise ValueError(f"{kind.value}: request is missing its inputs") diff --git a/src/omnimarket/nodes/node_linear_comments_decision_compute/metadata.yaml b/src/omnimarket/nodes/node_linear_comments_decision_compute/metadata.yaml new file mode 100644 index 0000000000..2fe77df771 --- /dev/null +++ b/src/omnimarket/nodes/node_linear_comments_decision_compute/metadata.yaml @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +name: node_linear_comments_decision_compute +version: "1.0.0" +description: Deterministic decisions of the hourly Linear-comments sweep +omnibase_core_compat: ">=0.47.28,<0.48.0" +entry_points: + onex.nodes: + node_linear_comments_decision_compute: omnimarket.nodes.node_linear_comments_decision_compute +capabilities: + standalone: true + full_runtime: true + requires_network: false + requires_repo: false + requires_secrets: false + side_effect_class: pure +dependencies: + - pydantic>=2.11.0 +authors: [OmniNode Team] +license: MIT +tags: [compute, linear, comments, sweep, hourly] +node_role: compute +pack: "routing" +display_name: "Linear Comments Decision Compute" diff --git a/src/omnimarket/nodes/node_linear_comments_decision_compute/models/__init__.py b/src/omnimarket/nodes/node_linear_comments_decision_compute/models/__init__.py new file mode 100644 index 0000000000..64f06c497f --- /dev/null +++ b/src/omnimarket/nodes/node_linear_comments_decision_compute/models/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT diff --git a/src/omnimarket/nodes/node_linear_comments_decision_compute/models/model_linear_comments_decision.py b/src/omnimarket/nodes/node_linear_comments_decision_compute/models/model_linear_comments_decision.py new file mode 100644 index 0000000000..8101b17455 --- /dev/null +++ b/src/omnimarket/nodes/node_linear_comments_decision_compute/models/model_linear_comments_decision.py @@ -0,0 +1,232 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +"""Typed request and result for the hourly Linear-comments sweep decisions (OMN-20680).""" + +from __future__ import annotations + +from enum import StrEnum +from typing import Literal, Self + +from omnibase_core.types import JsonType +from pydantic import BaseModel, ConfigDict, Field, model_validator + + +class EnumLinearCommentsDecisionKind(StrEnum): + """Which decision the caller asks for; one request carries one decision.""" + + DECIDE_ITEMS = "decide_items" + BUILD_PROMPT = "build_prompt" + CHECK_DRAFT = "check_draft" + DETECT_TRUNCATION = "detect_truncation" + CLASSIFY_HELD = "classify_held" + TALLY = "tally" + METRICS_ROW = "metrics_row" + + +class EnumAuthorKind(StrEnum): + HUMAN = "human" + AUTOMATION = "automation" + OPERATOR_SELF = "operator_self" + UNKNOWN = "unknown" + + +class EnumHeldClass(StrEnum): + """Closed set of reasons a human ask reached drafting and came back empty.""" + + FLOOR_REFUSED = "floor_refused" + TRUNCATED = "truncated" + UNSOURCED = "unsourced" + TRANSPORT = "transport" + TIMEOUT = "timeout" + + +class ModelCollectedItem(BaseModel): + """One Linear comment as the collector returned it.""" + + model_config = ConfigDict(frozen=True, extra="allow") + + ticket: str + ticket_title: str + comment_id: str + comment_url: str = "" + author_role: str + author_kind: str = "" + posted_at: str + text: str + needs_reply: bool + asks: tuple[str, ...] = () + already_answered_by: str = "" + + +class ModelReplyDraft(BaseModel): + """A drafted reply. comment_id may be absent: the schema requires it, so absence is a mismatch.""" + + model_config = ConfigDict(frozen=True, extra="allow") + + ticket: str = "" + comment_id: str | None = None + their_summary: str = "" + proposed_reply: str = "" + facts_cited: tuple[str, ...] = () + open_points_for_operator: tuple[str, ...] = () + confidence: str = "" + + +class ModelDelegateOutcome(BaseModel): + """The parts of a delegated call's result that decide whether it was cut off.""" + + model_config = ConfigDict(frozen=True) + + finish_reason: str | None = None + truncated: bool | None = None + response_head: str | None = None + response_tail: str | None = None + + +class ModelHeldInput(BaseModel): + """Where an item was lost and why; reason_class, when given, is trusted.""" + + model_config = ConfigDict(frozen=True) + + stage: str = "" + reason: str = "" + truncated: bool = False + reason_class: str | None = None + + +class ModelLinearCommentsDecisionRequest(BaseModel): + """One decision request; the fields a kind needs are validated, the rest must be absent.""" + + model_config = ConfigDict(frozen=True, extra="forbid") + + kind: EnumLinearCommentsDecisionKind + items: tuple[ModelCollectedItem, ...] | None = None + item: ModelCollectedItem | None = None + facts: str | None = None + draft: ModelReplyDraft | None = None + outcome: ModelDelegateOutcome | None = None + held: tuple[ModelHeldInput, ...] | None = None + needing_count: int | None = Field(default=None, ge=0) + accepted_count: int | None = Field(default=None, ge=0) + receipt: dict[str, JsonType] | None = None + + @model_validator(mode="after") + def _kind_needs_its_fields(self) -> Self: + needs: dict[EnumLinearCommentsDecisionKind, tuple[str, ...]] = { + EnumLinearCommentsDecisionKind.DECIDE_ITEMS: ("items",), + EnumLinearCommentsDecisionKind.BUILD_PROMPT: ("item", "facts"), + EnumLinearCommentsDecisionKind.CHECK_DRAFT: ("draft", "item", "facts"), + EnumLinearCommentsDecisionKind.DETECT_TRUNCATION: ("outcome",), + EnumLinearCommentsDecisionKind.CLASSIFY_HELD: ("held",), + EnumLinearCommentsDecisionKind.TALLY: ( + "held", + "needing_count", + "accepted_count", + ), + EnumLinearCommentsDecisionKind.METRICS_ROW: ("receipt",), + } + wanted = needs[self.kind] + supplied = { + name + for name in ( + "items", + "item", + "facts", + "draft", + "outcome", + "held", + "needing_count", + "accepted_count", + "receipt", + ) + if getattr(self, name) is not None + } + missing = [name for name in wanted if name not in supplied] + if missing: + raise ValueError(f"{self.kind.value} requires {', '.join(missing)}") + extra = sorted(supplied - set(wanted)) + if extra: + raise ValueError(f"{self.kind.value} does not take {', '.join(extra)}") + return self + + +class ModelItemDecision(BaseModel): + """THE decision for one comment: draft or not, with the reason the decision produced.""" + + model_config = ConfigDict(frozen=True) + + ticket: str + comment_id: str + comment_url: str + author_role: str + author_kind: EnumAuthorKind + draft: bool + reason_class: str + reason: str + + +class ModelDraftCitationCheck(BaseModel): + model_config = ConfigDict(frozen=True) + + ok: bool + reason: str + foreign_ids: tuple[str, ...] + comment_id_mismatch: bool + + +class ModelTruncationCheck(BaseModel): + model_config = ConfigDict(frozen=True) + + truncated: bool + signals: tuple[str, ...] + + +class ModelAttemptRow(BaseModel): + model_config = ConfigDict(frozen=True) + + tier: JsonType = None + backend_id: JsonType = None + model_id: JsonType = None + quality_score: JsonType = None + decision: JsonType = None + cost_usd: JsonType = None + + +class ModelMetricsRow(BaseModel): + """Which model answered, tokens, wall time and whether the draft was accepted.""" + + model_config = ConfigDict(frozen=True) + + model: JsonType = None + backend_id: JsonType = None + tier: JsonType = None + total_tokens: JsonType = None + input_tokens: JsonType = None + output_tokens: JsonType = None + latency_ms: JsonType = None + wall_ms: JsonType = None + cost_usd: JsonType = None + cost_savings_usd: JsonType = None + premium_counterfactual_model: JsonType = None + attempts: JsonType = None + escalations: JsonType = None + accepted: bool + tried: tuple[ModelAttemptRow, ...] + + +class ModelLinearCommentsDecisionResult(BaseModel): + """The answer to one request; exactly the fields of the requested kind are set.""" + + model_config = ConfigDict(frozen=True) + + kind: EnumLinearCommentsDecisionKind + decisions: tuple[ModelItemDecision, ...] | None = None + selected_comment_ids: tuple[str, ...] | None = None + prompt: str | None = None + citation: ModelDraftCitationCheck | None = None + truncation: ModelTruncationCheck | None = None + held_classes: tuple[EnumHeldClass, ...] | None = None + held_by_class: dict[str, int] | None = None + degraded: bool | None = None + metrics: ModelMetricsRow | None = None + status: Literal["decided"] = "decided" diff --git a/tests/fixtures/hourly_tick_parity.json b/tests/fixtures/hourly_tick_parity.json new file mode 100644 index 0000000000..66ae77d29e --- /dev/null +++ b/tests/fixtures/hourly_tick_parity.json @@ -0,0 +1,2568 @@ +{ + "source": "pre-conversion hourly tick", + "source_commit": "fb45651168a1bb5390f5120ceee0fe61b36111d3", + "cases": [ + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": null, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing n/a, accepted n/a, held n/a, phase=completed; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "completed", + "class": null + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "completed", + "class": null, + "needing": null, + "accepted": null, + "held": null, + "held_by_class": null + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": null, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing n/a, accepted n/a, held n/a, phase=completed; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "completed", + "class": null + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "completed", + "class": null, + "needing": null, + "accepted": null, + "held": null, + "held_by_class": null + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": {}, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing n/a, accepted n/a, held n/a, phase=completed; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "completed", + "class": null + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "completed", + "class": null, + "needing": null, + "accepted": null, + "held": null, + "held_by_class": null + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": {}, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing n/a, accepted n/a, held n/a, phase=completed; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "completed", + "class": null + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "completed", + "class": null, + "needing": null, + "accepted": null, + "held": null, + "held_by_class": null + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "collected": 5, + "needing": 3, + "accepted": 0, + "held": 3, + "held_by_class": { + "floor_refused": 2, + "transport": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected 5, needing 3, accepted 0, held 3, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 3, + "accepted": 0, + "held": 3, + "held_by_class": { + "floor_refused": 2, + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "collected": 5, + "needing": 3, + "accepted": 0, + "held": 3, + "held_by_class": { + "floor_refused": 2, + "transport": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected 5, needing 3, accepted 0, held 3, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 3, + "accepted": 0, + "held": 3, + "held_by_class": { + "floor_refused": 2, + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "collected": 5, + "needing": 3, + "accepted": 1, + "held": 2, + "linear_file": "draft.md" + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected 5, needing 3, accepted 1, held 2, phase=completed; drafts draft.md; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "completed", + "class": null + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "completed", + "class": null, + "needing": 3, + "accepted": 1, + "held": 2, + "held_by_class": null + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "collected": 5, + "needing": 3, + "accepted": 1, + "held": 2, + "linear_file": "draft.md" + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected 5, needing 3, accepted 1, held 2, phase=completed; drafts draft.md; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "completed", + "class": null + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "completed", + "class": null, + "needing": 3, + "accepted": 1, + "held": 2, + "held_by_class": null + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 1, + "accepted": 0 + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 1, accepted 0, held n/a, phase=degraded class=unclassified; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unclassified" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unclassified", + "needing": 1, + "accepted": 0, + "held": null, + "held_by_class": null + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 1, + "accepted": 0 + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 1, accepted 0, held n/a, phase=degraded class=unclassified; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unclassified" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unclassified", + "needing": 1, + "accepted": 0, + "held": null, + "held_by_class": null + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "floor_refused": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "floor_refused": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "floor_refused": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "floor_refused": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "floor_refused": 1, + "truncated": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "floor_refused": 1, + "truncated": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "floor_refused": 1, + "truncated": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "floor_refused": 1, + "truncated": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "floor_refused": 1, + "unsourced": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "floor_refused": 1, + "unsourced": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "floor_refused": 1, + "unsourced": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "floor_refused": 1, + "unsourced": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "floor_refused": 1, + "transport": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "floor_refused": 1, + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "floor_refused": 1, + "transport": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "floor_refused": 1, + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "floor_refused": 1, + "timeout": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "floor_refused": 1, + "timeout": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "floor_refused": 1, + "timeout": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "floor_refused": 1, + "timeout": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "truncated": 1, + "floor_refused": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "truncated": 1, + "floor_refused": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "truncated": 1, + "floor_refused": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "truncated": 1, + "floor_refused": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "truncated": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "truncated": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "truncated": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "truncated": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "truncated": 1, + "unsourced": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "truncated": 1, + "unsourced": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "truncated": 1, + "unsourced": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "truncated": 1, + "unsourced": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "truncated": 1, + "transport": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "truncated": 1, + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "truncated": 1, + "transport": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "truncated": 1, + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "truncated": 1, + "timeout": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "truncated": 1, + "timeout": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "truncated": 1, + "timeout": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "truncated": 1, + "timeout": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "unsourced": 1, + "floor_refused": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "unsourced": 1, + "floor_refused": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "unsourced": 1, + "floor_refused": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "unsourced": 1, + "floor_refused": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "unsourced": 1, + "truncated": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "unsourced": 1, + "truncated": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "unsourced": 1, + "truncated": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "unsourced": 1, + "truncated": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "unsourced": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=unsourced; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unsourced" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unsourced", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "unsourced": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "unsourced": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=unsourced; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unsourced" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unsourced", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "unsourced": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "unsourced": 1, + "transport": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=unsourced; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unsourced" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unsourced", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "unsourced": 1, + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "unsourced": 1, + "transport": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=unsourced; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unsourced" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unsourced", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "unsourced": 1, + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "unsourced": 1, + "timeout": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=unsourced; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unsourced" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unsourced", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "unsourced": 1, + "timeout": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "unsourced": 1, + "timeout": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=unsourced; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unsourced" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unsourced", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "unsourced": 1, + "timeout": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "transport": 1, + "floor_refused": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "transport": 1, + "floor_refused": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "transport": 1, + "floor_refused": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "transport": 1, + "floor_refused": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "transport": 1, + "truncated": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "transport": 1, + "truncated": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "transport": 1, + "truncated": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "transport": 1, + "truncated": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "transport": 1, + "unsourced": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=unsourced; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unsourced" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unsourced", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "transport": 1, + "unsourced": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "transport": 1, + "unsourced": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=unsourced; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unsourced" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unsourced", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "transport": 1, + "unsourced": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "transport": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=transport; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "transport" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "transport", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "transport": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=transport; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "transport" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "transport", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "transport": 1, + "timeout": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=transport; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "transport" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "transport", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "transport": 1, + "timeout": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "transport": 1, + "timeout": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=transport; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "transport" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "transport", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "transport": 1, + "timeout": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "timeout": 1, + "floor_refused": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "timeout": 1, + "floor_refused": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "timeout": 1, + "floor_refused": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=floor_refused; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "floor_refused" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "floor_refused", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "timeout": 1, + "floor_refused": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "timeout": 1, + "truncated": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "timeout": 1, + "truncated": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "timeout": 1, + "truncated": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=truncated; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "truncated" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "truncated", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "timeout": 1, + "truncated": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "timeout": 1, + "unsourced": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=unsourced; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unsourced" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unsourced", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "timeout": 1, + "unsourced": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "timeout": 1, + "unsourced": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=unsourced; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "unsourced" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "unsourced", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "timeout": 1, + "unsourced": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "timeout": 1, + "transport": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=transport; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "transport" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "transport", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "timeout": 1, + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "timeout": 1, + "transport": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=transport; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "transport" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "transport", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "timeout": 1, + "transport": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "timeout": 1 + } + }, + "run_id": "", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=unrecorded: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=timeout; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "timeout" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "timeout", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "timeout": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + }, + { + "state": { + "now": "2026-09-16T21:40:57Z", + "prev": "2026-09-16T21:27:48Z", + "sweepSince": "2026-09-16T21:27:48Z", + "rows": 6, + "terminal": 2, + "lanes": "lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800", + "ledgerTotal": 754, + "tail": [ + "- first fact", + "second fact" + ] + }, + "sweep": { + "needing": 2, + "accepted": 0, + "held_by_class": { + "timeout": 1 + } + }, + "run_id": "fire-123", + "checkpoint": "- 2026-09-16T21:40:57Z 60m checkpoint: ledger rows since 2026-09-16T21:27:48Z: 6; TERMINAL 2 [lane=omn18484-admission-labelled-ac-2005,lane=sweep-corpus-git-files-1800]; sweep since=2026-09-16T21:27:48Z", + "completion": "- 2026-09-16T21:40:57Z hourly-tick run=fire-123: checkpoint rows 6 / TERMINAL 2; sweep since=2026-09-16T21:27:48Z collected n/a, needing 2, accepted 0, held n/a, phase=degraded class=timeout; drafts none; NOT POSTED (per-message operator approval required)", + "outcome": { + "phase": "degraded", + "class": "timeout" + }, + "result": { + "fire_id": "fire-123", + "workflow": "hourly-tick", + "phase": "degraded", + "class": "timeout", + "needing": 2, + "accepted": 0, + "held": null, + "held_by_class": { + "timeout": 1 + } + }, + "facts": "Ground-truth facts, derived from the most recent orchestration checkpoint lines, oldest first.\nEach line is a timestamped statement of what a lane reported or what the operator ruled.\nCite only what appears below. Anything not here is not a fact you may state.\n\n- first fact\n- second fact" + } + ] +} diff --git a/tests/fixtures/linear_comments_hourly_parity.json b/tests/fixtures/linear_comments_hourly_parity.json new file mode 100644 index 0000000000..dbbe143048 --- /dev/null +++ b/tests/fixtures/linear_comments_hourly_parity.json @@ -0,0 +1,2681 @@ +{ + "_source": "Captured by running the pure block of omnibase_internal src/omnibase_internal/handlers/hourly_tick/linear-comments-hourly.js at commit 38c45975d93d870cb3f4f1a5eee2860357bd211f under node v24, over the inputs below (OMN-20680). The port in node_linear_comments_decision_compute must reproduce every output.", + "cases": { + "roles": [ + { + "role": "" + }, + { + "role": "autoclose-sweep bot" + }, + { + "role": "linear-closer app" + }, + { + "role": "the CTO candidate" + }, + { + "role": "someone from the design side" + }, + { + "role": "a collaborator" + }, + { + "role": "orchestrator/autoclose-sweep (posted as the operator's own Linear identity)" + }, + { + "role": "human collaborator (a collaborator) — not automation; posted under his own Linear identity, not the operator's" + }, + { + "role": "human collaborator (a collaborator) — explicitly @-mentions the operator; not automation" + }, + { + "role": "human collaborator (another collaborator); comment itself contains no automation markers" + }, + { + "role": "human collaborator, posting under their own identity" + }, + { + "role": "orchestrator/autoclose-sweep bot (posted as the operator's own Linear identity)" + }, + { + "role": "GitHub Actions workflow" + }, + { + "role": "github-actions" + }, + { + "role": "onexbot" + }, + { + "role": "cron job" + }, + { + "role": "webhook integration" + }, + { + "role": "teammate, not a bot" + }, + { + "role": "an engineer rather than an automated sweep" + }, + { + "role": "neither a bot nor an app, a real person" + }, + { + "role": "the operator's own account" + }, + { + "role": "posted by the operator" + }, + { + "role": "self-authored" + }, + { + "role": "as the operator" + }, + { + "role": "under the operator's own identity" + }, + { + "role": "contributor; no automation involved" + }, + { + "role": "robot" + }, + { + "role": "Workflow" + }, + { + "role": "colleague who is never automated" + }, + { + "role": "human, but also an app" + }, + { + "role": "not an automation, a person (a bot would not ask this)" + }, + { + "role": "co-founder" + }, + { + "role": "candidate" + }, + { + "role": "Unknown thing" + }, + { + "role": "nobody in particular" + }, + { + "role": "bots" + }, + { + "role": "Apps" + }, + { + "role": "INTEGRATION" + }, + { + "role": "real person; non-bot" + } + ], + "items": [ + { + "ticket": "OMN-18268", + "ticket_title": "AC-binding gap on the autoclose sweep", + "comment_id": "bf9a438b-ca16-4e7a-8c92-5f760f0f2df9", + "comment_url": "https://linear.app/omninode/issue/OMN-18268#comment-bf9a438b", + "author_role": "orchestrator/autoclose-sweep (posted as the operator's own Linear identity)", + "posted_at": "2026-09-14T11:36:52Z", + "text": "AC-binding gap: dod_verify 4/6 verified, 0 failed, but AC1-AC3 have no binds_ac entry in contracts/OMN-18268.yaml; not auto-closable until labeled.", + "needs_reply": true, + "asks": [ + "label the AC bindings" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-18086", + "ticket_title": "Evidence gap on the autoclose sweep", + "comment_id": "8354b627-f18a-4359-b1e3-3a9a873c7fcc", + "comment_url": "https://linear.app/omninode/issue/OMN-18086#comment-8354b627", + "author_role": "autoclose-sweep bot", + "posted_at": "2026-09-14T10:47:18Z", + "text": "Evidence gap: dod_verify 7/15 ACs verified, 2 failed - not all ACs receipt-proven, not flipped.", + "needs_reply": true, + "asks": [ + "prove the remaining ACs" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-18160", + "ticket_title": "MSK broker right-sizing", + "comment_id": "568e6a23-973d-4033-aaa6-be60c969de3f", + "comment_url": "https://linear.app/omninode/issue/OMN-18160#comment-568e6a23", + "author_role": "the CTO candidate", + "posted_at": "2026-09-11T09:23:45Z", + "text": "Broker right-sizing readback answering the three questions.", + "needs_reply": true, + "asks": [ + "confirm the disposition" + ], + "already_answered_by": "operator comment 22f885c7, 2026-09-11T12:54:59Z" + }, + { + "ticket": "OMN-16964", + "ticket_title": "Chain canary AC1 probe", + "comment_id": "aa11bb22-cccc-4444-8888-999900001111", + "comment_url": "https://linear.app/omninode/issue/OMN-16964#comment-aa11bb22", + "author_role": "a collaborator", + "posted_at": "2026-09-13T15:52:47Z", + "text": "What is the governed probe and the expected result for AC1?", + "needs_reply": true, + "asks": [ + "name the probe", + "name the expected result" + ], + "already_answered_by": "operator comment fd2a9c30, 2026-09-13T16:59:38Z" + }, + { + "ticket": "OMN-18052", + "ticket_title": "Runtime rebuild trigger", + "comment_id": "dd44ee55-6666-4777-8888-99990000aaaa", + "comment_url": "https://linear.app/omninode/issue/OMN-18052#comment-dd44ee55", + "author_role": "linear-closer app", + "posted_at": "2026-09-14T11:02:00Z", + "text": "Automated closure precheck ran; no action required.", + "needs_reply": false, + "asks": [], + "already_answered_by": "" + }, + { + "ticket": "OMN-18164", + "ticket_title": "C5 inbox - verify subdomain delegation", + "comment_id": "collab_unanswered_ask", + "comment_url": "https://linear.app/omninode/issue/OMN-18164#comment-collab", + "author_role": "a collaborator", + "posted_at": "2026-09-14T11:10:00Z", + "text": "Is the inbox path unblocked yet? I still cannot send to the verify subdomain and I do not know whether we are waiting on us or on the registrar.", + "needs_reply": true, + "asks": [ + "is the inbox unblocked", + "who are we waiting on" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "e1", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "e2", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": " \n", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "a1", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "actor: sweep (sonnet)\nCan this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "a2", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Actor: lane-1 (opus)\nbody", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "a3", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "actor: x\nno parens", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "a4", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "text first\nactor: x (y)", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "a5", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "actor: x (y) trailing", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "k1", + "comment_url": "u", + "author_role": "autoclose bot", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "", + "author_kind": "human" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "k2", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "", + "author_kind": "automation" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "k3", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "", + "author_kind": "operator_self" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "k4", + "comment_url": "u", + "author_role": "human collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "", + "author_kind": "unknown" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "k5", + "comment_url": "u", + "author_role": "", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "", + "author_kind": "HUMAN" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "k6", + "comment_url": "u", + "author_role": "a bot", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "", + "author_kind": "bogus" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "n1", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": false, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "n2", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "comment 22f885c7" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "n3", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": " " + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "n4", + "comment_url": "u", + "author_role": "", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "n5", + "comment_url": "u", + "author_role": "mystery", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "n6", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [], + "already_answered_by": "" + }, + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "n7", + "comment_url": "u", + "author_role": "mystery", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": false, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-17276", + "ticket_title": "T", + "comment_id": "selftest_human_with_ask", + "comment_url": "u", + "author_role": "human collaborator (a collaborator) — not automation; posted under his own Linear identity, not the operator's", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "" + }, + { + "ticket": "OMN-16106", + "ticket_title": "T", + "comment_id": "selftest_automation", + "comment_url": "u", + "author_role": "orchestrator/autoclose-sweep bot (posted as the operator's own Linear identity)", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Autoclose sweep: this ticket has no dod_evidence.", + "needs_reply": true, + "asks": [ + "land?" + ], + "already_answered_by": "" + } + ], + "drafts": [ + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "s", + "proposed_reply": "Verified per the facts.", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "s", + "proposed_reply": "See comment 0a1b2c3d-0000-4000-8000-00000000abcd.", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "s", + "proposed_reply": "See comment 0a1b2c3d-0000-4000-8000-00000000abcd.", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts 0a1b2c3d-0000-4000-8000-00000000abcd" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "other", + "their_summary": "s", + "proposed_reply": "Verified per the facts.", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts" + ], + [ + { + "ticket": "OMN-1", + "comment_id": null, + "their_summary": "s", + "proposed_reply": "Verified per the facts.", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts" + ], + [ + { + "ticket": "OMN-1", + "comment_id": " selftest_id_1 ", + "their_summary": "s", + "proposed_reply": "Verified per the facts.", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "s", + "proposed_reply": "comment_id: zzz999 is wrong", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "s", + "proposed_reply": "comment_id: aabbccdd is in the source text", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "s", + "proposed_reply": "comment above and comment thread", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "s", + "proposed_reply": "comment 2026-09-17 date, ticket comment OMN-123", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "\"comment_id\": \"qq11ww22\"", + "proposed_reply": "Verified per the facts.", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "s", + "proposed_reply": "Verified per the facts.", + "facts_cited": [ + "comment id c91f2a77" + ], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "s", + "proposed_reply": "Verified per the facts.", + "facts_cited": [], + "open_points_for_operator": [ + "comment: 4417aa90" + ], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "facts c91f2a77" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "s", + "proposed_reply": "comment 4417aa90 and comment 4417AA90", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "text": "ask about run 77ab12cd and comment aabbccdd" + }, + "x" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "selftest_id_1", + "their_summary": "s", + "proposed_reply": "Verified per the facts.", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "T", + "comment_id": "", + "text": "" + }, + "" + ], + [ + { + "ticket": "OMN-1", + "comment_id": "", + "their_summary": "s", + "proposed_reply": "Verified per the facts.", + "facts_cited": [], + "open_points_for_operator": [], + "confidence": "high" + }, + { + "ticket": "T", + "comment_id": "", + "text": "" + }, + "" + ] + ], + "trunc": [ + {}, + { + "truncated": true + }, + { + "finish_reason": "length" + }, + { + "finish_reason": "LENGTH " + }, + { + "finish_reason": "max_tokens" + }, + { + "finish_reason": "stop", + "response_head": "{\"a\"", + "response_tail": "}" + }, + { + "finish_reason": "stop", + "response_head": "{\"a\"", + "response_tail": "\"x" + }, + { + "response_head": "```json\n{\"a\"", + "response_tail": "\"x\"\n```" + }, + { + "response_head": "```json\n{\"a\"", + "response_tail": "\"x" + }, + { + "response_head": "no json here", + "response_tail": "abc" + }, + { + "response_head": "{", + "response_tail": "" + }, + { + "truncated": true, + "finish_reason": "length", + "response_head": "{", + "response_tail": "x" + }, + { + "finish_reason": "token_limit" + }, + { + "finish_reason": "max_output_tokens" + }, + { + "finish_reason": "stop" + } + ], + "held": [ + { + "stage": "delegate", + "reason": "onex delegate exited 3 ... below the proven floor" + }, + { + "stage": "delegate", + "reason": "exit code: 3" + }, + { + "stage": "delegate", + "reason": "exited 3" + }, + { + "stage": "delegate", + "reason": "ran about 12 minutes against a 5 minute shell budget" + }, + { + "stage": "delegate", + "reason": "execution budget exceeded" + }, + { + "stage": "delegate", + "reason": "timed out" + }, + { + "stage": "delegate", + "reason": "peer reconcile holds the lock" + }, + { + "stage": "delegate", + "reason": "background time limit" + }, + { + "stage": "delegate", + "reason": "cut off mid-sentence" + }, + { + "stage": "delegate", + "reason": "truncated response" + }, + { + "stage": "delegate", + "reason": "x", + "truncated": true + }, + { + "stage": "source_check", + "reason": "anything" + }, + { + "stage": "verify", + "reason": "the facts table does not support this" + }, + { + "stage": "verify", + "reason": "not in the facts" + }, + { + "stage": "verify", + "reason": " " + }, + { + "stage": "verify", + "reason": "nope" + }, + { + "stage": "verify", + "reason": "" + }, + { + "stage": "delegate", + "reason": "invented a PR" + }, + { + "stage": "delegate", + "reason": "fabricated id" + }, + { + "stage": "pipeline", + "reason": "the drafting stage returned no verdict at all" + }, + { + "stage": "delegate", + "reason": "unsupported claim" + }, + { + "stage": "delegate", + "reason": "" + }, + { + "stage": "", + "reason": "proven floor and timed out" + }, + { + "stage": "delegate", + "reason": "timed out", + "truncated": true + }, + { + "stage": "delegate", + "reason": "exited 30" + }, + { + "stage": "delegate", + "reason": "connection reset" + } + ], + "heldlists": [ + [], + [ + { + "stage": "delegate", + "reason": "onex delegate exited 3 ... below the proven floor" + }, + { + "stage": "delegate", + "reason": "exit code: 3" + }, + { + "stage": "delegate", + "reason": "exited 3" + } + ], + [ + { + "stage": "delegate", + "reason": "onex delegate exited 3 ... below the proven floor" + }, + { + "stage": "delegate", + "reason": "exit code: 3" + }, + { + "stage": "delegate", + "reason": "exited 3" + }, + { + "stage": "delegate", + "reason": "ran about 12 minutes against a 5 minute shell budget" + }, + { + "stage": "delegate", + "reason": "execution budget exceeded" + }, + { + "stage": "delegate", + "reason": "timed out" + }, + { + "stage": "delegate", + "reason": "peer reconcile holds the lock" + }, + { + "stage": "delegate", + "reason": "background time limit" + }, + { + "stage": "delegate", + "reason": "cut off mid-sentence" + }, + { + "stage": "delegate", + "reason": "truncated response" + }, + { + "stage": "delegate", + "reason": "x", + "truncated": true + }, + { + "stage": "source_check", + "reason": "anything" + }, + { + "stage": "verify", + "reason": "the facts table does not support this" + }, + { + "stage": "verify", + "reason": "not in the facts" + }, + { + "stage": "verify", + "reason": " " + }, + { + "stage": "verify", + "reason": "nope" + }, + { + "stage": "verify", + "reason": "" + }, + { + "stage": "delegate", + "reason": "invented a PR" + }, + { + "stage": "delegate", + "reason": "fabricated id" + }, + { + "stage": "pipeline", + "reason": "the drafting stage returned no verdict at all" + }, + { + "stage": "delegate", + "reason": "unsupported claim" + }, + { + "stage": "delegate", + "reason": "" + }, + { + "stage": "", + "reason": "proven floor and timed out" + }, + { + "stage": "delegate", + "reason": "timed out", + "truncated": true + }, + { + "stage": "delegate", + "reason": "exited 30" + }, + { + "stage": "delegate", + "reason": "connection reset" + } + ], + [ + { + "stage": "delegate", + "reason": "onex delegate exited 3 ... below the proven floor", + "reason_class": "timeout" + }, + { + "stage": "delegate", + "reason": "exit code: 3" + } + ], + [ + { + "stage": "delegate", + "reason": "exited 3", + "reason_class": "bogus" + } + ] + ], + "degraded": [ + [ + 0, + 0 + ], + [ + 1, + 0 + ], + [ + 3, + 0 + ], + [ + 3, + 1 + ], + [ + 0, + 3 + ], + [ + 2, + 2 + ] + ], + "metrics": [ + { + "_source": "Real receipt.json from a measured `onex delegate --task-type document` run on 2026-09-14, drafting a Linear reply for OMN-18164. Kept verbatim except prompt_text/response_text, which are stripped for size. It pins the trap: `model_cloud_baseline` is the PREMIUM COUNTERFACTUAL baseline, not the model that answered. A metrics reader that reports it as the answering model reports an Anthropic model for a call that never touched Anthropic.", + "backend_id": "cloud-gemini-flash", + "correlation_id": "1b815ba3-b6af-4bd3-a8a2-b2e97402fe70", + "endpoint": "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", + "model": "gemini-2.5-flash-lite", + "receipt": { + "artifact_refs": [ + { + "ref": "sha256:0616b35a018caa8647e0621f608e21516a9a1f8254276461908fa40267d6016f" + }, + { + "ref": "sha256:8f58ce558b4ac57412ae630aa9c2b658b87f1ed3e6cc2598e8ce5f6b5f0b7d41" + } + ], + "correlation_id": "1b815ba3-b6af-4bd3-a8a2-b2e97402fe70", + "duration_ms": 71092, + "exit_code": 0, + "metrics": { + "capture_log_bytes": 27242.0 + }, + "node_name": "node_delegate_skill_orchestrator", + "result": { + "attempts": [ + { + "acceptance_decision": "climb", + "acceptance_reason": "score_below_required_bar", + "backend_id": "local-heavy-reasoning", + "cost_usd": 0.0, + "error_message": "", + "failure_class": null, + "input_token_budget": null, + "input_tokens_measured": null, + "model_id": "Qwen3.6-35B-A3B", + "quality_gate_passed": false, + "quality_score": 0.9, + "tier": "local" + }, + { + "acceptance_decision": "climb", + "acceptance_reason": "score_below_required_bar", + "backend_id": "local-heavy-reasoning", + "cost_usd": 0.0, + "error_message": "", + "failure_class": null, + "input_token_budget": null, + "input_tokens_measured": null, + "model_id": "Qwen3.6-35B-A3B", + "quality_gate_passed": false, + "quality_score": 0.9, + "tier": "local" + }, + { + "acceptance_decision": "climb", + "acceptance_reason": "score_below_required_bar", + "backend_id": "local-heavy-reasoning", + "cost_usd": 0.0, + "error_message": "", + "failure_class": null, + "input_token_budget": null, + "input_tokens_measured": null, + "model_id": "Qwen3.6-35B-A3B", + "quality_gate_passed": false, + "quality_score": 0.9, + "tier": "local" + }, + { + "acceptance_decision": "accept", + "acceptance_reason": "quality_bar_met", + "backend_id": "cloud-gemini-flash", + "cost_usd": 0.001168, + "error_message": "", + "failure_class": null, + "input_token_budget": null, + "input_tokens_measured": null, + "model_id": "gemini-2.5-flash-lite", + "quality_gate_passed": true, + "quality_score": 1.0, + "tier": "cheap_cloud" + } + ], + "attempts_count": 4, + "correlation_id": "1b815ba3-b6af-4bd3-a8a2-b2e97402fe70", + "error_message": "", + "escalation_count": 1, + "metrics": { + "compliance_attempts": 0, + "cost_savings_usd": 0.020972, + "cost_usd": 0.001168, + "frontier_costs_usd": { + "claude-haiku-3-5": 0.0011808, + "claude-opus-4-6": 0.02214, + "claude-sonnet-4-20250514": 0.004428, + "gpt-4o": 0.0031325 + }, + "input_tokens": 361, + "latency_ms": 937, + "output_tokens": 223, + "premium_counterfactual": { + "as_of": "2026-02-01", + "counterfactual_cost_usd": "0.02214", + "measured": false, + "model": "claude-opus-4-6", + "price_in_per_1k": "0.015", + "price_out_per_1k": "0.075", + "pricing_source": "pricing_manifest", + "tokens_in": 361, + "tokens_out": 223 + }, + "tokens_to_compliance": 0, + "total_tokens": 584 + }, + "model_cloud_baseline": "claude-opus-4-6", + "model_name": "gemini-2.5-flash-lite", + "pricing_manifest_version": 1, + "provider": "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", + "quality_gate_passed": true, + "quality_gates_failed": [], + "quality_score": 1.0, + "response": "```json\n{\n \"ticket\": \"OMN-18164\",\n \"comment_id\": \"9f2a1c\",\n \"their_summary\": \"Collaborator is asking if the inbox path is unblocked and who is responsible for the delay.\",\n \"proposed_reply\": \"The inbox path is still blocked. We are waiting on the registrar to update the NS record for verify.omninode.ai. This is a DNS issue on their end. The AWS side of the work is complete.\",\n \"facts_cited\": [\n \"Ruling at ledger line 6419: the C5 inbox approach is NS delegation of verify.omninode.ai; the registrar record is pending at GoDaddy and it is their DNS issue.\",\n \"The AWS half of OMN-18164 has landed; the lane is blocked on the registrar NS record only.\"\n ],\n \"open_points_for_operator\": [],\n \"confidence\": \"high\"\n}\n```", + "status": "completed", + "task_type": "document", + "tenant_id": null + }, + "result_model": "omnimarket.models.delegation.wire.model_delegate_skill_response.ModelDelegateSkillCompleted", + "run_id": "0e1c073a-beb1-4cb5-adf8-d02b5d52b58b", + "runtime_identity": { + "config_source": "/opt/onex/venv/lib/python3.12/site-packages/omnimarket/nodes/node_delegate_skill_orchestrator/contract.yaml", + "execution_locus": "/opt/onex/venv", + "host": "Stickybeatz-Studio.local", + "interpreter": "/opt/onex/venv/bin/python3", + "locus_kind": "venv", + "packages": { + "omnibase_compat": { + "commit": null, + "import_path": null, + "name": "omnibase_compat", + "source": "registry", + "version": "0.5.7" + }, + "omnibase_core": { + "commit": null, + "import_path": null, + "name": "omnibase_core", + "source": "registry", + "version": "0.47.13" + }, + "omnibase_infra": { + "commit": null, + "import_path": null, + "name": "omnibase_infra", + "source": "local_path", + "version": "0.38.25" + }, + "omnibase_spi": { + "commit": null, + "import_path": null, + "name": "omnibase_spi", + "source": "registry", + "version": "0.23.3" + }, + "omnimarket": { + "commit": "ce01f7de22d7a247bd06327893d1cce831cc6cee", + "import_path": null, + "name": "omnimarket", + "source": "vcs", + "version": "0.4.83" + } + }, + "schema_version": { + "build": null, + "major": 1, + "minor": 0, + "patch": 0, + "prerelease": null + }, + "stamped_at": "2026-09-14T11:51:44.918385Z" + }, + "schema_version": { + "build": null, + "major": 1, + "minor": 1, + "patch": 0, + "prerelease": null + }, + "skill_name": "node_delegate_skill_orchestrator", + "status": "success" + }, + "receipt_id": "1b815ba3-b6af-4bd3-a8a2-b2e97402fe70", + "routing_tier": "cheap_cloud", + "run_id": "0e1c073a-beb1-4cb5-adf8-d02b5d52b58b", + "status": "success" + }, + { + "receipt": {} + }, + { + "backend_id": "b", + "receipt": { + "duration_ms": 5, + "result": { + "attempts": [ + { + "tier": "local", + "backend_id": "x", + "model_id": "m1", + "acceptance_decision": "climb" + }, + { + "tier": "cloud", + "backend_id": "y", + "model_id": "m2", + "acceptance_decision": "accept", + "quality_score": 1, + "cost_usd": 0.5 + } + ], + "metrics": { + "total_tokens": 10, + "latency_ms": 3, + "cost_usd": 0.1 + } + } + } + }, + { + "receipt": { + "result": { + "model_name": "mn", + "attempts_count": 4, + "escalation_count": 1, + "model_cloud_baseline": "premium" + } + } + } + ], + "prompts": [ + [ + { + "ticket": "OMN-18268", + "ticket_title": "AC-binding gap on the autoclose sweep", + "comment_id": "bf9a438b-ca16-4e7a-8c92-5f760f0f2df9", + "comment_url": "https://linear.app/omninode/issue/OMN-18268#comment-bf9a438b", + "author_role": "orchestrator/autoclose-sweep (posted as the operator's own Linear identity)", + "posted_at": "2026-09-14T11:36:52Z", + "text": "AC-binding gap: dod_verify 4/6 verified, 0 failed, but AC1-AC3 have no binds_ac entry in contracts/OMN-18268.yaml; not auto-closable until labeled.", + "needs_reply": true, + "asks": [ + "label the AC bindings" + ], + "already_answered_by": "" + }, + "Ground-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd." + ], + [ + { + "ticket": "OMN-18086", + "ticket_title": "Evidence gap on the autoclose sweep", + "comment_id": "8354b627-f18a-4359-b1e3-3a9a873c7fcc", + "comment_url": "https://linear.app/omninode/issue/OMN-18086#comment-8354b627", + "author_role": "autoclose-sweep bot", + "posted_at": "2026-09-14T10:47:18Z", + "text": "Evidence gap: dod_verify 7/15 ACs verified, 2 failed - not all ACs receipt-proven, not flipped.", + "needs_reply": true, + "asks": [ + "prove the remaining ACs" + ], + "already_answered_by": "" + }, + "Ground-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd." + ], + [ + { + "ticket": "OMN-18160", + "ticket_title": "MSK broker right-sizing", + "comment_id": "568e6a23-973d-4033-aaa6-be60c969de3f", + "comment_url": "https://linear.app/omninode/issue/OMN-18160#comment-568e6a23", + "author_role": "the CTO candidate", + "posted_at": "2026-09-11T09:23:45Z", + "text": "Broker right-sizing readback answering the three questions.", + "needs_reply": true, + "asks": [ + "confirm the disposition" + ], + "already_answered_by": "operator comment 22f885c7, 2026-09-11T12:54:59Z" + }, + "Ground-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd." + ], + [ + { + "ticket": "OMN-16964", + "ticket_title": "Chain canary AC1 probe", + "comment_id": "aa11bb22-cccc-4444-8888-999900001111", + "comment_url": "https://linear.app/omninode/issue/OMN-16964#comment-aa11bb22", + "author_role": "a collaborator", + "posted_at": "2026-09-13T15:52:47Z", + "text": "What is the governed probe and the expected result for AC1?", + "needs_reply": true, + "asks": [ + "name the probe", + "name the expected result" + ], + "already_answered_by": "operator comment fd2a9c30, 2026-09-13T16:59:38Z" + }, + "Ground-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd." + ], + [ + { + "ticket": "OMN-18052", + "ticket_title": "Runtime rebuild trigger", + "comment_id": "dd44ee55-6666-4777-8888-99990000aaaa", + "comment_url": "https://linear.app/omninode/issue/OMN-18052#comment-dd44ee55", + "author_role": "linear-closer app", + "posted_at": "2026-09-14T11:02:00Z", + "text": "Automated closure precheck ran; no action required.", + "needs_reply": false, + "asks": [], + "already_answered_by": "" + }, + "Ground-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd." + ], + [ + { + "ticket": "OMN-18164", + "ticket_title": "C5 inbox - verify subdomain delegation", + "comment_id": "collab_unanswered_ask", + "comment_url": "https://linear.app/omninode/issue/OMN-18164#comment-collab", + "author_role": "a collaborator", + "posted_at": "2026-09-14T11:10:00Z", + "text": "Is the inbox path unblocked yet? I still cannot send to the verify subdomain and I do not know whether we are waiting on us or on the registrar.", + "needs_reply": true, + "asks": [ + "is the inbox unblocked", + "who are we waiting on" + ], + "already_answered_by": "" + }, + "Ground-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd." + ], + [ + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "c1", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [], + "already_answered_by": "" + }, + "Ground-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd." + ], + [ + { + "ticket": "OMN-1", + "ticket_title": "T", + "comment_id": "we/ird id", + "comment_url": "u", + "author_role": "a collaborator", + "posted_at": "2026-09-17T13:01:36.497Z", + "text": "Can this land?", + "needs_reply": true, + "asks": [ + "a", + "b" + ], + "already_answered_by": "" + }, + "" + ] + ] + }, + "expected": { + "classify_author_role": [ + "unknown", + "automation", + "automation", + "human", + "unknown", + "human", + "automation", + "human", + "human", + "human", + "human", + "automation", + "automation", + "automation", + "automation", + "automation", + "automation", + "human", + "automation", + "human", + "operator_self", + "operator_self", + "operator_self", + "operator_self", + "operator_self", + "human", + "automation", + "automation", + "human", + "automation", + "automation", + "human", + "human", + "unknown", + "unknown", + "automation", + "automation", + "automation", + "human" + ], + "classify_author": [ + "automation", + "automation", + "human", + "human", + "automation", + "human", + "human", + "human", + "automation", + "automation", + "human", + "human", + "human", + "human", + "automation", + "human", + "human", + "human", + "automation", + "human", + "human", + "human", + "unknown", + "unknown", + "human", + "unknown", + "human", + "automation" + ], + "decide": [ + { + "ticket": "OMN-18268", + "comment_id": "bf9a438b-ca16-4e7a-8c92-5f760f0f2df9", + "comment_url": "https://linear.app/omninode/issue/OMN-18268#comment-bf9a438b", + "author_role": "orchestrator/autoclose-sweep (posted as the operator's own Linear identity)", + "author_kind": "automation", + "draft": false, + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"orchestrator/autoclose-sweep (posted as the operator's own Linear identity)\"" + }, + { + "ticket": "OMN-18086", + "comment_id": "8354b627-f18a-4359-b1e3-3a9a873c7fcc", + "comment_url": "https://linear.app/omninode/issue/OMN-18086#comment-8354b627", + "author_role": "autoclose-sweep bot", + "author_kind": "automation", + "draft": false, + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"autoclose-sweep bot\"" + }, + { + "ticket": "OMN-18160", + "comment_id": "568e6a23-973d-4033-aaa6-be60c969de3f", + "comment_url": "https://linear.app/omninode/issue/OMN-18160#comment-568e6a23", + "author_role": "the CTO candidate", + "author_kind": "human", + "draft": false, + "reason_class": "already_answered", + "reason": "already answered in-thread by operator comment 22f885c7, 2026-09-11T12:54:59Z" + }, + { + "ticket": "OMN-16964", + "comment_id": "aa11bb22-cccc-4444-8888-999900001111", + "comment_url": "https://linear.app/omninode/issue/OMN-16964#comment-aa11bb22", + "author_role": "a collaborator", + "author_kind": "human", + "draft": false, + "reason_class": "already_answered", + "reason": "already answered in-thread by operator comment fd2a9c30, 2026-09-13T16:59:38Z" + }, + { + "ticket": "OMN-18052", + "comment_id": "dd44ee55-6666-4777-8888-99990000aaaa", + "comment_url": "https://linear.app/omninode/issue/OMN-18052#comment-dd44ee55", + "author_role": "linear-closer app", + "author_kind": "automation", + "draft": false, + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"linear-closer app\"" + }, + { + "ticket": "OMN-18164", + "comment_id": "collab_unanswered_ask", + "comment_url": "https://linear.app/omninode/issue/OMN-18164#comment-collab", + "author_role": "a collaborator", + "author_kind": "human", + "draft": true, + "reason_class": "", + "reason": "" + }, + { + "ticket": "OMN-1", + "comment_id": "e1", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "draft": false, + "reason_class": "empty_text", + "reason": "the collector recorded no comment text for this item" + }, + { + "ticket": "OMN-1", + "comment_id": "e2", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "draft": false, + "reason_class": "empty_text", + "reason": "the collector recorded no comment text for this item" + }, + { + "ticket": "OMN-1", + "comment_id": "a1", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "automation", + "draft": false, + "reason_class": "automation", + "reason": "agent-posted: actor: sweep (sonnet)" + }, + { + "ticket": "OMN-1", + "comment_id": "a2", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "automation", + "draft": false, + "reason_class": "automation", + "reason": "agent-posted: Actor: lane-1 (opus)" + }, + { + "ticket": "OMN-1", + "comment_id": "a3", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "draft": true, + "reason_class": "", + "reason": "" + }, + { + "ticket": "OMN-1", + "comment_id": "a4", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "draft": true, + "reason_class": "", + "reason": "" + }, + { + "ticket": "OMN-1", + "comment_id": "a5", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "draft": true, + "reason_class": "", + "reason": "" + }, + { + "ticket": "OMN-1", + "comment_id": "k1", + "comment_url": "u", + "author_role": "autoclose bot", + "author_kind": "human", + "draft": true, + "reason_class": "", + "reason": "" + }, + { + "ticket": "OMN-1", + "comment_id": "k2", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "automation", + "draft": false, + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"a collaborator\"" + }, + { + "ticket": "OMN-1", + "comment_id": "k3", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "draft": true, + "reason_class": "", + "reason": "" + }, + { + "ticket": "OMN-1", + "comment_id": "k4", + "comment_url": "u", + "author_role": "human collaborator", + "author_kind": "human", + "draft": true, + "reason_class": "", + "reason": "" + }, + { + "ticket": "OMN-1", + "comment_id": "k5", + "comment_url": "u", + "author_role": "(none recorded)", + "author_kind": "human", + "draft": true, + "reason_class": "", + "reason": "" + }, + { + "ticket": "OMN-1", + "comment_id": "k6", + "comment_url": "u", + "author_role": "a bot", + "author_kind": "automation", + "draft": false, + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"a bot\"" + }, + { + "ticket": "OMN-1", + "comment_id": "n1", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "draft": false, + "reason_class": "no_reply_needed", + "reason": "the collector set needs_reply false for this comment" + }, + { + "ticket": "OMN-1", + "comment_id": "n2", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "draft": false, + "reason_class": "already_answered", + "reason": "already answered in-thread by comment 22f885c7" + }, + { + "ticket": "OMN-1", + "comment_id": "n3", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "draft": true, + "reason_class": "", + "reason": "" + }, + { + "ticket": "OMN-1", + "comment_id": "n4", + "comment_url": "u", + "author_role": "(none recorded)", + "author_kind": "unknown", + "draft": false, + "reason_class": "author_unclassified", + "reason": "HELD for operator adjudication: author_role \"(none recorded)\" names neither a human collaborator nor an automation, so this item was not classified either way" + }, + { + "ticket": "OMN-1", + "comment_id": "n5", + "comment_url": "u", + "author_role": "mystery", + "author_kind": "unknown", + "draft": false, + "reason_class": "author_unclassified", + "reason": "HELD for operator adjudication: author_role \"mystery\" names neither a human collaborator nor an automation, so this item was not classified either way" + }, + { + "ticket": "OMN-1", + "comment_id": "n6", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "draft": true, + "reason_class": "", + "reason": "" + }, + { + "ticket": "OMN-1", + "comment_id": "n7", + "comment_url": "u", + "author_role": "mystery", + "author_kind": "unknown", + "draft": false, + "reason_class": "no_reply_needed", + "reason": "the collector set needs_reply false for this comment" + }, + { + "ticket": "OMN-17276", + "comment_id": "selftest_human_with_ask", + "comment_url": "u", + "author_role": "human collaborator (a collaborator) — not automation; posted under his own Linear identity, not the operator's", + "author_kind": "human", + "draft": true, + "reason_class": "", + "reason": "" + }, + { + "ticket": "OMN-16106", + "comment_id": "selftest_automation", + "comment_url": "u", + "author_role": "orchestrator/autoclose-sweep bot (posted as the operator's own Linear identity)", + "author_kind": "automation", + "draft": false, + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"orchestrator/autoclose-sweep bot (posted as the operator's own Linear identity)\"" + } + ], + "select": [ + "collab_unanswered_ask", + "a3", + "a4", + "a5", + "k1", + "k3", + "k4", + "k5", + "n3", + "n6", + "selftest_human_with_ask" + ], + "refusals": [ + { + "ticket": "OMN-18268", + "comment_id": "bf9a438b-ca16-4e7a-8c92-5f760f0f2df9", + "comment_url": "https://linear.app/omninode/issue/OMN-18268#comment-bf9a438b", + "author_role": "orchestrator/autoclose-sweep (posted as the operator's own Linear identity)", + "author_kind": "automation", + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"orchestrator/autoclose-sweep (posted as the operator's own Linear identity)\"" + }, + { + "ticket": "OMN-18086", + "comment_id": "8354b627-f18a-4359-b1e3-3a9a873c7fcc", + "comment_url": "https://linear.app/omninode/issue/OMN-18086#comment-8354b627", + "author_role": "autoclose-sweep bot", + "author_kind": "automation", + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"autoclose-sweep bot\"" + }, + { + "ticket": "OMN-18160", + "comment_id": "568e6a23-973d-4033-aaa6-be60c969de3f", + "comment_url": "https://linear.app/omninode/issue/OMN-18160#comment-568e6a23", + "author_role": "the CTO candidate", + "author_kind": "human", + "reason_class": "already_answered", + "reason": "already answered in-thread by operator comment 22f885c7, 2026-09-11T12:54:59Z" + }, + { + "ticket": "OMN-16964", + "comment_id": "aa11bb22-cccc-4444-8888-999900001111", + "comment_url": "https://linear.app/omninode/issue/OMN-16964#comment-aa11bb22", + "author_role": "a collaborator", + "author_kind": "human", + "reason_class": "already_answered", + "reason": "already answered in-thread by operator comment fd2a9c30, 2026-09-13T16:59:38Z" + }, + { + "ticket": "OMN-18052", + "comment_id": "dd44ee55-6666-4777-8888-99990000aaaa", + "comment_url": "https://linear.app/omninode/issue/OMN-18052#comment-dd44ee55", + "author_role": "linear-closer app", + "author_kind": "automation", + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"linear-closer app\"" + }, + { + "ticket": "OMN-1", + "comment_id": "e1", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "reason_class": "empty_text", + "reason": "the collector recorded no comment text for this item" + }, + { + "ticket": "OMN-1", + "comment_id": "e2", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "reason_class": "empty_text", + "reason": "the collector recorded no comment text for this item" + }, + { + "ticket": "OMN-1", + "comment_id": "a1", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "automation", + "reason_class": "automation", + "reason": "agent-posted: actor: sweep (sonnet)" + }, + { + "ticket": "OMN-1", + "comment_id": "a2", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "automation", + "reason_class": "automation", + "reason": "agent-posted: Actor: lane-1 (opus)" + }, + { + "ticket": "OMN-1", + "comment_id": "k2", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "automation", + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"a collaborator\"" + }, + { + "ticket": "OMN-1", + "comment_id": "k6", + "comment_url": "u", + "author_role": "a bot", + "author_kind": "automation", + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"a bot\"" + }, + { + "ticket": "OMN-1", + "comment_id": "n1", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "reason_class": "no_reply_needed", + "reason": "the collector set needs_reply false for this comment" + }, + { + "ticket": "OMN-1", + "comment_id": "n2", + "comment_url": "u", + "author_role": "a collaborator", + "author_kind": "human", + "reason_class": "already_answered", + "reason": "already answered in-thread by comment 22f885c7" + }, + { + "ticket": "OMN-1", + "comment_id": "n4", + "comment_url": "u", + "author_role": "(none recorded)", + "author_kind": "unknown", + "reason_class": "author_unclassified", + "reason": "HELD for operator adjudication: author_role \"(none recorded)\" names neither a human collaborator nor an automation, so this item was not classified either way" + }, + { + "ticket": "OMN-1", + "comment_id": "n5", + "comment_url": "u", + "author_role": "mystery", + "author_kind": "unknown", + "reason_class": "author_unclassified", + "reason": "HELD for operator adjudication: author_role \"mystery\" names neither a human collaborator nor an automation, so this item was not classified either way" + }, + { + "ticket": "OMN-1", + "comment_id": "n7", + "comment_url": "u", + "author_role": "mystery", + "author_kind": "unknown", + "reason_class": "no_reply_needed", + "reason": "the collector set needs_reply false for this comment" + }, + { + "ticket": "OMN-16106", + "comment_id": "selftest_automation", + "comment_url": "u", + "author_role": "orchestrator/autoclose-sweep bot (posted as the operator's own Linear identity)", + "author_kind": "automation", + "reason_class": "automation", + "reason": "automation-authored: the collector's author_role reads \"orchestrator/autoclose-sweep bot (posted as the operator's own Linear identity)\"" + } + ], + "cite": [ + { + "ok": true, + "reason": "", + "foreign_ids": [], + "comment_id_mismatch": false + }, + { + "ok": false, + "reason": "the draft cites comment id(s) 0a1b2c3d-0000-4000-8000-00000000abcd that are in neither the source set nor the facts table", + "foreign_ids": [ + "0a1b2c3d-0000-4000-8000-00000000abcd" + ], + "comment_id_mismatch": false + }, + { + "ok": true, + "reason": "", + "foreign_ids": [], + "comment_id_mismatch": false + }, + { + "ok": false, + "reason": "the draft's comment_id \"other\" is not the item's \"selftest_id_1\"", + "foreign_ids": [], + "comment_id_mismatch": true + }, + { + "ok": false, + "reason": "the draft's comment_id null is not the item's \"selftest_id_1\"", + "foreign_ids": [], + "comment_id_mismatch": true + }, + { + "ok": true, + "reason": "", + "foreign_ids": [], + "comment_id_mismatch": false + }, + { + "ok": false, + "reason": "the draft cites comment id(s) zzz999 that are in neither the source set nor the facts table", + "foreign_ids": [ + "zzz999" + ], + "comment_id_mismatch": false + }, + { + "ok": true, + "reason": "", + "foreign_ids": [], + "comment_id_mismatch": false + }, + { + "ok": true, + "reason": "", + "foreign_ids": [], + "comment_id_mismatch": false + }, + { + "ok": true, + "reason": "", + "foreign_ids": [], + "comment_id_mismatch": false + }, + { + "ok": false, + "reason": "the draft cites comment id(s) qq11ww22 that are in neither the source set nor the facts table", + "foreign_ids": [ + "qq11ww22" + ], + "comment_id_mismatch": false + }, + { + "ok": false, + "reason": "the draft cites comment id(s) c91f2a77 that are in neither the source set nor the facts table", + "foreign_ids": [ + "c91f2a77" + ], + "comment_id_mismatch": false + }, + { + "ok": false, + "reason": "the draft cites comment id(s) 4417aa90 that are in neither the source set nor the facts table", + "foreign_ids": [ + "4417aa90" + ], + "comment_id_mismatch": false + }, + { + "ok": false, + "reason": "the draft cites comment id(s) 4417aa90 that are in neither the source set nor the facts table", + "foreign_ids": [ + "4417aa90" + ], + "comment_id_mismatch": false + }, + { + "ok": false, + "reason": "the draft's comment_id \"selftest_id_1\" is not the item's \"\"", + "foreign_ids": [], + "comment_id_mismatch": true + }, + { + "ok": true, + "reason": "", + "foreign_ids": [], + "comment_id_mismatch": false + } + ], + "truncation": [ + { + "truncated": false, + "signals": [] + }, + { + "truncated": true, + "signals": [ + "receipt_truncated" + ] + }, + { + "truncated": true, + "signals": [ + "finish_reason:length" + ] + }, + { + "truncated": true, + "signals": [ + "finish_reason:length" + ] + }, + { + "truncated": true, + "signals": [ + "finish_reason:max_tokens" + ] + }, + { + "truncated": false, + "signals": [] + }, + { + "truncated": true, + "signals": [ + "unclosed_json" + ] + }, + { + "truncated": true, + "signals": [ + "unclosed_json" + ] + }, + { + "truncated": true, + "signals": [ + "unclosed_json" + ] + }, + { + "truncated": false, + "signals": [] + }, + { + "truncated": false, + "signals": [] + }, + { + "truncated": true, + "signals": [ + "receipt_truncated", + "finish_reason:length", + "unclosed_json" + ] + }, + { + "truncated": true, + "signals": [ + "finish_reason:token_limit" + ] + }, + { + "truncated": true, + "signals": [ + "finish_reason:max_output_tokens" + ] + }, + { + "truncated": false, + "signals": [] + } + ], + "classify_held": [ + "floor_refused", + "floor_refused", + "floor_refused", + "timeout", + "timeout", + "timeout", + "timeout", + "timeout", + "truncated", + "truncated", + "truncated", + "unsourced", + "unsourced", + "unsourced", + "transport", + "unsourced", + "transport", + "unsourced", + "unsourced", + "transport", + "unsourced", + "transport", + "floor_refused", + "timeout", + "transport", + "transport" + ], + "held_by_class": [ + { + "floor_refused": 0, + "truncated": 0, + "unsourced": 0, + "transport": 0, + "timeout": 0 + }, + { + "floor_refused": 3, + "truncated": 0, + "unsourced": 0, + "transport": 0, + "timeout": 0 + }, + { + "floor_refused": 4, + "truncated": 3, + "unsourced": 7, + "transport": 6, + "timeout": 6 + }, + { + "floor_refused": 1, + "truncated": 0, + "unsourced": 0, + "transport": 0, + "timeout": 1 + }, + { + "floor_refused": 1, + "truncated": 0, + "unsourced": 0, + "transport": 0, + "timeout": 0 + } + ], + "degraded": [ + false, + true, + true, + false, + false, + false + ], + "metrics": [ + { + "model": "gemini-2.5-flash-lite", + "backend_id": "cloud-gemini-flash", + "tier": "cheap_cloud", + "total_tokens": 584, + "input_tokens": 361, + "output_tokens": 223, + "latency_ms": 937, + "wall_ms": 71092, + "cost_usd": 0.001168, + "cost_savings_usd": 0.020972, + "premium_counterfactual_model": "claude-opus-4-6", + "attempts": 4, + "escalations": 1, + "accepted": true, + "tried": [ + { + "tier": "local", + "backend_id": "local-heavy-reasoning", + "model_id": "Qwen3.6-35B-A3B", + "quality_score": 0.9, + "decision": "climb", + "cost_usd": 0 + }, + { + "tier": "local", + "backend_id": "local-heavy-reasoning", + "model_id": "Qwen3.6-35B-A3B", + "quality_score": 0.9, + "decision": "climb", + "cost_usd": 0 + }, + { + "tier": "local", + "backend_id": "local-heavy-reasoning", + "model_id": "Qwen3.6-35B-A3B", + "quality_score": 0.9, + "decision": "climb", + "cost_usd": 0 + }, + { + "tier": "cheap_cloud", + "backend_id": "cloud-gemini-flash", + "model_id": "gemini-2.5-flash-lite", + "quality_score": 1, + "decision": "accept", + "cost_usd": 0.001168 + } + ] + }, + { + "model": null, + "backend_id": null, + "tier": null, + "total_tokens": null, + "input_tokens": null, + "output_tokens": null, + "latency_ms": null, + "wall_ms": null, + "cost_usd": null, + "cost_savings_usd": null, + "premium_counterfactual_model": null, + "attempts": 0, + "escalations": null, + "accepted": false, + "tried": [] + }, + { + "model": "m2", + "backend_id": "b", + "tier": "cloud", + "total_tokens": 10, + "input_tokens": null, + "output_tokens": null, + "latency_ms": 3, + "wall_ms": 5, + "cost_usd": 0.1, + "cost_savings_usd": null, + "premium_counterfactual_model": null, + "attempts": 2, + "escalations": null, + "accepted": true, + "tried": [ + { + "tier": "local", + "backend_id": "x", + "model_id": "m1", + "decision": "climb" + }, + { + "tier": "cloud", + "backend_id": "y", + "model_id": "m2", + "quality_score": 1, + "decision": "accept", + "cost_usd": 0.5 + } + ] + }, + { + "model": "mn", + "backend_id": null, + "tier": null, + "total_tokens": null, + "input_tokens": null, + "output_tokens": null, + "latency_ms": null, + "wall_ms": null, + "cost_usd": null, + "cost_savings_usd": null, + "premium_counterfactual_model": "premium", + "attempts": 4, + "escalations": 1, + "accepted": false, + "tried": [] + } + ], + "prompt": [ + "Draft the operator's reply comment for Linear OMN-18268 (AC-binding gap on the autoclose sweep), replying to comment bf9a438b-ca16-4e7a-8c92-5f760f0f2df9 by orchestrator/autoclose-sweep (posted as the operator's own Linear identity) at 2026-09-14T11:36:52Z.\n\nSOURCE SET -- the only comment ids and comment texts you may cite. Cite no comment id outside this set; every comment_id in your answer is the one below.\ncomment id: bf9a438b-ca16-4e7a-8c92-5f760f0f2df9\ncomment text:\n---\nAC-binding gap: dod_verify 4/6 verified, 0 failed, but AC1-AC3 have no binds_ac entry in contracts/OMN-18268.yaml; not auto-closable until labeled.\n---\nAsks: [\"label the AC bindings\"].\n\nGround-truth facts you may cite, and nothing else:\nGround-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd.\n\nOperator voice: plain, direct, first person, SHORT (3 to 10 sentences), no bullets unless listing options, no em-dashes. Answer every ask with a verified fact and cite the ticket, PR, run id or ledger line it comes from. Never invent a fact, never use a placeholder, and never promise a date the facts do not support. Where only the operator can decide, write a bracketed [OPERATOR: ...] line naming the options.\n\nNever write a holding non-answer: no 'I'll look into it', no 'we're tracking this', no 'more soon', no bare acknowledgement of receipt. If the facts above do not answer an ask, that ask goes in open_points_for_operator and does not appear in proposed_reply at all. A reply that answers nothing must come back with proposed_reply empty.\n\nDo not echo the asker's own uncertainty phrasing back at them, and never write a disclaimer about what you or they do not know. State what is true. When something the asker wants is genuinely not in the facts above, do not hedge about it in the reply text: leave it out of proposed_reply and put it in open_points_for_operator instead.\n\nYour entire output must be one JSON object and nothing else. The first character you emit is { and the last is }. No reasoning, no preamble, no explanation, no code fence, no text after the object.\nKeys: ticket, comment_id, their_summary, proposed_reply, facts_cited (array of strings), open_points_for_operator (array of strings), confidence (one of high, medium, low).\nIn their_summary, paraphrase what they asked in your own words. Never quote their comment verbatim anywhere in the object.", + "Draft the operator's reply comment for Linear OMN-18086 (Evidence gap on the autoclose sweep), replying to comment 8354b627-f18a-4359-b1e3-3a9a873c7fcc by autoclose-sweep bot at 2026-09-14T10:47:18Z.\n\nSOURCE SET -- the only comment ids and comment texts you may cite. Cite no comment id outside this set; every comment_id in your answer is the one below.\ncomment id: 8354b627-f18a-4359-b1e3-3a9a873c7fcc\ncomment text:\n---\nEvidence gap: dod_verify 7/15 ACs verified, 2 failed - not all ACs receipt-proven, not flipped.\n---\nAsks: [\"prove the remaining ACs\"].\n\nGround-truth facts you may cite, and nothing else:\nGround-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd.\n\nOperator voice: plain, direct, first person, SHORT (3 to 10 sentences), no bullets unless listing options, no em-dashes. Answer every ask with a verified fact and cite the ticket, PR, run id or ledger line it comes from. Never invent a fact, never use a placeholder, and never promise a date the facts do not support. Where only the operator can decide, write a bracketed [OPERATOR: ...] line naming the options.\n\nNever write a holding non-answer: no 'I'll look into it', no 'we're tracking this', no 'more soon', no bare acknowledgement of receipt. If the facts above do not answer an ask, that ask goes in open_points_for_operator and does not appear in proposed_reply at all. A reply that answers nothing must come back with proposed_reply empty.\n\nDo not echo the asker's own uncertainty phrasing back at them, and never write a disclaimer about what you or they do not know. State what is true. When something the asker wants is genuinely not in the facts above, do not hedge about it in the reply text: leave it out of proposed_reply and put it in open_points_for_operator instead.\n\nYour entire output must be one JSON object and nothing else. The first character you emit is { and the last is }. No reasoning, no preamble, no explanation, no code fence, no text after the object.\nKeys: ticket, comment_id, their_summary, proposed_reply, facts_cited (array of strings), open_points_for_operator (array of strings), confidence (one of high, medium, low).\nIn their_summary, paraphrase what they asked in your own words. Never quote their comment verbatim anywhere in the object.", + "Draft the operator's reply comment for Linear OMN-18160 (MSK broker right-sizing), replying to comment 568e6a23-973d-4033-aaa6-be60c969de3f by the CTO candidate at 2026-09-11T09:23:45Z.\n\nSOURCE SET -- the only comment ids and comment texts you may cite. Cite no comment id outside this set; every comment_id in your answer is the one below.\ncomment id: 568e6a23-973d-4033-aaa6-be60c969de3f\ncomment text:\n---\nBroker right-sizing readback answering the three questions.\n---\nAsks: [\"confirm the disposition\"].\n\nGround-truth facts you may cite, and nothing else:\nGround-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd.\n\nOperator voice: plain, direct, first person, SHORT (3 to 10 sentences), no bullets unless listing options, no em-dashes. Answer every ask with a verified fact and cite the ticket, PR, run id or ledger line it comes from. Never invent a fact, never use a placeholder, and never promise a date the facts do not support. Where only the operator can decide, write a bracketed [OPERATOR: ...] line naming the options.\n\nNever write a holding non-answer: no 'I'll look into it', no 'we're tracking this', no 'more soon', no bare acknowledgement of receipt. If the facts above do not answer an ask, that ask goes in open_points_for_operator and does not appear in proposed_reply at all. A reply that answers nothing must come back with proposed_reply empty.\n\nDo not echo the asker's own uncertainty phrasing back at them, and never write a disclaimer about what you or they do not know. State what is true. When something the asker wants is genuinely not in the facts above, do not hedge about it in the reply text: leave it out of proposed_reply and put it in open_points_for_operator instead.\n\nYour entire output must be one JSON object and nothing else. The first character you emit is { and the last is }. No reasoning, no preamble, no explanation, no code fence, no text after the object.\nKeys: ticket, comment_id, their_summary, proposed_reply, facts_cited (array of strings), open_points_for_operator (array of strings), confidence (one of high, medium, low).\nIn their_summary, paraphrase what they asked in your own words. Never quote their comment verbatim anywhere in the object.", + "Draft the operator's reply comment for Linear OMN-16964 (Chain canary AC1 probe), replying to comment aa11bb22-cccc-4444-8888-999900001111 by a collaborator at 2026-09-13T15:52:47Z.\n\nSOURCE SET -- the only comment ids and comment texts you may cite. Cite no comment id outside this set; every comment_id in your answer is the one below.\ncomment id: aa11bb22-cccc-4444-8888-999900001111\ncomment text:\n---\nWhat is the governed probe and the expected result for AC1?\n---\nAsks: [\"name the probe\",\"name the expected result\"].\n\nGround-truth facts you may cite, and nothing else:\nGround-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd.\n\nOperator voice: plain, direct, first person, SHORT (3 to 10 sentences), no bullets unless listing options, no em-dashes. Answer every ask with a verified fact and cite the ticket, PR, run id or ledger line it comes from. Never invent a fact, never use a placeholder, and never promise a date the facts do not support. Where only the operator can decide, write a bracketed [OPERATOR: ...] line naming the options.\n\nNever write a holding non-answer: no 'I'll look into it', no 'we're tracking this', no 'more soon', no bare acknowledgement of receipt. If the facts above do not answer an ask, that ask goes in open_points_for_operator and does not appear in proposed_reply at all. A reply that answers nothing must come back with proposed_reply empty.\n\nDo not echo the asker's own uncertainty phrasing back at them, and never write a disclaimer about what you or they do not know. State what is true. When something the asker wants is genuinely not in the facts above, do not hedge about it in the reply text: leave it out of proposed_reply and put it in open_points_for_operator instead.\n\nYour entire output must be one JSON object and nothing else. The first character you emit is { and the last is }. No reasoning, no preamble, no explanation, no code fence, no text after the object.\nKeys: ticket, comment_id, their_summary, proposed_reply, facts_cited (array of strings), open_points_for_operator (array of strings), confidence (one of high, medium, low).\nIn their_summary, paraphrase what they asked in your own words. Never quote their comment verbatim anywhere in the object.", + "Draft the operator's reply comment for Linear OMN-18052 (Runtime rebuild trigger), replying to comment dd44ee55-6666-4777-8888-99990000aaaa by linear-closer app at 2026-09-14T11:02:00Z.\n\nSOURCE SET -- the only comment ids and comment texts you may cite. Cite no comment id outside this set; every comment_id in your answer is the one below.\ncomment id: dd44ee55-6666-4777-8888-99990000aaaa\ncomment text:\n---\nAutomated closure precheck ran; no action required.\n---\nAsks: the collector did not enumerate them. Read the comment above and answer what it actually asks. If it asks nothing answerable, say so in open_points_for_operator and leave proposed_reply empty rather than writing a filler acknowledgement.\n\nGround-truth facts you may cite, and nothing else:\nGround-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd.\n\nOperator voice: plain, direct, first person, SHORT (3 to 10 sentences), no bullets unless listing options, no em-dashes. Answer every ask with a verified fact and cite the ticket, PR, run id or ledger line it comes from. Never invent a fact, never use a placeholder, and never promise a date the facts do not support. Where only the operator can decide, write a bracketed [OPERATOR: ...] line naming the options.\n\nNever write a holding non-answer: no 'I'll look into it', no 'we're tracking this', no 'more soon', no bare acknowledgement of receipt. If the facts above do not answer an ask, that ask goes in open_points_for_operator and does not appear in proposed_reply at all. A reply that answers nothing must come back with proposed_reply empty.\n\nDo not echo the asker's own uncertainty phrasing back at them, and never write a disclaimer about what you or they do not know. State what is true. When something the asker wants is genuinely not in the facts above, do not hedge about it in the reply text: leave it out of proposed_reply and put it in open_points_for_operator instead.\n\nYour entire output must be one JSON object and nothing else. The first character you emit is { and the last is }. No reasoning, no preamble, no explanation, no code fence, no text after the object.\nKeys: ticket, comment_id, their_summary, proposed_reply, facts_cited (array of strings), open_points_for_operator (array of strings), confidence (one of high, medium, low).\nIn their_summary, paraphrase what they asked in your own words. Never quote their comment verbatim anywhere in the object.", + "Draft the operator's reply comment for Linear OMN-18164 (C5 inbox - verify subdomain delegation), replying to comment collab_unanswered_ask by a collaborator at 2026-09-14T11:10:00Z.\n\nSOURCE SET -- the only comment ids and comment texts you may cite. Cite no comment id outside this set; every comment_id in your answer is the one below.\ncomment id: collab_unanswered_ask\ncomment text:\n---\nIs the inbox path unblocked yet? I still cannot send to the verify subdomain and I do not know whether we are waiting on us or on the registrar.\n---\nAsks: [\"is the inbox unblocked\",\"who are we waiting on\"].\n\nGround-truth facts you may cite, and nothing else:\nGround-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd.\n\nOperator voice: plain, direct, first person, SHORT (3 to 10 sentences), no bullets unless listing options, no em-dashes. Answer every ask with a verified fact and cite the ticket, PR, run id or ledger line it comes from. Never invent a fact, never use a placeholder, and never promise a date the facts do not support. Where only the operator can decide, write a bracketed [OPERATOR: ...] line naming the options.\n\nNever write a holding non-answer: no 'I'll look into it', no 'we're tracking this', no 'more soon', no bare acknowledgement of receipt. If the facts above do not answer an ask, that ask goes in open_points_for_operator and does not appear in proposed_reply at all. A reply that answers nothing must come back with proposed_reply empty.\n\nDo not echo the asker's own uncertainty phrasing back at them, and never write a disclaimer about what you or they do not know. State what is true. When something the asker wants is genuinely not in the facts above, do not hedge about it in the reply text: leave it out of proposed_reply and put it in open_points_for_operator instead.\n\nYour entire output must be one JSON object and nothing else. The first character you emit is { and the last is }. No reasoning, no preamble, no explanation, no code fence, no text after the object.\nKeys: ticket, comment_id, their_summary, proposed_reply, facts_cited (array of strings), open_points_for_operator (array of strings), confidence (one of high, medium, low).\nIn their_summary, paraphrase what they asked in your own words. Never quote their comment verbatim anywhere in the object.", + "Draft the operator's reply comment for Linear OMN-1 (T), replying to comment c1 by a collaborator at 2026-09-17T13:01:36.497Z.\n\nSOURCE SET -- the only comment ids and comment texts you may cite. Cite no comment id outside this set; every comment_id in your answer is the one below.\ncomment id: c1\ncomment text:\n---\nCan this land?\n---\nAsks: the collector did not enumerate them. Read the comment above and answer what it actually asks. If it asks nothing answerable, say so in open_points_for_operator and leave proposed_reply empty rather than writing a filler acknowledgement.\n\nGround-truth facts you may cite, and nothing else:\nGround-truth facts.\n- 2026-09-17T13:00:00Z OMN-17276 merged in PR#12 at run 77ab12cd.\n\nOperator voice: plain, direct, first person, SHORT (3 to 10 sentences), no bullets unless listing options, no em-dashes. Answer every ask with a verified fact and cite the ticket, PR, run id or ledger line it comes from. Never invent a fact, never use a placeholder, and never promise a date the facts do not support. Where only the operator can decide, write a bracketed [OPERATOR: ...] line naming the options.\n\nNever write a holding non-answer: no 'I'll look into it', no 'we're tracking this', no 'more soon', no bare acknowledgement of receipt. If the facts above do not answer an ask, that ask goes in open_points_for_operator and does not appear in proposed_reply at all. A reply that answers nothing must come back with proposed_reply empty.\n\nDo not echo the asker's own uncertainty phrasing back at them, and never write a disclaimer about what you or they do not know. State what is true. When something the asker wants is genuinely not in the facts above, do not hedge about it in the reply text: leave it out of proposed_reply and put it in open_points_for_operator instead.\n\nYour entire output must be one JSON object and nothing else. The first character you emit is { and the last is }. No reasoning, no preamble, no explanation, no code fence, no text after the object.\nKeys: ticket, comment_id, their_summary, proposed_reply, facts_cited (array of strings), open_points_for_operator (array of strings), confidence (one of high, medium, low).\nIn their_summary, paraphrase what they asked in your own words. Never quote their comment verbatim anywhere in the object.", + "Draft the operator's reply comment for Linear OMN-1 (T), replying to comment we/ird id by a collaborator at 2026-09-17T13:01:36.497Z.\n\nSOURCE SET -- the only comment ids and comment texts you may cite. Cite no comment id outside this set; every comment_id in your answer is the one below.\ncomment id: we/ird id\ncomment text:\n---\nCan this land?\n---\nAsks: [\"a\",\"b\"].\n\nGround-truth facts you may cite, and nothing else:\n\n\nOperator voice: plain, direct, first person, SHORT (3 to 10 sentences), no bullets unless listing options, no em-dashes. Answer every ask with a verified fact and cite the ticket, PR, run id or ledger line it comes from. Never invent a fact, never use a placeholder, and never promise a date the facts do not support. Where only the operator can decide, write a bracketed [OPERATOR: ...] line naming the options.\n\nNever write a holding non-answer: no 'I'll look into it', no 'we're tracking this', no 'more soon', no bare acknowledgement of receipt. If the facts above do not answer an ask, that ask goes in open_points_for_operator and does not appear in proposed_reply at all. A reply that answers nothing must come back with proposed_reply empty.\n\nDo not echo the asker's own uncertainty phrasing back at them, and never write a disclaimer about what you or they do not know. State what is true. When something the asker wants is genuinely not in the facts above, do not hedge about it in the reply text: leave it out of proposed_reply and put it in open_points_for_operator instead.\n\nYour entire output must be one JSON object and nothing else. The first character you emit is { and the last is }. No reasoning, no preamble, no explanation, no code fence, no text after the object.\nKeys: ticket, comment_id, their_summary, proposed_reply, facts_cited (array of strings), open_points_for_operator (array of strings), confidence (one of high, medium, low).\nIn their_summary, paraphrase what they asked in your own words. Never quote their comment verbatim anywhere in the object." + ], + "source_set": [ + { + "comment_ids": [ + "bf9a438b-ca16-4e7a-8c92-5f760f0f2df9" + ], + "texts": [ + "AC-binding gap: dod_verify 4/6 verified, 0 failed, but AC1-AC3 have no binds_ac entry in contracts/OMN-18268.yaml; not auto-closable until labeled." + ] + }, + { + "comment_ids": [ + "8354b627-f18a-4359-b1e3-3a9a873c7fcc" + ], + "texts": [ + "Evidence gap: dod_verify 7/15 ACs verified, 2 failed - not all ACs receipt-proven, not flipped." + ] + }, + { + "comment_ids": [ + "568e6a23-973d-4033-aaa6-be60c969de3f" + ], + "texts": [ + "Broker right-sizing readback answering the three questions." + ] + }, + { + "comment_ids": [ + "aa11bb22-cccc-4444-8888-999900001111" + ], + "texts": [ + "What is the governed probe and the expected result for AC1?" + ] + }, + { + "comment_ids": [ + "dd44ee55-6666-4777-8888-99990000aaaa" + ], + "texts": [ + "Automated closure precheck ran; no action required." + ] + }, + { + "comment_ids": [ + "collab_unanswered_ask" + ], + "texts": [ + "Is the inbox path unblocked yet? I still cannot send to the verify subdomain and I do not know whether we are waiting on us or on the registrar." + ] + }, + { + "comment_ids": [ + "e1" + ], + "texts": [ + "" + ] + }, + { + "comment_ids": [ + "e2" + ], + "texts": [ + " \n" + ] + }, + { + "comment_ids": [ + "a1" + ], + "texts": [ + "actor: sweep (sonnet)\nCan this land?" + ] + }, + { + "comment_ids": [ + "a2" + ], + "texts": [ + "Actor: lane-1 (opus)\nbody" + ] + }, + { + "comment_ids": [ + "a3" + ], + "texts": [ + "actor: x\nno parens" + ] + }, + { + "comment_ids": [ + "a4" + ], + "texts": [ + "text first\nactor: x (y)" + ] + }, + { + "comment_ids": [ + "a5" + ], + "texts": [ + "actor: x (y) trailing" + ] + }, + { + "comment_ids": [ + "k1" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "k2" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "k3" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "k4" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "k5" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "k6" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "n1" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "n2" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "n3" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "n4" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "n5" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "n6" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "n7" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "selftest_human_with_ask" + ], + "texts": [ + "Can this land?" + ] + }, + { + "comment_ids": [ + "selftest_automation" + ], + "texts": [ + "Autoclose sweep: this ticket has no dod_evidence." + ] + } + ], + "extract_ids": [ + [], + [ + "0a1b2c3d-0000-4000-8000-00000000abcd" + ], + [ + "0a1b2c3d-0000-4000-8000-00000000abcd" + ], + [], + [], + [], + [ + "zzz999" + ], + [ + "aabbccdd" + ], + [], + [], + [ + "qq11ww22" + ], + [], + [], + [ + "4417aa90" + ], + [], + [] + ] + } +} diff --git a/tests/nodes/node_hourly_tick_decision_compute/test_golden_chain_hourly_tick_decision_compute.py b/tests/nodes/node_hourly_tick_decision_compute/test_golden_chain_hourly_tick_decision_compute.py new file mode 100644 index 0000000000..26d8aa7138 --- /dev/null +++ b/tests/nodes/node_hourly_tick_decision_compute/test_golden_chain_hourly_tick_decision_compute.py @@ -0,0 +1,309 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +"""OMN-20680: the hourly tick decisions, contract to bus to typed result, parity and refusals. + +Parity: tests/fixtures/hourly_tick_parity.json holds 60 cases captured from the +pre-conversion tick (source commit recorded in the file). Golden chain: probe then record +through the registered contract on the in-memory bus. Error chain: every refusal the old +tick made before any write is a refusal here. +""" + +from __future__ import annotations + +import importlib +import json +from importlib.metadata import entry_points +from pathlib import Path +from typing import Any + +import pytest +import yaml +from omnibase_core.enums.enum_workflow_result import EnumWorkflowResult +from pydantic import ValidationError + +import omnimarket.nodes.node_hourly_tick_decision_compute as node_package +from omnimarket.nodes.node_hourly_tick_decision_compute.handlers.handler_hourly_tick_decision import ( + HandlerHourlyTickDecision, + checkpoint_line, + completion_line, + facts_from_tail, + result_payload, + tick_outcome, +) +from omnimarket.nodes.node_hourly_tick_decision_compute.models.model_hourly_tick_decision import ( + ModelHourlyTickDecisionRequest, + ModelHourlyTickDecisionResult, + ModelSweepResult, + ModelTickState, +) +from tests.runtime_local_compat import RuntimeLocal + +pytestmark = pytest.mark.unit + +ROOT = Path(__file__).resolve().parents[3] +NODE_DIR = Path(node_package.__file__).parent +COMMAND_TOPIC = "onex.cmd.omnimarket.hourly-tick-decision-requested.v1" +TERMINAL_TOPIC = "onex.evt.omnimarket.hourly-tick-decision-completed.v1" +PARITY = json.loads((ROOT / "tests/fixtures/hourly_tick_parity.json").read_text()) +HANDLER = HandlerHourlyTickDecision() + +CHECKPOINTS = ( + "- 2026-09-16T21:27:48Z 60m checkpoint: earlier\n" + "- 2026-09-16T21:30:00Z operator mentioned 60m checkpoint later\n" +) +POINTER = "2026-09-16T21:20:00Z\n" +LEDGER = ( + "2026-09-16T21:27:48Z | CLAIM | lane=excluded | before\n" + "2026-09-16T21:30:00Z | TERMINAL | lane=codex:whole ticket=x | prose lane=wrong\n" + "2026-09-16T21:31:00Z | CLAIM | lane=other | body\n" + "2026-09-16T21:40:57Z | TERMINAL | lane=last | body\n" + "2026-09-16T21:40:58Z | CLAIM | lane=excluded | after\n" +) + + +def probe(**overrides: Any) -> dict[str, Any]: + return { + "kind": "probe", + "now": "2026-09-16T21:40:57Z", + "checkpoint_text": CHECKPOINTS, + "sweep_pointer_text": POINTER, + "ledger_text": LEDGER, + "fireId": "fire-123", + **overrides, + } + + +def _contract() -> dict[str, Any]: + return dict(yaml.safe_load((NODE_DIR / "contract.yaml").read_text())) + + +def test_contract_declares_topics_models_handler_and_entry_point() -> None: + contract = _contract() + assert contract["node_type"] == "compute" + assert contract["runtime_dispatch"]["command_topic"] == COMMAND_TOPIC + assert contract["event_bus"]["subscribe_topics"] == [COMMAND_TOPIC] + assert contract["event_bus"]["publish_topics"] == [TERMINAL_TOPIC] + assert contract["terminal_event"] == TERMINAL_TOPIC + binding = contract["handler"] + handler_type = getattr(importlib.import_module(binding["module"]), binding["class"]) + assert issubclass(node_package.NodeHourlyTickDecisionCompute, handler_type) + for side, model in ( + ("input_model", ModelHourlyTickDecisionRequest), + ("output_model", ModelHourlyTickDecisionResult), + ): + declared = getattr( + importlib.import_module(contract[side]["module"]), contract[side]["name"] + ) + assert declared is model + registered = {e.name: e for e in entry_points(group="onex.nodes")} + assert registered["node_hourly_tick_decision_compute"].load() is node_package + + +@pytest.mark.parametrize("case", PARITY["cases"]) +def test_old_behavior_parity(case: dict[str, Any]) -> None: + state = ModelTickState.model_validate(case["state"]) + sweep = ( + ModelSweepResult.model_validate(case["sweep"]) + if case["sweep"] is not None + else None + ) + assert checkpoint_line(state) == case["checkpoint"] + assert completion_line(state, sweep, str(case["run_id"])) == case["completion"] + assert tick_outcome(sweep) == case["outcome"] + assert result_payload("fire-123", sweep) == case["result"] + assert facts_from_tail(state.tail, 12) == case["facts"] + + +def test_parity_fixture_exercises_completed_and_every_degraded_class() -> None: + seen = {(c["outcome"]["phase"], c["outcome"]["class"]) for c in PARITY["cases"]} + assert ("completed", None) in seen + assert {k for p, k in seen if p == "degraded"} == { + "floor_refused", + "truncated", + "unsourced", + "transport", + "timeout", + "unclassified", + } + + +def test_probe_window_counts_lanes_and_sweep_request() -> None: + result = HANDLER.handle(ModelHourlyTickDecisionRequest.model_validate(probe())) + assert result.phase == "prepared" + assert (result.state.rows, result.state.terminal) == (3, 2) + assert result.state.lanes == "lane=codex:whole,lane=last" + assert result.state.ledger_total == 5 + assert result.state.prev == "2026-09-16T21:27:48Z" + assert result.sweep_request is not None + assert result.sweep_request.since == "2026-09-16T21:20:00Z" + assert "operator mentioned" in result.sweep_request.facts + assert result.checkpoint_line == checkpoint_line(result.state) + assert result.facts_source == "derived from the last 12 checkpoint lines" + assert result.completion_line is None + + +def test_probe_caller_facts_take_precedence() -> None: + result = HANDLER.handle( + ModelHourlyTickDecisionRequest.model_validate(probe(facts="given")) + ) + assert result.sweep_request is not None + assert result.sweep_request.facts == "given" + assert result.facts_source == "caller-supplied" + + +def test_skipped_sweep_completes_at_probe_without_a_sweep_request() -> None: + result = HANDLER.handle( + ModelHourlyTickDecisionRequest.model_validate(probe(skipSweep=True, runId="r1")) + ) + assert result.sweep_request is None + assert result.phase == "completed" + assert result.completion_line is not None + assert "hourly-tick run=r1" in result.completion_line + assert result.result_payload is not None + assert result.result_payload["fire_id"] == "fire-123" + + +def test_record_degrades_when_something_needed_a_reply_and_nothing_was_accepted() -> ( + None +): + prepared = HANDLER.handle(ModelHourlyTickDecisionRequest.model_validate(probe())) + done = HANDLER.handle( + ModelHourlyTickDecisionRequest.model_validate( + { + "kind": "record", + "state": prepared.state.model_dump(), + "sweep": { + "needing": 3, + "accepted": 0, + "held": 3, + "held_by_class": {"transport": 3}, + }, + "fireId": "fire-123", + } + ) + ) + assert done.phase == "degraded" + assert done.degraded is True + assert done.held_class == "transport" + assert done.completion_line is not None + assert "held 3, phase=degraded class=transport" in done.completion_line + assert done.result_payload is not None + assert done.result_payload["class"] == "transport" + + +@pytest.mark.parametrize( + ("file", "value"), + [ + ("sweep_pointer_text", "bad"), + ("ledger_text", "no timestamps"), + ("checkpoint_text", "no checkpoint"), + ], +) +def test_probe_refuses_invalid_or_missing_state(file: str, value: str) -> None: + request = ModelHourlyTickDecisionRequest.model_validate(probe(**{file: value})) + with pytest.raises(ValueError, match="hourly-tick"): + HANDLER.handle(request) + + +@pytest.mark.parametrize("tail", [[], ["", " "]]) +def test_empty_facts_refused(tail: list[str]) -> None: + with pytest.raises(ValueError, match="no facts supplied"): + facts_from_tail(tail, 12) + + +@pytest.mark.parametrize( + ("payload", "message"), + [ + ({"kind": "probe"}, "probe requires"), + (probe(now="bad"), "now"), + (probe(fireId="../escape"), "fireId|fire_id"), + (probe(tailLines=0), "greater"), + (probe(state=None, sweep={"needing": 1}), "does not take state or sweep"), + ({"kind": "record"}, "record requires the prepared state"), + ( + {"kind": "record", "state": {"now": "x"}, "ledger_text": "z"}, + "now|prev", + ), + ], +) +def test_error_chain_refuses_before_the_handler_runs( + payload: dict[str, Any], message: str +) -> None: + with pytest.raises(ValidationError, match=message): + ModelHourlyTickDecisionRequest.model_validate(payload) + + +def test_record_refuses_unexpected_probe_inputs() -> None: + prepared = HANDLER.handle(ModelHourlyTickDecisionRequest.model_validate(probe())) + with pytest.raises(ValidationError, match="record does not take ledger_text"): + ModelHourlyTickDecisionRequest.model_validate( + { + "kind": "record", + "state": prepared.state.model_dump(), + "ledger_text": LEDGER, + } + ) + + +async def _run(tmp_path: Path, payload: dict[str, Any]) -> RuntimeLocal: + input_path = tmp_path / "request.json" + input_path.write_text(json.dumps(payload)) + runtime = RuntimeLocal( + workflow_path=NODE_DIR / "contract.yaml", + input_path=input_path, + state_root=tmp_path / "state", + backend_overrides={"event_bus": "inmemory"}, + timeout=10, + ) + await runtime.run_async() + return runtime + + +@pytest.mark.asyncio +async def test_golden_chain_probe_then_record_over_the_bus(tmp_path: Path) -> None: + (tmp_path / "probe").mkdir() + probed = await _run(tmp_path / "probe", probe()) + prepared = probed.handler_result + assert isinstance(prepared, ModelHourlyTickDecisionResult) + assert prepared.phase == "prepared" + assert prepared.state.rows == 3 + (tmp_path / "record").mkdir() + recorded = await _run( + tmp_path / "record", + { + "kind": "record", + "state": json.loads(prepared.state.model_dump_json()), + "sweep": { + "needing": 2, + "accepted": 0, + "held": 2, + "held_by_class": {"timeout": 2}, + }, + "fireId": "fire-123", + }, + ) + done = recorded.handler_result + assert isinstance(done, ModelHourlyTickDecisionResult) + assert (done.phase, done.held_class) == ("degraded", "timeout") + assert ( + ModelHourlyTickDecisionResult.model_validate_json(done.model_dump_json()) + == done + ) + + +@pytest.mark.asyncio +async def test_error_chain_over_the_bus_fails_without_a_result(tmp_path: Path) -> None: + runtime = await _run(tmp_path, {"kind": "probe"}) + assert runtime.handler_result is None + (tmp_path / "state2").mkdir() + bad = tmp_path / "bad.json" + bad.write_text(json.dumps(probe(sweep_pointer_text="bad"))) + refused = RuntimeLocal( + workflow_path=NODE_DIR / "contract.yaml", + input_path=bad, + state_root=tmp_path / "state2", + backend_overrides={"event_bus": "inmemory"}, + timeout=10, + ) + assert await refused.run_async() is EnumWorkflowResult.FAILED + assert refused.handler_result is None diff --git a/tests/nodes/node_linear_comments_decision_compute/test_golden_chain_linear_comments_decision_compute.py b/tests/nodes/node_linear_comments_decision_compute/test_golden_chain_linear_comments_decision_compute.py new file mode 100644 index 0000000000..5e093290a4 --- /dev/null +++ b/tests/nodes/node_linear_comments_decision_compute/test_golden_chain_linear_comments_decision_compute.py @@ -0,0 +1,138 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +"""OMN-20680: contract -> bus -> compute -> typed result, plus the refusal chain. + +Golden chain: a typed decide_items request goes through the registered contract on the +in-memory event bus (RuntimeLocal) and the typed result comes back with the retired +sweep's decisions. Error chain: a request that names a kind without its inputs, or with +inputs the kind does not take, is refused before the handler runs. +""" + +from __future__ import annotations + +import importlib +import json +from importlib.metadata import entry_points +from pathlib import Path +from typing import Any + +import pytest +import yaml +from omnibase_core.enums.enum_workflow_result import EnumWorkflowResult +from pydantic import ValidationError + +import omnimarket.nodes.node_linear_comments_decision_compute as node_package +from omnimarket.nodes.node_linear_comments_decision_compute.models.model_linear_comments_decision import ( + ModelLinearCommentsDecisionRequest, + ModelLinearCommentsDecisionResult, +) +from tests.runtime_local_compat import RuntimeLocal + +pytestmark = pytest.mark.unit + +ROOT = Path(__file__).resolve().parents[3] +NODE_DIR = Path(node_package.__file__).parent +COMMAND_TOPIC = "onex.cmd.omnimarket.linear-comments-decision-requested.v1" +TERMINAL_TOPIC = "onex.evt.omnimarket.linear-comments-decision-completed.v1" +FIXTURE = json.loads( + (ROOT / "tests/fixtures/linear_comments_hourly_parity.json").read_text() +) + + +def _contract() -> dict[str, Any]: + return dict(yaml.safe_load((NODE_DIR / "contract.yaml").read_text())) + + +def test_contract_declares_topics_models_handler_and_entry_point() -> None: + contract = _contract() + assert contract["node_type"] == "compute" + assert contract["descriptor"]["purity"] == "pure" + assert contract["runtime_dispatch"]["command_topic"] == COMMAND_TOPIC + assert contract["event_bus"]["subscribe_topics"] == [COMMAND_TOPIC] + assert contract["event_bus"]["publish_topics"] == [TERMINAL_TOPIC] + assert contract["terminal_event"] == TERMINAL_TOPIC + binding = contract["handler"] + handler_type = getattr(importlib.import_module(binding["module"]), binding["class"]) + assert issubclass(node_package.NodeLinearCommentsDecisionCompute, handler_type) + for side, model in ( + ("input_model", ModelLinearCommentsDecisionRequest), + ("output_model", ModelLinearCommentsDecisionResult), + ): + declared = getattr( + importlib.import_module(contract[side]["module"]), contract[side]["name"] + ) + assert declared is model + registered = {e.name: e for e in entry_points(group="onex.nodes")} + assert registered["node_linear_comments_decision_compute"].load() is node_package + + +@pytest.mark.asyncio +async def test_golden_chain_decides_the_fixture_sweep_over_the_bus( + tmp_path: Path, +) -> None: + request = ModelLinearCommentsDecisionRequest.model_validate( + {"kind": "decide_items", "items": FIXTURE["cases"]["items"]} + ) + input_path = tmp_path / "request.json" + input_path.write_text(request.model_dump_json()) + runtime = RuntimeLocal( + workflow_path=NODE_DIR / "contract.yaml", + input_path=input_path, + state_root=tmp_path / "state", + backend_overrides={"event_bus": "inmemory"}, + timeout=10, + ) + assert await runtime.run_async() is EnumWorkflowResult.COMPLETED + result = runtime.handler_result + assert isinstance(result, ModelLinearCommentsDecisionResult) + round_trip = ModelLinearCommentsDecisionResult.model_validate_json( + result.model_dump_json() + ) + assert round_trip == result + assert result.status == "decided" + selected = list(result.selected_comment_ids or ()) + assert selected == FIXTURE["expected"]["select"] + assert "collab_unanswered_ask" in selected + by_id = {d.comment_id: d for d in result.decisions or ()} + assert by_id["bf9a438b-ca16-4e7a-8c92-5f760f0f2df9"].reason_class == "automation" + assert by_id["8354b627-f18a-4359-b1e3-3a9a873c7fcc"].draft is False + + +@pytest.mark.parametrize( + ("payload", "message"), + [ + ({"kind": "decide_items"}, "decide_items requires items"), + ({"kind": "check_draft", "draft": {}, "facts": "f"}, "requires item"), + ({"kind": "tally", "held": [], "needing_count": 1}, "requires accepted_count"), + ( + {"kind": "detect_truncation", "outcome": {}, "facts": "f"}, + "does not take facts", + ), + ({"kind": "metrics_row", "receipt": None}, "requires receipt"), + ({"kind": "no_such_decision"}, "kind"), + ( + {"kind": "tally", "held": [], "needing_count": -1, "accepted_count": 0}, + "greater", + ), + ], +) +def test_error_chain_refuses_before_the_handler_runs( + payload: dict[str, Any], message: str +) -> None: + with pytest.raises(ValidationError, match=message): + ModelLinearCommentsDecisionRequest.model_validate(payload) + + +@pytest.mark.asyncio +async def test_error_chain_over_the_bus_does_not_complete(tmp_path: Path) -> None: + input_path = tmp_path / "bad.json" + input_path.write_text(json.dumps({"kind": "decide_items"})) + runtime = RuntimeLocal( + workflow_path=NODE_DIR / "contract.yaml", + input_path=input_path, + state_root=tmp_path / "state", + backend_overrides={"event_bus": "inmemory"}, + timeout=10, + ) + assert await runtime.run_async() is EnumWorkflowResult.FAILED + assert runtime.handler_result is None diff --git a/tests/nodes/node_linear_comments_decision_compute/test_parity_linear_comments_decision_compute.py b/tests/nodes/node_linear_comments_decision_compute/test_parity_linear_comments_decision_compute.py new file mode 100644 index 0000000000..ab78f74061 --- /dev/null +++ b/tests/nodes/node_linear_comments_decision_compute/test_parity_linear_comments_decision_compute.py @@ -0,0 +1,186 @@ +# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +"""OMN-20680: the Python decisions reproduce the retired JS pure block, case for case. + +tests/fixtures/linear_comments_hourly_parity.json holds inputs and the outputs the JS pure +block of linear-comments-hourly.js produced for them (source commit recorded in the file). +This suite does not need node or the old script: the fixture is the old behaviour. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +import pytest + +from omnimarket.nodes.node_linear_comments_decision_compute.handlers.handler_linear_comments_decision import ( + HandlerLinearCommentsDecision, + classify_author_role, +) +from omnimarket.nodes.node_linear_comments_decision_compute.models.model_linear_comments_decision import ( + ModelLinearCommentsDecisionRequest as Request, +) + +pytestmark = pytest.mark.unit + +PARITY = json.loads( + ( + Path(__file__).resolve().parents[2] + / "fixtures/linear_comments_hourly_parity.json" + ).read_text() +) +CASES = PARITY["cases"] +EXPECTED = PARITY["expected"] +HANDLER = HandlerLinearCommentsDecision() + + +def _drop_none(value: Any) -> Any: + """JSON.stringify omits undefined; the typed result carries None. Compare without both.""" + if isinstance(value, dict): + return {k: _drop_none(v) for k, v in value.items() if v is not None} + if isinstance(value, list): + return [_drop_none(v) for v in value] + return value + + +def _dump(model: Any) -> Any: + return json.loads(model.model_dump_json()) + + +def test_fixture_is_not_empty_and_has_a_selection_positive_control() -> None: + assert len(CASES["items"]) >= 20 + assert "collab_unanswered_ask" in EXPECTED["select"] + assert "bf9a438b-ca16-4e7a-8c92-5f760f0f2df9" not in EXPECTED["select"] + + +@pytest.mark.parametrize("n", range(len(CASES["roles"]))) +def test_author_role_classification(n: int) -> None: + assert ( + classify_author_role(CASES["roles"][n]["role"]) + == (EXPECTED["classify_author_role"][n]) + ) + + +def test_item_decisions_match_the_old_block() -> None: + result = HANDLER.handle( + Request.model_validate({"kind": "decide_items", "items": CASES["items"]}) + ) + assert result.decisions is not None + got = [_drop_none(_dump(d)) for d in result.decisions] + assert got == [_drop_none(d) for d in EXPECTED["decide"]] + assert list(result.selected_comment_ids or ()) == EXPECTED["select"] + refused = [d for d in got if not d["draft"]] + assert [{k: v for k, v in r.items() if k != "draft"} for r in refused] == [ + _drop_none(r) for r in EXPECTED["refusals"] + ] + assert [d["author_kind"] for d in got] == [ + _drop_none(k) for k in EXPECTED["classify_author"] + ] + + +def test_selection_and_refusals_partition_the_input() -> None: + result = HANDLER.handle( + Request.model_validate({"kind": "decide_items", "items": CASES["items"]}) + ) + assert result.decisions is not None + assert len(result.selected_comment_ids or ()) + len(EXPECTED["refusals"]) == len( + result.decisions + ) + + +@pytest.mark.parametrize("n", range(len(CASES["prompts"]))) +def test_prompt_matches_the_old_block(n: int) -> None: + item, facts = CASES["prompts"][n] + result = HANDLER.handle( + Request.model_validate({"kind": "build_prompt", "item": item, "facts": facts}) + ) + assert result.prompt == EXPECTED["prompt"][n] + + +@pytest.mark.parametrize("n", range(len(CASES["drafts"]))) +def test_citation_check_matches_the_old_block(n: int) -> None: + draft, item, facts = CASES["drafts"][n] + full_item = { + "ticket": "T", + "ticket_title": "T", + "author_role": "r", + "posted_at": "t", + "needs_reply": True, + **item, + } + result = HANDLER.handle( + Request.model_validate( + {"kind": "check_draft", "draft": draft, "item": full_item, "facts": facts} + ) + ) + assert result.citation is not None + assert _dump(result.citation) == EXPECTED["cite"][n] + + +@pytest.mark.parametrize("n", range(len(CASES["trunc"]))) +def test_truncation_matches_the_old_block(n: int) -> None: + result = HANDLER.handle( + Request.model_validate( + {"kind": "detect_truncation", "outcome": CASES["trunc"][n]} + ) + ) + assert result.truncation is not None + assert _dump(result.truncation) == EXPECTED["truncation"][n] + + +@pytest.mark.parametrize("n", range(len(CASES["held"]))) +def test_held_classification_matches_the_old_block(n: int) -> None: + held = dict(CASES["held"][n]) + result = HANDLER.handle( + Request.model_validate({"kind": "classify_held", "held": [held]}) + ) + assert result.held_classes is not None + assert [c.value for c in result.held_classes] == [EXPECTED["classify_held"][n]] + + +@pytest.mark.parametrize("n", range(len(CASES["heldlists"]))) +def test_held_tally_matches_the_old_block(n: int) -> None: + held = CASES["heldlists"][n] + # reason_class "bogus" is not in the closed set: the old block re-classifies it. + result = HANDLER.handle( + Request.model_validate( + {"kind": "tally", "held": held, "needing_count": 1, "accepted_count": 1} + ) + ) + assert result.held_by_class == EXPECTED["held_by_class"][n] + + +@pytest.mark.parametrize("n", range(len(CASES["degraded"]))) +def test_degraded_matches_the_old_block(n: int) -> None: + needing, accepted = CASES["degraded"][n] + result = HANDLER.handle( + Request.model_validate( + { + "kind": "tally", + "held": [], + "needing_count": needing, + "accepted_count": accepted, + } + ) + ) + assert result.degraded is EXPECTED["degraded"][n] + + +@pytest.mark.parametrize("n", range(len(CASES["metrics"]))) +def test_metrics_row_matches_the_old_block(n: int) -> None: + result = HANDLER.handle( + Request.model_validate({"kind": "metrics_row", "receipt": CASES["metrics"][n]}) + ) + assert result.metrics is not None + assert _drop_none(_dump(result.metrics)) == _drop_none(EXPECTED["metrics"][n]) + + +def test_metrics_row_names_the_answering_model_not_the_premium_baseline() -> None: + result = HANDLER.handle( + Request.model_validate({"kind": "metrics_row", "receipt": CASES["metrics"][0]}) + ) + assert result.metrics is not None + assert result.metrics.model == "gemini-2.5-flash-lite" + assert result.metrics.premium_counterfactual_model != result.metrics.model