Skip to content

fix(operator-queue): an agent-written type is bounded at ingest and at the database (#3385) - #3421

Merged
vybe merged 7 commits into
devfrom
feature/3385-queue-type-bound
Oct 9, 2026
Merged

vybe merged 7 commits into
devfrom
feature/3385-queue-type-bound

Conversation

@vybe

@vybe vybe commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #3385 — one of ten small bug fixes in the trinity-pm chain-easy-1008 run. Stacked on #3414 (the fix for #3314): base is feature/3314-queue-fingerprint-falsy, because both issues edit _clamp_ingested_item and the fingerprint helpers. Merge #3414 first, then retarget this to dev. Draft until the operator merges.

What

  • src/backend/services/operator_queue_service.py: new OPERATOR_QUEUE_TYPE_MAX = 64 and _bounded_type. An agent-written type longer than 64 characters is shortened with the existing …[truncated] marker at ingest (_clamp_ingested_item); the ask is never refused or lost. _comparable_type applies 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 raises ValueError, like the id / title / question / context belts. All three create paths go through it.
  • Docs: five lines in architecture/api-endpoints.md, feature-flows/operating-room.md and requirements/security.md that list the clamp and belt fields now name type.
  • Tests in tests/unit/test_1632_operator_queue_caps.py: an oversized type is shortened with the marker; every platform-emitted type and every type ask_operator accepts 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)

Review + security

/review reading pass (claude-fable-5-1, report-only, against the stacked base): MERGEABLE, zero critical. Census of every literal type the 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_TYPES and neighbours). The new ValueError is 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-string type over 1 KiB now raises the named ValueError where 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 TestClampType cases, the belt test and the clamped-row fingerprint case go red. git merge-tree against dev f6bedcd 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

🤖 Generated with Claude Code

Trinity Agent (trinity) and others added 4 commits October 8, 2026 18:54
…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>
vybe and others added 2 commits October 9, 2026 08:33
…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
vybe marked this pull request as ready for review October 9, 2026 07:34
@vybe

vybe commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

merge-train: merged the updated base branch (feature/3314-queue-fingerprint-falsy @ bffddc0, the falsy-scalar guard) into this branch so the stack stays consistent; no conflict, no change of this PR's own diff. Will retarget to dev once #3414 lands.

@vybe
vybe changed the base branch from feature/3314-queue-fingerprint-falsy to dev October 9, 2026 08:22
# Conflicts:
#	src/backend/services/operator_queue_service.py
@vybe

vybe commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

merge-train: retargeted to dev after #3414 landed and merged dev in — one placement-only conflict in operator_queue_service.py (the _bounded_type insertion point beside the squashed #3314 hunk), resolved by keeping this branch's function; no code change. Now carries only this PR's own diff.

@trinity-ability trinity-ability left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

merge-train: batch validated on train/20261009-0735 (#3424); full CI re-read green after retarget to dev

@vybe
vybe merged commit bb29a62 into dev Oct 9, 2026
27 checks passed
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.

2 participants