Skip to content

fix(harness): take the build-hazard look's folders in name order (TH-9514) - #285

Merged
khushalsonawat merged 11 commits into
feat/TH-8804-source-subfolderfrom
feat/TH-9514-hazard-name-order
Nov 11, 2026
Merged

khushalsonawat merged 11 commits into
feat/TH-8804-source-subfolderfrom
feat/TH-9514-hazard-name-order

Conversation

@khushalsonawat

Copy link
Copy Markdown
Member

Review only the commits after bc3dd187

This branch starts from #273's head (bc3dd187, feat/TH-9449-scan-followups), so it carries #273's nine commits. Those are reviewed in #273. This PR's own change is the last two commits:

  • bc778b61 fix(harness): take the build-hazard look's folders in name order
  • 6c12b363 test(harness): hold the hazard look's order at its top and below it

Compare: bc3dd187...feat/TH-9514-hazard-name-order. Merge #273 first. This PR's diff then shrinks to those two commits.

Description

For each agent the scan lists, a quick "hazard" look asks whether a build of that folder may stop on its own fallback, such as two agent.py files below it. The look stops early at its caps:

It took folders in whatever order the file system lists them. So on a tree big enough to reach a cap, the same tree could say agent_py on a Mac and "" on Linux. That's how #273's CI failed on Linux after passing on the Mac.

Each folder's subfolders and files are now sorted before anything else, as the scan's walk (_source_files) and the framework check (folder_findings) already do. The look goes depth first in name order everywhere, so a cap stops it at the same folder.

Type of Change

  • Bug fix
  • Tests

Changes

  • scan_walk._hazard: directories.sort() and files.sort() before the ignore list.
  • build_hazard's docstring names its three stops and the order.
  • New test, test_a_hazard_look_stopped_by_its_cap_gives_one_answer_in_any_listing_order[found|stopped]-[top|nested]. It forces both listing orders through a wrapped os.walk, with the tree at the top and a level below it:
    • found must give agent_py in both orders;
    • stopped must give "" in both, with neither agent.py ever listed, so the "" comes from the cap.
  • One stale test comment, "(os.walk doesn't sort them)", is removed.

Testing

  • One file at a time, on Python 3.12.14 and 3.12.3:

    test file passed
    test_scan_walk 151
    test_agent_scan 63
    test_scan_folders 8
    test_scan_document 17
    test_scan_ai 59
    test_authoring_entrypoint_find_agents 5
    test_framework_detection 262
    test_reading_entry 61

    test_harness_architecture gives 35 passed and 11 failed, the same as at bc3dd187 without this change. The 11 are TH-9444's hosted-GitHub tests, which CI deselects at this base.

  • Planted breaks, each run against each of the four cases:

    • the sort removed: caught by all four;
    • the sort after the filter: caught by all four;
    • the sort skipped at the top: caught by the two top cases;
    • the sort only at the top: caught by the two nested cases.
  • git merge-tree into feat/TH-8804-source-subfolder is clean.

  • Review, on Lane N: the design review, the plan review and the change review each cleared in round 1. Both change reviewers ran every plant.

What wasn't reviewed

6c12b363 fixes the change review's one shared Medium, a test that missed the top folder. It changes tests only.

Not changed here

#273's listed-paths cap counts absolute paths. So where it stops still depends on the checkout's root length, not just the order. That's left as it is.

Related Issues

Refs: TH-9514

A settings template the decoder rejects marks the description's row
short, against a readable control, with no reader fault logged and the
next file still read. The walk's summary line tells reader faults from
other unread files. A kit fault escaping the description keeps what was
read, with its trace. A MemoryError in one folder's check comes back as
a trace, so the child's loop goes on.

TH-9451

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…ed reader

The walk's and the description's capped readers were copies. One reader
now counts reads and bytes, in the order every stop point relies on, and
each caller says what a cap means: the walk stops, the description marks
its row partial. No behaviour changes; the walk's tests pass unchanged.

TH-9450

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
What the walk keeps grows with the names its files hold, not with their
number: a crafted 51 KB file of unique dotted imports keeps 671 KB, so
5,100 of them, inside the byte cap and under the 40,000-file cap, kept
3.3 GiB in a 4 GB sandbox. Everything the walk and its join keep is now
charged where it is kept, at the allocator's blocks, against one
1,536 MiB budget: each file's facts, key and import list, its share of
the join, templates, graph entries, prompt paths, folder lookups,
project module sets and the folders os.walk holds to enter. Past it the
walk stops at "limit" with nothing of that file kept.

Four things stop growing instead of being counted: a resolved import
holds the walk's own path, the places an import is looked for follow a
link per folder to its nearest project, a row keeps at most the 50
setting names the answer file writes, and a hazard look gives up past
64 MiB of listed paths. The file cap now counts only files the join
works on, so many small files are read whole. The join's two quadratic
loops, and its pass over the files for each row, become linear.

TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
Round the measured blocks apart from the code's own rounding, measure a
project lookup on its own, hold the first-names pass's limit and early
stop, a row that leaves out a deeper listed folder and a sibling with
the same start, and the clock check counted only after the walk. The
description's names are capped once, where the row is built.

TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…s up

TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
Both reviewers cleared round 1. This fixes what they found:

- A cap reached while joining now ends as limit, like the edge budget.
  No lookup in the join charges today; this keeps a future one off the
  crash path.
- A row's pass skips the 50-name merge for a file with no settings.
- describe_entry's comment says only the platform's secret values are
  masked, and the hazard look's count says it is in characters.
- New tests hold what no test held: a charge outside one file's read
  (a listing, a prompt path) ends as limit; the file that crosses the
  budget is kept nowhere; a cap while joining ends as limit; an import
  inside an inner project resolves at an outer project's root; the
  description's byte cap, and that it reads nothing past its cap; each
  lookup charge and ancestor link; the hazard look counting full paths;
  import levels and line numbers over 256, as a shape and as pairs.

TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…isted in

os.walk doesn't sort, and Linux lists a and b before the long folders
under c, so the look found both agent.py files before its listed-path
bound was passed and CI failed. The long folders now sit beside a and b,
so the root's own listing passes the bound first on any file system.

TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
The hazard look walked folders in whatever order the file system listed
them. Its caps stop it partway, so on a tree big enough to reach one,
which agent.py files it had seen depended on the file system: the same
tree could say agent_py on a Mac and "" on Linux.

Each folder's subfolders and files are now sorted before anything else,
as the scan's walk and the framework check already do. The look then
goes depth first in name order everywhere, and a cap stops it at the
same folder. A test forces both listing orders a level below the top,
once where the cap is avoided and once where it stops the look.

TH-9514

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
Both change reviewers cleared round 1, and found the same gap: the test
put its whole tree a level down, so a sort skipped only at the folder
the look starts from passed. Each case now runs with its tree at the top
and a level below it. A sort skipped at the top fails the top cases, and
a sort only at the top fails the nested ones. The forced order comes
from a small factory rather than default arguments.

TH-9514

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
@khushalsonawat khushalsonawat self-assigned this Nov 11, 2026

@cdileep23 cdileep23 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check the comments.

Comment thread src/fi/alk/harness/scan_walk.py Resolved
@khushalsonawat
khushalsonawat merged commit d60dbf7 into feat/TH-8804-source-subfolder Nov 11, 2026
11 checks passed
@khushalsonawat
khushalsonawat deleted the feat/TH-9514-hazard-name-order branch November 11, 2026 05:48
khushalsonawat added a commit that referenced this pull request Nov 11, 2026
… into TH-9491

- tools.py: _save_contract keeps the single writer (write_contract) and takes
  #268's new reply text, which names unconfirmed prompt lines.
- contract.py: #268's new fields, unconfirmed_prompt_from and model (ModelSource),
  are marked as kept as written: they hold source lines and the cited model name,
  read by the build. The test that fails on an unmarked field caught them.

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
khushalsonawat added a commit that referenced this pull request Nov 11, 2026
[TH-9449] fix(harness): answer Dileep's P3 comments on #273, #275, #277 and #285
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants