Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
62 changes: 62 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading