fix(lvl5): lint table parser — abra renders HEAVY box verticals (┃ U+2503); accept both; meta registry EXPECTED_NA/BACKUP_CAPABLE wording → regenerated doc table
continuous-integration/drone/push Build is failing

Found by real-abra smoke on cc-ci: hedgedoc clean → pass; +lightweight tag →
fail R014. Full suite 246 passed on cc-ci venv.
This commit is contained in:
autonomic-bot
2026-06-11 07:49:29 +00:00
parent af7488a498
commit 1d3b61c6c2
4 changed files with 23 additions and 14 deletions
+1 -1
View File
@@ -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. | | `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. | | `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. | | `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}`. | | `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]`). | | `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. | | `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. |
+3 -2
View File
@@ -42,8 +42,9 @@ LINT_TIMEOUT = 60 # hard budget, seconds; observed ~0.7s per recipe
# Strip ANSI escape sequences from PTY output before parsing. # Strip ANSI escape sequences from PTY output before parsing.
_ANSI = re.compile(r"\x1b\[[0-9;?]*[A-Za-z]") _ANSI = re.compile(r"\x1b\[[0-9;?]*[A-Za-z]")
# A table row: R014 description error ✅/❌ skipped how-to-fix # A table row: R014 description error ✅/❌ skipped how-to-fix ┃ — abra renders the
_ROW = re.compile(r"^\s*│\s*(R\d+)\s*│(.*?)│\s*(warn|error)\s*│\s*(✅|❌)\s*│\s*([^│]*)│") # 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). # abra's trailing sentinel when any error-severity rule is unsatisfied (cross-check only).
_SENTINEL = "critical errors present" _SENTINEL = "critical errors present"
+2 -2
View File
@@ -70,13 +70,13 @@ KEYS: tuple[Key, ...] = (
"BACKUP_CAPABLE", "BACKUP_CAPABLE",
"bool", "bool",
None, 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( Key(
"EXPECTED_NA", "EXPECTED_NA",
"dict", "dict",
None, 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( Key(
"READY_PROBE", "READY_PROBE",
+17 -9
View File
@@ -16,28 +16,32 @@ import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
from harness import lint as L # noqa: E402 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 = ( TABLE_OK = (
"┏━━━━━━┳━━━━━━┓\r\n" "┏━━━━━━┳━━━━━━┓\r\n"
" R001 compose config has expected version warn - ensure \r\n" " R001 compose config has expected version warn - ensure \r\n"
" R015 long secret names warn - reduce \r\n" " R015 long secret names warn - reduce \r\n"
" R008 .env.sample provided error - create \r\n" " R008 .env.sample provided error - create \r\n"
" R014 only annotated tags used for recipe version error - retag \r\n" " R014 only annotated tags used for recipe version error - retag \r\n"
"┗━━━━━━┻━━━━━━┛\r\n" "┗━━━━━━┻━━━━━━┛\r\n"
"WARN secret session_secret is longer than 12 characters\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_R014_FAIL = (
TABLE_OK.replace( 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" + "WARN critical errors present in hedgedoc config\r\n"
) )
TABLE_SKIPPED_ERROR = TABLE_OK.replace( TABLE_SKIPPED_ERROR = 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 skipped ", " R014 only annotated tags used for recipe version error skipped ",
) )
@@ -59,6 +63,10 @@ def test_parse_table_strips_ansi():
assert len(rows) == 4 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(): def test_parse_table_garbage_is_empty():
assert L.parse_table("FATA something exploded\r\n") == [] assert L.parse_table("FATA something exploded\r\n") == []
assert L.parse_table("") == [] assert L.parse_table("") == []