Repository navigation
Description
Priority: P2
Area: Graft context adapter
Repository / target branch: wavect/semaprax / wavect/v090
Reviewed commit: 5428b0805fa3cf35859c843a53e30c3252ef378c
Review date: 6 October 2026
Failure and impact
The Graft adapter's response fitter only removes payload.items. Coverage, skipped-file descriptions, metadata, and diagnostics can already exceed the requested result budget with zero items. In that case the loop cannot shrink anything and the adapter sends the oversized envelope as complete.
The executed case requests a valid 4,096-byte result budget and receives 16,107 bytes, with zero items and 84 skipped files. The host's result parser applies the requested cap to the complete envelope, so this answer is not consumable through the bounded host contract. Mixed-language projects with many unsupported files can lose context responses even though their useful item list is empty or small.
Exact source and cause
graft/lib/ops.mjs:232–239:fitexits whenpayload.items.lengthbecomes zero, regardless of the serialized size.graft/lib/ops.mjs:291–323: coverage and metadata are added before fitting; diagnostics/status are finalized afterward. A fixed 1,024-byte reserve is not a measurement of the final envelope.contract/envelope.rs:375–383: the host parses the complete result frame with the actual requested byte limit.
Executed reproduction
Run the unchanged adapter through its existing Adapter fixture helper, including child process startup and JSON-RPC request/response handling:
- Make a project with the normal helper files and 80 extra unsupported
.spxfiles. Each extra filename contains a modest suffix of 35 characters. - Use the deterministic existing Graft upstream shim; keep its orientation metadata scalar so it satisfies the host's metadata shape.
- Initialize the adapter, then issue
context.repository/orientwithmax_items: 10,max_result_bytes: 4096, and full-length project identity digests. - Measure UTF-8 bytes of the complete returned result envelope.
{
"requested_bytes": 4096,
"response_bytes": 16107,
"status": "complete",
"items": 0,
"skipped": 84,
"diagnostics": ["graft.coverage-incomplete"]
}See repros/context/repro_graft_budget.mjs and evidence/graft-budget-evidence.json. The production adapter and response fitter were executed unchanged. Upstream indexing is a deterministic fixture, not a real installed Graft invocation. The host rejection is established by the cited parser size guard; the end-to-end host/adapter integration was not run in this probe.
Focused fix
Fit the complete serialized result envelope against its effective byte cap. Apply bounded truncation to variable-size non-item fields as well as items, with explicit omission counts and truthful incomplete coverage. Bound diagnostics and metadata before publishing; keep enough information to explain a partial or refused result. Recheck the final envelope after all fields are set.
If the smallest permitted envelope cannot fit, use the protocol's bounded failure path rather than claiming a complete result. Cover early returns that bypass the normal fitter too. Preserve source provenance, deterministic ordering, freshness checks, and the rule that incomplete coverage never proves absence. Reuse a small envelope-aware fitting helper instead of repeating an approximate reserve at each operation.
Acceptance criteria
- The supplied zero-item/many-skipped-file case fits within 4,096 UTF-8 bytes and remains truthful about omitted coverage.
- Long paths, multibyte filenames, many diagnostics, adoption metadata, and normal item truncation cannot exceed the final requested cap.
- All operations and early return paths produce bounded envelopes or an appropriate bounded refusal.
- Truncation updates omitted counts and prevents false complete/exhaustive/absence claims.
- A host-level test passes the actual returned bytes through
ResultEnvelope::parse_forusing the same request cap. - Ordinary under-budget results preserve their useful items and provenance without unnecessary truncation.
Existing issue boundary and implementation handoff
#511 (MC-06) repaired the analogous Graphify envelope-budget path. It did not repair Graft's item-only fitter; this issue targets the separate Node adapter owner. Read AGENTS.md and the context/adapter contracts, and extend the existing Graft fixture tests. Report actual envelope byte counts and nonzero test execution, not only payload size.
Activity
harness: qualified cost evidence, budget-fitted Graft envelopes, scop…
wsdt commentedon Oct 6, 2026
Fixed in 83044ff (pushed to main). New graft/lib/fit.mjs fits the entire serialized envelope (early returns and error paths included), trimming items, skipped-file list, metadata and bounded diagnostics with metadata.omitted_items/skipped_omitted, only ever moving coverage toward incomplete (absence_proven=false when items dropped), and refusing with graft.result-too-large when nothing fits. Evidence: the 4096-byte case produced 16029 bytes before the fix; node budget.test.mjs + 74/74 graft tests pass; graphify_budget dv13 test passes the real adapter output through ResultEnvelope::parse_for at 4096 and 65536.