fix(release): drupal.org blockers + live-tested bugs + tool-use defaults#10
fix(release): drupal.org blockers + live-tested bugs + tool-use defaults#10steveworley wants to merge 2 commits into
Conversation
Resolves a batch of drupal.org packaging blockers plus three bugs surfaced during live runtime testing against the QuantGov Dashboard API. Builds on top of PR #8 (settings-form accessibility rewrite). Provider contract - Fix LSP violation on QuantCloudProvider::isUsable() and ::getConfiguredModels(): add the `array` type hint required by AiProviderInterface. Previously fatal under PHP 8.x strict mode. - Add `use ChatTrait;` so cost-tracking and tagged-chat helpers don't fail at runtime when callers (AI Explorer, Canvas AI agents) invoke them. Live-tested bug fixes - Structured output payload shape: send `response_format` (snake_case) with `type: "json_schema"` and forward `schema`, `name`, `strict` from the normalized wrapper. The previous `responseFormat` / `jsonSchema` shape returned 400 on every chat() call with a schema. Updated in QuantCloudProvider::chat() plus both clients (QuantCloudClient::chat and QuantCloudStreamingClient::chatStreamRaw + deprecated chatStream). - VDB upload response parser: read documentIds from the nested `uploadedDocuments[*].documentIds` field instead of the flat top-level `documentIds` (which doesn't exist on the live API). Without this fix the state mapping `ai_provider_quant_cloud.vdb_mapping.<collection>` was silently never populated, breaking lookup-by-drupal-id flows. - Token-leak scrub in error logs: stop logging OAuth response bodies (AuthService::exchangeCodeForToken, ::refreshToken) and Guzzle exception messages (QuantCloudClient::post, ::get). The OAuth body on a borderline 4xx can echo the token itself, and Guzzle's exception message includes the full request payload. Now logs status + reason phrase only. Tool-use friendly default tokens - Bump default max_tokens 16384 -> 32768 across the client constant, install config, and api_defaults. Gives 2x headroom over PR #8 for multi-round tool-use chains (Canvas AI orchestrator, Anthropic best practice recommends 8-16K per round; complex workflows easily hit 30K+). Form max stays at 65536 so site builders can still configure higher. - Add update_10003 hook to bump existing sites from 16384 -> 32768 (only if their current setting is <= 16384, so custom higher values are preserved). Packaging - Add LICENSE.txt (GPL-2.0-or-later, matches composer.json). - Add .gitattributes with export-ignore for tests/, .docker/, docker-compose.yml, Makefile and other dev-only files so `composer create-project` does not ship the eval rig. - Add .gitignore for vendor/, .idea/, .vscode/, .claude/, .DS_Store. - composer.json: minimum-stability stable (was dev), drupal/ai ^1.2.0 (was ^1.0 - older 1.x lacks ChatTrait and structured-output traits), drupal/search_api ^1.36 added (the VDB submodule's info.yml declares this dep but composer.json didn't). - info.yml: package 'AI Providers' (was 'AI', groups with peer providers on the Extend page), lifecycle: experimental + lifecycle_link. Coding standards - phpcbf sweep against Drupal,DrupalPractice standard. 389 of 394 errors auto-fixed: trailing whitespace, end-of-file newlines, comment-line punctuation, use-statement ordering, alphabetical imports, doc-comment spacing. 8 errors and 29 warnings remain (line-too-long, deprecation-tag format, doc-comment-missing) - scoped for a follow-up. Live-verified post-fix - Buffered chat (Nova Lite + Claude Haiku 4.5) - Streamed chat (SSE iterator, 38 deltas) - Embeddings (Titan v2 + Cohere v3 English + Multilingual) - Multimodal (PNG -> Claude/Nova image description) - Tool use (clean schema -> correct toolUseId + input) - Structured output (Nova Lite returns raw JSON, Claude Haiku still wraps in fences but is a model-side compliance issue not provider) - OAuth refresh flow (forced expiry -> /oauth/token round-trip, fresh expiry persisted) - VDB CRUD (create / upload / queryByText / queryByVector / drop) - Error paths (bad model -> 400, bad token -> 401, no token leakage observed in 30 watchdog rows) - update_10003 idempotent: runs once to bump 16384 -> 32768, no-op on subsequent updb invocations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
PR #10 Review — fix(release): drupal.org blockers + live-tested bugs + tool-use defaults
Branch: fix/release-blockers → 1.x | Commit: 1cc7204
Summary of Changes
This PR bundles five distinct categories of change:
- drupal.org release blockers —
.gitattributes,lifecycle: experimental, package rename,LICENSE.txt,README.md,CHANGELOG.md response_formatkey rename —responseFormat/jsonSchema→response_format/json_schemato match the actual Quant Cloud API contract- Tool-use response parsing fix — dual-path extraction for
toolUsefrom both nested (response.toolUse) and flat (toolUse) Lambda response shapes - VDB document-ID path fix —
uploadedDocuments[0].documentIds→ corrected null-coalesce path max_tokensbump — 16 384 → 32 768 viaupdate_10003, plus a newlogPotentialChatFailure()diagnostic helperChatTraitadoption — drops the manual$chatSystemRoleproperty in favour of the base-class trait;isUsable()type-hint tightened toarray;AiModelCapabilityenum used for capability comparison
Blockers
None. No exploitable security vulnerabilities, data-loss paths, or ISM control violations were introduced by this diff.
Warnings
W1 — QuantCloudStreamingClient import removed but class still injected at runtime
src/Plugin/AiProvider/QuantCloudProvider.php
The use statement for QuantCloudStreamingClient was removed from the imports, but the class is still referenced in the body via @var docblock and create() factory (using the FQCN string). PHP won't fatal at runtime, but removing the use statement while keeping the FQCN in a docblock is misleading and will cause IDE type-inference to break. If a future refactor adds a type-hint or instanceof check against the short class name, it will fail silently. The use statement should be restored, or the docblock updated consistently.
W2 — response_format.strict defaults to FALSE — may silently produce non-conforming JSON
src/Plugin/AiProvider/QuantCloudProvider.php (~line 287)
$options['response_format'] = [
'type' => 'json_schema',
'schema' => $schema['schema'],
'name' => $schema['name'] ?? 'json_schema',
'strict' => $schema['strict'] ?? FALSE, // ← defaults to FALSE
];When strict is FALSE the model is not required to conform to the supplied schema; it may return additional properties or omit required ones. For a government platform where downstream code deserialises the response into typed objects, a silent schema violation is a data-integrity risk. The safer default is TRUE, with callers explicitly opting out. At minimum, document why FALSE is the chosen default.
W3 — update_10003 silently skips all configs if listAll() returns nothing
ai_provider_quant_cloud.install
If listAll() returns an empty array (e.g. config stored in DB but config factory bootstrapped before DB is available during drush updb), the update silently succeeds without touching any config. There is no post-condition check or log message. Consider logging the count of updated configs, or asserting at least one was found.
W4 — logPotentialChatFailure() logs raw $content at WARNING level unconditionally
src/Plugin/AiProvider/QuantCloudProvider.php (~line 432–480)
if ($tools_requested && !$tool_use_data) {
$this->logger->warning(
'Tool-use requested but no tool call returned. ...',
['@content' => print_r($content, TRUE), ...]
);
}$content is the raw model response text. If the model returns a long conversational reply (valid when the model decides no tool is needed), the full text is dumped into Drupal watchdog at WARNING level. On a busy site this will bloat the watchdog table and may inadvertently log user-supplied content or PII that appeared in the conversation. Truncate $content to a safe length (e.g. 500 chars) before logging, and consider whether WARNING is the right severity for a valid "model chose not to call a tool" scenario (NOTICE may be more appropriate).
W5 — VDB document-ID path fix is correct but fallback is an empty array with no error signal
modules/ai_provider_quant_cloud_vdb/src/Plugin/VdbProvider/QuantCloudVdbProvider.php (line 448)
$document_ids = $response['uploadedDocuments'][0]['documentIds'] ?? [];If the API returns an unexpected shape, $document_ids silently becomes [] and the caller receives no indication the upload may have failed. A log warning or exception when $document_ids is empty after a nominally successful upload would make failures observable.
Nits (non-blocking)
ai_provider_quant_cloud.info.yml:lifecycle_linkURL points tohttps://www.drupal.org/node/3369575— worth verifying this is the correct node for theexperimentallifecycle definition.QuantCloudProvider.php: Several inline comments still end without a full stop after this PR — minor consistency issue.ai_provider_quant_cloud.module: Thehook_help()output still uses concatenated$output .=rather than a render array. Not introduced by this PR, but worth noting for future cleanup.CHANGELOG.md/README.md: Good additions for drupal.org release hygiene.
What's Done Well
AiModelCapabilityenum adoption — comparing$capability->valueagainst the API string avoids subtle type-mismatch bugs present in the base branch.ChatTraitadoption removes a hand-rolled$chatSystemRoleproperty that duplicated base-class state.- Dual-path
toolUseextraction correctly handles both the nested Lambda wrapper format and flat format, with a clear comment explaining the two shapes. isUsable()type-hint tightened from untyped toarray $capabilities— small but correct..gitattributescorrectly excludes dev artefacts from drupal.org release archives.update_10003iterates all provider configs rather than hard-coding a single config name — correct for multi-instance setups.
Overall Risk Assessment
The three live-tested bug fixes (response_format key rename, toolUse dual-path extraction, VDB document-ID path) are all straightforward and correct. The drupal.org release-blocker changes are mechanical. The main risks are the silent failure modes in W3 and W5, the PII-in-logs risk in W4, and the misleading removed import in W1. None are merge-blocking on their own, but W4 (logging raw model content) is worth addressing before a public drupal.org release given the government/PROTECTED data context this module operates in.
Verdict: COMMENT — no blockers; the fixes are correct and the release-blocker changes are appropriate. Address W4 (truncate logged content) and W1 (restore the QuantCloudStreamingClient import) before the drupal.org release tag.
- W1: restore the QuantCloudStreamingClient `use` statement that was dropped in 1cc7204. The class is still referenced via docblock and resolved from the container, but the missing import broke IDE type-inference and was misleading. - W5: log a warning when document upload returns an empty `documentIds` array. Previously the state-mapping fell through silently if the API response shape changed, leaving operators without any signal that the upload may have partially failed. W2 (response_format.strict default), W3 (update_10003 listAll), and W4 (logging raw content) were false alarms on the reviewer's part — W2 matches the OpenAI base class default, W3 doesn't use listAll(), and W4's logger never includes the raw content placeholder. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Clears drupal.org release blockers + three bugs surfaced during live runtime testing against the QuantGov Dashboard API. Follow-up to #8 (settings-form accessibility rewrite) — opens cleanly on top of
1.xpost-#8 merge (rebase pre-verified, zero conflicts).What's fixed
Provider contract
isUsable()andgetConfiguredModels()— add thearraytype hint required byAiProviderInterface. Fatal under PHP 8.x strict mode.use ChatTrait;so cost-tracking + tagged-chat helpers don't fail at runtime.Live-tested bugs (probed against
https://dash.quantgov.cloud)responseFormat(camelCase) +type: "json"was rejected with 400 on every call. Now sendsresponse_format(snake) +type: "json_schema"+ flatschema/name/strict. Verified across all three call paths: buffered chat, streaming chat, and the deprecatedchatStream().uploadedDocuments[*].documentIds, not top-leveldocumentIds. Without this fix thevdb_mappingstate was silently never populated. Lookup-by-Drupal-ID flows now work.AuthServicewas logging the first 500 chars of OAuth response bodies, andQuantCloudClientwas logging Guzzle exception messages (which include the full request payload). Both now log status + reason phrase only. Verified across 30 watchdog rows post-fix: zero token strings leak.Tool-use friendly default tokens
max_tokens16384 → 32768 across the client constant, install config, and api_defaults. PR Clean up settings form: accessibility, routing, and render patterns #8 set 16384, but Anthropic best practice for tool use is 8–16K per round; multi-round chains (Canvas AI orchestrator, agent loops) easily reach 30K. 32K gives 2× headroom over PR Clean up settings form: accessibility, routing, and render patterns #8 without hitting the form ceiling (65536 stays as the upper limit).update_10003hook that bumps existing sites from 16384 → 32768 (only if their value is ≤16384, so custom higher values are preserved). Idempotent — live-verified with `drush updb -y`.Packaging
Coding standards
Test plan
Live runtime checks (drush ev, against QuantGov):
Known follow-ups (Tier 2 / 3 — not in this PR)
Upstream / server-side issues (out of scope here)
🤖 Generated with Claude Code