Skip to content

fix(runtime-api): preserve runtime API task priority through queue consumption - #5505

Merged
keweichen merged 1 commit into
mainfrom
factory/kc/revalidate-9d91bb5fd090411fc55b5d3b-db03c4712d8d
Oct 14, 2026
Merged

keweichen merged 1 commit into
mainfrom
factory/kc/revalidate-9d91bb5fd090411fc55b5d3b-db03c4712d8d

Conversation

@keweichen

Copy link
Copy Markdown
Collaborator

Priority

Level: medium
Reason: Owner-approved reliability improvement.

Closes

N/A — owner-approved work; no public issue is claimed as closed.

Summary

fix(runtime-api): preserve runtime API task priority through queue consumption

Review first: src/runtime-api/tasks_view.py, tests/runtime-api-tasks-view.test.py.

Checklist

  • Confirmed no other open PR closes the same issue (gh pr list --repo sonichi/sutando --search "closes #N")
  • Git author email matches my CLA-signed email (git log -1 --format='%ae' shows a GH-mapped email, not *.local or noreply@anthropic.com)
  • Single concern per PR — no bundled refactors / drive-by feature additions
  • Confirmed bug exists on upstream/main (or feature isn't already covered)
  • Test added (or N/A explained below)
  • Before/after evidence pasted below — the actual command output for both states, not a description of it (this is the Add demo video to README #1 reason PRs bounce)
  • Every claim in this PR body is verifiable from the diff or the pasted output — no statement the reviewer can't check
  • Live path? If this touches a bridge, network path, delivery loop, or startup, I included a real post-restart round-trip — unit tests / harnesses alone are not accepted for these
  • Stacked PR? I named the parent + merge order and will rebase/update + rerun full checks after the parent lands (or N/A)
  • Scanned added lines for host-specific hardcoded paths and inline home/workspace fallbacks; fixtures are narrowly scoped
  • If this PR adds or edits a CI workflow, I confirmed it actually runs on this PR (see the Checks tab — a branches: filter can silently exclude a stacked PR)
  • Doesn't reinvent workspace / home-path resolution — uses the resolve_workspace / resolveWorkspace / claude_home_path helpers (see CLAUDE.md "Workspace contract"; enforced by the lint-workspace-resolution, lint-claude-home-path, and workspace-leak-check CI checks)

Contribution observations (unchecked items remain reviewer decisions):

  • Duplicate search: Searched PRs (all states) and open issues using Preserve runtime API task priority through queue consumption. Related results for reviewer comparison: feat(bee-channel): Bee wearable as inbound-only channel — SSE watcher onto the broker's /v1/ingest hop #2706, feat(runtime-api): add idempotent task submission #4488, docs(pool): worker-pool design v1 — step 1 of staging #3604 (no router process) #3860, fix(watcher): leave an in-flight routed claim to the next watcher's sweep instead of announcing it #4826, fix(watcher): scope automatic task-event-handler resolution to core only #4503, feat(mentra-bridge): track WIP bridge + wire to DeviceSession registry #740 No close title duplicate was detected; this is not a claim of exhaustive semantic coverage.

  • Current upstream: 7ac30fc87f6281404b48ae8e0d4f3837c571d68b. Current main equals the tested source base; required GitHub checks still run on the PR.

  • Diff review: - review-correctness: Inspected the writer, receipt validation, safe and lenient parsers, envelope stamping and cancellation flow. Moving task: after metadata exposes priority to the unchanged safe reader. Receipt validation accepts precisely the legacy and corrected known-header orders while preserving canonical-byte equality and submission-field comparisons.

  • review-scope: HEAD matches the exact candidate commit; working tree is clean. Base-to-candidate diff changes only the two allowed paths. SHA-256 is 71a43c417fbbec7e43431d7dc37a005f4a22756e40d288c6c3d81f2bf2ec151a. Shared parsing, adapters, claiming, execution and cancellation methods are unchanged.

  • review-maintainability: The production change is small and retains the existing shared submission writer. Compatibility derives the corrected order from the legacy order without duplicating publication logic. Tests use production readers and independently constructed legacy fixtures. Added lines contain no hardcoded host paths; git diff --check passed.

  • Documentation: No standalone documentation files changed. The reviewer should assess the code and contract comments for documentation completeness.

  • Live-path verification: - monitor-user-journey: Executed python3 -B - via heredoc using TasksView and temporary task/result directories. Ordinary and keyed submissions preserved tiers; production sorting returned indices [2, 1, 3, 0] (urgent, normal FIFO, low). Details preserved instance metadata and body injection stayed confined. Cancellation produced an urgent signal without changing the original task.

  • monitor-repeatability: The same Python journey reopened TasksView and retried a keyed submission: identity, file names and bytes remained unchanged. Two retries against an explicitly constructed legacy receipt retained its identity and bytes; conflicting text raised ValueError without publication. Reading a completed result and cancelling twice preserved all files. A separate python3 -B - run independently repeated reopened retry and completed cancellation, exiting 0.

These observations used the isolated candidate; no production deployment was performed.

  • Risks / rollback: Change scope: Place runtime API submission priority where the existing safe queue reader can consume it. Cover ordinary and idempotent submissions and cancellation signals in the existing task-view tests. Preserve compatibility with existing idempotency receipts, collision validation, actor/instance metadata, body confinement and independent submissions. Leave shared priority parsing, other adapters, task claiming, worker execution and cancellation semantics unchanged. Small writer change with receipt-compatibility regression risk.
    Rollback: revert this PR. Production behavior after deployment remains to be observed.
  • Stacking: N/A; one concern against main.
  • Author maps to keweichen; CLA status must be observed on the PR.

Before / after evidence

Before

  • triage-evidence: HEAD matches the pinned commit and git status is clean. Local temporary-directory observations show ordinary and idempotent low/urgent submissions parse as normal; cancellation also parses as normal. Controlled mtimes produce low, normal, urgent order. The writer emits task: before priority:, and the production reader stops at task:. Existing regression suites pass.

Recorded command/output on the pinned source:

/bin/zsh -lc 'python3 -B tests/task-priority-stop-at-task-delimiter.test.py'
✓ test_real_priority_header_parsed
  ✓ test_priority_in_task_body_ignored
  ✓ test_no_priority_header_defaults_normal
  ✓ test_legacy_dashes_separator_still_breaks
  ✓ test_legacy_blank_line_separator_still_breaks
  ✓ test_multi_paragraph_task_with_priority_word_ignored
All parse_priority stop-at-task tests passed.

Exit: 0

/bin/zsh -lc "python3 -B - <<'PY'
import sys,tempfile,os
from pathlib import Path
sys.path[:0]=['src/runtime-api','src']
from tasks_view import TasksView
from task_priority import parse_priority_from_file,sort_tasks_by_priority
t=tempfile.TemporaryDirectory(); v=TasksView(Path(t.name)/'tasks',Path(t.name)/'results','fixture'); paths=[]
for i,p in enumerate(('low','normal','urgent')):
 r=v.submit('work '+p,priority=p); f=v.tasks_dir/(r['taskId']+'.txt'); os.utime(f,(100+i,100+i)); paths.append(f); assert parse_priority_from_file(f)==p
 q=v.submit_idempotent('keyed '+p,idempotency_key=p,priority=p); assert parse_priority_from_file(v.tasks_dir/(q['taskId']+'.txt'))==p
assert sort_tasks_by_priority(paths)==list(reversed(paths))
c=v.cancel(r['taskId']); assert parse_priority_from_file(v.tasks_dir/(c['cancelTaskId']+'.txt'))=='urgent'
a=v.submit('same tier older'); b=v.submit('same tier newer'); af=v.tasks_dir/(a['taskId']+'.txt'); bf=v.tasks_dir/(b['taskId']+'.txt'); os.utime(af,(200,200)); os.utime(bf,(201,201)); assert sort_tasks_by_priority([bf,af])==[af,bf]
r=v.submit('body\\npriority: urgent',priority='low'); assert parse_priority_from_file(v.tasks_dir/(r['taskId']+'.txt'))=='low'
print('PASS')
PY"
Traceback (most recent call last):
  File "<stdin>", line 8, in <module>
AssertionError

Exit: 1

After

  • acceptance-1: Ordinary and idempotent submissions preserved urgent, normal and low through the production queue priority reader.

  • acceptance-2: Controlled-mtime checks confirmed urgent precedes normal precedes low, with FIFO ordering for equal priorities.

  • acceptance-3: Cancellation signals parsed as urgent. Completed-task cancellation returned false and left the task-file set unchanged.

  • acceptance-4: Legacy-receipt tests passed for pending, claimed, archived and result-only states, with and without instance metadata. Retries preserved identity and receipt bytes without duplicate publication; conflicting text, priority, actor and instance were rejected.

  • acceptance-5: Ownership, instance metadata, confinement and independent-submission tests passed. Embedded priority text could not override declared priority in either submission path.

  • check-priority-roundtrip: Executed the frozen Python program via stdin; exit 0, output PASS.

  • check-task-view-regressions: python3 -B tests/runtime-api-tasks-view.test.py exited 0: Ran 67 tests in 0.324s; OK.

  • check-priority-boundary: python3 -B tests/task-priority-stop-at-task-delimiter.test.py exited 0; all six parser boundary cases passed.

Test plan

python3 -B -c 'import sys,tempfile,os; from pathlib import Path; sys.path[:0]=['"'"'src/runtime-api'"'"','"'"'src'"'"']; from tasks_view import TasksView; from task_priority import parse_priority_from_file,sort_tasks_by_priority; t=tempfile.TemporaryDirectory(); v=TasksView(Path(t.name)/'"'"'tasks'"'"',Path(t.name)/'"'"'results'"'"','"'"'fixture'"'"'); paths=[]
for i,p in enumerate(('"'"'low'"'"','"'"'normal'"'"','"'"'urgent'"'"')):
 r=v.submit('"'"'work '"'"'+p,priority=p); f=v.tasks_dir/(r['"'"'taskId'"'"']+'"'"'.txt'"'"'); os.utime(f,(100+i,100+i)); paths.append(f); assert parse_priority_from_file(f)==p
 q=v.submit_idempotent('"'"'keyed '"'"'+p,idempotency_key=p,priority=p); assert parse_priority_from_file(v.tasks_dir/(q['"'"'taskId'"'"']+'"'"'.txt'"'"'))==p
assert sort_tasks_by_priority(paths)==list(reversed(paths))
c=v.cancel(r['"'"'taskId'"'"']); assert parse_priority_from_file(v.tasks_dir/(c['"'"'cancelTaskId'"'"']+'"'"'.txt'"'"'))=='"'"'urgent'"'"'
a=v.submit('"'"'same tier older'"'"'); b=v.submit('"'"'same tier newer'"'"'); af=v.tasks_dir/(a['"'"'taskId'"'"']+'"'"'.txt'"'"'); bf=v.tasks_dir/(b['"'"'taskId'"'"']+'"'"'.txt'"'"'); os.utime(af,(200,200)); os.utime(bf,(201,201)); assert sort_tasks_by_priority([bf,af])==[af,bf]
r=v.submit('"'"'body\npriority: urgent'"'"',priority='"'"'low'"'"'); assert parse_priority_from_file(v.tasks_dir/(r['"'"'taskId'"'"']+'"'"'.txt'"'"'))=='"'"'low'"'"'
print('"'"'PASS'"'"')'

Observed: PASS; exit 0.

PASS
python3 -B tests/runtime-api-tasks-view.test.py

Observed: PASS; exit 0.

test_archived_after_the_ready_read_still_returns_the_answer (__main__.ArchivalRaceTests.test_archived_after_the_ready_read_still_returns_the_answer) ... ok
test_archived_during_enumeration_is_absent_not_an_error (__main__.ArchivalRaceTests.test_archived_during_enumeration_is_absent_not_an_error) ... ok
test_positive_control_the_result_is_listed_normally (__main__.ArchivalRaceTests.test_positive_control_the_result_is_listed_normally) ... ok
test_submit_idempotency_rejects_invalid_keys_and_collisions (__main__.DispatchTests.test_submit_idempotency_rejects_invalid_keys_and_collisions) ... ok
test_submit_idempotency_retry_after_restart_and_completion (__main__.DispatchTests.test_submit_idempotency_retry_after_restart_and_completion) ... ok
test_submit_status_result_dispatch (__main__.DispatchTests.test_submit_status_result_dispatch) ... ok
test_submit_without_key_remains_non_idempotent (__main__.DispatchTests.test_submit_without_key_remains_non_idempotent) ... ok
test_unconfigured_tasks_fails_loudly (__main__.DispatchTests.test_unconfigured_tasks_fails_loudly) ... ok
test_empty_result_is_not_a_terminal_state (__main__.ForeignTaskOwnershipTests.test_empty_result_is_not_a_terminal_state) ... ok
test_foreign_result_is_not_readable_by_id (__main__.ForeignTaskOwnershipTests.test_foreign_result_is_not_readable_by_id) ... ok
test_foreign_task_cannot_be_cancelled (__main__.ForeignTaskOwnershipTests.test_foreign_task_cannot_be_cancelled) ... ok
test_foreign_task_details_are_not_exposed (__main__.ForeignTaskOwnershipTests.test_foreign_task_details_are_not_exposed) ... ok
test_foreign_task_is_not_enumerated (__main__.ForeignTaskOwnershipTests.test_foreign_task_is_not_enumerated) ... ok
test_foreign_task_status_reads_as_absent (__main__.ForeignTaskOwnershipTests.test_foreign_task_status_reads_as_absent) ... ok
test_positive_control_own_task_is_still_visible_everywhere (__main__.ForeignTaskOwnershipTests.test_positive_control_own_task_is_still_visible_everywhere) ... ok
test_the_answer_landing_later_is_seen (__main__.ForeignTaskOwnershipTests.test_the_answer_landing_later_is_seen) ... ok
test_torn_utf8_result_is_not_a_terminal_state (__main__.ForeignTaskOwnershipTests.test_torn_utf8_result_is_not_a_terminal_state) ... ok
test_unready_newest_does_not_mask_the_answer_behind_it (__main__.ForeignTaskOwnershipTests.test_unready_newest_does_not_mask_the_answer_behind_it) ... ok
test_unready_result_is_not_listed (__main__.ForeignTaskOwnershipTests.test_unready_result_is_not_listed) ... ok
test_whitespace_only_result_is_not_a_terminal_state (__main__.ForeignTaskOwnershipTests.test_whitespace_only_result_is_not_a_terminal_state) ... ok
test_a_listed_id_can_fetch_its_result (__main__.TasksViewAssignedIdTests.test_a_listed_id_can_fetch_its_result) ... ok
test_a_name_the_grammar_rejects_is_skipped_not_emitted_as_null (__main__.TasksViewAssignedIdTests.test_a_name_the_grammar_rejects_is_skipped_not_emitted_as_null) ... ok
test_assigned_and_claimed_files_list_their_canonical_id (__main__.TasksViewAssignedIdTests.test_assigned_and_claimed_files_list_their_canonical_id) ... ok
test_concurrent_key_collision_fails_closed (__main__.TasksViewIdempotencyTests.test_concurrent_key_collision_fails_closed) ... ok
test_concurrent_same_key_publishes_one_task_and_one_receipt (__main__.TasksViewIdempotencyTests.test_concurrent_same_key_publishes_one_task_and_one_receipt) ... ok
test_existing_task_id_collision_is_not_mistaken_for_the_receipt (__main__.TasksViewIdempotencyTests.test_existing_task_id_collision_is_not_mistaken_for_the_receipt) ... ok
test_extra_trusted_header_is_not_the_same_submission (__main__.TasksViewIdempotencyTests.test_extra_trusted_header_is_not_the_same_submission) ... ok
test_invalid_idempotency_keys_publish_nothing (__main__.TasksViewIdempotencyTests.test_invalid_idempotency_keys_publish_nothing) ... ok
test_key_collision_covers_every_submission_field (__main__.TasksViewIdempotencyTests.test_key_collision_covers_every_submission_field) ... ok
test_legacy_submit_remains_non_idempotent (__main__.TasksViewIdempotencyTests.test_legacy_submit_remains_non_idempotent) ... ok
test_non_utf8_idempotency_key_publishes_nothing (__main__.TasksViewIdempotencyTests.test_non_utf8_idempotency_key_publishes_nothing) ... ok
test_pre_change_receipts_preserve_identity_and_reject_conflicts (__main__.TasksViewIdempotencyTests.test_pre_change_receipts_preserve_identity_and_reject_conflicts) ... ok
test_publication_race_accepts_result_after_identical_winner_finishes (__main__.TasksViewIdempotencyTests.test_publication_race_accepts_result_after_identical_winner_finishes) ... ok
test_publication_race_revalidates_the_winning_task (__main__.TasksViewIdempotencyTests.test_publication_race_revalidates_the_winning_task) ... ok
test_publication_race_with_no_observable_winner_fails_closed (__main__.TasksViewIdempotencyTests.test_publication_race_with_no_observable_winner_fails_closed) ... ok
test_receipt_recovers_a_crash_before_publication (__main__.TasksViewIdempotencyTests.test_receipt_recovers_a_crash_before_publication) ... ok
test_result_only_receipt_never_requeues_completed_work (__main__.TasksViewIdempotencyTests.test_result_only_receipt_never_requeues_completed_work) ... ok
test_retry_waits_for_publication_lock_through_claim_transition (__main__.TasksViewIdempotencyTests.test_retry_waits_for_publication_lock_through_claim_transition) ... ok
test_same_key_survives_restart_and_archive_without_a_second_task (__main__.TasksViewIdempotencyTests.test_same_key_survives_restart_and_archive_without_a_second_task) ... ok
test_unreadable_receipt_fails_closed (__main__.TasksViewIdempotencyTests.test_unreadable_receipt_fails_closed) ... ok
test_unready_result_evidence_also_prevents_requeue (__main__.TasksViewIdempotencyTests.test_unready_result_evidence_also_prevents_requeue) ... ok
test_cancel_done_task_is_noop_and_unknown_raises (__main__.TasksViewTests.test_cancel_done_task_is_noop_and_unknown_raises) ... ok
test_cancel_writes_cancel_instruction_signal (__main__.TasksViewTests.test_cancel_writes_cancel_instruction_signal) ... ok
test_details_roundtrip (__main__.TasksViewTests.test_details_roundtrip) ... ok
test_genuine_gateway_suffix_still_resolves_beside_the_longer_id (__main__.TasksViewTests.test_genuine_gateway_suffix_still_resolves_beside_the_longer_id) ... ok
test_get_result_live_and_archived (__main__.TasksViewTests.test_get_result_live_and_archived) ... ok
test_get_result_monthly_and_gateway_archives (__main__.TasksViewTests.test_get_result_monthly_and_gateway_archives) ... ok
test_get_result_no_id_empty_is_none (__main__.TasksViewTests.test_get_result_no_id_empty_is_none) ... ok
test_get_result_no_id_returns_newest_runtime_api_only (__main__.TasksViewTests.test_get_result_no_id_returns_newest_runtime_api_only) ... ok
test_list_results_runtime_api_only_newest_first (__main__.TasksViewTests.test_list_results_runtime_api_only_newest_first) ... ok
test_list_results_truncated_counts_ready_not_files (__main__.TasksViewTests.test_list_results_truncated_counts_ready_not_files) ... ok
test_list_results_window_is_spent_on_ready_answers_only (__main__.TasksViewTests.test_list_results_window_is_spent_on_ready_answers_only) ... ok
test_list_tasks_empty_dir (__main__.TasksViewTests.test_list_tasks_empty_dir) ... ok
test_list_tasks_enumerates_live_only (__main__.TasksViewTests.test_list_tasks_enumerates_live_only) ... ok
test_longer_task_ids_gateway_archive_is_not_this_task (__main__.TasksViewTests.test_longer_task_ids_gateway_archive_is_not_this_task)
A longer id's epoch-suffixed archive shares this id's prefix. ... ok
test_queue_priority_roundtrip_order_and_body_confinement (__main__.TasksViewTests.test_queue_priority_roundtrip_order_and_body_confinement) ... ok
test_status_broken_hitl_lookup_fails_open (__main__.TasksViewTests.test_status_broken_hitl_lookup_fails_open) ... ok
test_status_lifecycle_pending_claimed_done (__main__.TasksViewTests.test_status_lifecycle_pending_claimed_done) ... ok
test_status_unknown_task (__main__.TasksViewTests.test_status_unknown_task) ... ok
test_status_waiting_for_states_from_hitl_lookup (__main__.TasksViewTests.test_status_waiting_for_states_from_hitl_lookup) ... ok
test_submit_confines_newline_injection (__main__.TasksViewTests.test_submit_confines_newline_injection) ... ok
test_submit_rejects_empty_and_bad_priority (__main__.TasksViewTests.test_submit_rejects_empty_and_bad_priority) ... ok
test_submit_stamps_instance_id_when_scoped (__main__.TasksViewTests.test_submit_stamps_instance_id_when_scoped) ... ok
test_submit_writes_canonical_headers_real_parser_roundtrip (__main__.TasksViewTests.test_submit_writes_canonical_headers_real_parser_roundtrip) ... ok
test_get_result_traversal_reads_as_absent (__main__.TraversalGuardTests.test_get_result_traversal_reads_as_absent) ... ok
test_hostile_ids_absent_on_every_entry_point (__main__.TraversalGuardTests.test_hostile_ids_absent_on_every_entry_point) ... ok
test_legit_ids_still_resolve (__main__.TraversalGuardTests.test_legit_ids_still_resolve) ... ok

----------------------------------------------------------------------
Ran 67 tests in 0.281s

OK
python3 -B tests/task-priority-stop-at-task-delimiter.test.py

Observed: PASS; exit 0.

✓ test_real_priority_header_parsed
  ✓ test_priority_in_task_body_ignored
  ✓ test_no_priority_header_defaults_normal
  ✓ test_legacy_dashes_separator_still_breaks
  ✓ test_legacy_blank_line_separator_still_breaks
  ✓ test_multi_paragraph_task_with_priority_word_ignored
All parse_priority stop-at-task tests passed.

Canonical review scanner:

review-checks: PASS (hardcoded-paths + root-artifacts + prose-cap clean)
prose-cap: PASS (comment blocks within cap 2, exts .py)

Verified candidate: cb4c4012ec38c8f3a4f3eb82ea9aeb64bdb770de. Publication commit: 10d9ea902c8b15d73e397f9782b46b149100cbb2. Both have tree 9ce834687d2389b6411a7acecc5fd5507505744f; only factory-authored commit metadata changed. Acceptance evidence refers to the verified candidate; the scanner ran on the publication tree.

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at 10d9ea9 by running the diff, not just reading it.

  • Copied the new test file onto the parent commit (7ac30fc) and ran it: 5 failures, including the exact described symptom — parse_priority_from_file reads back normal for a task actually submitted with priority=low or priority=urgent. At head, all 67 tests pass.
  • Read the fix: _submission_text previously wrote task: {text}\n as the THIRD line, before source/channel_id/user_id/access_tier/priority. Per this repo's own documented header-parser contract (CLAUDE.md "Chat-path task tracking": "task: must be the LAST header... a task written with task: earlier routes nowhere and sorts as normal however it declares itself"), every real header after task: was being swallowed into the body by the strict parser — this PR moves task: to actually be last, matching the documented contract. Clean, well-targeted fix of a real silent-priority-loss bug, not a new invention.
  • _check_record's widened record_keys in (expected_keys, task_last_keys) check is the right backward-compatibility move: an idempotency receipt published under the OLD ordering (before this fix) still validates as the same submission, so an in-flight retry across the deploy doesn't spuriously see "already bound" or duplicate.
  • test_pre_change_receipts_preserve_identity_and_reject_conflicts covers the legacy-ordering compatibility directly (8 state/instance combinations), and the negative cases (different task text/priority/actor/scope) still correctly raise "already bound".
  • No regressions: tests/runtime-api-dispatcher.test.py passes at head. review-checks.sh on the diff: clean.
  • Coordination note, not a blocker: this diff touches tests/runtime-api-tasks-view.test.py, same file as #5490 (already approved, not yet merged). I merged #5490's head into this branch locally as a check — it auto-merges cleanly, different regions of the file, so no rebase conflict expected either order.

APPROVE.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve at 10d9ea9. I reviewed this independently in an isolated worktree. No blockers.

The bug exists on main (base 7ac30fc). _submission_text wrote task: third. parse_priority_from_text stops at the first task:, so it never saw priority:. Every runtime-API submission and cancel signal read back as normal. The head writes task: last, so the declared tier now reaches task_queue.py and delivery/task_dispatch.py through sort_tasks_by_priority.

Before/after evidence. I ran the same script against both trees. It submits low@mtime100, normal@101, urgent@102 through submit and submit_idempotent, then checks a default submit, a cancel, and a forged body.

BASE 7ac30fc:

submit low    -> normal; idempotent low    -> normal
submit normal -> normal; idempotent normal -> normal
submit urgent -> normal; idempotent urgent -> normal
default(no priority) -> normal
cancel signal -> normal
forged body urgent, declared low -> normal
queue order (low@100,normal@101,urgent@102): ['low', 'normal', 'urgent']
header order: ['id', 'envelope_hmac', 'timestamp', 'task', 'source', 'channel_id', 'user_id', 'access_tier', 'priority']

HEAD 10d9ea9:

submit low    -> low; idempotent low    -> low
submit normal -> normal; idempotent normal -> normal
submit urgent -> urgent; idempotent urgent -> urgent
default(no priority) -> normal
cancel signal -> urgent
forged body urgent, declared low -> low
queue order (low@100,normal@101,urgent@102): ['urgent', 'normal', 'low']
header order: ['id', 'envelope_hmac', 'timestamp', 'source', 'channel_id', 'user_id', 'access_tier', 'priority', 'task']
  • low stays low end to end and is not normalized to normal.
  • Normal and default behavior is unchanged. With no priority, dispatcher.py:306-308 defaults to "normal", which matches default_priority_for_source("runtime-api").
  • task: is now the last header, as CLAUDE.md requires. The HMAC stamp is still inserted after id:.

The PR's new tests fail on base and pass on head:

  • base with the head test file: Ran 67 tests ... FAILED (failures=5). The failures are test_cancel_writes_cancel_instruction_signal and 4 subtests of test_queue_priority_roundtrip_order_and_body_confinement.
  • head: Ran 67 tests ... OK.

Mutations at head. Each one was caught by tests/runtime-api-tasks-view.test.py:

  • M1: invalid priority passes and is written as urgent. Caught: FAIL test_submit_rejects_empty_and_bad_priority.
  • M2: low is written as normal. Caught: FAIL test_queue_priority_roundtrip... (priority='low') plus test_details_roundtrip.
  • M3: the legacy (task-third) receipt order is no longer accepted. Caught: 8 ERROR test_pre_change_receipts_preserve_identity_and_reject_conflicts subtests (pending, claimed, archived and result-only, each with and without an instance).
  • M4: the cancel signal is written as normal. Caught: FAIL test_cancel_writes_cancel_instruction_signal.

Invalid input and authorization.

  • _validated_submission still rejects anything outside urgent|normal|low.
  • The dispatcher maps that ValueError to JSON-RPC -32602. An invalid value cannot become urgent. On the read side, task_priority still fails open to normal.
  • A forged priority: urgent in the body stays confined, because _one_line keeps the text on one line and the reader stops at task:.
  • The tier is unchanged. The socket is same-user 0600, access_tier: owner is fixed by the writer, and the actor comes from resolve_actor_id on the daemon side, not from params. An owner choosing urgent is within policy.
  • Transport/dispatcher boundaries are unchanged. The fix stays in tasks_view.py, behind the dispatcher.

Worst-case disruption.

  • Runtime-API tasks submitted as urgent now really do jump ahead of voice-tier tasks. That is the intent, and only a same-user owner caller can trigger it.
  • Receipts and queued files written before the upgrade keep the old order. They are accepted by _check_record (record_keys in (expected_keys, task_last_keys)) and keep reading as normal, which is harmless.
  • Rollback is a clean revert.

Other tests run at head, all exit 0: task-priority*.test.py (3 files), task-queue*.test.py, task-envelope*.test.py, gateway-task-envelope-golden, bridges-envelope-stamp, health*task-queue* (3 files), and every runtime-api-*.test.py (dispatcher, e2e, server, push, store and others).

Hardcoded-path scan: git diff origin/main...HEAD | bash scripts/review-checks.sh gives review-checks: PASS (hardcoded-paths + root-artifacts + prose-cap clean). A manual grep of the added lines for /Users/, /home/, ~/. and /usr/bin/ found nothing.

Nits (non-blocking):

  • task_last_keys sits next to the still-named expected_keys legacy list. Renaming it to legacy_keys, or adding a 1-line comment that the task-third order is pre-change receipts only, would make it clearer which order is canonical.
  • The PR body checklist is left unchecked even though the evidence below it covers most items.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Gate

✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 91%.

Diff Coverage

Diff: origin/main...HEAD, staged and unstaged changes

  • src/runtime-api/tasks_view.py (100%)

Summary

  • Total: 1 line
  • Missing: 0 lines
  • Coverage: 100%

@keweichen
keweichen merged commit 14d3f74 into main Oct 14, 2026
34 checks passed
@keweichen
keweichen deleted the factory/kc/revalidate-9d91bb5fd090411fc55b5d3b-db03c4712d8d branch October 14, 2026 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants