From 20fb22810e085e6e66a272393ed77d73e615578e Mon Sep 17 00:00:00 2001 From: "David E. Weekly" Date: Sat, 19 Sep 2026 12:44:03 -0700 Subject: [PATCH 1/2] docs(roadmap): add origin-precision candidates for 0.3.2 Two measured origin-analysis gaps found running luad 0.3.1 over a private OpenWrt-derived Lua 5.1 LNUM32 firmware corpus as the fact engine under a downstream security-analysis pipeline: - Emit a bounded `alternatives` set instead of the opaque `control-flow-conflict` where the reaching set is finite. The 0.3.1 loop-widening fix (PR #85) widens loop-carried slots to `control-flow-conflict`, which blocks their resolvable sibling expressions downstream. `control-flow-conflict` sits behind 99 of 175 unresolved sink-argument origins. - Make the origin unknown-reason cap explicit (B-9): two unknown-reason totals saturate at exactly 2000 corpus-wide, a silent clamp that makes the counts unusable as a release-over-release measurement. Both carry machine-testable acceptance criteria pairing a public fixture with a re-measurement against the private corpus. Bumps the ROADMAP freshness date in the README index. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01FtirLUpUr9aWq57opQ3WVi --- README.md | 2 +- ROADMAP.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a12d4a..108ba1d 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ delete the document in the same change and update this index. | [`CHANGELOG.md`](CHANGELOG.md) | Backward-facing record of released and unreleased user-visible changes. | 2026-09-18 | Every user-visible change or release; never use it as a forward plan. | | [`CONTRIBUTING.md`](CONTRIBUTING.md) | Contributor verification commands, test taxonomy, fixture provenance, and definition of done. | 2026-09-18 | Toolchain, test commands, gates, fixture policy, or contribution workflow changes. | | [`PRD.md`](PRD.md) | Product users, firmware-tree workflows, factual analysis boundary, requirements, non-goals, and release outcomes. | 2026-09-06 | Product scope, target users, supported workflows, factual-analysis boundary, or product-level requirements change. | -| [`ROADMAP.md`](ROADMAP.md) | Release completion boundary, future researcher outcomes, sequencing, and target support boundaries. | 2026-09-17 | Release scope, dependencies, parallel ownership, acceptance criteria, support boundaries, or exclusions change. | +| [`ROADMAP.md`](ROADMAP.md) | Release completion boundary, future researcher outcomes, sequencing, and target support boundaries. | 2026-09-19 | Release scope, dependencies, parallel ownership, acceptance criteria, support boundaries, or exclusions change. | | [`SECURITY.md`](SECURITY.md) | Supported-version policy, the planned 1.0 target matrix, vulnerability reporting, and hostile-input threat model. | 2026-09-16 | Support policy, the planned target matrix, reporting channel, trust boundary, or threat model changes. | | [`docs/ROADMAP-1.0.md`](docs/ROADMAP-1.0.md) | The full qualification program a future 1.0 would need: milestones, evidence gates, target promotion, and release acceptance. | 2026-09-16 | The 1.0 destination, milestone order, release acceptance, or the qualification lifecycle changes. | | [`docs/BRINGUP.md`](docs/BRINGUP.md) | Setup for a developer machine, a self-hosted Actions runner, and a release builder, with the owning file for every tool pin. | 2026-09-02 | A tool pin, its owning file, the doctor's checks, runner labels or security boundary, or the release dry-run and rehearsal entry points change. | diff --git a/ROADMAP.md b/ROADMAP.md index 44f97b3..87c3285 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -19,6 +19,64 @@ dialects experimental until their named evidence gates authorize promotion. The [1.0 program](docs/ROADMAP-1.0.md) does not gate an experimental 0.x release. Implementation history belongs in [CHANGELOG.md](CHANGELOG.md) and Git history. +## Origin precision + +Two measured origin-analysis gaps, stack-ranked. Both surfaced from running `luad` +0.3.1 over a private OpenWrt-derived Lua 5.1 LNUM32 firmware corpus (~260 files) as the +whole fact engine under a downstream security-analysis pipeline. A private corpus can +find defects and measure usefulness but cannot promote a format; the acceptance criteria +below therefore pair a public fixture with a re-measurement against that corpus. The +counts are stated once: after the 0.3.x work, `control-flow-conflict` is the largest +remaining origin unknown-reason (2,940 down to 1,016 corpus-wide), and it sits behind 99 +of 175 unresolved call-argument origins that reach shell and filesystem sinks. + +### Emit a bounded `alternatives` set instead of `control-flow-conflict` + +When origin analysis reaches a control-flow join, or widens a loop-carried slot, it +collapses the slot to the opaque `control-flow-conflict` unknown-reason. `alternatives` +already exists for some bounded joins, so where the set of reaching definitions is +finite the origin should be that `alternatives` expression, not the opaque reason. A +consumer cannot see through `control-flow-conflict`; an `alternatives` set it can. + +The 0.3.1 loop-widening fix (PR #85) sharpened this rather than caused it. That fix +deliberately widens a loop-carried slot to `control-flow-conflict` after +`MAX_BLOCK_REVISITS` to stop unbounded lattice growth, which was the right call against +the alternative of reporting the whole prototype as `analysis-limit`. But the widened +slots are frequently the sibling expressions of a resolvable argument in the same +constructed command string, so widening to the top of the lattice makes those siblings +unreadable downstream. The gap to close: widen to a bounded `alternatives` set where one +exists (respecting `MAX_ALTERNATIVES`), and reserve `control-flow-conflict` for the +genuinely unbounded case. + +The concrete repro prototype (a constructed shell-command argument whose siblings are +loop-widened) is recorded in the consumer's private notes, not here, because it names an +unreleased finding in a shipping product. + +**Acceptance.** A public fixture that exercises both a bounded join and a loop-carried +slot in one command-shaped expression; `luad origins` emits `alternatives` with the +candidate set where the reaching set is bounded, and keeps `control-flow-conflict` only +where it is genuinely unbounded (the existing loop-widening regression test still holds +there). Re-run over the private corpus: the `control-flow-conflict` count falls and +previously-blocked bounded sink arguments resolve, with no argument losing an expression +it had in 0.3.1. + +### Make the origin unknown-reason cap explicit (B-9) + +Two origin unknown-reason totals saturate at exactly 2000 corpus-wide while no single +file approaches that number, which is the signature of a silent global cap on a counted +or de-duplicated set. It is not in the `origins.rs` analysis constants +(`MAX_EXPRESSION_*`, `MAX_TRANSFER_STEPS`, `MAX_ALTERNATIVES`, `MAX_BLOCK_REVISITS`); the +next place to look is the export and records/dedup path. A consumer measuring +release-over-release deltas read `unsupported-value` as falling 2394 to 2000 and +`overwritten` as rising 1976 to 2000, both cap artifacts rather than real movement, and +nearly reported a regression and an improvement that did not happen. Per the sequencing +rule below, a silently clamped count is a correctness defect, not an ergonomics one. + +**Acceptance.** Locate the cap; either lift it or make truncation explicit with a +diagnostic and a truncated flag so a clamped total is never presented as a measured one. +A test asserts that when the cap would bind, the output carries the truncation signal +rather than a silently clamped count. + ## Later - Promote an exact target once one profile's evidence is complete end to end. See the From 2d6564b6a0f08823e7ef567f6eed77487a022b58 Mon Sep 17 00:00:00 2001 From: "David E. Weekly" Date: Sat, 19 Sep 2026 12:45:56 -0700 Subject: [PATCH 2/2] docs(roadmap): frame B-9 saturation as unverified, locate the layer first Codex review (P1): the exactly-2000 saturation was asserted as a silent luad cap, but handle_origins serializes every call and export caps per-file via max_facts_per_file with truncation surfaced through file_end.is_truncated. No corpus-wide cap exists in luad, so the claim overstated a root cause the code disagrees with. Reword to an observation whose first acceptance condition is reproducing it across raw luad output and the consumer aggregation to find the responsible layer. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01FtirLUpUr9aWq57opQ3WVi --- ROADMAP.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 87c3285..e992a94 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -60,22 +60,26 @@ there). Re-run over the private corpus: the `control-flow-conflict` count falls previously-blocked bounded sink arguments resolve, with no argument losing an expression it had in 0.3.1. -### Make the origin unknown-reason cap explicit (B-9) - -Two origin unknown-reason totals saturate at exactly 2000 corpus-wide while no single -file approaches that number, which is the signature of a silent global cap on a counted -or de-duplicated set. It is not in the `origins.rs` analysis constants -(`MAX_EXPRESSION_*`, `MAX_TRANSFER_STEPS`, `MAX_ALTERNATIVES`, `MAX_BLOCK_REVISITS`); the -next place to look is the export and records/dedup path. A consumer measuring -release-over-release deltas read `unsupported-value` as falling 2394 to 2000 and -`overwritten` as rising 1976 to 2000, both cap artifacts rather than real movement, and -nearly reported a regression and an improvement that did not happen. Per the sequencing -rule below, a silently clamped count is a correctness defect, not an ergonomics one. - -**Acceptance.** Locate the cap; either lift it or make truncation explicit with a -diagnostic and a truncated flag so a clamped total is never presented as a measured one. -A test asserts that when the cap would bind, the output carries the truncation signal -rather than a silently clamped count. +### Investigate the origin unknown-reason saturation at 2000 (B-9) + +In one downstream aggregation, two origin unknown-reason totals read exactly 2000 +corpus-wide while no single file approached that number. That is the shape of a silent +cap, but the responsible layer is not yet identified and this is not confirmed to be a +`luad` defect. It is not in the `origins.rs` analysis constants (`MAX_EXPRESSION_*`, +`MAX_TRANSFER_STEPS`, `MAX_ALTERNATIVES`, `MAX_BLOCK_REVISITS`), and `luad`'s own paths do +not obviously clamp a global unknown-reason count: `handle_origins` serializes every call +site, and `export` uses a per-file `FactEmitter` with an optional `max_facts_per_file` +whose truncation is surfaced explicitly through `file_end.is_truncated`. So the +saturation may be a consumer-side aggregation artifact rather than anything `luad` does. +It still matters because a consumer read the totals as `unsupported-value` falling 2394 +to 2000 and `overwritten` rising 1976 to 2000, both of which are suspect if either side +is clamping, and nearly reported movement that may not have happened. + +**Acceptance.** First reproduce the 2000 saturation across raw `luad` output and the +consumer aggregation separately, to locate the responsible layer. If it is `luad`, make +the truncation explicit with a diagnostic and a truncated flag, and add a test asserting +the signal is present when the limit binds. If it is the consumer, close this as +not-a-`luad`-defect and record why, so the observation is not re-filed. ## Later