# G-082 punk::args single-form parse error selection: specific validation failures preferred over generic overflow Status: achieved 2026-07-14 Scope: src/modules/punk/args-999999.0a1.0.tm (get_dict/parse error selection, validation-failure capture around the choice/type screens), src/tests/modules/punk/args/testsuites/args/ (characterization + regression suite) Goal: when a single-form parse fails because a word was rejected by an optional argument's validation (restricted choices, type) and consequently overflowed the argument list, the reported error names the specific rejection (the choiceviolation with the offending word and choice list, or the type failure) instead of the generic "Received more values than can be assigned" overflow - restoring the pointedness punk::args 0.5.0-era parsing had for optional choice-restricted leaders/values, and removing the need for the -optional 0 authoring workaround adopted by make.tcl's dispatch definitions. Acceptance: a definition with an optional choice-restricted leader or value that receives a non-matching word reports a choiceviolation-class error naming the word and the allowed choices (message and -errorcode), not toomanyarguments - pinned for the make.tcl-style dispatch shape (optional subcommand leader with choices) and a values-position equivalent; a word rejected by a typed optional argument (e.g int) that then overflows reports the type failure similarly; genuinely-surplus cases (all optional args satisfied, extra word remains) still report toomanyarguments; multiform error selection (rank_form_failures) is unchanged or its pins deliberately updated; current 0.12.x behaviour is characterized first and the pins flipped deliberately; full punk::args suite passes with no expectations weakened; on completion the G-030 -optional 0 workaround sites are revisited (make.tcl dispatch definitions, the src/modules/AGENTS.md "punk::args definition authoring ergonomics" bullet) and relaxed or re-documented. ## Context Found 2026-07-14 during G-030 (make.tcl dogfooding punk::args). `make.tcl frobnicate` under bootsupport punk::args 0.5.0 reported a pointed choiceviolation naming the word and the subcommand choices; under 0.12.0 the identical definition reports `PUNKARGS VALIDATION {toomanyarguments 1 index 0}` ("Received more values than can be assigned to argument names"). Verified by A/B probes that the discriminator is `-optional 1` on the choice-restricted argument - NOT -choicegroups membership, NOT @form/-synopsis presence, NOT leader-vs-value position (both positions affected). Worked around in G-030 by declaring the dispatch `subcommand`/`subject` arguments `-optional 0` - safe there because the dispatch only parses when a word is present, so the required-leader-missing case is unreachable. The workaround is recorded in goals/archive/G-030-maketcl-punkargs.md and as an authoring guideline in src/modules/AGENTS.md "punk::args definition authoring ergonomics" - both should be revisited when this ships. Assessed mechanism (not yet verified in code): the allocator treats an optional argument whose candidate word fails validation as "argument omitted" and moves on; when no later slot can consume the word, the final error is the overflow, and the earlier, far more informative rejection is discarded. The likely shape of a fix is recording candidate rejections during allocation and preferring the most specific/nearest one at error-report time - the single-form analogue of the multiform rank_form_failures selection. ## Progress - 2026-07-14 (achieved): implemented in punk::args 0.12.1 (source module). Mechanism: private::get_dict_can_assign_value returns a 'rejection' record (kind choice|type, argname, word, choices/nocase/prefix or type) when its optionality-gated screens reject a single-member clause's candidate word; get_dict_form collects first-rejection-per-word from the three allocation call sites (leader/value split trial, leaders loop, values loop) into a per-form alloc_rejections store; the two toomanyarguments raise sites consult the store for the unassignable word and prefer the specific report - choiceviolation with -badarg/-badval and the final-validation message template, or typemismatch naming the declared (normalized) type. Literal-category mismatches are deliberately not recorded (syntax-word shapes like lseq's 'to' - overflow remains the clearer report). The basic-type screen covers int/double/bool/number/dict; other types are not screened at allocation and were never part of the regression shape. - Verification (Tcl 9.0.3 via src/tests/runtests.tcl): errorselection.test characterized pre-fix behaviour first (7/7 against the unfixed module, commit "G-082 increment 1"), then its three _GAP pins were flipped to the pointed choiceviolation/typemismatch expectations; full punk/args suite 228 total / 224 passed / 0 failed / 4 intended skips (allocation.test G-071 pins, parsestatus.test, forms.test all unchanged); punk/ns suite 57/57; errorselection.test 7/7 under Tcl 8.7. Multiform selection unchanged (rank_form_failures untouched; the multiform probe reports identical noformmatch classes pre/post fix, pinned by errsel_multiform_selection). make.tcl frobnicate still reports the pointed choiceviolation (bootsupport punk::args 0.12.0 - the regressed snapshot - with the retained -optional 0 workaround; the workaround stays needed there until a bootsupport refresh ships >= 0.12.1). - Workaround-site revisit: make.tcl dispatch definitions keep -optional 0 (semantically sound - the dispatch only parses when a word is present, and make.tcl runs against the bootsupport punk::args 0.12.0 snapshot, not the fixed source module); the src/modules/AGENTS.md authoring-ergonomics bullet was re-documented (workaround no longer needed for pointed errors on punk::args >= 0.12.1). - Residue (out of this goal's acceptance): a REQUIRED typed value whose word fails the basic-type screen still reports missingrequiredvalue rather than a typemismatch naming the word (pre-existing, pinned nowhere) - candidate follow-on; the rejection-record seam would cover it. ## Notes - Related: G-073 (unavailable-choice tailored rejection messages want the same "surface the specific rejection" seam - whichever lands first should leave the other a clean hook. G-082 landed first: the hook is the 'rejection' record returned by private::get_dict_can_assign_value plus the per-form alloc_rejections store consulted at the overflow raise sites - G-073 can extend the record's kind vocabulary for recognised-but-unavailable words), G-071 achieved 2026-07-12 (the optional-element allocation machinery this selection logic sits beside - goals/archive/G-071-punkargs-optional-allocation.md). - Archived-goal references in this file: G-030 achieved 2026-07-14 (goals/archive/G-030-maketcl-punkargs.md); G-074 achieved 2026-07-13 (goals/archive/G-074-punkargs-multiform-ambiguity-lint.md).