docs(skills): objectstack-data factual sweep (2/3) — SKILL.md - #13694
Merged
Conversation
Three measured-false behavioral claims corrected against the implementation: - titleFormat is labelled 'Retired (ADR-0079)' but is DEPRECATED: still a declared ObjectSchema key, still parses, still read by objectql's registry. - tenancy.tenantField is shown with '// default: tenant_id'. It carries NO default; the platform tenant column is organization_id. - maskingRule is claimed 'pruned from FieldSchema' (twice). It is live, with a runtime enforcer in plugin-security's FieldMasker. Token ratchet: SKILL.md shrinks (13783 -> 13782). Part of #13675 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EXxTW8mvPBhoHxmyPZ63de
This was referenced Aug 31, 2026
os-zhuang
approved these changes
Aug 31, 2026
os-zhuang
marked this pull request as ready for review
August 31, 2026 07:16
os-zhuang
enabled auto-merge
August 31, 2026 07:16
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.
Part of #13675
Program anchor: #13658 — flight ② of the published-skills factual sweep, PR 2 of three at whole-file boundaries. The card and the anchor both stay open; only PR 3 carries a closing keyword. PR 1 is #13687 (
rules/field-types.md,rules/relationships.md,rules/indexing.md); this PR is based onorigin/main, not stacked on it — the two diffs are disjoint.Session, for durable attribution:
https://claude.ai/code/session_01EXxTW8mvPBhoHxmyPZ63deGoverned surface — human merge, deliberately parked
skills/**is the published, customer-facing surface. This PR is a draft on purpose: auto-merge is not armed, it is not marked ready, no review is requested, and this seat will not arm it. A draft awaiting the maintainer's own merge is the correct terminal state here, not a stuck PR.Non-vacuity control — claims proven TRUE by execution
1. The
enablecapability defaults, all seven at once. The Object Capabilities table gives a default per flag. Parsedenable: {}throughObjectSchema:Every value matches the table's Default column —
trackHistoryfalse,searchabletrue,apiEnabledtrue,filesfalse,feedstrue,activitiestrue,clonetrue.2.
apiMethodsis a whitelist over exactly six primitives. All six accepted; the derived verbs are accepted-but-stripped with the platform's own explanation:3. The seed table's four defaults, in one parse.
SeedSchemaover a minimal seed:externalIddefault'name',modedefault'upsert',envdefault['prod','dev','test']— exactly the table. The five import modes are also exactly the five listed (upsert|insert|update|ignore|replace); a sixth (delete) is refused by name.4. The nine lint rules and their severities. Read out of
packages/lint/src/data-model-rules.ts, every row matches:5. The R9-vs-ADR-0079 callout, the subtlest claim in the file. It asserts two deliberately different "name-like" sets. Both measured:
codeis in the lint set and absent from the title-derivation set — exactly as the callout says.6. The strict/closed surfaces, each with a live control.
namespace,permissions,hooks,workflows,stateMachines,softDeleteare all refused byObjectSchemawith a located message (the skill claims each).permissionSets:on the stack is refused and renamed for you:tenancy.strategyis refused ("removed from @objectstack/spec after v15.0 — it never had a consumer"). Theprotectionblock is.strict()withreasonrequired at 1–500 chars and exactly four lock levels (read-onlyrefused).fieldGroups[].collapseis exactlynone|expanded|collapsedand a non-snake_case group key is refused.readScopeis exactlyown|own_and_reports|unit|unit_and_below|org; RLSoperationis exactlyselect|insert|update|delete|all,objectreally is required per policy, and the pluraloperationsis refused with a rename hint. All seven permission bits (allowCreate/allowRead/allowEdit/allowDelete/allowTransfer/viewAllRecords/modifyAllRecords) parse green, while an inventedallowFrobnicateis refused — the control that makes the seven greens mean something.objectExtensions[].priorityis.min(0).max(999).default(200), byte-exact with the stated "default200; range0–999".The three FALSE claims, and what measured them
titleFormat… Retired (ADR-0079) — a render-only template the server can't return or query. UsenameField; …"titleFormat… Deprecated, not removed (ADR-0079) — render-only: the server can't return or query it. UsenameField(it wins); …"tenantField: 'tenant_id', // default: 'tenant_id'"// tenantField — NO default; omit it and the driver usesorganization_id``"encryptionConfigandmaskingRulefield keys were pruned fromFieldSchema— they had no runtime consumer (dead surface; setting them protected nothing)." / "theencryptionConfig/maskingRulefield keys were pruned"encryptionConfigfield key was pruned fromFieldSchema— it had no runtime consumer.maskingRuleis live (plugin-security's FieldMasker enforces it)." / "theencryptionConfigfield key was pruned"1.
titleFormatis deprecated, not retiredThe skill's own vocabulary makes this a distinction with teeth: it uses "Retired" for keys that are now a parse error (
indexes[].type,indexes[].partial— "Both are now atscerror and a parse error"), and one row abovetitleFormatit correctly writes "Deprecated alias fornameField(still honored as a fallback)" fordisplayNameField. So a reader takes "Retired" to mean authoring it fails the build. Measured:It is a declared, optional
ObjectSchemakey —packages/spec/src/data/object.zod.ts:2076describes it as[DEPRECATED → nameField (ADR-0079)] Render-only title template; the server cannot return or query it, and an explicit nameField now takes precedence. It is still read (packages/objectql/src/registry.ts:1617namesresolveDisplayField/titleFormatas the live read path for title-less system tables) and it still has a semantic migration entry (packages/spec/src/migrations/entries/semantic/11.object-titleFormat-to-nameField.ts). The control is thenamespacerow in the same table, which really is retired and really does reject:The correction keeps the advice (use
nameField) and fixes the mechanism, adding the part an author needs:nameFieldtakes precedence.2.
tenancy.tenantFieldhas no default, and'tenant_id'is the wrong columnThe example wrote
tenantField: 'tenant_id'with the comment// default: 'tenant_id'. Measured:No
tenantFieldmaterializes. The schema's own docblock (object.zod.ts:575) records that the default was removed and why, and the reason is that the documented value was actively wrong:This mattered inside this very file: three sections below, the RLS and visibility-posture recipes correctly use
organization_idthroughout, so the tenancy example was teaching a column name the rest of the page contradicts. Note the docblock also records thattenantis a word ADR-0120 refuses for the authorable vocabulary. The corrected comment names the fallback column instead of inventing a default.3.
maskingRulewas never pruned — it is a live, enforced capabilityThe Sensitive-fields section grouped
maskingRulewithencryptionConfigas "pruned fromFieldSchema", "dead surface; setting them protected nothing", and the Advanced Features note repeated it. Measured — and the two keys behave in opposite ways:encryptionConfigreally was pruned, with its own tombstone message.maskingRuleparses, retains its value, and is declared infield.zod.tsas "Partial masking rule enforced by the runtime FieldMasker (single channel — API, UI, export and AI context all see the same masked value)", with named presets (phone138****5678,id_card,bank_account,email,name) or{ keepHead, keepTail }. Its consumers are real and plural:packages/plugins/plugin-security/src/field-masker.tsand its dedicatedfield-masking-rule.test.ts,explain-engine.ts,security-plugin.ts,packages/plugins/plugin-approvals/src/payload-redaction.ts,packages/plugins/plugin-audit/src/read-audit.ts, a dogfood test, and the metadata form modules with translations in four locales.This is the most consequential of the three: the skill was telling authors that a shipped, enforced data-protection capability does not exist and "protected nothing". Under the ratchet the correction is one clause, so the full
maskingRulereference (presets, the unmask gate, the interaction withrequiredPermissions) is recorded as acontent/docs/**follow-up rather than expanded here.Written to the token ratchet, not around it
SKILL.mdhad 6 tokens of headroom, and the corrections needed more than that, so they are paid for by genuine deletion inside the same file — chiefly the "dead surface; setting them protected nothing" clause, which described a key that no longer exists. No ceiling was raised; that is a maintainer's call, not a dev's.SKILL.mdlines (whole file)SKILL.mdtokens (ceiling 13783)The ratchet's own verdict line at head
0ccf1b1e7:Gates — derived, not recalled, run locally at head
0ccf1b1e7Family derived from the real diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, harvested with--commands. It derives the same 13 families as PR 1. 12 green, 1 NOT MEASURED; exit codes captured before any pipe:Two families beyond the derived list, because AGENTS.md names them for this exact edit and the derivation did not. AGENTS.md's "Touched
packages/spec? Regenerate its artifacts" table has the row "ASKILL.md(frontmatter or body) →check:skill-docs,check:skill-refs". The path derivation named neither, so they were run anyway rather than trusted to be irrelevant:Both are green because this diff changes body prose only, not front-matter — but that is a measurement, not an assumption, and it is the one this PR could most plausibly have broken.
check:skill-examplesis green and load-bearing here, unlike in PR 1:SKILL.mdcarries 4os:checkblocks. It requires@objectstack/client-reactto be built (it refuses rather than degrading), so that closure was built first and the gate re-run:check-test-completeness.mjsexits 3 with its own text "PREREQUISITE NOT MET — this gate grades a savedturbo run testlog, and no log was named". Recorded as NOT MEASURED, not as a red.check-nul-bytesclean:OK (scanned 7557 text file(s) … no raw ASCII control bytes), plus a direct control-byte scan overSKILL.mdwith no hits.ESLint was not run repo-wide, and the narrowing is measured. Same three readings as PR 1, re-taken for this file: (1) no
files:block ineslint.config.mjsnames.md, and the strings.md/markdownappear nowhere in it; (2)--format jsonoverSKILL.mdreturns 1 result,errorCount: 0, one message withruleId: nullreading "File ignored because no matching configuration was supplied."; (3) type-aware linting is not enabled anywhere (noparserOptions.project, which the config's own line 327 states), so this diff cannot move a verdict on an untouched file.No changeset
This PR releases nothing from any package — a pure
skills/**documentation correction.skip-changesetis applied.Calibration numbers for the anchor
Same counting rule as PR 1: one claim per assertable proposition about platform behaviour. Of
SKILL.md's 1,210 lines, ~250 are out of the inventory (front-matter, the Skill Boundaries routing table, "When to Use This Skill", the Quick Reference link list, section headings, cross-skill pointers, Common Patterns restatements ofrules/**already inventoried in PR 1).281 behavioral claims inventoried in
SKILL.md. 3 FALSE (1.1%), ~29 NOT MEASURABLE, the rest VERIFIED with executed probes wherever behaviour-bearing.The concentration reading from PR 1 holds and sharpens. By markdown rendering, 1 of 3 falsehoods is a table cell (the
titleFormatrow); the other two are a code-comment and a prose sentence. By content class, 3 of 3 are surface/liveness claims — "is this key retired, deprecated, or live?" All three are the same error in the same direction: the document declares a surface deader than it is. That is the mirror image of flight ①'s dominant flavour (rows outliving a removal), and it is arguably the more dangerous one, because it hides a shipped capability (maskingRule) and a live fallback (titleFormat) instead of merely advertising a dead one.Running total for flight ② so far: 595 claims inventoried, 9 FALSE (1.5%) across 4 of 12 files and 2,429 of 4,935 lines.
What is not here, on purpose
SEARCHABLE_ENUM_TYPESinpackages/spec/src/data/search-fields.tsisnew Set(['select', 'status']), andstatusis not a member of the 49-valueFieldTypeenum. The skill reproduces the implementation's list faithfully (so its sentence is VERIFIED, not false); the dead vocabulary entry is an implementation-side observation for the PM, not something this PR should change.check:skill-examplesbuild prerequisite (@objectstack/client-reactand its service closure) was satisfied for real rather than skipped, because this file's 4os:checkblocks are exactly what that gate exists to type-check.Generated by Claude Code