typedJSONValue marshaled a typed value and then routed the result through
rawJSONValue, which runs json.Valid before decoding. On that path the input is
whatever json.Marshal has just produced, so the validation scan can only ever
succeed: it re-read every marshaled document for nothing.
The decode step is now shared by both entry points. rawJSONValue keeps its
json.Valid check, because it still accepts untrusted input, while typedJSONValue
decodes what it marshaled directly. Across the 1121-tool set this removes about a
third of the Schema Catalog projection work: the internal/app schema suite goes
from 26.0s to 17.2s uninstrumented, and from 291.1s to 241.0s under -race.
The delivered Catalog is byte-for-byte unchanged. check-generated-drift,
check-schema-catalog and check-schema-binary each regenerate the same
source_hash sha256:93b8d44eb163bd2898c78397d22af92d378e3dc4e20f56b33277b51e4342e2e6,
and the two error contracts are preserved: typedJSONValue still rejects a value
json.Marshal cannot encode, and rawJSONValue still rejects invalid JSON.
Eval-driven optimizations from the 2026-08-14/15 agent evaluations:
- abstained lexical responses now carry a bounded hint naming the one
permitted verification step (dws schema <product> --compact) and
licensing the conclusion 'unsupported', so agents stop enumerating
every product to prove a capability is missing
- responses whose candidates only matched weak fields (parameters/
description, no identity/summary hit) set weak_match=true with a
matching hint; the flag is field-class based, not a score threshold,
preserving the RFC ban on absolute score cutoffs
- exact_filtered keeps its typed refusal without inheriting either hint;
budget-truncation to zero candidates downgrades to the abstain hint
- dingtalk-shared gains the matching convergence rule: one search, at
most one schema <product> verification, then conclude honestly
Evidence: the nonexistent-capability intent scenario took 15 turns/177s
to conclude 'no poll capability' with identical correctness to a 4-turn
run; these signals bound that path. Note: on the real catalog the poll
phrasing still matches summary fields of generic chat tools, so the
behavioral fix for that specific case rides on the shared-skill rule;
the wire flags cover genuinely unmatched and weak-field-only queries.
- effect filter: case-insensitive against the reviewed vocabulary with a
typed invalid_effect refusal; unknown product ids get a typed
unknown_product refusal instead of a silent abstain (probe-confirmed:
--effect Read used to return 0 candidates silently)
- DWS_TOOL_SEARCH_* unparsable values now warn on stderr while keeping
the resolved default, so ranking experiments cannot silently measure
defaults (probe-confirmed: K1='0,9' was ignored with empty stderr)
- --explain help text now declares it a dev-only diagnostic that exposes
raw ordering scores; the default wire stays score-free
- lexical ordering quantizes the sort key at 1e-9 so architecture
dependent FMA fusion (arm64 vs amd64) cannot flip near-tied candidates
past the canonical tie-break; reported scores are unchanged
Contract probe re-run against this build: 11/11 checks OK (was 3 FINDINGs)
- effect filters are case-normalized against the reviewed vocabulary and
reject anything else with a typed invalid_effect error instead of a
silent abstain (probe-confirmed: --effect Read now equals --effect read)
- unknown product ids reject with unknown_product instead of returning
abstained=true, so caller typos cannot masquerade as empty results
- unparsable DWS_TOOL_SEARCH_* float env values now warn on stderr;
previously a typo like K1=0,9 silently measured the default config
- --explain help text declares the raw-score disclosure as a dev-only
diagnostic exception to the score-free public wire
Self-review follow-ups on the ranking hardening series:
- the avoid_when boilerplate filter compared lowercased phrases against
a mixed-case constant, so the "需要该 Shortcut..." noise filter never
fired; the constant is now lowercase (no ranking change: the boilerplate
was never contained by any query, sentinel 990/1123 unchanged)
- add direct unit coverage for the avoid_when penalty (echoed-phrase
match, unrelated-query pass-through, boilerplate filter, partial
overlap, explain annotation)
- update tool-search-ranking-evaluation.md to the post-hardening
numbers (ensemble R@5 88.16%, Forbidden 0%/0%, action_v1 shadow
84.24% with the trade-off recorded)
- drop the dead algorithm_freeze manifest block; nothing consumes it
after the hash decoupling
- default engine: fielded BM25 ensemble (action_v1 remains a shadow candidate)
- add --explain score_breakdown: per-field contributions, structured
multiplier, query classification and avoid_when penalty
- add DWS_TOOL_SEARCH_* env overrides (algorithm, k1/b, field weights,
explain) for release-independent tuning and rollback
- tokenizer: CJK unigram+bigram so single-character intents match; align
isToolSearchCJK with unicode.Han used by the evaluation slices
- structured routing: extend vocabularies with English synonyms,
word-boundary matching for ASCII values, restrict the rerank gate to
technical identifiers only
- avoid_when demotion layer wraps every lexical algorithm (Forbidden@5
proxy 37.55% -> 0); intent-overlap matching rejected after control
showed it demoted gold answers (Recall@5 0.8664 -> 0.7845)
- remove the OA task_id +1.00 fixture-specific multiplier
- build the default engine once per process for long-lived hosts
- evaluation baseline: intent-proxy R@5 990/1123 (88.16%); update
integration sentinels accordingly
Both capabilities were added as flags on an existing leaf, and in both cases
the leaf's published interface stopped describing what the command did:
- `sheet create --values/--sheets/--styles` orchestrates create → probe →
resolve default worksheet → write → read back → optional styles, yet the
leaf still published `interface_mode: mcp` + `create_workspace_sheet`.
- `sheet export --export-format csv` reads `get_range_as_csv` and never
invokes `submit_export_job`, yet the leaf published `submit_export_job`.
Each moves to its own command, declaring the interface it actually uses:
`sheet create-with-data` is `composite` with a reviewed reason and no
`interface_ref`; `sheet export-csv` is `mcp` + `get_range_as_csv`. Both are
pinned in the interface-disposition contract test.
`sheet create` and `sheet export` are restored byte-for-byte to main, so the
compatibility gates see two `command_added` additions instead of four
locked-field changes. The split also removes a user-visible trap: `--range`
without `--export-format csv` used to be silently discarded and the whole
workbook exported; the cross-format flags no longer exist, pinned by
TestSheetExportAndExportCsvFlagsDoNotLeak.
Drops the 8 now-stale mapping-ledger exclusions that covered the flags on
`sheet.create_workspace_sheet` / `sheet.submit_export_job`, shrinking this
branch's exclusion surface. Skill references and CHANGELOG follow the split.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>