mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
P5.1: record a refused child-result disposition as an argument error
Owner item I23: a typed refusal was being recorded as status=ok. Add one row to _EXACT_IDENTIFIER_CODES mapping CHILD_RESULT_DISPOSITION_INVALID to the EXISTING TOOL_ARG_ERROR code, matching the typed arms join_ledger.py already publishes for the single form. The register is BY CODE, so this one row also covers the three plain-string producers that never publish a typed result (the batch envelope and per-entry rejections in join_ledger.py, the ledger-append path in task_tree_ledger.py, and the task_tree.py schema text) and re-reads the stored traces of every past refusal, which a producer-only cutover cannot. CHILD_RESULT_DISPOSITION_PARTIAL deliberately stays LEGACY_WARNING: those entries did record. ouroboros/tools/tool_result.py is SAFETY_CRITICAL (runtime_mode_policy.py:229). The diff in that file is exactly the one table row named by the phase; no spec, no bucket, no producer and no classifier branch changed. HELD, not implemented here: the STEER_REJECTED and STEER_UNCONFIRMED rows of the same owner item wait for an owner answer on whether a refused steer touches the task's health axis (batch #3, R3-32). Tests: one APPROVED_DELTAS row in tests/test_tool_classification_differential.py (the table fails both on unapproved divergence and on rows that no longer fire), and the four text-only pins in tests/test_child_result_disposition.py now also assert the typed code, including the partial-batch carve-out. The frozen golden fixture is untouched: ident:CHILD_RESULT_DISPOSITION_INVALID already exists in it.
This commit is contained in:
parent
8c9ca1ea1c
commit
2e525cb138
3 changed files with 25 additions and 0 deletions
|
|
@ -717,6 +717,7 @@ _EXACT_IDENTIFIER_CODES = MappingProxyType(
|
|||
"BROWSER_BACKLOG_RETIRED_SESSIONS": "LEGACY_UNAVAILABLE",
|
||||
"TOOL_ARG_ERROR": "TOOL_ARG_ERROR",
|
||||
"INVALID_ARG": "TOOL_ARG_ERROR",
|
||||
"CHILD_RESULT_DISPOSITION_INVALID": "TOOL_ARG_ERROR",
|
||||
"TOOL_ERROR": "TOOL_ERROR",
|
||||
"TOOL_INTERNAL_ERROR": "TOOL_INTERNAL_ERROR",
|
||||
"EXECUTOR_UNAVAILABLE": "LEGACY_UNAVAILABLE",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,17 @@ def _parent_ctx(tmp_path, task_id: str = "parent1") -> SimpleNamespace:
|
|||
)
|
||||
|
||||
|
||||
def _typed_code(text: str) -> str:
|
||||
"""The code the one classifier assigns to a refusal sentence (owner item I23).
|
||||
|
||||
The plain-string producers below never publish a typed result, so the
|
||||
identifier table is what decides whether a refused disposition is recorded as
|
||||
an argument error or as a success."""
|
||||
from ouroboros.tools.tool_result import LegacyTextResultAdapter
|
||||
|
||||
return LegacyTextResultAdapter.from_text("tree_note", text).code
|
||||
|
||||
|
||||
def _payload(child_id: str, disposition: str, result_sha256: str) -> dict:
|
||||
return {
|
||||
"type": "child_result_disposition",
|
||||
|
|
@ -236,6 +247,7 @@ def test_malformed_disposition_names_every_violation_in_one_reply(tmp_path):
|
|||
result = _tree_note(_parent_ctx(tmp_path), "decision", "x" * 501, payload=bad)
|
||||
|
||||
assert result.count("CHILD_RESULT_DISPOSITION_INVALID") == 1
|
||||
assert _typed_code(result) == "TOOL_ARG_ERROR"
|
||||
for fragment in (
|
||||
"unknown key(s) supports_claims",
|
||||
"disposition must be one of",
|
||||
|
|
@ -281,6 +293,7 @@ def test_ledger_append_renders_the_same_aggregated_violations(tmp_path):
|
|||
)
|
||||
|
||||
assert out.startswith("⚠️ CHILD_RESULT_DISPOSITION_INVALID:")
|
||||
assert _typed_code(out) == "TOOL_ARG_ERROR"
|
||||
for fragment in (
|
||||
"unknown key(s) supports_claims",
|
||||
"disposition must be one of",
|
||||
|
|
@ -394,6 +407,8 @@ def test_batch_disposition_rejects_invalid_entries_individually(tmp_path):
|
|||
assert "[stranger9] ⚠️ CHILD_RESULT_LINEAGE_FORBIDDEN" in result
|
||||
assert "disposition must be one of" in result
|
||||
assert "[entry 4] ⚠️ CHILD_RESULT_DISPOSITION_INVALID: entry must be a JSON object." in result
|
||||
# A partial batch stays a warning: the recorded entries are real work.
|
||||
assert _typed_code(result) == "LEGACY_WARNING"
|
||||
rows = tree_ledger_rows("parent1", data_root=tmp_path)
|
||||
assert [row["payload"]["child_task_id"] for row in rows] == ["child1"]
|
||||
|
||||
|
|
@ -413,6 +428,7 @@ def test_batch_disposition_envelope_is_validated_atomically(tmp_path):
|
|||
result = _tree_note(_parent_ctx(tmp_path), "decision", "why", payload=payload)
|
||||
assert "CHILD_RESULT_DISPOSITION_INVALID" in result
|
||||
assert "atomic no-op" in result
|
||||
assert _typed_code(result) == "TOOL_ARG_ERROR"
|
||||
wrong_kind = _tree_note(
|
||||
_parent_ctx(tmp_path),
|
||||
"note",
|
||||
|
|
|
|||
|
|
@ -259,6 +259,14 @@ APPROVED_DELTAS: Mapping[str, Delta] = MappingProxyType({
|
|||
"native:LEGACY_UNAVAILABLE:CHILD_RESULT_STALE": Delta(False, "ok", True, "unavailable", "A.B7", "join_ledger has no current child result to bind, unlike a changed result's policy denial"),
|
||||
"native:LEGACY_UNAVAILABLE:TASK_NOT_FOUND": Delta(False, "ok", True, "unavailable", "A.B7", "forward_to_worker has no registered target for this task id"),
|
||||
"native:TOOL_ARG_ERROR:CHILD_RESULT_DISPOSITION_INVALID": Delta(False, "ok", True, "argument_error", "A.B7", "join_ledger rejects malformed disposition arguments before recording them"),
|
||||
# Owner item I23: a typed refusal must be recorded as a refusal, not as ok.
|
||||
# The single form already publishes the typed argument error above; the batch
|
||||
# envelope, the per-entry rejections and the ledger-append path return the
|
||||
# same sentence as a PLAIN STRING, so only the identifier table reaches them
|
||||
# (and the stored traces of every past refusal). The register is BY CODE, so
|
||||
# one row covers all four producers. CHILD_RESULT_DISPOSITION_PARTIAL keeps
|
||||
# its warning: those entries did record.
|
||||
"CHILD_RESULT_DISPOSITION_INVALID": Delta(False, "ok", True, "argument_error", "A.I23", "a disposition the ledger refused to record is an argument error, not a success"),
|
||||
"native:TOOL_ARG_ERROR:ERROR": Delta(False, "ok", True, "argument_error", "A.B7", "both commit entry points reject an empty commit message before attempting a commit"),
|
||||
"native:TOOL_ARG_ERROR:REJECTED": Delta(False, "ok", True, "argument_error", "A.B7", "scratchpad and identity writers reject empty or malformed content before writing"),
|
||||
"native:TOOL_ERROR:TASK_MESSAGE_UNWRITTEN": Delta(False, "ok", True, "error", "A.B7", "forward_to_worker failed to persist the requested message"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue