R-758: mem_limit is the sum of the compose limits — eight figures corrected, gate mem-limit-sum (--fast, stdlib) with decoys

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-05 21:28:10 +02:00
parent 8940d768d3
commit 1b24d139bd
15 changed files with 292 additions and 29 deletions
+62
View File
@@ -57,6 +57,7 @@ COVERS = {
"probe-measured": "the measurement written in the TAGLINE or another comment block, not directly above setup_done_probe:; a date with no before/after; before/after with no date; 'read upstream' instead of 'measured' - vs a genuine measured comment (R-715)",
"family-gate": "family_gate written only in a COMMENT (not gated, no min_controller owed); min_controller only in a comment; a golden DIRECTORY named 0.287.0 with no bake log (the mkdir shape, R-410); a sibling with no golden (stated NOT CHECKED, never a pass of rule 3) - vs the facts: an unanchorable exception (regex, '/', '..'), an exception list with no gate, min_controller below 0.287.0, the newest baked golden below 0.287.0; and a genuine family app passes (decisions 63/64, finding F1)",
"onboarding": "a NEW template with no record; a record missing an id, or carrying it only inside an HTML comment; a `done` whose path does not exist, is an EMPTY directory (the mkdir shape, R-410) or names an absent sibling-repo file; an `n/a` with an empty or two-word reason; an `open` row; `opened:` backdated before the checklist; the template a new app copies lacking a new id - vs a complete record, an id added after `opened:`, and an exempt app's record with open rows (NEW-APP-CHECKLIST.md)",
"mem-limit-sum": "the right figure only in a COMMENT (the compose header's 'mem_limit: 640M', the .felhom.yml arithmetic) while the field is wrong; a `memory:` under reservations: (not a limit) making the sum come out right; a `mem_limit:` outside resources:; a steps/ file with the old figure (not judged) - vs the facts: a field under the sum, a service with no limit, an unreadable size (R-758)",
"copy-i18n": "Hungarian edited in a COMMENT/README/display_name (label, not copy) vs a real frozen string changed; an English block that is not English, is not matched to a Hungarian twin, or rewrites a credential (R-560). A retrieval promise REGISTERED in ALLOWLIST_EN passes only for its own app+path+sentence with a real reason; an entry for another app, a rewritten sentence, a stale entry or a two-word reason convicts (R-594). Also the DEGRADED mode CI actually runs — PyYAML shadowed out, freeze only (R-595)",
}
@@ -686,6 +687,66 @@ def family_gate_cases():
shutil.rmtree(ws, ignore_errors=True)
def mem_sum_cases():
"""check-mem-limit-sum.py reads FILES: templates/*/docker-compose.yml + .felhom.yml, via --root (R-758)."""
global ran
import tempfile
ws = tempfile.mkdtemp(prefix="catalog-memsum-")
try:
COMPOSE = (
"# demo - header\n# RAM: ~100M (mem_limit: 640M)\n"
"services:\n"
" demo:\n image: demo/demo:1.0\n container_name: demo\n environment:\n - X=1\n"
" deploy:\n resources:\n limits:\n memory: 384M\n"
" demo-db:\n image: postgres:16-alpine\n command: |\n memory: 9999M\n"
" deploy:\n resources:\n limits:\n memory: 256M # the DB\n"
"volumes:\n demo_data:\n")
META = ('display_name: "Demo"\n# mem_limit: "999M" is not this line\n'
'resources:\n mem_request: "100M"\n mem_limit: "640M" # 384+256\n pi_compatible: true\n')
def run(name, compose, meta, expect_rc, must=(), extra=None):
global ran
cat = os.path.join(ws, "cat")
shutil.rmtree(cat, ignore_errors=True)
d = os.path.join(cat, "templates", "demo")
os.makedirs(d)
io.open(os.path.join(d, "docker-compose.yml"), "w", encoding="utf-8").write(compose)
io.open(os.path.join(d, ".felhom.yml"), "w", encoding="utf-8").write(meta)
if extra:
extra(d)
r = sh([sys.executable, os.path.join(ROOT, "scripts", "check-mem-limit-sum.py"), "--root=" + cat], ROOT)
out = r.stdout + r.stderr
ran += 1
ok = r.returncode == expect_rc and all(m in out for m in must)
print(" %s %-70s rc=%d (expected %d)" % ("ok" if ok else "XX", name, r.returncode, expect_rc))
if not ok:
fails.append("%s: rc=%d expected %d; missing %s\n%s" % (
name, r.returncode, expect_rc, [m for m in must if m not in out], out[-400:]))
def steps(d):
os.makedirs(os.path.join(d, "steps"))
io.open(os.path.join(d, "steps", "abc.felhom.yml"), "w").write('resources:\n mem_limit: "384M"\n')
print("\n-- mem-limit-sum: genuine and decoys")
run("GENUINE: 384+256 = 640M, the field says 640M", COMPOSE, META, 0, ("mem-limit-sum gate OK",))
run("GENUINE: 1G is 1024M (1G + 256M = 1280M)", COMPOSE.replace("memory: 384M", "memory: 1G"),
META.replace('"640M"', '"1280M"'), 0, ("gate OK",))
run("DECOY: a steps/ file with an old figure is not judged", COMPOSE, META, 0, ("gate OK",), extra=steps)
print("-- mem-limit-sum: the facts (each MUST be refused)")
run("FACT: the field under the sum; the right figure only in comments", COMPOSE,
META.replace('mem_limit: "640M" # 384+256', 'mem_limit: "384M" # 384+256=640M'), 1, ("not the sum", "384+256=640M"))
run("FACT: reservations: memory makes nothing a limit (service w/o limit)",
COMPOSE.replace(" limits:\n memory: 256M", " reservations:\n memory: 256M"),
META.replace('"640M"', '"384M"'), 1, ("NO deploy.resources.limits.memory", "demo-db"))
run("FACT: mem_limit only OUTSIDE resources: (top level) is no declaration", COMPOSE,
META.replace(' mem_limit: "640M" # 384+256\n', '').replace('display_name: "Demo"\n', 'display_name: "Demo"\nmem_limit: "640M"\n'),
1, ("declares no resources.mem_limit",))
run("FACT: an unreadable size is INCONCLUSIVE, never a pass", COMPOSE.replace("memory: 384M", "memory: lots"), META, 2,
("INCONCLUSIVE",))
finally:
shutil.rmtree(ws, ignore_errors=True)
def main():
gate = os.path.join(ROOT, "scripts", "check-engine-major.py")
if not os.path.isfile(gate):
@@ -1197,6 +1258,7 @@ i18n:
onboarding_cases()
family_gate_cases()
mem_sum_cases()
if fails:
print()