Repository navigation
fix(operator-queue): an agent-written type is bounded at ingest and at the database (#3385) - #3421
Merged
Merged
Conversation
…d as a rewrite (#3314) The #2915 sync fingerprint must reproduce what ingest stored. Two seams diverged, so an untouched agent entry read SYNC_CHANGED and every operator answer was refused 409 item_diverged: - options: the store keeps a falsy value ([], "", {}, 0, False) as NULL, but `_canonical_options` serialised it ("[]"). It now canonicalises any falsy value like None, mirroring the store's truthiness. - title / question: a JSON number or bool passed the clamp untouched and was stored verbatim (read back backend-dependently: SQLite True -> "1", PG "true"), while `_entry_content` fingerprinted it as the default. A new `_scalar_text` stores it as its text ("123", "True") in the clamp and mirrors that in `_entry_content`, so both sides match on every backend. Not covered: no number conversion before the #3243 title cap check (a numeric title is still never cap-held; scope kept narrow). `type` handling untouched (#3385). Stale rows: asks stored before this fix with a boolean title hold "1" on SQLite and stay flagged as diverged; integer-titled rows recover on the next sync. No migration. Tests: the strict xfails r88-r91 in test_ec_operator_queue_edges.py are removed and r90b (boolean title) added. With the fix absent all five go red: r88/r89 `['options'] == []`, r90/r90b `['title'] == []`, r91 `['question'] == []`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sy options and non-string titles (#3314) The Tier-2 real-SQLite property (entry -> clamp -> store -> sync read -> changed_fields == []) excluded the #3314 inputs while they were strict xfails. It now generates falsy options ([], "", {}, 0, False) and int/bool/float(nan, inf)/[]/{} titles and questions, with explicit examples for `options: []` and `title: 123`. This is the parity guard for the fingerprint mirroring the store's options truthiness. Only the null `type` exclusion (#3315/#3385) remains. With the #3314 service fix reverted the property goes red (`['title', 'question'] == []` on the explicit examples). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e DB sink (#3385) `_clamp_ingested_item` bounded title, question, context and options but copied an agent's free-text `type` straight through. A queue-file entry could therefore store, and broadcast, a multi-MB type. - `_bounded_type`: truncate-with-marker at OPERATOR_QUEUE_TYPE_MAX = 64, strings only. A fixed constant: every platform type literal (the longest is 24 chars) and every ASK_TYPES value passes unchanged. - The clamp applies it. `_comparable_type` applies the same bound before the empty-value default, so the #2915 fingerprint does not report a clamped row as `changed` against its own raw entry. Without that, responses would be refused with 409 `item_diverged`. - DB belt: `_insert_values` raises ValueError for a type over 1 KiB (`_DB_BELT_TYPE_MAX_BYTES`), like the id/title/question/context belts, so a caller that skips the clamp is quarantined (#1525). - Reworded the `ingested` audit comment that said the clamp does not bound `type`. Stacked on feature/3314-queue-fingerprint-falsy (923b1ed). Mutation record (fix reverted on a scratch copy, then restored byte-identical): - clamp line removed: TestClampType::test_oversize_type_shortened_with_marker and ::test_one_over_cap_gets_marker go red - `_comparable_type` bound removed: test_3385_clamped_type_is_not_a_rewrite [clamped-row-vs-raw-entry] goes red - belt removed: TestDbBelt::test_belt_rejects_oversize_type goes red Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…3385) The three docs that enumerate the _clamp_ingested_item truncate-with-marker fields and the create_item DB belt now name type: 64 characters at ingest (OPERATOR_QUEUE_TYPE_MAX, with the truncation marker) and 1 KiB at the belt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…3314) merge-train: mechanical, per the merge-train note on the PR. `_scalar_text(0)` returned "0", but `create_item` stores the `or`-default ("Agent request") for a falsy title/question, and every row ingested before this fix carries that default — so the entry fingerprint "0" diverged from the row and flipped such rows to 409 `item_diverged` on the next sync. Only truthy scalars are stringified now; a 0/False title keeps displaying the default as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
vybe
marked this pull request as ready for review
October 9, 2026 07:34
Contributor
Author
# Conflicts: # src/backend/services/operator_queue_service.py
Contributor
Author
trinity-ability
approved these changes
Oct 9, 2026
trinity-ability
left a comment
Contributor
There was a problem hiding this comment.
merge-train: batch validated on train/20261009-0735 (#3424); full CI re-read green after retarget to dev
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3385 — one of ten small bug fixes in the trinity-pm
chain-easy-1008run. Stacked on #3414 (the fix for #3314): base isfeature/3314-queue-fingerprint-falsy, because both issues edit_clamp_ingested_itemand the fingerprint helpers. Merge #3414 first, then retarget this todev. Draft until the operator merges.What
src/backend/services/operator_queue_service.py: newOPERATOR_QUEUE_TYPE_MAX = 64and_bounded_type. An agent-writtentypelonger than 64 characters is shortened with the existing…[truncated]marker at ingest (_clamp_ingested_item); the ask is never refused or lost._comparable_typeapplies the same bound, so a shortened row does not read as a rewrite (bug(operator-queue): a pending approval can sit unseen for days — the human's card goes stale while the container↔platform sync stays silent #2915) and a row stored before this fix at full length still compares equal.src/backend/db/operator_queue.py::_insert_values:_DB_BELT_TYPE_MAX_BYTES = 1024; above it the insert raisesValueError, like the id / title / question / context belts. All three create paths go through it.architecture/api-endpoints.md,feature-flows/operating-room.mdandrequirements/security.mdthat list the clamp and belt fields now nametype.tests/unit/test_1632_operator_queue_caps.py: an oversized type is shortened with the marker; every platform-emitted type and every typeask_operatoraccepts is unchanged; the belt rejects an oversized type and passes 64 four-byte characters;test_3385_clamped_type_is_not_a_rewrite(clamped row vs raw entry, legacy full-length row, a real rewrite inside the first 52 characters).Rulings carried (orchestrator, on the operator's behalf — plan file)
workspace_problem_report, 24.options: []as a rewrite, so answers are refused with 409 item_diverged #3314 instead of branching fromdev, so the two fixes do not hand the operator a conflict in the same two functions.Review + security
/reviewreading pass (claude-fable-5-1, report-only, against the stacked base): MERGEABLE, zero critical. Census of every literaltypethe platform and the enterprise submodule pass to the insert paths: longest is 24 characters, none is altered. Every truncated value ends in the marker, which no set member can match, so truncation cannot move a value into or out of a set that decides control flow (_BUDGETED_ALERT_TYPESand neighbours). The newValueErroris reachable only from the file sync loop, which quarantines it under its existing handler./cso --diff: no findings; the error text carries the constant, never the value.Fixed after review:
eab91c10— I1, the five doc lines. Noted, not changed: I2, a non-stringtypeover 1 KiB now raises the namedValueErrorwhere the base failed at the bind — same quarantine outcome.Evidence sweep after the fix (claude-opus-5-5): GREEN. 13 operator-queue files ran shuffled (seed 12345), one file per process: 836 passed, 7 xfailed. With both production files taken from the #3314 branch, the two
TestClampTypecases, the belt test and the clamped-row fingerprint case go red.git merge-treeagainstdevf6bedcd is clean; the base branch has not moved (923b1edc).Tests
Counts above. Not run here:
test_ent815_queue_walk,test_ent815_broad_list_agent_scope,test_ent751_gate_entries(2–4 minutes each; they ran green on the #3314 base in that PR's sweep) and the full unit island.Before merge
backend-unit-testdoes not run. The first full run happens when it retargets todevafter fix(operator-queue): an empty options list or a numeric title no longer reads as a rewrite (#3314) #3414 merges — read that run before merging.tests/registry.jsonanddocs/memory/architecture/api-endpoints.md; feat(operator-queue): an agent sees its pending asks and can replace one (#3247) #3256 alsodb/operator_queue.pyand the three docs). A test-merge of this head against each of fix(url-validation): a token in a protocol-relative skills-library URL is refused, not stored (#3323) #3420, fix(credentials): a malformed credential archive is a 400 and an unstorable secret value is a named 422 (#3325) #3417, feat(skills): one Skills tab — own and shared skills, Run, Requires approval (abilityai/trinity-enterprise#754) #3412, fix(public-links): an expired visitor session asks for the email again, never the operator /login (#3406) #3411, feat(workspace): the role card's objectives in plain words — owned first, supported folded, no metric code names (abilityai/trinity-enterprise#843) #3407, fix(files): credential files are owner-tier to read; file reads never follow a link; runtime config files join the write deny lists #3304, feat(access): who an agent serves, its seat, and what reads it (abilityai/trinity-enterprise#810–#814, #817) #3271, feat(operator-queue): an agent sees its pending asks and can replace one (#3247) #3256, feat(workspace): the autonomy dial — what a companion may do unprompted (ent#641) #2984, feat(a2a): AAuth agent identity across instances, behind an off-by-default flag (abilityai/trinity-enterprise#623) #2956, feat(workspace): a stage advance reaches the Work card on the write, not the next poll (ent#533) #2713 and feat(workspace): the rail's Canvas/Files dot lights on the write, not the next refetch (ent#532) #2709 adds no conflict beyond what that PR already has withdev.🤖 Generated with Claude Code