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..e992a94 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -19,6 +19,68 @@ 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. + +### 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 - Promote an exact target once one profile's evidence is complete end to end. See the