mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
Re-pin the chat contracts on the activity-block predicate
The combined tree's full serial suite ended with 30 failures; 21 of them were static string contracts and source pins that still described the sticky card-presence machinery WP-G deleted (taskUiStates, forceCard/forceTaskCard, markTaskToolCall, markAssistantReply, scheduleTaskUiCleanup, retiredTaskIds, the cardVisible latch, the replay `needsVisibleTerminal` branch, the applyEventTelemetry closure and the HTML-template conversion button). Every one of them is now pinned on the mechanism that replaced it, so the contracts say what the code does instead of guarding a deleted symbol: - presence is ONE predicate (`blockVisible`) whose inputs are record facts, including that the completion note is not content and that a terminal outcome other than Done keeps the block; - `ensureLiveCardVisible` both mounts and unmounts from that predicate, and the rebuild sweep re-runs it for every record instead of reading a latch; - tool rows are content keyed `tool:<group>:<call id>`, so a failure or timeout evolves its own call's row, and replay shows the one summary row `noteToolMetrics` derives from the recorded per-tool counts; - the refetch after a lost task_done is owed only by a visible block; - a lineage-known child is minted as its parent's nested card by whichever path reaches it first, and the conversion button carries the design-system role and marker on the node the chrome sync builds. Two fixture group ids and one test name that named the retirement/force writers were renamed to the behaviour they now pin; the `retiredTaskIds.clear` row is dropped because the stronger `retiredTaskIds`-absent pin owns that fact. Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
This commit is contained in:
parent
e40a3334da
commit
16b8895ced
4 changed files with 39 additions and 11 deletions
File diff suppressed because one or more lines are too long
|
|
@ -531,7 +531,14 @@ def test_project_lifecycle_rows_render_design_system_action_static_contract():
|
|||
# the shared design-system role beside their `btn btn-xs btn-danger` sibling.
|
||||
assert "chat-live-project-btn" not in chat
|
||||
assert "chat-live-project-btn" not in style
|
||||
assert 'class="btn btn-xs btn-default" data-turn-into-project' in chat
|
||||
# The conversion button is now built by the chrome sync from the record's
|
||||
# facts (an HTML template could not be re-derived when a turn is direct),
|
||||
# so the design-system role and the marker app.js queries are set on the
|
||||
# node itself.
|
||||
chrome = chat[chat.index("function syncBlockChrome(record) {"):chat.index("function syncCancelRunButton(record) {")]
|
||||
assert "btn.className = 'btn btn-xs btn-default';" in chrome
|
||||
assert "btn.dataset.turnIntoProject = '1';" in chrome
|
||||
assert "btn.textContent = 'Turn into project';" in chrome
|
||||
# The identity chip keeps its own role, now built once in ui_helpers and
|
||||
# shared by the converted card (chat.js) and the bound-task footer (app.js).
|
||||
assert "chat-live-project-card-btn" in helpers
|
||||
|
|
@ -703,7 +710,9 @@ def test_web_frames_keep_reference_order_and_one_authored_reply():
|
|||
assert reference.index("isModelWaitReference(row)") < reference.index("reviewReferenceFromRow(row)")
|
||||
logs = chat[chat.index("function updateLiveCardFromLogEvent"):chat.index("function addMessage")]
|
||||
assert logs.index("handleCardReference(evt)") < logs.index("const taskId = getLogTaskGroupId(evt)")
|
||||
assert logs.index("handleCardReference(evt)") < logs.index("applyEventTelemetry")
|
||||
# Tool accounting (the telemetry closure's one surviving job) also runs
|
||||
# after the reference seam.
|
||||
assert logs.index("handleCardReference(evt)") < logs.index("noteToolMetrics(taskId, evt, rawTs)")
|
||||
history = chat[chat.index("function applyHistoryMessages"):chat.index("async function syncHistory")]
|
||||
assert history.index("handleCardReference(msg)") < history.index("updateLiveCardFromProgressMessage(msg,")
|
||||
fanout = chat[chat.index("onWs('chat'"):chat.index("onWs('message_annotation'")]
|
||||
|
|
|
|||
|
|
@ -287,13 +287,21 @@ def test_task_done_live_summary_distinguishes_typed_failure():
|
|||
assert "headline: presentation.headline" in source
|
||||
|
||||
|
||||
def test_chat_warning_task_summaries_force_visible_cards():
|
||||
def test_chat_warning_task_summaries_keep_a_visible_block():
|
||||
"""A task that ended warn/error/cancelled keeps its block in the transcript.
|
||||
|
||||
No writer forces that any more (the sticky `forceCard` flag and the replay's
|
||||
`needsVisibleTerminal` branch are gone): the ONE presence predicate admits
|
||||
any terminal outcome whose phase chip is not `done`, so the same summary
|
||||
row mounts the block live, after a reload and after a reconnect, while a
|
||||
zero-tool Done turn leaves nothing behind.
|
||||
"""
|
||||
source = _read("web/modules/chat.js")
|
||||
assert "summary.terminal && summary.phase === 'warn'" in source
|
||||
assert (
|
||||
"const needsVisibleTerminal = severity === 'error' || severity === 'warn'"
|
||||
" || severity === 'cancelled';"
|
||||
) in source
|
||||
predicate = source[source.index("function blockVisible(record) {"):source.index("function noteDirectTurn")]
|
||||
assert "|| (record.finished && record.phaseEl?.dataset?.phase !== 'done');" in predicate
|
||||
# The completion note itself is not content, so Done alone admits nothing.
|
||||
assert "|| record.items.some((item) => !String(item.dedupeKey || '').startsWith('task_done|'))" in predicate
|
||||
assert "forceCard" not in source
|
||||
|
||||
|
||||
def test_chat_scrolls_to_bottom_after_first_history_load():
|
||||
|
|
@ -319,7 +327,13 @@ def test_chat_scrolls_to_bottom_after_first_history_load():
|
|||
"The anchors factory must receive the live-card registry it reads"
|
||||
assert "liveCardRecords.get(entry.taskId)" in anchor_source, \
|
||||
"A rebuilt live card whose earliest timestamp changed needs canonical task lookup"
|
||||
assert "reorderExisting: anchorMovedEarlier" in source, \
|
||||
# One reanchor owner: a card is re-sorted only when its own anchor actually
|
||||
# moved earlier (or its history position did), never on every mutation.
|
||||
assert "const movedEarlier = stampNodeTimestamp(record.root, rawTs, { anchor: true });" in source, \
|
||||
"The reanchor owner must read the node's own anchor move"
|
||||
assert "if (!movedEarlier && !positionChanged) return false;" in source, \
|
||||
"An unmoved anchor must not re-sort a mounted card"
|
||||
assert "ensureLiveCardVisible(record, { reorderExisting: true });" in source, \
|
||||
"A mounted task card must be re-sorted if a later event lowers its anchor"
|
||||
assert "record._anchorOrderDirty = true;" in source
|
||||
assert "reorderDirtyCardIfNeeded(rec);" in source, \
|
||||
|
|
|
|||
|
|
@ -66,7 +66,12 @@ def test_replay_learns_subagent_lineage_before_merging_card_rows():
|
|||
# child card; ephemeral registration now lives in the early reference seam.
|
||||
assert fanout.index("learnSubagentLineage(msg);") < fanout.index("updateLiveCardFromProgressMessage(msg,")
|
||||
assert fanout.index("learnSubagentLineage(msg);") < fanout.index("routeSubagentFinalMessageToCard(explicitTaskId, msg)")
|
||||
assert "forceTaskCard(childId, rawTs);" in src
|
||||
# A lineage-known child is minted as its parent's nested card by whichever
|
||||
# path reaches it first (#636) — no sticky force writer: the parent's anchor
|
||||
# follows the child's frame and the predicate always admits a child block.
|
||||
assert "reanchorTaskCard(getLiveCardRecord(parentId), rawTs);" in src
|
||||
assert "const record = getSubagentCardRecord(childId, parentId, role);" in src
|
||||
assert "if (!record || record.isSubagent) return true;" in src
|
||||
# A child is locked terminal from EITHER a terminal subagent event OR a
|
||||
# genuinely-settled server task_terminal_status; interrupted stays retryable.
|
||||
assert "const replayTerminal = msg.task_terminal_status" in src
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue