docs-audit: qualify data-property anchors by their declaring container against the authorable surface - #14607
Merged
baozhoutao merged 5 commits intoSep 2, 2026
Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…13713) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…ts pinned anchor (#13713) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…cs-audit-container-qualified-anchors
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
baozhoutao
marked this pull request as ready for review
September 2, 2026 14:56
This was referenced Sep 2, 2026
baozhoutao
enabled auto-merge
September 2, 2026 14:57
baozhoutao
deleted the
claude/issue-13713-docs-audit-container-qualified-anchors
branch
September 2, 2026 15:31
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.
Fixes #13713
The docs-audit half of option D on #12824 (maintainer ruling 2026-08-31, comment 5475441954). Option C (#13738) is already on
main; this builds on the provenance shape it left.The rule, in one paragraph
When the most specific declaration enclosing a changed line is a data property (
name:/name?:/name =), its declaring container is resolved through the generatedpackages/spec/declaration-map/*.json(TS declaration name to spec type name) and the resultingcat/Type:propertykey is tested against the generated authorable surface. Three outcomes, and the third is the whole safety property: (a) the container resolves and the key is authorable, so the property anchor is minted exactly as before and its provenance clause now names the spec key (a field of const object ObjectSchemaBase, an authorable key of data/Object); (b) the container resolves and the key is not authorable, so the property anchor is dropped and the existing container branch decides on exactly the terms it already decides on for every other line that reaches it — no new fallback is invented; (c) the container is unmapped or ambiguous, so today's behaviour is kept unchanged. Only members whose form reads as a data property are qualified; a method, a nested interface, a namespaced type are all outside the rule. Option B (blanket prefer-container) is not implemented, including as a fallback: (c) mints. Fail toward noise, never toward silence.Reconciliation with the projection: the ruled rule realises 0 rows removed, not minus 17.4 percent
This is the honest headline and it is measured, not argued. The projection on #12824 hand-classified 20 containers that minted a dropped data-property anchor and reported 18 of them internal implementation types. Under the ruled rule an internal container that the generated map does not carry is case (c) and therefore keeps. Only containers the map resolves can drop. Every named container from that list, checked against the map on this branch:
declaration-mapMetaOverlayCacheKeyMetaOverlayCacheEntryLocalizationCacheEntryAuthzCachePostureInputMintScimConnectionCredentialInputSysScimConnectionBindingAUTH_MODEL_TO_PROTOCOLenObjectsDatasourceDefschemaModepin; see below)ObjectSchemaBasedata/Objectdata/Object:userActionsis authorable (case a)So none of the projected 20 drops. The reduction the card quotes is not reachable by the ruled rule against the map #13712 delivered, because the map's coverage is
packages/specdeclarations while the noisy containers all live outside it. ⛔ The drop set was not widened to reach the projected number — that would be option B by another name. The number moves when the map's coverage grows, which is spec-lane work, not a local table here.What the change actually does, measured end to end
Two replay populations, both arms pinned as copies and run against the same tree at the same commit in a detached replay worktree, since-ref =
SHA^.Population 1 — 100 consecutive
maincommits touchingpackages/(the shape #12824 and #13738 both used):Byte-identical on every commit, with the two additive fields removed and
anchors[].fromnormalised away. Zero rows lost, zero rows gained.Population 2 — 120 consecutive
maincommits touchingpackages/spec/src(deliberately chosen as the population where the rule can bite, since only spec declarations are in the map):The 10 drops are all
historyandguidancekeys of thestrictObjecthelper's own config block on nine spec schemas (ObjectSchemaBase,ApiEndpointSchema,ExpressionBodySchema,ExternalDatasourceSettingsSchema,ObjectExternalBindingSchema,RowCrudActionOverrideSchema,ScriptBodySchema,ActionAiSchema,PageContainerProps,ReferenceRailEntrySchema) — genuine noise, and noise that already matched no page, which is why the row count does not move. Every one is named in the newcontainerQualifiedDropsfield.On the same population, 8 anchors were kept and annotated as authorable spec keys, including
userActionstwice — the ruled true positive, arriving through real commits rather than through a probe.Measured cost, stated rather than hidden. Case (b) hands the line to the container branch, and the container anchor is coarser than the property it replaces, so this rule is not purely subtractive: on a constructed probe editing
surface: 'this object'insideObjectSchemaBase, the base arm derived no anchor at all (surfaceis not code-shaped, so the shape guard already dropped it and published it inweakAnchorsDropped) while this branch drops it earlier and falls toObjectSchemaBase, adding one row (concepts/metadata-driven.mdx). Neither replay population contains such a case, but the class is real and it is the same class option B would have.The two ruled true positives, end to end, and they survive by different routes
Each pin: a one-line widening committed at the real declaration site in a throwaway worktree, then both arms run over that commit.
userActions(packages/spec/src/data/object.zod.ts) —data-modeling/objects.mdxlisted in both arms, 5 docs to 5 docs, no row moved. Kept through case (a):ObjectSchemaBaseresolves todata/Objectanddata/Object:userActionsis authorable. The row now readsuserActions (symbol, a field of const object ObjectSchemaBase, an authorable key of data/Object).schemaMode(packages/objectql/src/engine.ts) —data-modeling/drivers.mdxlisted in both arms, 3 docs to 3 docs, no row moved.DatasourceDef, an objectql-local interface the generated map does not carry.--self-testpins that absence as a fact rather than papering over it, and pins alongside it thatdata/Datasource:schemaModeis authorable — so the day the map carriesDatasourceDef, the pin survives via case (a) instead. ⛔ No mapping was hard-coded to make it resolve.A defect found while measuring: the anchor artifact is stale by design
packages/spec/authorable-surface.base.json— the artifact #12824 and this card both name — is not the live surface. Its owndescriptionsays it is "a verbatim copy of the keys inauthorable-surface/as they stood atbaseRev", a fixed commit for the deletion gate (53ef05744, 2026-08-08). Measured on this tree it lags the live per-category ratchet by 532 keys, and the lag is load-bearing:data/Object:editModeand every key ofsecurity/OrgScopingEntitlementandapi/ProvenanceWaiverare absent from the anchor and present in the ratchet. Reading the anchor alone suppressed anchors on genuinely authorable keys — a false negative, growing with every key added afterbaseRev, which is the one direction the ruling forbids. Measured directly: the first build of this branch produced 20 drops on population 2, ten of which were that class (editMode,platformGlobalObjects,supportedPostures,suppressUnboundedOrgAdminGrant,package,reason,registeredUnder,body).So the authorable surface is read as the union of
packages/spec/authorable-surface/(the live ratchet, read as one set, exactly as its own gate reads it) and the named anchor. A union can only ever keep an anchor one source vouches for, never drop one more.[RETIRED]is stripped for the same reason — the ratchet's description says a tombstoned key "still rejects with an upgrade prescription", so it is still surface a page documents, and an exact-match lookup would have silently dropped all 103 of them.Verification
node scripts/docs-audit/affected-docs.mjs --self-test— its own verdict line:✓ affected-docs self-test: 545 cases pass.(516 onorigin/main; +29, and the count confirms all 29 executed).Reverse verification, twice, on the committed tree. Mutation and restore both proven on disk; there is no build or
distleg for this file — it is a plain node script with no compile step, so none is claimed. Each leg ran under atrapwith absolute paths, and each restore isgit checkout HEAD -- ABSOLUTE_PATHproven by observed state (git diff HEADempty and worktree blob equal to the HEAD blobcc226a4f3), never by an exit code.schemaModelive pin, and three pre-existing [decision] docs-audit: a data-property anchor is both the noisiest and the most valuable anchor the tool mints — 70 of 402 rows, and no cheap discriminator survives measurement #12824 provenance pins.EXIT=1.EXIT=1.The gate union below was run at this branch's final commit
7ea5cf10a(the merge oforigin/main, which moved twice during this task), on a clean tree.Run reconciliation:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RECORDat7ea5cf10a—✓ dispatch-gates --ran: 18 derived famil(ies) accounted for — 17 run, 1 NOT-MEASURED.Gate family derived in this worktree with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/docs-audit/affected-docs.mjs(the script confirmed the answer is aboutobjectstack-ai/objectstackat this checkout). All green, each read from the gate's own verdict line, exit codes captured before any pipe:check-affected-docs.mjs,check-drift-comment.mjs(56 cases pass across 5 fixture diff(s)— it drives the workflow's real comment script over real mapper output),check-self-test-wired.mjs,check-ci-filter-parity.mjs,check-cross-package-test-inputs.mjs,check-shard-attestation.mjs,bare-root-worklist.mjs --self-test,check:docs-audit-scope,check:agent-test-spelling,check:bash32-floor,check:cli-command-ids,check:cross-package-test-inputs,check:entry-guard,check:parse-guard,check:pnpm-filter-targets,check:watch-hint-literal,check:nul-bytes(scanned 7976 text file(s) ... no raw ASCII control bytes).pnpm check:pm-dispatch-gates(the heavy family, underscripts/pm/os-verify-lock.sh):VERDICT command-exit 0 · held the lock 295s · waited 6s, self-test✓ dispatch-gates self-test: 1240 cases pass.— run twice, once before theorigin/mainmerge and again at7ea5cf10aafter it, green both times.check-test-completeness.mjs: NOT MEASURED, not a red —EXIT=3 PREREQUISITE NOT MET, the gate grades a saved turbo test log and the family names it with no argument.Gate-script rule: no
*.test.ts/*.test.mjsin the repo namesaffected-docsordocs-audit(git grepreturned nothing). This script's suite is--self-testplus the twocheck-*.mjsgates above; all three run.Scope
packages/specis untouched:git diff origin/main --stat -- packages/specis empty. The declaration map and both authorable-surface artifacts are generated (gen:declaration-map,gen:schema, covered bycheck:generated) and are only ever read here.NON_DECLARATION_HEADS, the generic-name list and every threshold are untouched.scripts/**only, no package publishes, soskip-changeset.🤖 Generated with Claude Code
https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
Generated by Claude Code