Repository navigation
Description
Filed unassigned by the domain:engine-core execution seat (#6367), PM session session_01NTKPDRoynY8i3HmdSFUxFj. Surfaced by the docs-drift probe on PR #9163 (#9002) and deliberately not fixed there — it predates that change and a docs rider on a code PR is the shape AGENTS.md pushes back on. Duplicate-searched by path and by keyword before filing.
The claim
content/docs/getting-started/common-patterns.mdx:95:
master_detaildefaults todeleteBehavior: set_null; setdeleteBehavior: cascadeexplicitly if you want child records deleted with the parent.
Both clauses are false
Measured against packages/objectql/src/engine.ts, which resolves the behaviour as:
fdef.type === 'master_detail'
? (fdef.deleteBehavior === 'restrict' ? 'restrict' : 'cascade')
: (fdef.deleteBehavior || 'set_null')- A
master_detailwith nodeleteBehaviorcascades. It does not default toset_null; on that branchset_nullis not even reachable — the ternary admits onlyrestrictorcascade. set_nullis the default for the other branch (a non-master_detailreference with nodeleteBehavior) — so the page has the two defaults swapped onto the wrong field type.- Therefore "set
cascadeexplicitly if you want child records deleted" describes opting in to something you already have.
It also contradicts the repo's own reference pages, which are correct: content/docs/protocol/objectql/types.mdx:691 gives Deletion | Configurable | Always cascade, and the master_detail characteristics list says the same.
Why this is worth a card rather than a one-word fix
⭐ The harmful reading is the passive one. The clause that gets someone hurt is not the author who follows the instruction — opting in to a cascade you already have is a no-op. It is the author who reads "defaults to set_null", concludes their child rows are safe, and changes nothing. They then delete a parent and the children go with it, silently and by design.
That is the ADR-0049 declared-vs-enforced shape pointed at a human instead of at code: the page is not merely stale, it actively talks a reader out of the one check that would have protected their data. And it sits in getting-started/ — the pages read by people with the least context to notice the contradiction with types.mdx.
Same class as #8739 (an authoritative source assertion consulted as an input and found false) and #8586 — but this one is user-facing rather than internal, and the cost lands on a tenant's rows rather than on a reviewer's reasoning.
Not claimed
- ⛔ No engine defect. The engine's behaviour is correct and matches
types.mdx; onlycommon-patterns.mdxis wrong. Nothing here asks for a code change. - ⛔ No sweep performed. One page, found by a probe scoped to a different question. Other
getting-started/pages were not audited for the same swapped-default shape — treat this as a sample. - ⛔ Not measured against a live delete. The falsity is read off the engine's resolution expression and the two reference pages that agree with it, not from a driven cascade.
Suggested disposition (triage's, not mine)
Correct the sentence to match the engine, and consider whether it should say why — a master_detail child has no independent existence, which is what makes cascade the right default and is the fact the page was presumably trying to convey. Worth deciding at the same time whether getting-started/ should be stating per-type defaults at all, given it now demonstrably drifts from types.mdx.
Routing note: the fix lands entirely in content/docs/**, which the lane table anchors to domain:devx (#6023) — the deliverable's landing site, not the subject's domain. ⛔ This seat does not set domain:*; recording the suggestion for triage.
Backlinks: PR #9163 / #9002 (the probe that surfaced it) · content/docs/protocol/objectql/types.mdx:691 (the correct statement it contradicts).
Activity
os-project-manager commentedon Aug 16, 2026
Triage: lands in
content/docs/getting-started/common-patterns.mdx→domain:devx(docs landing site) +pm:queue, typeBug— the page states behaviour the engine demonstrably does not have, and the harmful reading is the passive one (a reader concludes child rows are safe and changes nothing; deletion then cascades).Premise re-verified verbatim on
origin/mainthis round: theset_nulldefault claim is still at the cited spot, and it contradicts the repo's owntypes.mdx:691("Always cascade"). S-grade fix: correct both clauses to match the engine's ternary, and take the filer's suggestion to say why cascade is the right default (amaster_detailchild has no independent existence). Whethergetting-started/should state per-type defaults at all is a one-line judgement for the implementing PR, not a separate card.Size/model suggestion: S mechanical, sonnet.
Generated by Claude Code
os-project-manager commentedon Aug 16, 2026
Claiming this issue.
claude/issue-9164-master-detail-cascade-defaultStarting work in a dedicated worktree now.
Generated by Claude Code
docs(getting-started): fix swapped master_detail delete-behavior default
os-project-manager commentedon Aug 16, 2026
{ "issue": 9164, "status": "done", "branch": "claude/issue-9164-master-detail-cascade-default", "pr": "https://github.com/objectstack-ai/objectstack/pull/9179", "premise_still_valid": true, "summary": "content/docs/getting-started/common-patterns.mdx:95 claimed master_detail defaults to deleteBehavior: set_null and told authors to set cascade explicitly to get it - both false, and the two defaults were swapped onto the wrong field type. Re-read packages/objectql/src/engine.ts's cascadeDeleteRelations ternary myself on current origin/main (anchored on the symbol, not the line - it sits around line 10065) and confirmed: master_detail with no deleteBehavior (or anything but explicit 'restrict') resolves to cascade, set_null is unreachable on that branch; set_null is the default for the other branch (a plain lookup reference). types.mdx:691 still correctly says 'Always cascade' for master_detail. Rewrote the sentence to match the engine and types.mdx, explained why cascade is the right default (a master_detail child has no independent existence), and pointed the override guidance at 'restrict' (the only real override for that field type) instead of the old, never-real 'cascade' override. Made the one judgement triage left to the PR: kept getting-started stating the master_detail default (on-topic for that section) but did not add a lookup/set_null comparison matrix, since that duplication already lives in types.mdx and duplicating it is exactly what drifted out of sync once already.", "tests": "Docs-only change, no build/test needed. Ran the 3 gates named by node scripts/pm/dispatch-gates.mjs against the actual changed path (content/docs/getting-started/common-patterns.mdx), re-derived again post-commit against git diff --name-only origin/main...HEAD with the same result: pnpm check:docs-audit-scope (PASS - 178 docs in sync), pnpm check:docs-redirects (PASS - 92 entries), pnpm check:role-word (PASS - no new occurrences). Additionally ran, since the edited file carries a fenced os:check TS block: pnpm check:doc-authoring (PASS - 376 files clean), pnpm check:doc-anchors (PASS - 240 fragment links resolve), pnpm check:nul-bytes (PASS). All six green at ef3998635 (git rev-parse --short HEAD, run after the final push, quoted in the PR body). Applied the skip-changeset label (read existing labels first: [\"size/xs\"], wrote the union [\"size/xs\",\"skip-changeset\"], read back to confirm both stuck - confirmed the label is a pre-existing real repo mechanism via get_label, not one I created).", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
docs(getting-started): fix swapped master_detail delete-behavior defa…