Repository navigation
Conversation
One small app (TypeScript API, Python prompt worker, Postgres) set up twice on the published 1.2.0 packages: a competent do-it-yourself baseline (TypeSpec, OpenAPI, openapi-typescript, datamodel-code-generator, Atlas, oasdiff, a StrictUndefined render test) and MetaObjects. `./run.sh` makes six changes to each, runs each setup's own pull-request gate, and writes SCORECARD.md from the exit codes. Layer consistency and the partner-contract check are scored separately, so the row MetaObjects has no check for is shown. Self-measured; the README says what it does not measure.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Changed
examples/head-to-head/: one small app (a TypeScript API plus a Python prompt-rendering worker on Postgres) set up twice.baseline/is a DIY stack: TypeSpec to OpenAPI,openapi-typescript,datamodel-code-generator, Atlas, a JinjaStrictUndefinedrender test,git diff --exit-codeandoasdiffagainst the released contract.metaobjects/uses a single MetaObjects model withmeta gen,metaobjects gen,meta migrateandmeta verify. Each setup has its ownci.sh, smoke probe and live-drift script.harness/run.py,harness/lines.py,run.sh) that applies six scripted drift scenarios to a throwaway copy of each setup (S1 rename a field, S2 add a required field, S3 change a prompt variable, S4 skip regeneration, S5 hand-edit the live DB, S6 change a field on the partner API), plus a no-change control S0. It records which checks go red and the lines each setup needs, and writesSCORECARD.md.SCORECARD.mdand a README. Both state that the result is self-measured by the MetaObjects maintainers, who chose the baseline's tools. The scorecard includes the row MetaObjects loses: partner-breaking changes, which only the baseline'soasdiffflags. The rootREADME.mdanddocs/README.mdlink to the demo.Findings and run notes
SCORECARD.md): layer checks go red on 4 of 6 scenarios on both sides (S1, S3, S4, S5). Only the baseline has a partner-contract check (oasdiff), and it flags S1, S2 and S6. MetaObjects has no such check, so that row is the one it loses. The baseline did better on that row; MetaObjects did better on none of the layer rows. Self-measured by the MetaObjects maintainers; the scorecard and README say so and list what is not measured.meta verify/metaobjects verifyreportERR_VAR_NOT_ON_PAYLOAD(S3). The baseline'sStrictUndefinedfails at render time.meta genprints a "no prompt generator is wired" warning on every run in a model that declares atemplate.promptand renders it only from Python.meta migratewarns when a required column is added to a table that may have rows (S2, S6); the migration is still written.tscfor column changes) and do not validate request bodies at run time; MetaObjects' handlers validate with the generated Zod schema. Both are listed under known limits in the README.Validation
Risk Assessment
✅ Low: Docs-and-example-only change under examples/head-to-head; prior-round fixes check out (line counts recomputed to 6 files/425 and 9 files/376, flags removed, unknown ids rejected, S5 control added, INT8 parser added) and no new defects found.
Testing
The baseline
ts-fast/ts-unitcommand had already passed. I then drove the real harness against Docker Postgres 16, Atlas, oasdiff and the published npm and PyPI packages. A full run completed (exit 0, 123 s). Its regenerated scorecard differs from the committed one by one line, the run duration (224 s committed). S0 is green on both sides. S6 shows the baseline flagging the partner break while MetaObjects reports "no such check". The scorecard carries the self-measured disclaimer. The S5 control runs before the ALTER. Subset runs print the table without rewriting SCORECARD.md. Unknown ids and the removed--json/--outflags are rejected. I restored SCORECARD.md after the full run and left no stray containers. I did not run a separate repo-wide regression suite beyond the configured baseline, because the change is an isolated examples directory.Evidence: Subset run S1 S5 S6 transcript and scorecard
Evidence: Full run transcript
Evidence: Regenerated SCORECARD.md from the full run
Pipeline
Updates from git push no-mistakes
✅ **Intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
examples/head-to-head/harness/run.py:216- The published S1 description is false for both setups. It says "The prompt payload, the database column and the hand-written code are left as they were." The baseline edit renames the payload field (PickProperties<Customer, "name" | "contactEmail" | "plan">, run.py:157). That rename is what turnsmypyandprompt-renderred. The MetaObjects edit changes the payload'sextends(run.py:163) and renames the column throughmeta migrate --rename-column customers.email=contact_email(run.py:166). A reader therefore misreads why the baseline shows three reds against one: the MetaObjects payload keeps the nameemail, the baseline payload does not. Same claim at SCORECARD.md:48. README.md:49-51 says each scenario "leaves anything else alone", and README.md:63 sends readers to these edits. Fix: state per setup what S1 changes, in the Scenario text, then regenerate or edit SCORECARD.md.examples/head-to-head/harness/run.py:432-live-driftis the only scored check with no clean control, and a tool failure counts as a catch. S0 is kind "pr" (run.py:214), so the S0 row shows·forlive-drift(SCORECARD.md:61 and 75). The S0 text still says "Every check must pass, so a red cell elsewhere is not noise from the setup itself" (run.py:214, SCORECARD.md:47). Failure sequence: baseline/live-drift.sh:5-8 discards stderr underset -e, so any Atlas or Docker error exits non-zero with empty output. run.py:434-435 then records a red S5 with blank evidence. metaobjects/live-drift.sh:5 has the same exit-code path for ameta verify --dbconnection failure. The committed run's evidence names onlyvip, so the committed numbers look right, but the harness would publish the false red without complaint. Fix: run ./live-drift.sh on the freshly provisioned database before the ALTER (run.py:429), and raise SystemExit unless it exits 0. Then state that control in the S0 or S5 text.examples/head-to-head/harness/lines.py:107- The published 'not counted' file totals are wrong, and the counting rule omits an exclusion.generated_filesislen(generated), which includes package-lock.json and requirements.txt.generated_linesexcludes those two (lines.py:105-106). SCORECARD.md:142 therefore prints 'baseline 8 files / 425 lines' and 'MetaObjects 11 files / 376 lines'. The directories it names hold 6 and 9 files; hand summing the lines of those files gives exactly 425 and 376. Fix: compute the file total from the same filtered list, and update SCORECARD.md:142 (rendered at run.py:681-684). Same class of defect: the rule says 'every committed file ... counts' except locks, generated output and the probe (README.md:103-105, run.py:662, lines.py:3-5), yet.gitignoreis silently excluded through the 'ignore' category (lines.py:45 and 59). That hides 6 baseline lines and 8 MetaObjects lines. Add.gitignoreto the stated exclusions.examples/head-to-head/baseline/api/src/app.ts:12- The baseline API returnsidas a JSON string, which violates its own contract.idisbigint(atlas/schema.sql:2) and node-postgres returns int8 as a string by default.pool.query<Customer>at app.ts:19, 23 and 33 is an unchecked cast, so tsc stays silent.POST /customersthen answers{"id":"1",...}while generated/openapi.yaml:86-88 and api.d.ts declare an integerid. The smoke probe hides this:console.log(id)prints 1 either way. The MetaObjects side usesbigint("id", { mode: "number" }), so the comparison carries an accidental baseline bug although README.md:8 claims a competent baseline. Fix: add a one-line int8 parser, for examplepg.types.setTypeParser(pg.types.builtins.INT8, Number), then rerun to refresh the line count.examples/head-to-head/harness/run.py:725- A partial run or a mistyped scenario id silently replaces the committed scorecard../run.sh S1 S6is documented at README.md:38, and it rewrites SCORECARD.md with only those rows, with totals such as 'red on 2 of 2'. Only the full-run line at README.md:37 mentions the rewrite. Unknown ids are dropped without error:./run.sh s1runs only S0, publishes a control-only scorecard and exits 0 (run.py:725, 751-753). Fix: reject ids not in SCENARIOS, and skip the default SCORECARD.md write when a subset was requested.examples/head-to-head/harness/run.py:715- The--outand--jsonflags (run.py:715-716, 753, 755-759) are not needed by any intent requirement. The intent requires a published scorecard, and only the default SCORECARD.md write delivers it. Neither flag is documented in the README, and no script calls either one. Recommend removing both flags, along with the now-unused top-leveljsonimport.npm_versionat run.py:453 re-imports json as_json, and that import can then stay as a plain local or move to the top.examples/head-to-head/harness/run.py:527- The S0 false-alarm rule is defined twice, at run.py:527 inverdictand run.py:539 inlayers_cell.Outcome.clean_gatealso accepts aNonegate_rc that never occurs, because every path sets gate_rc. Fix: extract onecontrol_ok(o)helper for both sites and compare gate_rc to 0.examples/head-to-head/README.md:76- Inline fix: README said every row of the partner table reads "no such check" for MetaObjects, but S5 readsn/a. Changed the wording to "every pull-request row" and noted that S5 isn/aon both sides.examples/head-to-head/README.md:37- Inline fix: README said a full run takes about three minutes, but SCORECARD.md:5 records 232 s. Changed the text to "about four minutes".🔧 Fix applied.
2 infos still open:
examples/head-to-head/README.md:76- Inline fix: README said every row of the partner table reads "no such check" for MetaObjects, but S5 readsn/a. Changed the wording to "every pull-request row" and noted that S5 isn/aon both sides.examples/head-to-head/README.md:37- Inline fix: README said a full run takes about three minutes, but SCORECARD.md:5 records 232 s. Changed the text to "about four minutes".✅ **Test** - passed
✅ No issues found.
scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains./run.sh s1(unknown scenario id, rejected with an error)./run.sh --json x(removed flag, rejected)./run.sh S1 S5 S6(subset run; prints the table and leaves SCORECARD.md untouched)./run.sh(full run, 7 scenarios × 2 setups, then diffed against the committed SCORECARD.md)Baseline:scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains(already passed before this step)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.