From 7952b5caa49a1bec5690567b782e77dc2b39751a Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Mon, 13 Jul 2026 02:27:41 +1000 Subject: [PATCH] goals: add G-074 (punk::args multiform ambiguity lint-time analysis) Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- GOALS.md | 4 + ...G-074-punkargs-multiform-ambiguity-lint.md | 86 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 goals/G-074-punkargs-multiform-ambiguity-lint.md diff --git a/GOALS.md b/GOALS.md index 106c0a4f..6c3a6953 100644 --- a/GOALS.md +++ b/GOALS.md @@ -285,3 +285,7 @@ Detail: goals/G-072-punkargs-compound-clause-types.md ### G-073 [proposed] punk::args unavailable choices: displayed with notes and prefix-reserving, but rejected with a tailored message Scope: src/modules/punk/args-999999.0a1.0.tm (spec key, choiceword_match pool, choices rendering in arg_error table+string renderers, validation message), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm ('string is' forward-class adoption + per-class virtual id), src/tests/modules/punk/args/testsuites/args/ (new suite + tclcoreparity.test exemption) Detail: goals/G-073-punkargs-unavailable-choices.md + +### G-074 [proposed] punk::args multiform ambiguity analysis: on-demand form-overlap detection with sanctioned-overlap annotation +Scope: src/modules/punk/args-999999.0a1.0.tm (analysis command, @form sanction key as decided in the work), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (::after/::lseq as proving consumers, sanction adoption), src/tests/modules/punk/args/testsuites/args/ (new suite) +Detail: goals/G-074-punkargs-multiform-ambiguity-lint.md diff --git a/goals/G-074-punkargs-multiform-ambiguity-lint.md b/goals/G-074-punkargs-multiform-ambiguity-lint.md new file mode 100644 index 00000000..c831eb40 --- /dev/null +++ b/goals/G-074-punkargs-multiform-ambiguity-lint.md @@ -0,0 +1,86 @@ +# G-074 punk::args multiform ambiguity analysis: on-demand form-overlap detection with sanctioned-overlap annotation + +Status: proposed +Scope: src/modules/punk/args-999999.0a1.0.tm (analysis command, @form sanction key as decided in the work), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (::after/::lseq as proving consumers, sanction adoption), src/tests/modules/punk/args/testsuites/args/ (new suite) +Goal: an on-demand analysis command (candidate spelling punk::args::formcheck ) reports, for a multiform definition, the form pairs an argument list could cleanly match simultaneously - a conservative static pass over the forms' leading slots and arity windows that flags discriminator-vs-permissive-type alignments (the 'lseq 1 count 5' class, where a literal/choice discriminator in one form aligns with a non-validating type like expr/any/string in another) and discriminator-free overlaps (the 'after cancel id|script' class) - with a definition-level annotation (spelling decided in the work, e.g @form key) that sanctions known/documented overlaps so they report as acknowledged rather than as findings, keeping the unsanctioned report actionable; the define hot path is untouched (analysis runs only on demand - interactive, test-time, and as a G-055 verification-gate step for regenerated multiform commands). +Acceptance: the analysis command reports the two known real cases from the G-041 closeout - ::lseq range/start_count overlap via the expr-typed end slot (unsanctioned finding: type-weakness class) and ::after cancelid/cancelscript (documented-overlap class) - and reports nothing for multiform definitions whose forms are fully discriminated (the parse withid/withdef pair, the forms.test afterish fixture); classifications distinguish at minimum type-weakness overlap (a discriminator aligned with a permissive type) from structural overlap (no discriminating slot exists); the sanction annotation silences (or downgrades to acknowledged) a listed form pair without affecting parse behaviour, is rejected at definition resolve when it names unknown forms, and is adopted for the after cancel pair in the tclcore moduledoc; the analysis is conservative in the documented direction (may miss deep ambiguities, must not false-alarm on discriminated forms - the miss/report boundary is documented with the slot model); running it performs no parse of user-supplied words and adds no work to define/resolve for definitions that never call it; a new testsuite covers the finding classes, the sanction, the unknown-form rejection and the no-finding cases; full punk::args suite passes. + +## Context + +User direction 2026-07-13 (during the G-041 closeout): "The encountered ambiguities +suggest weakness in the doc-blocked types - and perhaps requirement to evaluate for +ambiguities at define time to reject / warn"; the lint-time (on-demand) shape of this +goal was recommended and user-selected ("draft lint-time analysis"). + +The two ambiguities G-041's candidacy machinery surfaced on the tclcore models: + +- 'lseq 1 count 5' -> multipleformmatches range+start_count. Root cause is a + TYPE-WEAKNESS overlap: start_count's literalprefix(count) discriminator aligns + positionally with range's end slot typed number|expr, and -type expr performs no + validation, so the word 'count' satisfies both forms. Real lseq resolves it + semantically (start_count). The G-055 detail file carries the operand-typing + probes (2026-07-13): indexexpression is a poor lexical fit (under-accepts + 1e2/2*3/sqrt(9)/bracketed exprs, over-accepts end/end-1); the precise + discriminator is expr SYNTAX validation, needing a non-evaluating parser + (G-069/G-070 territory); TIP 746 makes the operands number-only on 9.1+. +- 'after cancel someid' -> multipleformmatches cancelid+cancelscript. A GENUINE + documented overlap: 'after cancel id' vs 'after cancel script ?script?...' are + textually indistinguishable for one trailing word - real Tcl disambiguates by + the id's runtime shape. Correct in a doc-faithful model, but indistinguishable + from an authoring mistake without a sanction mechanism. + +Full ambiguity detection is language intersection between forms - not decidable in +general (types like regex-validated strings, unbounded -multiple tails) - but the +encountered classes are cheap to detect statically: walk the aligned leading slots +of each form pair over their overlapping total-arity windows and test whether every +slot pair is co-satisfiable, flagging pairs where a discriminator slot +(literal/literalprefix/restricted-choices) faces a permissive type (expr, any, +string, none-validating) and pairs with no discriminating slot at all. + +## Approach + +- Pairwise form analysis over the resolved spec (FORMS/ARG_INFO - no re-parse of + definitions): compute each form's value-arity window (VAL_MIN/VAL_MAX plus + optional-member clause arithmetic as in the allocator); for pairs with + intersecting windows, align leading slots and classify each aligned pair as + disjoint (two different literals; literal vs type that rejects it), + co-satisfiable-weak (discriminator vs permissive type), or co-satisfiable-equal + (same/overlapping permissive types). A pair with no disjoint slot in any aligned + position within the shared window is a finding: type-weakness if some slot is + discriminator-vs-permissive, structural otherwise. +- Choice/literal words screened via choiceword_match (the shared G-040 resolver) so + the static disjointness test cannot diverge from parse acceptance - the same + principle that kept G-071's allocation screen and G-051's doc walk honest. +- Options: forms whose option sets differ do not discriminate positionally (options + are order-free); first pass ignores opts for alignment (documented as part of the + conservative boundary) - the arity windows still gate. +- Sanction: an @form-level key (spelling decided in the work; candidates + -overlapallowed on either member, or a definition-level + directive) recorded in the spec, validated at resolve (unknown form names error), + consumed only by the analysis (parse behaviour unchanged - G-041's + multipleformmatches error still fires; the sanction documents intent, it does + not pick a winner). +- Report shape: machine-parsable dict (per finding: forms, class, witness slot(s), + an example witness arglist shape where derivable, sanctioned flag) plus a human + summary - the dict is what the G-055 verification gate and test pins consume. +- Consumers: G-055's regeneration workflow gains a gate step "run formcheck on + every regenerated multiform command; new unsanctioned findings block acceptance"; + interactive use for definition authors debugging multipleformmatches errors. + +## Notes + +- Related: G-041 achieved 2026-07-13 (goals/archive/G-041-punkargs-form-matching.md) + - its candidacy machinery is the runtime oracle for a GIVEN arglist; this goal is + the static answer for ALL arglists, and its Progress section records both + motivating ambiguities. G-055 (verification-gate consumer; operand-typing probes + in its notes), G-072 (compound clause types change the slot model - if active + concurrently, coordinate the alignment walk), G-069/G-070 (a future expr + syntax-validating type would convert the lseq finding from type-weakness to + resolved - the analysis should then report the pair as discriminated). +- Define-time always-on checking was considered and rejected with the user's + concurrence: define/resolve is a hot path and most definitions are single-form; + on-demand covers the authoring and regeneration moments where findings are + actionable. A future opt-in (@dev-time strict mode) could call the same analysis. +- The 'after cancel' sanction is also the display question's anchor: a sanctioned + overlap is why the G-041 doc surface marks BOTH forms and shows the ambiguity + message - the sanction must not suppress that runtime honesty.