feat(ci,tests): PR-4b — promote teaching onto the gate, and close pin 3's own blind spot
The reachability gap turns by PROMOTION for the first time: 15 -> 14. PR-3b's
earlier turn was by deletion, which is free. This one costs 22.1s on every push,
which is exactly why it is a decision rather than a default.
THE RECOMMENDATION WAS RE-DERIVED, NOT EXECUTED — and it did not survive as
written. PR-4b recommended `teaching` on the strength of G-22:
test_ratification_ceremony.py was red on main, in `teaching`, run by no gate.
That hole was already closed a different way — G-22's fix promoted that single
file into smoke. So `teaching` would have added 9 net-new files, NONE with a
demonstrated escape, and promoting on a justification that has already been
spent is precisely the failure this arc exists to close.
Promoted on a stronger, doctrinal basis instead.
tests/test_epistemic_invariants.py enforces AGENTS.md's "Teaching and mutation
safety" non-negotiables in substance — INV-22/INV-23: an unmarked proposal and
an unmarked reviewed example default to SPECULATIVE, the status enum has exactly
its four positions, a pack-mutation proposal carries no hardening flag. Durable
corpus mutation is the one irreversible thing in this system, and the suite
guarding it ran on no pre-merge gate. That justification does not require an
incident to have happened yet, which makes it the better one.
Measured: 22.1s / 122 tests across 9 net-new files (the tenth was already on the
gate). Both invocation sites updated, GATE_SUITES extended, `teaching` removed
from UNREACHABLE_BASELINE in the same edit, ratchet lowered <= 15 to <= 14 —
the both-directions discipline that has caught this author twice.
A HOLLOW SPOT IN PIN 3 ITSELF, found by sabotage-testing the promotion rather
than trusting it. _suites_invoked_by_gate_tiers() takes the UNION of
scripts/hooks/pre-push and scripts/ci/local-ci.sh, so a suite present in either
satisfies it. Removing `teaching` from local-ci.sh alone PASSED SILENTLY: the
two scripts could drift from each other invisibly while local-ci.sh states in
prose that it runs "the same four steps as scripts/hooks/pre-push". A developer
running one would get coverage the other advertises and does not deliver.
Closed by test_the_two_gate_scripts_invoke_the_same_suites, scoped to the
numbered (n/m) gate steps — a discriminator both files already use — so it reads
the gate tier without parsing shell control flow. The identical sabotage now
fires. Verified in three directions: removed from one script (fires, and did not
before), removed from both (the pre-existing assertion fires), restored (green).
THIS IS NOT THE CI-PARITY PIN N-9 KILLED, and the distinction is recorded at the
assertion site so it cannot be re-litigated a third time. That one asserted
TEST_SUITES["smoke"] == smoke.yml and was reverted in 50fa287d because
AGENTS.md:280 makes GitHub Actions billing-locked dead signals — parity with a
dead signal buys nothing. Both scripts here are LIVE local merge bars.
Gate is now four steps: smoke + warmed_session + deductive + teaching.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
This commit is contained in:
parent
71d6366fa3
commit
294bbe7b9b
5 changed files with 93 additions and 23 deletions
|
|
@ -112,6 +112,8 @@ Pin 1 exists because the two gates have **already** drifted (see H-12): the loca
|
||||||
|
|
||||||
**What pin 3 deliberately does not do — and what is now Shay's to decide:** *which* of the 19 belong on the gate. That costs gate time and is a real decision. `teaching` is the obvious first candidate: it just shipped a red ratification pin that no gate ran. Recorded as the open half of this entry.
|
**What pin 3 deliberately does not do — and what is now Shay's to decide:** *which* of the 19 belong on the gate. That costs gate time and is a real decision. `teaching` is the obvious first candidate: it just shipped a red ratification pin that no gate ran. Recorded as the open half of this entry.
|
||||||
|
|
||||||
|
**CLOSED 2026-07-28 — `teaching` promoted (PR-4b), gap 15 → 14, the first turn by promotion rather than deletion. Sabotage-testing that promotion also found pin 3's own union blind spot (the two gate scripts could drift from each other undetected) and closed it.**
|
||||||
|
|
||||||
**The gap is 15, not 19, and the open half is now a scheduled plan item.** PR-3b (Wave 1, per R-7) deleted four aliases with zero `--suite` references anywhere — `refusal`, `margin`, `rotor`, `inner-loop` — taking the curated set 21 → 17 and the unreachable set **19 → 15, by deletion**, which costs no gate time at all. That is the ratchet turning the way it is meant to. The remaining fifteen are each named in AGENTS.md, CLI help, a README or an ADR, so deletion is not available for them and promotion is the only way the number falls further. The decision was a parenthetical in this entry and in PR-4 — which is precisely how fifteen suites came to be unreachable without anyone choosing it — so it is now **PR-4b** in `50-execution-plan.md` §4, sized, recommended (`teaching` alone, first), and prepared so ruling costs one word.
|
**The gap is 15, not 19, and the open half is now a scheduled plan item.** PR-3b (Wave 1, per R-7) deleted four aliases with zero `--suite` references anywhere — `refusal`, `margin`, `rotor`, `inner-loop` — taking the curated set 21 → 17 and the unreachable set **19 → 15, by deletion**, which costs no gate time at all. That is the ratchet turning the way it is meant to. The remaining fifteen are each named in AGENTS.md, CLI help, a README or an ADR, so deletion is not available for them and promotion is the only way the number falls further. The decision was a parenthetical in this entry and in PR-4 — which is precisely how fifteen suites came to be unreachable without anyone choosing it — so it is now **PR-4b** in `50-execution-plan.md` §4, sized, recommended (`teaching` alone, first), and prepared so ruling costs one word.
|
||||||
|
|
||||||
*One real gap the entry was circling, now fixed:* the parity pin lived in `fast`, which the pre-push gate does not run — **the pin guarding the gate did not run on the gate**. It is now in `smoke`.
|
*One real gap the entry was circling, now fixed:* the parity pin lived in `fast`, which the pre-push gate does not run — **the pin guarding the gate did not run on the gate**. It is now in `smoke`.
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,17 @@ Promoted out of a parenthetical in PR-4, because a parenthetical is how fifteen
|
||||||
|
|
||||||
**Explicitly not recommended:** promoting all fifteen. That is the ceremony failure this plan rejected twice already (749 exclusion reasons; nineteen justifications in one sitting). Nor is deletion the default — PR-3b deleted the four with *zero* references anywhere; the remaining fifteen are all named in AGENTS.md, CLI help, READMEs or ADRs, so deleting them breaks documented commands.
|
**Explicitly not recommended:** promoting all fifteen. That is the ceremony failure this plan rejected twice already (749 exclusion reasons; nineteen justifications in one sitting). Nor is deletion the default — PR-3b deleted the four with *zero* references anywhere; the remaining fifteen are all named in AGENTS.md, CLI help, READMEs or ADRs, so deleting them breaks documented commands.
|
||||||
|
|
||||||
**Verification:** `teaching` invoked from `scripts/hooks/pre-push` and `local-ci.sh --tier gate`, `GATE_SUITES` updated, `teaching` removed from `UNREACHABLE_BASELINE` in the same edit (`test_suite_reachability.py` fails in both directions if either half is missed — it has already caught this author twice), and the ratchet's `<= 15` lowered to `<= 14`. The added gate seconds reported as a number in the PR body.
|
**LANDED 2026-07-28 — and the recommendation was re-derived rather than executed, because its original justification had been spent.**
|
||||||
|
|
||||||
|
This item recommended `teaching` on the strength of G-22: `test_ratification_ceremony.py` was red on `main`, in `teaching`, run by no gate. **That hole was already closed a different way** — G-22's fix promoted that single file into `smoke`. So `teaching` would have added **9 net-new files, none with a demonstrated escape**, and promoting on a spent justification is the failure this arc exists to close.
|
||||||
|
|
||||||
|
**Promoted on a stronger, doctrinal basis instead.** `tests/test_epistemic_invariants.py` enforces AGENTS.md's *"Teaching and mutation safety"* non-negotiables in substance — INV-22/INV-23, that an unmarked proposal and an unmarked reviewed example default to **SPECULATIVE**, that the status enum has exactly its four positions, that pack-mutation proposals carry no hardening flag. Durable corpus mutation is the one irreversible thing in CORE, and the suite guarding it ran on no pre-merge gate. That justification does not depend on an incident having happened yet, which makes it the better one.
|
||||||
|
|
||||||
|
**Measured: 22.1s for 9 net-new files** (the tenth, `test_ratification_ceremony.py`, was already on the gate). Both invocation sites updated, `GATE_SUITES` extended, `teaching` removed from `UNREACHABLE_BASELINE` in the same edit, ratchet lowered `<= 15` → `<= 14`. **The reachability gap turned by promotion for the first time** — PR-3b's earlier turn was by deletion, which is free; this one costs, which is why it is a decision.
|
||||||
|
|
||||||
|
**A hollow spot in pin 3 itself, found by sabotage-testing the promotion.** `_suites_invoked_by_gate_tiers()` takes the **union** of `pre-push` and `local-ci.sh`, so a suite present in either satisfies it. Removing `teaching` from `local-ci.sh` alone **passed silently** — the two scripts could drift from each other invisibly, while `local-ci.sh` states in prose that it runs "the same four steps as `scripts/hooks/pre-push`." Closed by `test_the_two_gate_scripts_invoke_the_same_suites`, scoped to the numbered `(n/m)` gate steps so it reads the gate tier without parsing shell control flow. The identical sabotage now fires.
|
||||||
|
|
||||||
|
**This is not the CI-parity pin N-9 killed**, and the distinction is recorded at the assertion site so it is not re-litigated: that one asserted parity with `smoke.yml`, which `AGENTS.md:280` makes a billing-locked dead signal. Both scripts here are live local merge bars, and a developer running either is entitled to the coverage the other advertises.
|
||||||
|
|
||||||
### PR-5 · The flag-default register · **M** · G-8/H-6
|
### PR-5 · The flag-default register · **M** · G-8/H-6
|
||||||
`docs/specs/flag_register.md`: all **28** default-off flags plus the 4 default-on — current default, deliberate-posture vs accumulated-hesitancy, **what evidence flips it**, and named profiles (one-shot / eval / continuous-life) per R-4. Declared in the table, not the call site (ADR-0263 Rule 5). A pin asserts the register lists exactly the flags `core/config.py` defines — so a new flag cannot land unregistered. Includes the F-6 accrual resolution from R-3 and the N-6 docstring correction.
|
`docs/specs/flag_register.md`: all **28** default-off flags plus the 4 default-on — current default, deliberate-posture vs accumulated-hesitancy, **what evidence flips it**, and named profiles (one-shot / eval / continuous-life) per R-4. Declared in the table, not the call site (ADR-0263 Rule 5). A pin asserts the register lists exactly the flags `core/config.py` defines — so a new flag cannot land unregistered. Includes the F-6 accrual resolution from R-3 and the N-6 docstring correction.
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ usage: local-ci.sh [--tier TIER] [--allow-interpreter-fallback] [--list] [-- ARG
|
||||||
|
|
||||||
tiers:
|
tiers:
|
||||||
smoke smoke suite only (~1 min) = CI smoke.yml parity
|
smoke smoke suite only (~1 min) = CI smoke.yml parity
|
||||||
gate smoke + warmed_session + deductive (~2 min) = the pre-push gate
|
gate smoke + warmed_session + deductive + teaching (~9 min) = the pre-push gate
|
||||||
full the entire tests/ tree, parallel (~10 min) = the merge bar
|
full the entire tests/ tree, parallel (~10 min) = the merge bar
|
||||||
|
|
||||||
options:
|
options:
|
||||||
|
|
@ -165,10 +165,11 @@ case "${TIER}" in
|
||||||
step "smoke suite" suite smoke
|
step "smoke suite" suite smoke
|
||||||
;;
|
;;
|
||||||
gate)
|
gate)
|
||||||
echo "[local-ci] tier=gate — same three steps as scripts/hooks/pre-push"
|
echo "[local-ci] tier=gate — same four steps as scripts/hooks/pre-push"
|
||||||
step "(1/3) smoke suite" suite smoke
|
step "(1/4) smoke suite" suite smoke
|
||||||
step "(2/3) warmed_session lane" pytest_ tests/test_warmed_session_lane.py -q --no-header
|
step "(2/4) warmed_session lane" pytest_ tests/test_warmed_session_lane.py -q --no-header
|
||||||
step "(3/3) deductive suite" suite deductive
|
step "(3/4) deductive suite" suite deductive
|
||||||
|
step "(4/4) teaching suite" suite teaching
|
||||||
;;
|
;;
|
||||||
full)
|
full)
|
||||||
jobs="$(nproc 2>/dev/null || echo 4)"
|
jobs="$(nproc 2>/dev/null || echo 4)"
|
||||||
|
|
|
||||||
|
|
@ -53,23 +53,23 @@ if [ "${read_any}" -eq 1 ] && [ "${only_deletes}" -eq 1 ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[pre-push] CORE local-first gate: smoke + warmed_session lane pin + deductive"
|
echo "[pre-push] CORE local-first gate: smoke + warmed_session lane pin + deductive + teaching"
|
||||||
|
|
||||||
echo "[pre-push] (1/3) smoke suite (core test --suite smoke) ..."
|
echo "[pre-push] (1/4) smoke suite (core test --suite smoke) ..."
|
||||||
if ! uv run core test --suite smoke -q; then
|
if ! uv run core test --suite smoke -q; then
|
||||||
echo "[pre-push] BLOCKED: smoke suite failed. Fix before pushing." >&2
|
echo "[pre-push] BLOCKED: smoke suite failed. Fix before pushing." >&2
|
||||||
echo "[pre-push] (emergency bypass, discouraged: git push --no-verify)" >&2
|
echo "[pre-push] (emergency bypass, discouraged: git push --no-verify)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[pre-push] (2/3) warmed_session consistency lane ..."
|
echo "[pre-push] (2/4) warmed_session consistency lane ..."
|
||||||
if ! uv run python -m pytest tests/test_warmed_session_lane.py -q; then
|
if ! uv run python -m pytest tests/test_warmed_session_lane.py -q; then
|
||||||
echo "[pre-push] BLOCKED: warmed_session lane failed (T13 regression class)." >&2
|
echo "[pre-push] BLOCKED: warmed_session lane failed (T13 regression class)." >&2
|
||||||
echo "[pre-push] (emergency bypass, discouraged: git push --no-verify)" >&2
|
echo "[pre-push] (emergency bypass, discouraged: git push --no-verify)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[pre-push] (3/3) deductive suite (core test --suite deductive) ..."
|
echo "[pre-push] (3/4) deductive suite (core test --suite deductive) ..."
|
||||||
if ! uv run core test --suite deductive -q; then
|
if ! uv run core test --suite deductive -q; then
|
||||||
echo "[pre-push] BLOCKED: deductive suite failed — deduction serving is a" >&2
|
echo "[pre-push] BLOCKED: deductive suite failed — deduction serving is a" >&2
|
||||||
echo "[pre-push] LIVE capability (ADR-0256), not an experiment." >&2
|
echo "[pre-push] LIVE capability (ADR-0256), not an experiment." >&2
|
||||||
|
|
@ -77,5 +77,16 @@ if ! uv run core test --suite deductive -q; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "[pre-push] (4/4) teaching suite (core test --suite teaching) ..."
|
||||||
|
if ! uv run core test --suite teaching -q; then
|
||||||
|
echo "[pre-push] BLOCKED: teaching suite failed — durable mutation is governed by" >&2
|
||||||
|
echo "[pre-push] AGENTS.md non-negotiables (INV-21..INV-30). An unmarked" >&2
|
||||||
|
echo "[pre-push] proposal or reviewed example defaulting to anything but" >&2
|
||||||
|
echo "[pre-push] SPECULATIVE is a corpus-truth defect, and corpus mutation" >&2
|
||||||
|
echo "[pre-push] is the one thing here that is irreversible." >&2
|
||||||
|
echo "[pre-push] (emergency bypass, discouraged: git push --no-verify)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[pre-push] gate PASSED — push proceeding."
|
echo "[pre-push] gate PASSED — push proceeding."
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
||||||
|
|
@ -45,13 +45,14 @@ _ROOT = Path(__file__).resolve().parents[1]
|
||||||
#: that silently matched nothing would make this pin vacuous — the exact
|
#: that silently matched nothing would make this pin vacuous — the exact
|
||||||
#: failure mode (N-1) that has produced three false conclusions in this repo.
|
#: failure mode (N-1) that has produced three false conclusions in this repo.
|
||||||
#: So the set is declared here and *verified* against the shell below.
|
#: So the set is declared here and *verified* against the shell below.
|
||||||
GATE_SUITES: frozenset[str] = frozenset({"smoke", "deductive"})
|
GATE_SUITES: frozenset[str] = frozenset({"smoke", "deductive", "teaching"})
|
||||||
|
|
||||||
#: Curated suites with members and no gate caller.
|
#: Curated suites with members and no gate caller.
|
||||||
#: 19 at first measurement; **15 after PR-3b (Wave 1) deleted four unreferenced
|
#: 19 at first measurement; **15 after PR-3b (Wave 1)** deleted four unreferenced
|
||||||
#: per-phase aliases** — `refusal`, `margin`, `rotor`, `inner-loop`. That is the
|
#: per-phase aliases — `refusal`, `margin`, `rotor`, `inner-loop` — the ratchet
|
||||||
#: ratchet turning the way it is supposed to: the gap shrank by deletion rather
|
#: turning by deletion, which costs no gate time at all. **14 after PR-4b promoted
|
||||||
#: than by promotion, which costs no gate time at all.
|
#: `teaching` onto the gate (2026-07-28)** — the first time it turned by PROMOTION,
|
||||||
|
#: which is the direction that actually buys coverage and the direction that costs.
|
||||||
#: This list may only SHRINK. A suite leaves it by being invoked from a gate
|
#: This list may only SHRINK. A suite leaves it by being invoked from a gate
|
||||||
#: tier — not by being deleted from this file.
|
#: tier — not by being deleted from this file.
|
||||||
UNREACHABLE_BASELINE: frozenset[str] = frozenset(
|
UNREACHABLE_BASELINE: frozenset[str] = frozenset(
|
||||||
|
|
@ -70,7 +71,6 @@ UNREACHABLE_BASELINE: frozenset[str] = frozenset(
|
||||||
"pulse",
|
"pulse",
|
||||||
"runtime",
|
"runtime",
|
||||||
"sensorium",
|
"sensorium",
|
||||||
"teaching",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -164,14 +164,16 @@ def test_baseline_has_no_stale_entries() -> None:
|
||||||
def test_recorded_gate_gap_matches_the_measurement() -> None:
|
def test_recorded_gate_gap_matches_the_measurement() -> None:
|
||||||
"""Pin the number so the gap moving is a reviewed decision, not a drift.
|
"""Pin the number so the gap moving is a reviewed decision, not a drift.
|
||||||
|
|
||||||
15 of 17, after PR-3b. (19 of 21 before it.) Shrinking this is the point — it shrinks by putting
|
14 of 17, after PR-4b. (15 after PR-3b; 19 of 21 before it.) Shrinking this is
|
||||||
a suite on the gate, which costs gate time and is therefore a decision
|
the point, and it shrinks two ways: by deleting a suite nobody calls (free) or by
|
||||||
someone has to make deliberately.
|
putting one on the gate (costs gate time on every push, and is therefore a
|
||||||
|
decision someone has to make deliberately). PR-4b was the first of the second
|
||||||
|
kind — `teaching`, at a measured 22.1s for 9 net-new files.
|
||||||
"""
|
"""
|
||||||
assert len(UNREACHABLE_BASELINE) <= 15, (
|
assert len(UNREACHABLE_BASELINE) <= 14, (
|
||||||
f"gate-unreachable suites grew to {len(UNREACHABLE_BASELINE)} (was 15; "
|
f"gate-unreachable suites grew to {len(UNREACHABLE_BASELINE)} (was 14; 15 "
|
||||||
"19 before PR-3b deleted four unreferenced aliases). The ratchet only "
|
"before PR-4b promoted `teaching`, 19 before PR-3b deleted four unreferenced "
|
||||||
"turns one way."
|
"aliases). The ratchet only turns one way."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -182,3 +184,47 @@ def test_the_two_gate_suites_actually_exist() -> None:
|
||||||
for name in GATE_SUITES:
|
for name in GATE_SUITES:
|
||||||
assert name in suites, f"gate tier invokes undefined suite {name!r}"
|
assert name in suites, f"gate tier invokes undefined suite {name!r}"
|
||||||
assert suites[name], f"gate suite {name!r} is empty"
|
assert suites[name], f"gate suite {name!r} is empty"
|
||||||
|
|
||||||
|
|
||||||
|
#: A gate step in either script, e.g. ``(4/4) teaching suite ... suite teaching``.
|
||||||
|
#: The ``(n/m)`` prefix is the discriminator: ``local-ci.sh`` defines other tiers
|
||||||
|
#: whose steps are unnumbered, so this scopes the parse to the gate tier without
|
||||||
|
#: needing to parse shell control flow.
|
||||||
|
_GATE_STEP_RE = re.compile(r"\(\d+/\d+\)[^\n]*?\bsuite[= ]+([a-z0-9-]+)")
|
||||||
|
|
||||||
|
|
||||||
|
def _gate_suites_in(rel: str) -> set[str]:
|
||||||
|
text = (_ROOT / rel).read_text(encoding="utf-8")
|
||||||
|
return set(_GATE_STEP_RE.findall(text))
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_two_gate_scripts_invoke_the_same_suites() -> None:
|
||||||
|
"""``pre-push`` and ``local-ci.sh --tier gate`` must not drift apart.
|
||||||
|
|
||||||
|
Both scripts state that they are the same gate — ``local-ci.sh`` says so in
|
||||||
|
prose ("same four steps as scripts/hooks/pre-push"). Two records asserting the
|
||||||
|
same thing is the shape that goes stale, and until this pin the drift was
|
||||||
|
invisible: :func:`test_declared_gate_suites_match_the_shell` takes the UNION of
|
||||||
|
the two files, so a suite present in either satisfies it. Removing `teaching`
|
||||||
|
from ``local-ci.sh`` alone was sabotage-tested and passed — which is how this
|
||||||
|
gap was found.
|
||||||
|
|
||||||
|
**This is NOT the CI-parity pin that N-9 correctly killed.** That one asserted
|
||||||
|
``TEST_SUITES["smoke"] == smoke.yml`` and was reverted in ``50fa287d`` because
|
||||||
|
``AGENTS.md:280`` makes GitHub Actions billing-locked dead signals — parity with
|
||||||
|
a dead signal buys nothing. Both scripts here are live local merge bars, and a
|
||||||
|
developer running one is entitled to the coverage the other advertises.
|
||||||
|
"""
|
||||||
|
hook = _gate_suites_in("scripts/hooks/pre-push")
|
||||||
|
local = _gate_suites_in("scripts/ci/local-ci.sh")
|
||||||
|
assert hook, "parsed no numbered gate steps from scripts/hooks/pre-push"
|
||||||
|
assert local, "parsed no numbered gate steps from scripts/ci/local-ci.sh --tier gate"
|
||||||
|
assert hook == local, (
|
||||||
|
"the two gate scripts have drifted — a push through the hook and a run of "
|
||||||
|
"local-ci.sh --tier gate would cover different suites:\n"
|
||||||
|
f" pre-push only: {sorted(hook - local)}\n"
|
||||||
|
f" local-ci only: {sorted(local - hook)}"
|
||||||
|
)
|
||||||
|
assert hook <= set(GATE_SUITES), (
|
||||||
|
f"the scripts invoke suites GATE_SUITES does not declare: {sorted(hook - set(GATE_SUITES))}"
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue