From 1d3b61c6c212693ae983a52a7c64920df88c589d Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Thu, 11 Jun 2026 07:49:29 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(lvl5):=20lint=20table=20parser=20?= =?UTF-8?q?=E2=80=94=20abra=20renders=20HEAVY=20box=20verticals=20(?= =?UTF-8?q?=E2=94=83=20U+2503);=20accept=20both;=20meta=20registry=20EXPEC?= =?UTF-8?q?TED=5FNA/BACKUP=5FCAPABLE=20wording=20=E2=86=92=20regenerated?= =?UTF-8?q?=20doc=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found by real-abra smoke on cc-ci: hedgedoc clean → pass; +lightweight tag → fail R014. Full suite 246 passed on cc-ci venv. --- docs/recipe-customization.md | 2 +- runner/harness/lint.py | 5 +++-- runner/harness/meta.py | 4 ++-- tests/unit/test_lint.py | 26 +++++++++++++++++--------- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/docs/recipe-customization.md b/docs/recipe-customization.md index c772044..a562288 100644 --- a/docs/recipe-customization.md +++ b/docs/recipe-customization.md @@ -116,7 +116,7 @@ _This table is GENERATED from the `runner/harness/meta.py` KEYS registry by `scr | `DEPLOY_TIMEOUT` | `int` | `600` | Max seconds to wait for swarm convergence per deploy. | | `HTTP_TIMEOUT` | `int` | `300` | Max seconds to wait for HTTP health after convergence. | | `BACKUP_CAPABLE` | `bool` | `None` | Override the backup-tier capability auto-detect (compose `backupbot.backup` labels). `False` forces an intentional skip of the backup/restore rung; `True` forces the tier on; unset = auto-detect. | -| `EXPECTED_NA` | `dict` | `None` | Declare a non-run rung an INTENTIONAL skip: `{rung: reason}`. The level climbs past an intentional skip; an undeclared non-run rung is *unverified* and blocks the level above it (phase lvl5; classification table in `machine-docs/DECISIONS.md`). Never overrides an exercised pass/fail; the `lint` rung has no escape hatch. | +| `EXPECTED_NA` | `dict` | `None` | Declare a non-run rung an INTENTIONAL skip: `{rung: reason}` — the level climbs past it; an undeclared non-run rung is *unverified* and blocks the level above it (classification table: machine-docs/DECISIONS.md phase lvl5). Never overrides an exercised pass/fail; the `lint` rung has no escape hatch. | | `READY_PROBE` | `hook` | `None` | Callable `(ctx) -> [probe, ...]` returning extra readiness probes, run after install AND after upgrade: HTTP `{host, path, ok}` or TCP `{tcp_host, tcp_port, stable}`. | | `UPGRADE_BASE_VERSION` | `str` | `None` | Exact published tag overriding the upgrade tier's base (default: `recipe_versions[-2]`). | | `BACKUP_VERIFY` | `hook` | `None` | Callable `(ctx) -> bool` post-backup data-capture check; `False` re-runs the backup (truncated-dump race guard), retried up to 3 attempts. | diff --git a/runner/harness/lint.py b/runner/harness/lint.py index 60c317d..26b9df1 100644 --- a/runner/harness/lint.py +++ b/runner/harness/lint.py @@ -42,8 +42,9 @@ LINT_TIMEOUT = 60 # hard budget, seconds; observed ~0.7s per recipe # Strip ANSI escape sequences from PTY output before parsing. _ANSI = re.compile(r"\x1b\[[0-9;?]*[A-Za-z]") -# A table row: │ R014 │ description │ error │ ✅/❌ │ skipped │ how-to-fix │ -_ROW = re.compile(r"^\s*│\s*(R\d+)\s*│(.*?)│\s*(warn|error)\s*│\s*(✅|❌)\s*│\s*([^│]*)│") +# A table row: ┃ R014 ┃ description ┃ error ┃ ✅/❌ ┃ skipped ┃ how-to-fix ┃ — abra renders the +# grid with HEAVY box-drawing verticals (┃ U+2503); accept the light variant (│ U+2502) too. +_ROW = re.compile(r"^\s*[│┃]\s*(R\d+)\s*[│┃](.*?)[│┃]\s*(warn|error)\s*[│┃]\s*(✅|❌)\s*[│┃]\s*([^│┃]*)[│┃]") # abra's trailing sentinel when any error-severity rule is unsatisfied (cross-check only). _SENTINEL = "critical errors present" diff --git a/runner/harness/meta.py b/runner/harness/meta.py index e1d786a..ff722b3 100644 --- a/runner/harness/meta.py +++ b/runner/harness/meta.py @@ -70,13 +70,13 @@ KEYS: tuple[Key, ...] = ( "BACKUP_CAPABLE", "bool", None, - "Override the backup-tier capability auto-detect (compose `backupbot.backup` labels). `False` forces N/A; `True` forces the tier on; unset = auto-detect.", + "Override the backup-tier capability auto-detect (compose `backupbot.backup` labels). `False` forces an intentional skip of the backup/restore rung; `True` forces the tier on; unset = auto-detect.", ), Key( "EXPECTED_NA", "dict", None, - "Declare an N/A rung intentional: `{rung: reason}`. The cap stands either way; only the report wording changes.", + "Declare a non-run rung an INTENTIONAL skip: `{rung: reason}` — the level climbs past it; an undeclared non-run rung is *unverified* and blocks the level above it (classification table: machine-docs/DECISIONS.md phase lvl5). Never overrides an exercised pass/fail; the `lint` rung has no escape hatch.", ), Key( "READY_PROBE", diff --git a/tests/unit/test_lint.py b/tests/unit/test_lint.py index 729d749..32e1edc 100644 --- a/tests/unit/test_lint.py +++ b/tests/unit/test_lint.py @@ -16,28 +16,32 @@ import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) from harness import lint as L # noqa: E402 -# Realistic abra lint table rows (unicode box drawing, ✅/❌ marks), as captured on cc-ci. +# Realistic abra lint table rows, as captured on cc-ci: abra renders HEAVY box-drawing +# verticals (┃ U+2503) — the parser must match those, not just the light │. TABLE_OK = ( "┏━━━━━━┳━━━━━━┓\r\n" - "│ R001 │ compose config has expected version │ warn │ ✅ │ - │ ensure │\r\n" - "│ R015 │ long secret names │ warn │ ❌ │ - │ reduce │\r\n" - "│ R008 │ .env.sample provided │ error │ ✅ │ - │ create │\r\n" - "│ R014 │ only annotated tags used for recipe version │ error │ ✅ │ - │ retag │\r\n" + "┃ R001 ┃ compose config has expected version ┃ warn ┃ ✅ ┃ - ┃ ensure ┃\r\n" + "┃ R015 ┃ long secret names ┃ warn ┃ ❌ ┃ - ┃ reduce ┃\r\n" + "┃ R008 ┃ .env.sample provided ┃ error ┃ ✅ ┃ - ┃ create ┃\r\n" + "┃ R014 ┃ only annotated tags used for recipe version ┃ error ┃ ✅ ┃ - ┃ retag ┃\r\n" "┗━━━━━━┻━━━━━━┛\r\n" "WARN secret session_secret is longer than 12 characters\r\n" ) +# The light-vertical variant must parse identically (defensive: abra theme/version drift). +TABLE_OK_LIGHT = TABLE_OK.replace("┃", "│") + TABLE_R014_FAIL = ( TABLE_OK.replace( - "│ R014 │ only annotated tags used for recipe version │ error │ ✅", - "│ R014 │ only annotated tags used for recipe version │ error │ ❌", + "┃ R014 ┃ only annotated tags used for recipe version ┃ error ┃ ✅", + "┃ R014 ┃ only annotated tags used for recipe version ┃ error ┃ ❌", ) + "WARN critical errors present in hedgedoc config\r\n" ) TABLE_SKIPPED_ERROR = TABLE_OK.replace( - "│ R014 │ only annotated tags used for recipe version │ error │ ✅ │ - │", - "│ R014 │ only annotated tags used for recipe version │ error │ ❌ │ skipped │", + "┃ R014 ┃ only annotated tags used for recipe version ┃ error ┃ ✅ ┃ - ┃", + "┃ R014 ┃ only annotated tags used for recipe version ┃ error ┃ ❌ ┃ skipped ┃", ) @@ -59,6 +63,10 @@ def test_parse_table_strips_ansi(): assert len(rows) == 4 +def test_parse_table_light_verticals_too(): + assert L.parse_table(TABLE_OK_LIGHT) == L.parse_table(TABLE_OK) + + def test_parse_table_garbage_is_empty(): assert L.parse_table("FATA something exploded\r\n") == [] assert L.parse_table("") == [] From 3d8d286cf3f2df7d164bf458f07bbb916cc18f2b Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Thu, 11 Jun 2026 07:49:47 +0000 Subject: [PATCH 2/2] chore(lvl5): ruff format lint.py --- runner/harness/lint.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/runner/harness/lint.py b/runner/harness/lint.py index 26b9df1..9e28ae1 100644 --- a/runner/harness/lint.py +++ b/runner/harness/lint.py @@ -44,7 +44,9 @@ _ANSI = re.compile(r"\x1b\[[0-9;?]*[A-Za-z]") # A table row: ┃ R014 ┃ description ┃ error ┃ ✅/❌ ┃ skipped ┃ how-to-fix ┃ — abra renders the # grid with HEAVY box-drawing verticals (┃ U+2503); accept the light variant (│ U+2502) too. -_ROW = re.compile(r"^\s*[│┃]\s*(R\d+)\s*[│┃](.*?)[│┃]\s*(warn|error)\s*[│┃]\s*(✅|❌)\s*[│┃]\s*([^│┃]*)[│┃]") +_ROW = re.compile( + r"^\s*[│┃]\s*(R\d+)\s*[│┃](.*?)[│┃]\s*(warn|error)\s*[│┃]\s*(✅|❌)\s*[│┃]\s*([^│┃]*)[│┃]" +) # abra's trailing sentinel when any error-severity rule is unsatisfied (cross-check only). _SENTINEL = "critical errors present"