Repo hygiene only - no code change by this task.
scripts/verify_import_closure.py (512 lines, py_compile OK) was already present but
untracked in the apps/pbls working tree when the Q4 task started. It is the companion
verifier for deploy_test round 3 question Q6 (28-module import closure: IMPORT_OK 15/28),
i.e. it belongs to a different task, not to Q4 (build.sh gate 6.4).
It was deliberately kept OUT of the Q4 commit bc4aaa2 (selective git add, never
git add -A) so the Q4 change set stays exactly build.sh + scripts/ddl_gate_check.py +
scripts/ddl/pbls_tables.sql.
Committed separately here because leaving a dirty working tree blocks the develop-stage
git closure gate. Content is untouched; authorship of the tool remains with the Q6 work.
Defect (deploy_test round 3, Q4 / deploy-evidence E15):
1. Gate 6.4 grepped the raw DDL for dialect keywords and was killed by 3 false positives
that are pure documentation text: L6 header line comment, L220 business word
"references" inside a COMMENT string, L891 contract self-check block that literally
lists FOREIGN KEY / REFERENCES / ENUM( / SERIAL / BIGSERIAL / nextval / TIMESTAMP.
The gate lost all discriminating power (false positive die) and real violations
would be hidden in the noise.
2. Table count assertion 36 != actual DDL 37 (grep -c CREATE TABLE IF NOT EXISTS = 37;
the 37th table is pbl_compile_task, pbl_compiler domain 4 to 5). Related: the
AUTOINC assertion used a literal multi-space pattern and missed the UNSIGNED single
space variant of pbl_compile_task (counted 36, accidentally matching the old assert);
L210 post-create information_schema assertion was also 36.
Fix:
- Add scripts/ddl_gate_check.py: strip_sql_noise() single-pass char scanner removing
block comments, -- / # line comments (-- must be followed by whitespace, so a--b is
safe) and single/double quoted string literals (with doubled and backslash escapes)
replaced by a placeholder, so COMMENT strings and DEFAULT strings no longer interfere.
Assertions run only on the denoised DDL body: zero dialect keyword hits (prints
keyword + line number + line text), CREATE TABLE count = 37 (plus duplicate table and
non pbl_ prefix checks), BIGINT [UNSIGNED] NOT NULL AUTO_INCREMENT = 37 (table option
AUTO_INCREMENT=n excluded first). Ships --selftest with 9 minimal unit checks (no
pytest needed) and --dump-body for debugging; expected values are module constants.
- Rewrite build.sh gate 6.4: step 4a runs --selftest first (if the scanner is not
trustworthy its verdict is not accepted), step 4b runs the gate with
--expect-tables 37 --expect-autoinc 37; return codes captured explicitly (not via
"if ! cmd" which resets $? to 0); missing scanner file dies and forbids falling back
to raw grep. Header comment and L210 actual table assertion synced 36 to 37.
- pbls_tables.sql trailing contract self-check block: total tables 36 to 37
(pbl_compiler 4 to 5, new pbl_compile_task); the zero-hit section now describes the
forbidden constructs in prose instead of listing SQL keywords verbatim (defence in
depth against a future regression to naive grep).
- projects/pbls/env/test.json gate.build_sh text "36 tables" to "37 tables"
(json.load validity self-check passed).
Verification (real local run output, details in
projects/pbls/docs/02-develop/dev-notes-deploy-q4-build-gate64.md):
- --selftest 9/9 PASS, including a negative fixture proving real violations are still
caught, idempotency of denoising, and that -- / # inside strings do not truncate.
- Real DDL: denoising strips 10974 chars / 89 line comments / 493 string literals;
dialect zero hits, CREATE TABLE = 37, AUTO_INCREMENT = 37, RESULT PASS rc=0.
- build.sh gate sections 0-4 sliced into a temporary harness and executed as a whole
inside apps/pbls, rc=0 (6.1/6.2/6.4 all pass). Sections 7+ not entered: no MariaDB,
no venv deps, port 9300 unavailable - honestly declared in the dev notes.
- bash -n build.sh passes; no live assertion still says 36 (only 2 comment lines).
No real table structure, app entry, conf/config.json, requirements or modules/pbl_*
were modified.