Repository navigation
fix: keep credential and excluded fields out of generated routes, forms and reports - #503
Conversation
…lds on write, never return credentials (#484) A field marked @formExclude, or a credential (a field declaring a view.password child), is now ignored by the generated create, PATCH and PUT in every port and a credential is never returned by list, get, write echoes, M:N targets or TPH rows. Generated forms and api docs omit credentials too. Pinned by the new api-contract secret-fields/ sub-corpus (generated lane, all five ports). Also: meta gen / meta verify --codegen refuse an unknown codegen dialect ("d1" points at "sqlite"; meta init --d1 scaffolds "sqlite"), and five docs recipes (drizzle-kit migrations, Workers + D1 per-request example, guarding generated routes, proving a gate fails). Fixes #484
…hon redaction covers dataclass and mapping rows (#484)
…er boundary, prove denied writes on stored rows (#484)
…ate, behavioral projection test
…rojection and TPH M:N
…, widen discriminatorRoot
… that view.password is TS-only
|
Fix is up in #503. Which ports were affected: all five. On Existing vocabulary only, no new attributes. A credential is a field that declares or inherits a child Judgment calls to review:
The same PR also carries the five docs recipes and a clear error for |
…#484) A report column is derived from its @Of field and lost its view.password, so a report that groups by a credential, or takes its min or max, served it from the list route and the export. A derived field over a credential is now a credential in every port: the list route omits the key and an export names no column for it. Also documents it in the CHANGELOG entry for #484, the api-contract and reporting guides.
…rtable (#484) A served report marks every derived field filterable and sortable, so a column hidden from the response could still be probed with a like-prefix filter or a sort, recovering the secret one prefix at a time. A derived field over a credential now carries no filter or sort entry in every port. Also documents it in the CHANGELOG, api-contract and reporting guides.
) * fix: replace regex and string-rewrite matching with authoritative parsers across ports (audit findings 5, 7-15, 17, 18) Currency parsing via Intl, SQL splitter, view comparators, Python YAML 1.2, Mustache tag scanning, forgiving XML reader, JSONC-aware upgrade rewriter, leak-scan renames, Java authored-name serialization, ERR_COLUMN_NAME_COLLISION, pnpm workspace parsing and assorted smaller instances. * fix: import the shared support class in the Spring and Kotlin filter-allowlist generators An ejected copy is package-renamed, so same-package bare-name resolution stopped compiling once #503 started calling the support class there. * fix: Roslyn namespace/handoff detection, ASCII Pydantic patterns, route-segment collision (audit finding 18 follow-ups) * feat: enum strip fold-collision error, numeric CSV exemption, one filter glob grammar in Java ERR_ENUM_NORMALIZE_COLLISION in all ports; CSV formula guard skips strict numeric literals; <filters> use Scope.compilePattern semantics and fail generation when a pattern would select fewer objects than before. * fix(render,cli): decode every JSON escape and close fences properly in extract; make eject --undo order-sensitive The forgiving JSON reader dropped \uXXXX, \b and \f escapes and the fence matcher closed at the first inner triple backtick, in all five ports. eject --undo treated a copy whose only edit was a reorder as stock (TS, Python, C#). Python LONG coercion no longer rounds through float. Adds three shared extract-conformance cases. * fix: address pre-gate review findings (schema-valid pattern, caches, workspace walk, splitter dialect, constraint fallback) * refactor: simplify pass over the audit follow-up changes * no-mistakes(review): apply reviewer inline fixes * no-mistakes(review): Fix XML child loss, eject whitespace, delimiter verify, JPY parse * no-mistakes(review): apply reviewer inline fixes * no-mistakes(review): Drop C# source inference and Roslyn; tighten delimiter checks * no-mistakes(review): Pass inlinedAs only for partials; root ref renders * no-mistakes(review): Add csharp.namespace config, match pnpm globs, fix {{=}} crash * no-mistakes(review): Reject non-identifier derived C# namespace segments * no-mistakes(review): apply reviewer inline fixes * no-mistakes(review): Resolve C# namespaces via package bindings; require Pydantic 2.8 * no-mistakes(review): Validate all package-binding namespaces as C# identifier paths * no-mistakes(review): Accept csharp.namespace in TS config schema; explicit handoff note * no-mistakes(review): apply reviewer inline fixes * no-mistakes(review): Skip pnpm test dirs, reject C# keyword namespaces * no-mistakes(document): Fixed four stale doc facts from the regex-followups change: Java <filters> upgrade example (java.md and CHANGELOG), eject reorder wording (own-your-codegen.md), CSV numeric exemption (reporting.md). Edits verified by grep and diff stat. * no-mistakes(document): Fixed document-1: removed the stale single-Math.round claim from docs/ports/typescript-client.md, the parseCurrency header in currency.ts, and the currency-input.tsx comment. Doc and comment edits only; no remaining findings. * no-mistakes(lint): Update AGENTS.md and site payload fixture count to 421
What Changed
Generated routes in all five ports (TypeScript, C#, Java, Kotlin, Python) no longer accept writes to
@formExcludeor credential fields, and no longer return credential values on read. Report list and export output omits credential columns, and the report export path is filtered, sorted and hidden the same way.Generated React and Angular forms skip credential fields, so secret hashes are no longer rendered as editable inputs. Covered by new per-port codegen and conformance tests (including an API-contract
secret-fieldscorpus).Docs: adds recipes for drizzle-kit-owned migrations, guarding generated routes inside your own auth scope, and proving a verify gate fails. Rewrites the Cloudflare Workers recipe around a real per-request D1 example. Codegen now rejects
dialect: "d1"as an error instead of silently using the Postgres branch;dialect: "sqlite"is the correct value for D1. CHANGELOG updated.A served report that groups by a credential column, or takes its
minormax, no longer leaks it: the derived column stays a credential in every port, so the list route omits it, a report export names no column for it, and it is neither filterable nor sortable (a hidden column that could be filtered on would still be an oracle).countover a credential is a number and is still returned.Upgrade note: a
@requiredfield that is also write-denied makes the generated create fail, since the route now ignores that field's value.Testing exception and gate history
main.MetaObjects.Codegen.Tests1051 pass and the integration report and secret-fields lanes 114 pass; Java 43 targeted pass; Python 57 targeted pass; Kotlin report classes pass; TypeScript metadata 3769 and codegen-ts 2897 pass; and the api-contractsecret-fieldscorpus. The fullscripts/ci-local.shlanes were not run, and hosted CI is off. The Java, Kotlin and C# report redaction is pinned by generated-source and compile tests, plus a C# test of the JSON export, not an HTTP check.@formExcludedescription ("omitted from generated forms") is stale, because writes also ignore the field. Fixing it changesexpected-registry.jsonand four registries, so it is carried into a separate change rather than this PR.Validation
Risk Assessment
Testing
Baseline (ts-fast, ts-unit) plus targeted runs: TS generated routes deny writes to @formExclude and credential fields and never return credentials over HTTP against Postgres (pass); TS report credential omission and TPH M:N credential tests (pass); codegen dialect validation (pass live through the CLI:
d1exits 1 with a clearsqlitehint,sqlitegenerates, exit 0); verify-gate-not-vacuous test (pass). Not verified live: Java, Kotlin, C# and Python secret-field write denial and read redaction, the docs recipe examples run as written, and the full regression suite. Overall inconclusive.Pipeline
Updates from git push no-mistakes
✅ **Intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
server/csharp/MetaObjects.Codegen/Runtime/ReportExportHttp.cs:71- This finding is about test quality, not intent conformance; ignore the category tag. The new C# runtime pathReportExportHttp.Json(http, segment, body, omit, envelope)serializes the body to a JsonNode and removes credential keys withStrip(lines 71-91). It is the only thing that keeps a grouped or min/max credential out of a C# report's?format=jsonexport. No test executes it.ReportCredentialCodegenTestschecks that the generated route text containsrows, new string[] { "pinHash" }and that the code compiles, and the api-contractreport/corpus has no credential column. A plausible wrong behavior these tests would accept: withenvelope: true,env["rows"]is looked up under a casing the serializer options did not write, soStripsees null and the export still returnspinHash. The independent oracle is the documented contract in docs/features/api-contract.md: a credential report column is never returned, as the key is absent. TS (runtime-ts/test/report-hidden-column.test.ts) and Python (test_served_report_list_omits_the_credential_key_over_http) drive this over HTTP. Remedy: add an xUnit test that callsReportExportHttp.Jsonwith aDefaultHttpContext(with and withoutenvelope), runs theIResultinto a response body stream, parses the JSON, and asserts thatpinHashis absent andaccountsis present. The Java and Kotlin report redaction (redactAll(rows, "pinHash"),redact<R>(objectMapper, …)) is also checked only by generated text. It reuses the redactor that the secret-fields corpus already drives, so this finding does not ask for it, following the standing TPH decision.🔧 Fix applied.
✅ Re-checked - no issues remain.
server/typescript/packages/cli/test/integration/verify-gate-not-vacuous.test.ts- Fullscripts/ci-local.sh(Java, Kotlin, C#, Python lanes) was not run here. Java/Kotlin/C#/Python secret-field behavior was not driven live.scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchainsbun test packages/codegen-ts/test/codegen-dialect-validation.test.ts packages/codegen-ts/test/report-credential.test.ts packages/codegen-ts/test/tph-m2m-credential.test.ts packages/runtime-ts/test/report-hidden-column.test.ts packages/integration-tests/test/api-contract-secret-fields.test.ts (in server/typescript) — 20 pass, 0 fail; secret-fields scenarios use a Postgres testcontainer (docker available)live CLI: disposable project in /tmp,bunrunner on packages/cli/src/index.ts run(['gen']) with dialect "d1" → exit 1, message names "sqlite" and migrate transport; dialect "sqlite" with outDir → exit 0bun test ./packages/cli/test/integration/verify-gate-not-vacuous.test.ts — 2 pass, 0 failChecked recipe presence: docs/recipes/cloudflare-workers.md (D1 sqlite dialect section), drizzle-kit-migrations.md, prove-your-gate-fails.md, guarding-generated-routes.mdNot run: full scripts/ci-local.sh; Java/Kotlin/C#/Python secret-field runners; Cloudflare Workers example run as written; drizzle-kit re-export run as written; auth-scope mount example run as writtenspec/metamodel/ui.json:11- The registered@formExcludedescription says only "omitted from generated forms". Since Generated forms and write routes expose credential fields; @formExclude does not block writes #484 the generated create, PATCH and PUT routes also ignore the field, and reads of it are unchanged. The same string is copied into server/{typescript,python,csharp,java} registry sources, fixtures/registry-conformance/expected-registry.json, fixtures/metamodel-docs/expected/types/field.md and site-reference/types/field.html.🔧 No changes applied.
1 warning still open:
spec/metamodel/ui.json:11- Registered @formExclude description says only "omitted from generated forms". Generated create, PATCH and PUT routes also ignore the field. Same string is copied into the TS, Python, C# and Java registry sources, fixtures/registry-conformance/expected-registry.json, and the generated fixtures/metamodel-docs/expected/types/field.md and site-reference/types/field.html. Not fixed in this phase: the owner is registry source and conformance fixture (executable behavior, tests), which this phase may not edit. Fix: one identical sentence in every registry source and expected-registry.json, then regenerate the two docs with the repo generator. Suggested text: 'Omit the field from generated forms and ignore it in the body of generated create, PATCH and PUT routes (reads are unchanged).' Do not say it is hidden from responses.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.