Skip to content

fix(model): harden model-layer SHOULDs M2–M8 - #3399

Merged
bpamiri merged 5 commits into
developfrom
cursor/model-hardener-m2-m8-0057
Aug 24, 2026
Merged

fix(model): harden model-layer SHOULDs M2–M8#3399
bpamiri merged 5 commits into
developfrom
cursor/model-hardener-m2-m8-0057

Conversation

@bpamiri

@bpamiri bpamiri commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Model-layer Hardener SHOULDs M2–M8. Red proofs and green locks live in vendor/wheels/tests/specs/model/hardener/.

M6 is default-inner + opt-in outer. belongsTo include still defaults to joinType="inner" (orphans are dropped). Pass joinType="outer" on the association to keep parent rows with no associated record. This PR does not change the belongsTo default to outer.

Does not close #3378 or basecoat #15. Does not close unrelated issues. Do not merge. Keep draft.

Type of Change

  • Bug fix
  • Enhancement to existing feature
  • Documentation update

Feature Completeness Checklist

  • DCO sign-off -- Every commit carries Signed-off-by:
  • Tests -- vendor/wheels/tests/specs/model/hardener/ModelHardenerM2M8Spec.cfc plus existing-spec pin updates in validationsSpec, crudSpec, queryBuilderSpec
  • Framework Docs -- web/sites/guides/src/content/docs/v4-0-0/basics/query-builder-and-scopes.mdx, upgrading/whats-new-in-4.mdx; API belongsto.md (default inner), validatesuniquenessof.md; vendor/wheels/public/docs/reference/model/{belongsto,validatesuniquenessof,accessibleproperties,protectedproperties}.txt
  • AI Reference Docs -- CLAUDE.md query-builder line names the 1-arg raw-SQL exception
  • CLAUDE.md -- updated for the query-builder contract
  • Changelog fragment -- changelog.d/model-hardener-m2-m8.{added,changed,fixed}.md (no "belongsTo now defaults to outer" claim)
  • Test runner -- filters below on HEAD 596f22e3c. Full LuCLI still needs CI confirmation on this HEAD.

Test Plan

wheels test --core --ci --filter=model.hardener
wheels test --core --ci --filter=model

(directory= must point at a folder; a single-file scope discovers 0 bundles.)

  1. Red (add1daaa4 prove-red only): 4 passed, 7 failed, 1 error(s).
  2. Green (acfdf85ca fix commit): 12 passed.
  3. HOLD rewrite (596f22e3c): restore default-inner + opt-in outer; afterFind on returnAs=struct rows.

Local on 596f22e3c:

PROVEN / UNPROVEN

ID Claim Status Filter / evidence
M2 validatesUniquenessOf default includeSoftDeletes=false so a soft-deleted value can be reused PROVEN wheels test --core --ci --filter=model.hardener14 passed
M3 QueryBuilder 1-arg where() is raw SQL; class/docs no longer claim universal injection-safety PROVEN same filter
M4 create() does not mass-assign onto the shared class model (setOnModel=false) PROVEN same filter
M5 Nested hasMany keys use new- / _new / blank PK, not a GetTickCount window PROVEN same filter
M6 Default belongsTo include is inner (orphans dropped). Opt-in joinType=outer keeps orphans. PROVEN default-inner + opt-in outer. Do not claim outer default. same filter + restored #449/#3334 crudSpec SQL pins in filter=model
M7 hasChanged treats StructDelete of a persisted property as a change; findAll(returnAs="struct") afterFind no longer reads columns off the class PROVEN hardener + filter=model (983 passed; VIEWS path in serialize.cfc)
M8 Fail-closed mass assignment by default UNPROVEN / DEFERRED. Opt-in only: set(massAssignmentStrict=true). Default stays open. same filter

HEAD: 596f22e3c0d7e42ff29d510e6a6826fe0aa9ddec

Open in Web Open in Cursor 

Failing specs for uniqueness soft-delete default, QueryBuilder raw
where docs, create() class leak, GetTickCount nested keys, belongsTo
inner orphans, hasChanged StructDelete, and open mass assignment.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Peter Amiri <peter@alurium.com>
Change validatesUniquenessOf and belongsTo defaults toward the
fail-safe forms, keep create() mass-assign on the instance, replace
the GetTickCount nested-key heuristic, detect StructDelete in
hasChanged, document raw query-builder where(), and add an opt-in
massAssignmentStrict guard.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Peter Amiri <peter@alurium.com>
@github-actions github-actions Bot added the docs label Aug 24, 2026
cursoragent and others added 3 commits August 24, 2026 03:48
Nested belongsTo joins are now LEFT OUTER JOIN, so the #449/#3334
fixtures emit flat joins instead of parenthesized INNER groups.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Peter Amiri <peter@alurium.com>
Revert belongsTo include default joinType to inner. Opt-in
joinType=outer still keeps orphan parents (hardener + restored
#449/#3334 SQL pins). Invoke afterFind on each returnAs=struct
row so callbacks no longer read columns off the class singleton.

Co-authored-by: Peter Amiri <peter@alurium.com>
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Prove default include drops orphans, and that a declared
joinType=outer association keeps rows with no parent.

Co-authored-by: Peter Amiri <peter@alurium.com>
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
@bpamiri
bpamiri marked this pull request as ready for review August 24, 2026 04:03
@bpamiri
bpamiri merged commit 886eda8 into develop Aug 24, 2026
22 checks passed
@bpamiri
bpamiri deleted the cursor/model-hardener-m2-m8-0057 branch August 24, 2026 04:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

problems with sample app plus basecoat install on wheels 4

2 participants