Skip to content

feat(repo): task host requirements, unsupported instead of a false pass (#41 D3) - #112

Merged
napetrov merged 18 commits into
intel:mainfrom
napetrov:d3-host-requirements
Oct 20, 2026
Merged

napetrov merged 18 commits into
intel:mainfrom
napetrov:d3-host-requirements

Conversation

@napetrov

@napetrov napetrov commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Part of #41 (step D3).

Summary

A task can now say what hardware its hidden test needs to show the bug. On a host that misses it, the task is reported as unsupported instead of getting a false pass or a false "grader broken". The motivating case is a oneDAL thread race that only reproduces when oneTBB sees 3 or more CPUs, and was never reproduced on AMD EPYC.

"host": {"min_cpus": 4, "cpu_vendors": ["GenuineIntel"], "cpu_flags": ["avx512f"]}

All three keys are optional. The check uses what a container of the evaluation sees, not what the host has:

 run_check / run_batch
+  validate_tasks(tasks)               # a bad `host` shape is refused before any container starts
+  host = probe_host(ctx)              # once per invocation: `python3 -c <stdlib probe>` in the image
+                                      #   cpus = min(affinity, ceil(cgroup cpu.max quota))
+                                      #   vendor, model, flags from /proc/cpuinfo
   per task / cell
+    if unmet(task, host):
+      tasks check -> verdict "unsupported", problem "host: needs min_cpus 4 ...; the grading container has 2 CPU(s) < min_cpus 4 (...)"
+      repo run    -> cell not run; <cell>/unsupported.json {reason, host}
     null / oracle / agent + grade as before
+    meta.json["host"] = host
 dokimi/repos/
+├── host.py        # requirements(), probe_host(), host_facts(), unmet()
 ├── selfcheck.py   # unsupported verdict; check.json gains "host"
 ├── runner.py      # unsupported cells skipped and marked; _eval/host.json; meta.json "host"
 └── report.py      # unsupported cells are not missing; listed with their reason
  • Exit codes. By default tasks check exits 1 for an unsupported task, the same as it already does for a task that needs egress. With --allow-unsupported, host-unsupported tasks are still printed with their reason but don't make the exit code 1; broken tasks and egress tasks still do. This is for CI on hosts that cannot show some bugs, like oneDAL's old SKIPPED. check.json records unsupported_by: egress | host. repo run exits 0 when the only unrun cells are unsupported ones.
  • Changing hosts. If a later run on a host that meets the requirements reaches an unsupported cell, it runs the cell and the marker is removed. A cell that already has a result.json keeps it on a host that misses the requirements, even with --force.
  • No probe result. If the probe fails (for example, the image has no python3), a task with host requirements is unsupported with the reason "cannot tell on this host". A task without requirements is not affected.

Evidence

New tests/test_repo_host.py (29 tests, no docker). The key ones:

test_tasks_check_reports_a_task_the_host_cannot_show_as_unsupported
  probe says 2 CPUs, AuthenticAMD; task fix_race needs min_cpus 4, GenuineIntel
  -> fix_race UNSUPPORTED "host: needs min_cpus 4 ...", no null/oracle container for it, exit 1, 1 probe
  -> probe says 8 CPUs, GenuineIntel: fix_race OK
test_run_skips_unsupported_cells_and_the_report_shows_them
  -> 2 fix_add cells ran, 2 fix_race cells hold only unsupported.json, report: 0 missing, 2 unsupported
  -> rerun on a capable host: fix_race runs, markers gone; back on the small host with --force: results kept
test_allow_unsupported_lets_host_unsupported_tasks_pass_the_exit_code   (exit 0, reason still printed)
test_allow_unsupported_still_fails_broken_and_egress_tasks[broken|egress] (exit 1)
test_run_refuses_a_bad_host_shape_before_any_agent / test_tasks_check_refuses_...
test_a_bad_host_shape_is_a_clear_error[...]  (9 shapes)
test_cgroup_quota_in_cpus[...]                (cgroup v1 and v2, --cpus 1.5 rounds up)
  • Before: on main with only host.py added, the 4 check/run/report tests fail: the race task is checked and run as usual, and there is no unsupported.json and no host in check.json. The rest fail to import without host.py.
    Without the flag's code, the 3 --allow-unsupported tests fail (unrecognized arguments: --allow-unsupported).
    After: all 29 pass. Full suite: 5207 passed, 2 skipped. ruff, mypy, schema_check, bandit -ll, pre-commit and the changelog fragment check all pass. Every new or touched function is at radon CC ≤ 15 except run_cell (18, unchanged from main) and summarize (25, unchanged).

  • Real docker smoke of probe_host in python:3.12-slim on a 224-thread Xeon host:

    extra docker run flag cpus affinity cgroup quota race task
    none 224 224 none supported
    --cpuset-cpus 0-1 2 2 none unsupported
    --cpus 2 2 224 2 unsupported
    --cpus 1.5 2 224 2 unsupported

Merge Danger

Door: two-way. host is a new optional key, so a task without it behaves as before. The new files (unsupported.json, _eval/host.json) and the new keys (meta.json host, check.json host) are additions.

Blast Radius: small

  • Every tasks check and repo run now starts one extra short container, the probe, even when no task declares host. This is how the facts get recorded. If the image has no python3, the probe failure is recorded and only tasks with host requirements are affected.
  • Run summaries gain unsupported and unsupported_tasks. The repo run command line still prints the old counts, and unsupported tasks are logged one line each.
  • repo report and repo compare stop counting unsupported cells as missing.
  • repo validate (manifest.py) does not check host yet; tasks check and run do.

Checklist

  • Tests added/updated and pytest passes
  • ruff, mypy, and schema checks pass
  • Docs updated (or N/A): contract §3 task.json table and §4.3 Self-check
  • No ad-hoc run artifacts committed
  • New files carry the SPDX license header (or are exempt in .github/.licenserc.yaml)

🤖 Generated with Claude Code

…ss (intel#41 D3)

task.json `host: {min_cpus, cpu_vendors, cpu_flags}` declares what a hidden
test needs to show its bug. `repo tasks check` and `repo run` probe once, in
a container of the evaluation's image, what a grading container sees (CPUs =
affinity capped by the cgroup quota; vendor and flags from /proc/cpuinfo).
An unmet task is `unsupported` with the reason in tasks check; in repo run
its cells are not run but marked with unsupported.json, which repo report
lists apart from failures and missing cells. The probed facts go to
check.json, _eval/host.json and meta.json. A bad `host` shape is refused
before any container starts.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@codecov-commenter

codecov-commenter commented Oct 19, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 94.97717% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.30%. Comparing base (a2280c2) to head (8bc27ce).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
dokimi/dokimi/repos/host.py 90.81% 7 Missing and 2 partials ⚠️
dokimi/dokimi/repos/selfcheck.py 88.23% 1 Missing and 1 partial ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #112      +/-   ##
==========================================
+ Coverage   91.23%   91.30%   +0.06%     
==========================================
  Files         213      216       +3     
  Lines       26357    26565     +208     
  Branches     4299     4337      +38     
==========================================
+ Hits        24047    24254     +207     
  Misses       1643     1643              
- Partials      667      668       +1     
Files with missing lines Coverage Δ
dokimi/dokimi/commands/repo_run.py 92.10% <100.00%> (+1.37%) ⬆️
dokimi/dokimi/repos/errors.py 100.00% <100.00%> (ø)
dokimi/dokimi/repos/host_spec.py 100.00% <100.00%> (ø)
dokimi/dokimi/repos/manifest.py 94.40% <100.00%> (+0.08%) ⬆️
dokimi/dokimi/repos/report.py 94.40% <100.00%> (+0.76%) ⬆️
dokimi/dokimi/repos/runner.py 93.72% <100.00%> (+0.71%) ⬆️
dokimi/dokimi/repos/workspace.py 87.23% <100.00%> (ø)
dokimi/dokimi/repos/selfcheck.py 89.79% <88.23%> (-0.80%) ⬇️
dokimi/dokimi/repos/host.py 90.81% <90.81%> (ø)

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

With the flag, a task that is unsupported because the host misses its
task.json `host` requirements is still printed with its reason but does not
make the exit code 1; broken tasks and tasks that need egress still do.
check.json records why a task is unsupported in `unsupported_by`
(egress | host). The default is unchanged.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The probe can miss container CPU quotas, and CLI requirement validation occurs after preflight containers start.

2 open issues
What changed in this PR

Adds task host requirements to repository evaluation, advancing #41’s D3 step so unsuitable hardware produces unsupported results rather than misleading grades.

Changes:

  • Validates requirements and probes container-visible CPU capabilities.
  • Skips unsupported tasks and records reasons and host facts.
  • Adds CLI handling, reporting, documentation, and regression tests.
File Description
dokimi/​tests/​test_repo_host.py Tests probing, validation, skipping, and reporting.
dokimi/​dokimi/​repos/​selfcheck.py Reports host-unsupported tasks.
dokimi/​dokimi/​repos/​runner.py Skips unsupported cells and records host facts.
dokimi/​dokimi/​repos/​report.py Separates unsupported cells from missing results.
dokimi/​dokimi/​repos/​host.py Implements requirements and capability probing.
dokimi/​dokimi/​commands/​repo_run.py Adds --allow-unsupported.
dokimi/​docs/​repo-evaluation-contract.md Documents requirements and unsupported behavior.
dokimi/​changelog.d/​20261018_120000_napetrov_d3-host-requirements.md Records the feature.

Review effort: Balanced

Comment thread dokimi/dokimi/repos/host.py Outdated
The host is probed once, before any cell (``_eval/host.json``); a cell of a task whose ``host``
requirements it does not meet is marked unsupported instead of run. One that already has a
``result.json`` (from a host that met them) keeps it, even with ``force``."""
validate_tasks(tasks)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 67d3a98: Host requirement validation now runs immediately after load_tasks() in _run_locked() before harness_version() and isolation_probe() calls. Malformed host requirements are rejected before any preflight containers start. Regression test strengthened to ensure no containers run before error, not just agent containers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed in 67d3a98: commands/repo_run.py calls validate_tasks right after load_tasks (line 182-183), before harness_version and isolation_probe, and the CLI test asserts no containers at all. No change in this round.

Copilot AI balanced review requested due to automatic review settings October 19, 2026 08:03
…arly in run

Thread 1 (host.py line 57): The PROBE script now reads /proc/self/cgroup and
/proc/self/mountinfo to correctly resolve CPU limits accounting for cgroup
namespace visibility. Falls back to v1 paths for systems without cgroup v2.
Regression test verifies the namespace-aware resolution logic is in place.

Thread 2 (runner.py line 249): Host requirement validation now happens
immediately after load_tasks() in _run_locked(), before harness_version() and
isolation_probe() calls. This ensures malformed host requirements are rejected
before any preflight containers start, not cell-by-cell during run_batch().
Regression test strengthens verification that no containers run before error.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The cgroup-v2 parser skips valid membership entries, allowing CPU-limited containers to run tasks they cannot meaningfully evaluate.

2 open issues
1 resolved since last review

Review effort: Balanced

Comment thread dokimi/dokimi/repos/host.py Outdated

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The feature is sound and the flow is well covered, but the cgroup-namespace fix in the last commit does not work, and the test added for it cannot tell. Two risks and a doc nit are below.

What I ran, on a Linux review host at 67d3a98:

  • make ci-local: 19 passed, 0 failed, 4 skipped (docker/semgrep/deploy jobs).
  • Reproductions for the findings, with the container runner mocked.
  • A mutation run over the new code: 32 mutants, 8 of which pass test_repo_host.py, test_repo_runner.py and test_repo_run.py.

I agree with @copilot-pull-request-reviewer on host.py:58: the cgroup v2 branch is dead. New evidence: with /proc/self/cgroup = 0::/system.slice/x.scope, a cgroup2 mount and cpu.max = 200000 100000, the PROBE text returns cpu_max: None, so a private cgroup namespace works only through the old fixed-path fallback and the last commit changes nothing there.

Other findings

  1. The contract still shows the old tasks check usage. (outside this diff) docs/repo-evaluation-contract.md:47 lists [--task <id>] [--warm] without --allow-unsupported, and line 670 says exits 1 if any task is broken or unsupported with no exception. The contract also does not mention _eval/host.json or check.json host, which the changelog does. Nit.

Checked, no issues

  • Schemas: no schema or version covers task.json host, meta.json or check.json in this tree, so no SCHEMA_VERSIONS or schema_compat change is needed; schema_compat passes.
  • Marker handling: _mark_unsupported is skipped for a cell with a result.json, load_unsupported ignores such cells, and --force keeps results on a small host.
  • Exit codes: --allow-unsupported exempts only unsupported_by == "host"; broken and egress tasks still exit 1 (mutants of this logic are killed).
  • Validation order: validate_tasks in _run_locked and run_check runs before harness_version, isolation_probe and the probe. _context() can still run the image build first.

Comment thread dokimi/tests/test_repo_host.py Outdated
Comment thread dokimi/dokimi/repos/selfcheck.py Outdated
Copilot AI balanced review requested due to automatic review settings October 19, 2026 14:04
…res from host misses

- Fix cgroup resolver to split on at most 2 colons, correctly parsing v2 paths
  like 0::/docker/eval (was: split("::"') breaks with extra colons).
- Change unmet() to return (reason, unsupported_by) tuple, distinguishing
  'host-probe' (probe failed) from 'host' (hardware insufficient). This ensures
  --allow-unsupported does not hide broken probes (e.g., python3 not in image).
- Add regression tests for cgroup v2 split and probe-failure marking.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…s test; untrack uv.lock

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

6 open issues

Review effort: Lite

cpuinfo = read("/proc/cpuinfo") or ""
cgroup_limits = resolve_cgroup_limits()
print(json.dumps({**cgroup_limits, "affinity": affinity, "cpuinfo": cpuinfo.strip().split("\n\n")[0]}))
"""

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No change: a cpu count of None means the probe ran but could not tell, and unmet() deliberately reports that as host-probe (cannot tell), not as a hardware miss. Defaulting to 1 would let a min_cpus 1 task run on an unknown host and misreport a min_cpus 2 task as a hardware miss, so the fail-closed reason stays.

Comment thread dokimi/dokimi/repos/host.py Outdated
Comment thread dokimi/dokimi/repos/host.py Outdated
Comment thread dokimi/dokimi/repos/selfcheck.py
Copilot AI balanced review requested due to automatic review settings October 19, 2026 14:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment thread dokimi/dokimi/repos/host.py Outdated

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review at 7b1a22d. The probe fix is right: the v2 branch now resolves, the new tests run PROBE against fake trees, and a probe failure is now host-probe, which --allow-unsupported does not exempt for tasks check. One thing is still open on the repo run side, and one of my earlier test-gap points is only partly addressed.

What I ran, on a Linux review host at 7b1a22d:

  • make ci-local: 19 passed, 0 failed, 4 skipped (docker/semgrep/deploy jobs).
  • Reproductions for the code finding, with the container runner mocked.
  • A mutation run over the new code: 49 mutants, 10 of which pass test_repo_host.py, test_repo_runner.py and test_repo_run.py.

Findings

  1. repo run exits 0 when the probe failed, so a broken image looks like a clean run. (commands/repo_run.py:207, outside this diff.) The exit check only looks at summary["errors"] and summary["budget_stopped"]. run_batch marks every task with host requirements unsupported on a host-probe failure, as it does for a hardware miss, and _run_locked never tells the two apart. With a fake runner whose probe exits 127 (python3: not found), repo run --task fix_race prints fix_race: UNSUPPORTED, its cells are not run: needs min_cpus 4, cpu_vendors GenuineIntel; cannot tell on this host: host probe exited 127 ... python3: not found and then 0 ran, 0 already done, 0 crashed, 0 not started (batch budget), exit 0. This is the case you fixed for tasks check in 2fb4001. A CI job that runs repo run on an image without python3 gets a green result with nothing graded. Suggestion: have run_batch return unsupported_by per task and exit 1 from _run_locked when any cell is host-probe. test_run_skips_unsupported_cells_and_the_report_shows_them only gets exit 0 because fix_add also runs; add a test with an all-unsupported batch and one with a failed probe.
  2. Nit: the contract and the help text do not describe the final behaviour. (outside this diff) docs/repo-evaluation-contract.md:47 still lists tasks check [--task <id>] [--warm] without --allow-unsupported, and line 670 says exits 1 if any task is broken or unsupported with no exception. The host-probe value of unsupported_by and the fact that it is never exempted appear in no doc or changelog line. _eval/host.json, unsupported.json and check.json host are not in the contract's artifact lists either.

I agree with @copilot-pull-request-reviewer on host.py:129: "host": {} returns a default HostRequirements, so unmet() skips its early exit and a failed probe marks a task that needs nothing as host-probe, with the reason text needs nothing; cannot tell on this host. New evidence: {"min_cpus": 1} and {"cpu_vendors": []} behave the same.

Still open from my earlier review:

  • dokimi/tests/test_repo_host.py: the probe tests are fixed. The three mutants named in the thread still pass the suite: _mark_unsupported without the rmtree/mkdir (116 tests pass), load_unsupported without the result.json skip, and validate_tasks removed from run_batch. The author deferred these, so I keep it open as a risk.

Checked, no issues

  • Probe cgroup v2: run against fake trees through DOKIMI_PROBE_ROOT: private namespace (0::/), host namespace, child max under a limited ancestor, child limit above an ancestor limit, missing cpu.max. Each gives the expected CPU count.
  • --allow-unsupported: exempts only unsupported_by == "host". Probe failure, egress and broken tasks still exit 1 for tasks check; mutants of this logic are killed.
  • Markers: a cell with a result.json is kept with --force on a small host. report and compare do not count unsupported cells as missing.
  • Validation order: validate_tasks now runs right after load_tasks in _run_locked, before harness_version and the isolation probe.
  • Repo rules: no schema covers these files, changelog fragment present, schema_compat and mkdocs build pass, tests offline.

…e; empty host asks for nothing

The probe walks the cpu controller of cgroup v1 the way it walks cgroup v2: the container's own
cgroup and every visible ancestor, with the tightest quota winning, and a combined "cpu,cpuacct"
mount is found from mountinfo. A task whose host object sets no constraint is treated as having no
requirements, so a probe failure cannot mark it unsupported. The selfcheck docstring lists host-probe.
Adds tests for the v1 layout, the empty host object, and the unsupported-cell bookkeeping and
bad-host refusal in run_batch that were not covered.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
A failed probe left every task with host requirements unrun, yet the run
exited 0, unlike tasks check. run_batch now returns the tasks the probe
could not decide and _run_locked exits 1 on them; a real hardware miss
still exits 0.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
A marker went only when run_cell reached its cell, so a capable run cut
short by the budget or an interrupt left the old host's marker, and repo
report showed the cell as unsupported with that host's reason, not missing.
run_batch now drops the markers of every planned cell this host supports.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
No fake tree had two numeric limits, so flipping either comparison to
loosest-wins passed the suite while a 2-CPU container read as 4.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
AVX512F, as vendor docs write it, was a host miss on every machine and
--allow-unsupported let it pass, so the task was never graded anywhere.
cpuinfo flags are lower case, so folding cannot make a false match.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Oct 20, 2026

Copy link
Copy Markdown
Contributor
  • Ran: the full suite on the head and on its merge with main, plus ruff, mypy and bandit, and differential repo run / repo report runs with faked probes.
  • Probe failure: repo run with a probe that exits 127 printed "0 ran ... 0 crashed" and exited 0 with nothing graded. It should exit 1, as tasks check does. Fixed in e764f81.
  • Stale markers: a run on a capable host that the budget stops leaves the old host's unsupported.json. repo report then shows the cell as unsupported with the old reason and "0 missing". It should be missing. Fixed in 1f56480.
  • Quota order: flipping either tightest-quota comparison (v1 or v2) passed the suite. Pinned in 16dcd50.
  • cpu_flags: ["AVX512F"] was a host miss on a host that has avx512f, and --allow-unsupported passed it. It should match. Fixed in 3dd1a5c.
  • Left for the author: cpu_vendors: ["Intel"] still misses everywhere (refuse unknown vendors?), and the contract usage nits at lines 47 and 670 are still open.

@xaleryb xaleryb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved at 3dd1a5c. No must-fix or mid item remains after our commits, each red without its fix; full suite, ruff, mypy and bandit clean on Linux; CI green.

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review at 3dd1a5c. The delta is right: the empty host object, the v1 combined-controller walk, the exit code on a failed probe, the stale-marker clearing and the case-insensitive flags each do what the commit says. Three things are worth fixing before merge, plus one nit.

What I ran, on a Linux review host at 3dd1a5c:

  • make ci-local: 19 passed, 0 failed, 4 skipped (docker/semgrep/deploy jobs). 5225 tests passed, 1 skipped.
  • Reproductions for the code findings, with the container runner mocked and the probe run against fake /proc and /sys trees.
  • A mutation run over the new code: the tightest-quota comparisons (v1 and v2), the ancestor walks, < to <= on min_cpus, the probe_failed exit condition and the marker-clearing loop are all caught. The ones that survive are in the inline comment on the tests.

Checked, no issues

  • Marker clearing: it touches only unsupported.json of the cells this invocation selects. Other tasks, arms and reps, and any result.json, are untouched. A capable run that the budget stops leaves the unreached cells missing, not unsupported.
  • tasks check: a failed probe is unsupported_by=host-probe, exits 1 even with --allow-unsupported, and a task without host stays ok.
  • Quota maths: 150000/100000 gives 2, 100001/100000 gives 2, 50000/100000 gives 1. Mount lines with extra or missing optional fields, a cpuset-only mount and a truncated line resolve correctly.
  • Repo rules: one changelog fragment, no generated artifacts, the contract page is already in nav:, no schema covers these files.

Comment thread dokimi/dokimi/repos/runner.py Outdated
Comment thread dokimi/dokimi/repos/host.py Outdated
Comment thread dokimi/tests/test_repo_host.py
Comment thread dokimi/docs/repo-evaluation-contract.md Outdated
…un is not a probe failure

The probe joined the whole cgroup path onto the mount point, so a mount that
shows only a subtree (mountinfo root /kubepods) read no limit. Strip the
mount root first, for v1 and v2.

repo run exited 1 on a complete re-run whenever the probe failed, because
probe_failed ignored cells that already have a result. A task now counts
only when one of its cells is left unrun.

Also: the contract and changelog say unsupported cells carry host facts in
unsupported.json, not meta.json; the probe tests no longer skip on small
hosts and cover the cpuset mount, cpuset cgroup line, first cpuinfo block
and upper-case host flags that the mutation check showed uncovered.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review at 0862ecd. The mount-root walk and the probe_failed fix are right, and all four of my earlier threads are addressed. Two test gaps and one nit are left.

What I ran, on a Linux review host at 0862ecd:

  • make ci-local: 19 passed, 0 failed, 4 skipped (docker/semgrep/deploy jobs). Changed-line coverage 96%.
  • Probe runs against fake /proc and /sys trees for root /, root equal to the cgroup path, root a prefix, root unrelated, root deeper than the path, trailing slashes and a sibling with a common string prefix, on v1 and v2. Every layout gives the right CPU count.
  • Mutation runs over the delta: removing inside(), ignoring the root on either leg, and using mountinfo field 4 as the root are all caught. probe_failed with the old condition and with any(_done is caught. The two mutants named inline would pass the file, by reading the tests; I could not run them.

Checked, no issues

  • Partial batch and --force: a complete fix_race batch re-run with --force and a failing probe exits 0 and keeps both result.json files. A batch with one finished and one unfinished cell exits 1, and the finished cell keeps its result.
  • Order: _done is evaluated before the stale-marker loop and before any cell runs, so the loop cannot change probe_failed. The budget stop and the unsupported path do not interact.
  • Docs: the contract's host row and §4.3 sentence match the code, and the changelog fragment has the same wording.

Comment thread dokimi/dokimi/repos/host.py
Comment thread dokimi/tests/test_repo_host.py
Comment thread dokimi/dokimi/commands/repo_run.py Outdated
…re-run

Add probe cases where the mount root is a string prefix of the cgroup path
(/kube vs /kubepods), v1 and v2, and a re-run where one of a task's cells
is unfinished on a failed probe (exit 1). The probe-failure message now
says the task has cells that were not run, since a finished cell is kept.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review at cce629c. The three earlier threads are fixed: the /kube and /kubepods string-prefix tests, the partly finished batch test and the reworded message all do what the commit says. Two things are worth fixing before merge, both small.

What I ran, on a Linux review host at cce629c:

  • make ci-local: 19 passed, 0 failed, 4 skipped (docker/semgrep/deploy jobs). Changed-line coverage is above the 80% gate.
  • Mutation runs over the delta, against test_repo_host.py (55 tests): dropping + "/" in inside() fails the v1 and the v2 string-prefix test, and nothing else. any(not _done -> all(not _done fails the new partial-batch test. any(not _done -> any(_done fails two tests. return own -> return "" in inside() fails both string-prefix tests. One mutant survives; it is in the inline comment.
  • Probe runs against fake /proc and /sys trees for a trailing-slash mount root, a root equal to the path, an unparsable cpu.max below a limited ancestor, and the ceil boundaries (150000/100000 gives 2, 100001/100000 gives 2, 50000/100000 gives 1). All give the right CPU count.

I agree with @xaleryb on cpu_vendors: ["Intel"] (it is exact and case-sensitive, and the contract does not say so) and on the contract usage lines 47 and 670, which still read as before at this head.

Checked, no issues

  • Message: "have cells that were not run" appears only at repo_run.py:208 and in the test that asserts it. The contract and the changelog fragment do not quote it, so they stay accurate.
  • Lint: ruff check passes on the changed files, including the longer print line.
  • Repo rules: no schema covers these files, one changelog fragment, SPDX headers present, no generated artifacts, tests offline.

Comment on lines +258 to +259
and any(not _done(batch_dir / cell_name(t.id, a, o.model, r))
for t, a, o, r in cells if t.id == task_id)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The if t.id == task_id filter in probe_failed is not pinned by any test. Mutation check: replacing for t, a, o, r in cells if t.id == task_id) with for t, a, o, r in cells) leaves all 55 tests in test_repo_host.py green.

Without the filter, one unfinished cell of any task flags every host-probe task. Case: fix_race (min_cpus 4) and fix_add (no host) both finished on a good host, then one fix_add result deleted and the batch re-run with a failing probe. fix_race has nothing left to run, and fix_add runs normally. With the mutant the run prints the host probe failed, so these tasks have cells that were not run: fix_race and exits 1. The unmutated code exits 0. The new partial-batch test only deletes a fix_race cell, so it cannot tell.

Suggestion: add a test beside test_a_partly_finished_batch_rerun_on_a_failed_probe_exits_1 that runs fix_add and fix_race on a good host, unlinks one fix_add result.json, re-runs with HostRunner(None), and asserts no host probe failed on stderr (no SystemExit). I ran exactly this: it passes on the head and fails on the mutant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 11b5f54: added test_an_unfinished_cell_of_another_task_does_not_fail_a_task_that_is_done (fix_add and fix_race finished, one fix_add result deleted, re-run on a failing probe, exits 0). Dropping the t.id == task_id filter makes it fail; it passes now.

Comment on lines +298 to +300
check_p.add_argument("--allow-unsupported", action="store_true", dest="allow_unsupported",
help="Exit 0 when the only tasks that are not ok are unsupported because this host "
"misses their task.json `host` requirements (still printed with the reason). Broken "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The --allow-unsupported help says only broken and egress tasks still exit 1, but a failed host probe exits 1 too. repo_run.py:150 allows only unsupported_by == "host", so a task whose probe failed (host-probe, for example an image without python3) is not exempted.

A CI job that sets the flag on such an image fails with exit 1, and neither this help text, the contract (§4.3, "broken tasks and tasks that need egress still do") nor the changelog fragment says why. The distinction exists only in the unmet() and check_task docstrings.

Suggestion: add "and tasks whose host probe failed" to the help text, the contract sentence and the changelog line.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 11b5f54: the --allow-unsupported help, the contract sentence (§4.3) and the changelog line now say tasks whose host probe failed still exit 1.

…robe is not exempted by --allow-unsupported

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Re-added by mistake in 11b5f54; main does not track it.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Comment thread dokimi/dokimi/repos/selfcheck.py Outdated
Comment on lines 74 to 77
reason, unsupported_by = _host_reason(ctx, task, work_dir, host)
if reason:
res.update(verdict="unsupported", unsupported_by=unsupported_by or "host", problems=[f"host: {reason}"])
return res

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

On a host miss this returns before _prepare, the oracle and isolation_probe run, but only the null expectation depends on the hardware. Repro with the fake runner: RACE with gate ["tests_pas"] is BROKEN on 8 CPUs (exit 1), and on 2 CPUs with --allow-unsupported it is UNSUPPORTED and exits 0, though the help and the changelog say broken tasks still exit 1. So CI on a small runner never sees a broken grader of a host task. Could it still prepare the workspace and run isolation (and the oracle when only min_cpus is short), and skip only the null check? With --warm, the host check could be skipped, as the egress check is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f2d3932. A host miss no longer returns early: the workspace is still prepared and isolation probed, and the oracle runs when only CPUs are short. Only the null check is skipped. --warm skips the host check, as it does egress. Tests: test_a_host_miss_still_checks_the_oracle_when_only_cpus_are_short, test_a_host_miss_still_probes_isolation_and_skips_only_null, test_warm_does_not_judge_the_host.

host = probe_host(ctx, batch_dir / "_eval")
_write_json(batch_dir / "_eval" / "host.json", host)
verdicts = {t.id: unmet(t, host) for t in tasks}
unsupported = {task_id: why for task_id, (why, _) in verdicts.items() if why}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This dict also takes host-probe verdicts, so a failed probe writes unsupported.json into every cell of the task. repo run exits 1, but a later repo report on that batch says "0 missing" and lists the cells as "unsupported (not run, not failed)", and repo compare drops them from its missing count too. Nothing was measured there, so they should stay missing ("rerun the batch"). Could _mark_unsupported be skipped when unsupported_by is "host-probe"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f2d3932. A host-probe failure writes no unsupported.json, so repo report/compare count the cells as missing. Test: test_a_probe_failure_leaves_no_marker_for_report_to_count.

return ", ".join(parts) or "nothing"


def requirements(task: Task) -> HostRequirements | None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

repo validate does not call this, so task.json "host": {"min_cpu": 4, "cpu_flags": "avx2"} passes the static check (✓) and is refused only by tasks check / run. Could manifest._check_task call requirements() and add the HostSpecError text to the task's problems, so the static check and the run use the same rule?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f2d3932. manifest._check_task now calls requirements() and adds the HostSpecError text to the task's problems, so repo validate reports a bad host. Test: test_validate_reports_a_bad_host_shape.

Comment on lines +265 to +269
if not facts or facts.get("cpus") is None:
# A probe failure (error set, or no CPU count). Mark as "host-probe" so
# --allow-unsupported does not hide a broken measurement.
why = (facts or {}).get("error") or "the host probe reported no CPU count"
return (f"needs {need.describe()}; cannot tell on this host: {why}", "host-probe")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Only a missing CPU count is treated as "cannot tell". If /proc/cpuinfo could not be read, the facts have cpu_vendor None and cpu_flags [], and a task with cpu_flags or cpu_vendors becomes a "host" miss that --allow-unsupported passes. Could it return "host-probe" when the task needs vendor or flags and the probe gave neither?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f2d3932. A task that needs a vendor or flags, when the probe read neither from /proc/cpuinfo, now returns host-probe instead of a hardware miss. Test: test_missing_cpuinfo_is_a_probe_failure_not_a_miss.

Comment thread dokimi/dokimi/repos/host.py Outdated
short = []
if facts["cpus"] < need.min_cpus:
short.append(f"{facts['cpus']} CPU(s) < min_cpus {need.min_cpus}")
if need.cpu_vendors and facts.get("cpu_vendor") not in need.cpu_vendors:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Flags are matched in any case, vendors exactly, so cpu_vendors ["Intel"] or ["genuineintel"] is a
host miss on every machine, and --allow-unsupported hides it. Could vendors be compared case-insensitively too, and an unknown vendor name (not GenuineIntel, AuthenticAMD, ...) be refused by requirements()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f2d3932. Vendors compare case-insensitively, and requirements() refuses names outside GenuineIntel, AuthenticAMD, HygonGenuine, CentaurHauls and Shanghai. Contract doc and changelog updated. Test: test_vendors_compare_in_any_case_and_unknown_names_are_refused.

…lation, probe-failure markers, validate, cpuinfo, vendor case

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…ifest does not import host

manifest validated task.json host by importing host (and workspace) inside a
function, which grew the bases/manifest import cycle. host_spec parses the raw
value, errors holds PrepError for both workspace and host_spec; host re-exports.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…requirements

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

# Conflicts:
#	dokimi/dokimi/repos/report.py
@napetrov
napetrov merged commit 60ef4ff into intel:main Oct 20, 2026
19 of 20 checks passed
xaleryb pushed a commit that referenced this pull request Oct 21, 2026
…#144)

Both files came in with #112 without the SPDX header, so the
skywalking-eyes header check in Static checks fails on main and on
every PR that merges main.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
antonwolfy added a commit that referenced this pull request Oct 26, 2026
…record, both new arms in the README (#199)

Leftovers from the reviews of #117 and #112:
- The contract said every character outside [A-Za-z0-9.-] becomes `_`.
  The code turns each run into one `_`, drops a leading or trailing `_`
  and cuts at 40, so `my fix (v2).diff` is `my_fix_v2_.diff`. Also re-wrap
  the paragraph's 130-character line.
- The S5 usage of `repo tasks check` lacked `--allow-unsupported`, and the
  example's "exits 1 if any task is unsupported" had no exception for it.
- §4.3 names where the host probe's facts go: `check.json` `host` and the
  batch's `_eval/host.json`.
- The README's arm table lists `guidance:rev:<rev>` and `patch:<file>`.

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
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.

6 participants