docs(spec): the block-level publicSharing TSDoc states the standing policy, not just the mint half - #16964
Conversation
…olicy, not just the mint half The block comment above `ObjectSchema.publicSharing` said that with the block omitted or `enabled:false` "the platform refuses to create share-link rows for this object — independent of any permission the caller holds". Literally true, and the mint half only. `publicSharing.enabled` is a STANDING policy held again at every redemption: `resolveToken` re-reads the object's CURRENT block before it probes the record or stamps the counters, so a block that is off refuses every token on it — those minted while it was on, and those minted through the system-context / `permissive` mint bypass alike. The `permissive` option is documented and implemented as MINT-only; the redemption gate takes no bypass. That correction already reached the `enabled` property comment thirty lines below, `IShareLinkService.resolveToken`'s `null`-cause list and design note 7 of `contracts/share-link-service.ts`. This carrier was left behind, so the same file stated one fact two ways with nothing telling a reader which was current, and the block doc is the more likely landing site of the two: it is what a reader scanning the object schema for the sharing policy meets first, and it carries the `@see` pointers that make it read as the block's orientation text. The sentence now names both halves and points at `enabled` for the full statement rather than restating the mechanism a third time. Comment text only: no schema arm, bound, default, `.describe()` string or runtime behaviour moves, and no generated artefact changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 240ac34797b7a9f690cadf71ec204db3120e196c && git checkout 240ac34797b7a9f690cadf71ec204db3120e196c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 07c56b5388a214b9cf8bcf4c3d60fab14c91ced3 7be4c2b3b26cda831490a9dd43c98257c87d2a3e && git checkout -B drift-repro 07c56b5388a214b9cf8bcf4c3d60fab14c91ced3 && git merge --no-ff 7be4c2b3b26cda831490a9dd43c98257c87d2a3e
node scripts/docs-audit/affected-docs.mjs --json 07c56b5388a214b9cf8bcf4c3d60fab14c91ced3
|
Fixes #16198
Clause-②: no
This PR rewrites one sentence of block-level TSDoc so it states the standing-policy half the
same file already states correctly thirty lines below. No accept set moves, no export is added,
no schema arm, bound, default or
.describe()string changes, and no runtime behaviour changes.check:api-surfacereports the public API surface and factory signatures unchanged. The fencethe dispatch carried was not tripped: the correct wording needed no schema, type or behaviour
change.
Why this sentence is admissible at all
Triage's boundary refinement, quoted rather than paraphrased:
PR #16196 corrected the same fact in three places — the
publicSharing.enabledproperty commentabout thirty lines below,
IShareLinkService.resolveToken'snull-cause list, and design note 7of
contracts/share-link-service.ts— and left this carrier behind. So the sentence is no longermerely incomplete; it is the contradicted half of a fact the same file now states correctly
nearby, with nothing telling a reader which is current.
The containment argument (the block doc encloses the corrected property comment, so a reader gets
the correction anyway) was ruled wrong by triage and is not re-litigated here.
Before and after
packages/spec/src/data/object.zod.ts, the block TSDoc immediately abovepublicSharing: strictObject({(that line measured at:2250on the base commit97adce2fa;the stale sentence
refuses to create share-link rowsoccurred exactly once in the file, andexactly once repo-wide).
Before:
After:
The landed #16196 wording this was aligned to
The wording is taken from the
enabledproperty comment that PR #16196 landed in this same file,not invented a second time. Its opening, verbatim:
Reused verbatim from it:
a STANDING policy held at every redemption, not a mint-time check,(#14033),stop serving the moment it is turned off, andthe system-context / permissive mint bypass included. The block sentence keeps the two halvesand the bypass clause and then points at
enabled; it deliberately does not repeat the restof that comment (retroactive on deploy, not a revocation, re-enabling serves again, off means
nothing inside the block is evaluated, the refusal is the undifferentiated
null). Per the card:a pointer plus the missing clause is the whole job, and a second full copy is the drift this
family keeps generating.
The runtime claim, proved by reading rather than asserted
The lead the dispatch named — the
resolveTokennull-cause path — holds up. Inpackages/plugins/plugin-sharing/src/share-link-service.ts,resolveTokenreads the object'scurrent policy and refuses on it, with no bypass parameter in scope:
Three readings support each clause of the new sentence:
getSchema(row.object_name)per call — it does not consult anything stamped on the row — and it sits after the cheap
in-memory gates but before the record probe and the usage stamp, so a switched-off link reads
no record and bumps no counter.
getPolicyreturnsenabled: falsewheneverisPublicSharingEnabled(schema)is false, which covers an absent block as well asenabled: false— so an object with nopublicSharingblock refuses its already-minted rowstoo. The package's own suite pins this under a
[#14033] REVERSAL REGISTER, includingREVERSED by #14033 — reverse control: a block-less object is switched off, its seeded link refusesand
a standing policy, not a revocation — turning the block back ON restores the SAME token.permissivebypass is mint-only. The bypass appears at exactly onedecision point, in
createLink:if (!policy.enabled && !this.permissive && !context.isSystem).The redemption gate quoted above tests
policy.enabledalone. The option's own doc comment saysso in the same words: "[[Decision] What does turning
publicSharing.enabledoff mean for an ALREADY-MINTED share link? — the parent switch is mint-only while its own child predicate is now a standing policy #14033] Mint ONLY … a link minted under this bypass while the block isoff does not resolve until the block is enabled."
Design note 7 in
packages/spec/src/contracts/share-link-service.tsstates the same contract("Note 2 is the mint half only"), so the tree agrees with the card in all three respects — nothing
here contradicts it.
Changeset — both halves measured, with controls
The changeset is
.changeset/public-sharing-block-doc-standing-policy.md,@objectstack/spec: patch.Half 1 — is the changed path inside the package's
files[]? Yes.packages/spec'sfiles[]carries
src/**/*.zod.ts, andobject.zod.tsmatches. Not left on the glob's word:npm pack --dry-run --jsonenumerates 2073 files in the tarball andsrc/data/object.zod.tsisone of them, so the edited source ships verbatim.
Half 2 — is the changed text present in the published
dist? Yes, in 22 files.packages/specbuilds unminified bundles that carry source comments through, so the new sentence appears in
dist/index.js,dist/data/index.js, the.mjstwins and thebrowser/andapi/variants.packages/spec/dist/AND refuses to.describe()string from the same block,Allow records of this object to be published via share linkenabledTSDoc,Master switch — a STANDING policy held at every redemption, not aThe negative control proves the instrument can return zero, and the positive and neighbour
controls prove it reads real published text. One correction worth recording: the neighbour
control was run first against the phrase
how the row got there buys it nothingand read 0 —a broken control, not a reading. That phrase wraps across a line break in the source
(
… anonymous act; how/the row got there buys it nothing) …), so a single-line grep could nevermatch it. It was re-run against a non-wrapping phrase and reads 22, in agreement with the rest.
Both halves say yes, so
skip-changesetwould be wrong here.Checks run, by name
Every exit code below was captured into a variable before any pipe, and the verdict lines quoted
are the ones the checks print themselves.
pnpm lint(whole repo,eslint . --no-inline-config)7be4c2b3bwith a clean treepnpm --filter @objectstack/spec typecheckcheck:test-typecheck: OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.jsonpnpm --filter @objectstack/spec testTest Files 467 passed (467)/Tests 13100 passed (13100)pnpm --filter @objectstack/spec buildpnpm --filter @objectstack/spec run check:api-surface@objectstack/spec public API surface + factory signatures unchanged ✓pnpm --filter @objectstack/spec run check:authorable-surfaceSuccessfully generated 1588 schemas.pnpm --filter @objectstack/spec run check:docs228 generated files in sync with packages/speccheck:llms-txt·check:liveness·check:yaml-examples·check:variant-docs·check:skill-refs·check:export-origins·check:spec-changes·check:meta-url-spelling(all--filter @objectstack/spec)pnpm check:nul-bytes·check:published-files·check:doc-authoring·check:dts-closure·check:spec-parsed-alias·check:cross-package-test-inputspnpm check:objectui-changeset·check-changeset-no-major --base origin/main·check-adr-0087-registration --base origin/main·check-empty-changeset --base origin/maincheck-closing-keyword-parity·check-comment-mask-adoption·check-comment-mask-corpus·check-keyed-text-bounds·check-affected-docs·check-system-context-census·check-registry-log-declared·check-undeclared-dep-imports·check-platform-object-tenancy-census·check-plugin-teardown-shape·check-reference-carrier-shapeThe gate list was derived mechanically rather than guessed:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, re-derived afterthe changeset existed (it named four further families, all run above).
Named as NOT MEASURED, so no absence is read as success:
node scripts/check-partof-closing-keyword.mjs— exit 2, and it says why itself:NOT WIRED — neither PR_BODY nor PR_NUMBER is set, so this run was handed no pull request and judged nothing. This is a wiring or usage failure, NOT a verdict. It needs a live PR; it isre-run against this PR's real body and commit list below.
(
check-shard-attestation,check-test-completeness), ten that declare a population too wide toplace, and a whole-tree always-runs tail. Those are CI's, not this branch's.
silentare not read as a clearance ineither direction, per the tool's own warning; two of them keep their roster under a directory this
diff is in (
check:meta-url-spelling,check:spec-changes) and both were run explicitly above.Fences respected
.describe()untouched — the diff is comment text only, andgit status --porcelainafter thefull gate sweep showed 0 generated files changed.
gen:schema/gen:docswere not neededand were not run.
publicSharing.enabledis a standing policy held at redemption;resolveTokennames the switched-off block among itsnullcauses #16196 landed;content/docs/releases/untouched;sharing.zod.ts(spec: threesharing.zod.tsdocblock sentences go false when #15072 lands — thefieldrecipient stops being "not yet authorable" #15239) not folded in.+18 / −2: seven lines added and two removed inobject.zod.ts, plus thechangeset.
Landing
Draft, and it stays draft: this seat does not flip ready, arm auto-merge, or merge. Not a governed
surface — today's register prints
docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md,and neither changed path is in it.
Generated by Claude Code