Skip to content

Commit c57a711

Browse files
dmealingclaude
andcommitted
fix(python): verify --codegen fails when committed output would go unchecked
`metaobjects verify --codegen` with no --generators printed a note and exited 0. There has been no default suite since 1.0.4, so a CI step written before then, relying on the implied default, regenerated nothing and passed. An adopter's prompt-codegen gate covered 159 generated files this way. With no selection it now exits 1 when --out holds generated .py files (they would go unchecked), and still exits 0 when there is no generated output. Both verify paths (explicit metadata dir and the config-less fallback) share one helper. The config schema and the C# verify help no longer describe a default suite. Also: the ADR-0056 guide now says a Python field with a `default:` is typed as its plain type, so passing None fails (an adopter hit it). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M1mRRB1K2WdYy82kntMQdx
1 parent 03d803f commit c57a711

6 files changed

Lines changed: 59 additions & 14 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ here.**
2121

2222
### Fixed
2323

24+
- **Python `verify --codegen` no longer passes while checking nothing.** With no
25+
`--generators` it printed a note and exited 0. Since 1.0.4 there is no default suite, so a CI
26+
step written before then regenerated nothing and passed over committed output (one adopter's
27+
gate covered 159 generated files this way). It now fails when `--out` holds generated files
28+
and nothing is selected, and still passes when there is no generated output. The config
29+
schema and the C# `verify` help no longer mention a default suite.
2430
- **TypeScript `meta gen` regenerates output written outside the project root.** Since 1.0.5 such
2531
files (a monorepo target at `../shared/src/generated`, say) are not recorded, and the next run
2632
REFUSED every one whose content had changed, as "no record of generating this file", though

‎docs/features/migrations/value-object-types-are-generated-once.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ The tolerant tier's all-nullable mirror is named after the value object too: `<V
112112
- Models come from the `entity` generator's `<Name>.py`. A value object whose short name
113113
another top-level object shares — a second value object, or an entity — is now
114114
package-qualified (`AcmeAlphaNote.py`); before, both wrote the same module.
115+
- A field with a `default:` is typed as its plain type with that default (`user_id: str = ""`),
116+
where the copy typed it `str | None = None`. Passing `None` to it now fails validation: a
117+
caller that relied on `None` must pass a value, or the field must drop its `default:` so it
118+
is optional. Found upgrading an adopter, whose tests caught one such call.
115119
- The request model no longer sets `extra="forbid"`, so a mistyped keyword argument is ignored
116120
rather than rejected. The model does carry the value object's declared validators
117121
(`validator.*`, `@maxLength`), which the copy did not, so construction enforces them.

‎server/csharp/MetaObjects.Cli/VerifyCommand.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public sealed record Options
108108
/// <c>--codegen</c> runs, the namespace is inferred from the committed output so a
109109
/// regen matches output generated with any namespace (avoids spurious drift).</summary>
110110
public bool NamespaceExplicit { get; init; }
111-
/// <summary>Optional generator selection for the <c>--codegen</c> regen (else the default suite).</summary>
111+
/// <summary>Generator selection for the <c>--codegen</c> regen. There is no default suite: name the ones <c>gen</c> ran.</summary>
112112
public IReadOnlyList<string>? Generators { get; init; }
113113
/// <summary>Template root for render-helper/template generators in the codegen regen.</summary>
114114
public string? TemplateRoot { get; init; }

‎server/python/src/metaobjects/cli.py‎

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,32 @@ def _diff_report(
16121612
return 1
16131613

16141614

1615+
1616+
def _no_generators_selected(out_dir: str) -> int:
1617+
"""``verify --codegen`` with nothing selected: pass only if nothing would go unchecked.
1618+
1619+
There is no default suite (1.0.4), so with no ``--generators`` nothing is regenerated.
1620+
If ``out_dir`` already holds generated files, reporting success would be a false green
1621+
— a gate written before 1.0.4, relying on the default, silently stopped checking
1622+
anything. That is an error; an empty or absent ``out_dir`` is not.
1623+
"""
1624+
committed = [p for p in Path(out_dir).rglob("*.py") if p.name != "__init__.py"] if Path(out_dir).is_dir() else []
1625+
if committed:
1626+
print(
1627+
f"error: verify --codegen: no generators selected, but {out_dir} holds "
1628+
f"{len(committed)} generated file(s) that would go unchecked. There is no "
1629+
"default suite: pass --generators <a,b,c> naming the same suite `gen` ran "
1630+
"(metaobjects gen --list is the catalog).",
1631+
file=sys.stderr,
1632+
)
1633+
return 1
1634+
print(
1635+
"verify --codegen: no generators selected and no generated output to check. "
1636+
"Pass --generators <a,b,c> naming the same suite `gen` ran "
1637+
"(metaobjects gen --list is the catalog).",
1638+
)
1639+
return 0
1640+
16151641
def _verify_codegen(args: argparse.Namespace) -> int:
16161642
"""``verify --codegen`` — regenerate to a temp dir + diff vs committed ``--out``.
16171643
@@ -1676,12 +1702,7 @@ def _verify_codegen(args: argparse.Namespace) -> int:
16761702
print(f" {msg}", file=sys.stderr)
16771703
return 1
16781704
elif not spec_gens:
1679-
print(
1680-
"verify --codegen: no generators selected, so there is no generated output "
1681-
"to check. Pass --generators <a,b,c> naming the same suite `gen` ran "
1682-
"(metaobjects gen --list is the catalog).",
1683-
)
1684-
return 0
1705+
return _no_generators_selected(args.out)
16851706

16861707
with tempfile.TemporaryDirectory() as tmp:
16871708
entities = _parse_entities(getattr(args, "entities", None))
@@ -1767,12 +1788,7 @@ def _verify_codegen_neutral_fallback(args: argparse.Namespace) -> int:
17671788
# Same rule as _verify_codegen: the selection must be named, because there is no
17681789
# default suite to regenerate and diff against.
17691790
if not getattr(args, "generators", None):
1770-
print(
1771-
"verify --codegen: no generators selected, so there is no generated output "
1772-
"to check. Pass --generators <a,b,c> naming the same suite `gen` ran "
1773-
"(metaobjects gen --list is the catalog).",
1774-
)
1775-
return 0
1791+
return _no_generators_selected(args.out)
17761792
selection, gen_errors = _resolve_generators(
17771793
args.generators, GeneratorBuildContext(_template_root_for(args)))
17781794
if gen_errors:

‎server/python/src/metaobjects/codegen/metaobjects-config.schema.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"generators": {
3939
"type": "array",
4040
"items": { "type": "string" },
41-
"description": "Stable generator names (see `metaobjects gen --list`). Omit to run the default suite."
41+
"description": "Stable generator names (see `metaobjects gen --list`). Required: there is no default suite, so a target that names none generates nothing and `verify --codegen` checks nothing."
4242
},
4343
"entities": {
4444
"type": "array",

‎server/python/tests/codegen/test_cli.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,3 +495,22 @@ def test_prompts_and_templates_root_are_the_same_flag(tmp_path: Path) -> None:
495495
rc = main(["verify", "--codegen", "--generators", GEN_SUITE, str(root / "meta"), "--out", str(out),
496496
"--prompts", str(root / "no-such-dir")])
497497
assert rc != 0, "--prompts value was ignored — a missing dir still regenerated the spec files"
498+
499+
500+
# `verify --codegen` with no --generators used to print a note and exit 0. Since 1.0.4
501+
# there is no default suite, so a CI step written before then — naming no generators
502+
# because the default used to be implied — regenerated nothing and passed while the
503+
# committed output went unchecked (found upgrading an adopter: 159 generated files, gate
504+
# green). With committed generated output present and nothing selected, it now fails.
505+
def test_verify_without_generators_fails_when_committed_output_would_go_unchecked(tmp_path: Path) -> None:
506+
meta_dir = _meta_dir(tmp_path)
507+
out = tmp_path / "out"
508+
assert main(["gen", "--generators", GEN_SUITE, meta_dir, "--out", str(out)]) == 0
509+
assert main(["verify", meta_dir, "--out", str(out)]) != 0
510+
511+
512+
def test_verify_without_generators_still_passes_with_no_generated_output(tmp_path: Path) -> None:
513+
meta_dir = _meta_dir(tmp_path)
514+
out = tmp_path / "out"
515+
out.mkdir()
516+
assert main(["verify", meta_dir, "--out", str(out)]) == 0

0 commit comments

Comments
 (0)