Uh oh!
There was an error while loading. Please reload this page.
docs(skills): master_detail deleteBehavior admits cascade/restrict only — drop the set_null teaching #9689 refuses - #11869
Merged
Conversation
The published objectstack-data skill taught `deleteBehavior: 'set_null'` on a `master_detail` field in four places. That was already wrong before #9689 — the engine resolved every non-`restrict` value on this type to `cascade`, so the declaration promised keep-the-children and delivered delete-the-children — and since #9689 landed (PR #11406) it is a named parse-time rejection: an AI author following the skill produced metadata that fails publish with a 422. Teaches the per-type vocabulary instead: `master_detail` admits `cascade` / `restrict`; `set_null` belongs on `lookup`, where children survive the parent. Correction, not expansion — net line delta is exactly 0 in both files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RMTpSRF5CjMmQBFfPtPCwJ
hotlong
marked this pull request as ready for review
August 25, 2026 00:34
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Fixes#11409
The published
objectstack-dataskill taughtdeleteBehavior: 'set_null'on amaster_detailfield. That was already wrong before #9689 — the engine resolved every non-restrictvalue on this type tocascade, so the declaration promised keep-the-children and delivered delete-the-children — and since #9689 landed (PR #11406) it is a named parse-time rejection: an AI author following the skill produced metadata that fails publish with a 422, manufacturing exactly the error the rejection exists to catch.Premise re-verified on
origin/main(c4db3116)Holds. All four spots the card names were present at the branch point, and the contract is settled in
packages/spec/src/data/field.zod.ts:1634:whose message is the source of truth for the wording: "a detail row cannot outlive its master … declare 'restrict' … declare 'cascade' (or omit the key) … or use a
lookupfield if the children must survive the parent."set_nullremains legal onlookup— thesuperRefinefires only onmaster_detail, and the migration entry pins it: "set_nullonlookupparse [unchanged]".What changed — the per-type vocabulary, in four in-place edits
Whole-package sweep, not one line:
grep -rn "set_null" skills/was the entry point and is now clean of the wrong combination in every published skill.rules/relationships.mdmaster_detailadmitscascadeorrestrictonly; an authoredset_nullis refused at publish.set_nulltable row is deleted. Its "Manager → Employees" example was doubly wrong — a relationship whose children must survive the parent wants alookup, never amaster_detail.// or 'restrict' or 'set_null'→// or 'restrict' — 'set_null' is refused here.cascadeorrestrict".set_null's real home is now stated where it belongs, on the existing UselookupWhen bullet — no new paragraph.rules/field-types.mdmaster_detailrow:deleteBehavior(cascade/restrict—set_nullis refused).lookuprow gainsdeleteBehaviorto its Key Config, so the reader of the type table alone can see which type carries the key without it.Size discipline (maintainer ruling 2026-08-21)
Both readings, tokens in the ratchet convention
ceil(utf8 bytes / 4):rules/relationships.md(whole file) — linesrules/relationships.md(whole file) — tokensrules/field-types.md(whole file) — linesrules/field-types.md(whole file) — tokens.mdunderskills/objectstack-data) — linesNet line delta is exactly zero in both files and across the package: every correction is paid for in place, and the deleted table row funds the two-line intro. The +25 tokens (+0.05% of the package) is the residue of replacing a shorter wrong statement with a slightly longer right one; a first draft that added a trailing explanatory paragraph measured +136 bytes and was cut back to this. Nothing was expanded, no section was added, and one table row is gone.
Token ratchet: nothing to lower here, and that is measured, not assumed
The card's shape (mirroring #11748) expected a ceiling to come down. It does not move, for a checkable reason:
scripts/check-skills-token-ratchet.mjspricesskills/<name>/SKILL.mdonly — itsdiscoverSkillFiles()maps each directory to exactlyskills/${entry.name}/SKILL.md. This diff touches noSKILL.md, so no priced file changed:#11748 differed precisely here: it edited
skills/objectstack-api/SKILL.md, which is priced, so it could lock its saving in at6348 → 6342.rules/*.mdis unpriced by any ratchet in the tree. Lowering theobjectstack-dataceiling anyway would price a file this PR did not shrink, against the gate's own discipline.scripts/pm/dispatch-gates.mjsflags this gate for exactly this trap — "⛔ One of YOUR paths is underskills. … thissilentis not evidence about your path in EITHER direction — it is the shape that reads as a clearance and is not. Read the gate before treating it as one." It was read; the silence is correct.Changeset: none,
skip-changesetappliedJudged from the diff, same test as #11748:
skills/has nopackage.json, no published workspace package'sfiles[]covers it, and the repo root isprivate: true. Both touched paths are published-catalog source, not an npm artifact — nothing publishable moves.Gates — all run locally at
ebe0d9ba(the final commit), each quoting its own verdict lineDerived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no path args), re-derived after the commit — same 7 families, change set read ascommitted 2, working tree 0. Exit codes captured by redirecting to a file before any pipe, never from$?after atail.check:agent-test-spelling✓ 0 violations — 357 file(s)check:doc-authoring✓ doc authoring guard: 389 files clean — no bare metadata literals.check:doc-formula-expressions✓ 22 record-scoped formula example(s) across 421 files / 1448 TS blocks judged cleancheck:pm-governed-merges✓ check-governed-merges --self-test: 129 assertionscheck:role-wordcheck-role-word: OK, no new occurrences of the reserved word.check:skill-compatibility✓ 11 SKILL.md file(s) reconciled against 78 workspace packagescheck:skill-frame-sync✓ 4 copies of the decision frame are structurally isomorphic across 3 filesPlus the families a
skills/**edit earns beyond the path derivation — run because the derivation is a clue, not a spec:check:nul-bytescheck-nul-bytes: OK (scanned 6601 text file(s) … no raw ASCII control bytes)check-skills-token-ratchet.mjs✓ 11 published SKILL.md within their ceilings.check:skill-examples✅ 256 prose examples type-check across 3 surface(s)check:skill-docs✅ Skill docs in synccheck:skill-refs✅ 9 generated files in sync with packages/speccheck:skill-examplestype-checks the```typescriptfence this PR edits — the editedmaster_detailsample compiles, which is the proof the surviving example is still valid metadata.Two gates refused rather than measured on first run, both for missing builds, and neither was a finding:
check:doc-formula-expressionscould not resolve@objectstack/formula/@objectstack/lintdist, andcheck:skill-examplesrefused on an unbuiltpackages/client-react/dist(it fails loudly rather than returning a false green). Both were re-run to a real verdict after building the closures; the greens above are the post-build runs, at the final commit.ESLint — a measured narrowing, not a skipped run.
pnpm lintwas narrowed to the two changed paths, and the narrowing is proven rather than asserted: (1) the population is read from eslint's own configuration, which reportsFile ignored because no matching configuration was supplied.for both — markdown is not in eslint's configured population at all; (2) the file count is2, read from--format json, 0 errors; (3) invariance for untouched files follows from (1) — a diff confined to a file type eslint has no configuration for cannot move any other file's verdict.Landing
⛔ Governed surface (
skills/**, Prime Directive #14) — this PR stays draft, is never queued, never armed for auto-merge, and never flipped ready. Human merge only.Generated by Claude Code