Skip to content

docs(spec): the block-level publicSharing TSDoc states the standing policy, not just the mint half - #16964

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-16198-public-sharing-block-doc-standing-policy
Sep 8, 2026
Merged

docs(spec): the block-level publicSharing TSDoc states the standing policy, not just the mint half#16964
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-16198-public-sharing-block-doc-standing-policy

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

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-surface reports the public API surface and factory signatures unchanged. The fence
the 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:

a true-but-partial statement becomes admissible once its complement has landed elsewhere and it was not updated — one fact, N carriers, N−1 corrected. … ⛔ The trigger is the landed correction, not the partiality — before #16196 this really was out of class.

PR #16196 corrected the same fact in three places — the publicSharing.enabled property comment
about thirty lines below, IShareLinkService.resolveToken's null-cause list, and design note 7
of contracts/share-link-service.ts — and left this carrier behind. So the sentence is no longer
merely 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 above
publicSharing: strictObject({ (that line measured at :2250 on the base commit 97adce2fa;
the stale sentence refuses to create share-link rows occurred exactly once in the file, and
exactly once repo-wide).

Before:

   * the link" style). When omitted or `enabled:false`, the platform
   * refuses to create share-link rows for this object — independent of
   * any permission the caller holds.

After:

   * the link" style). When omitted or `enabled:false`, the platform
   * refuses to create share-link rows for this object AND refuses to
   * resolve any that already exist — a STANDING policy held at every
   * redemption, not a mint-time check (#14033), so links minted while the
   * block was on stop serving the moment it is turned off, those minted
   * through the system-context / `permissive` mint bypass included. Both
   * halves are independent of any permission the caller holds; see
   * `enabled` below for the whole statement.

The landed #16196 wording this was aligned to

The wording is taken from the enabled property comment that PR #16196 landed in this same file,
not invented a second time. Its opening, verbatim:

     * Master switch — a STANDING policy held at every redemption, not a
     * mint-time check (#14033; the same shape as the `eligibility` predicate
     * below, #13608).
     *
     * When false (default), no share links can be issued for this object AND
     * no share link on it resolves: `resolveToken` re-reads this switch on
     * every redemption, so links minted while it was on stop serving the
     * moment it is turned off — links minted through the system-context /
     * `permissive` mint bypass included …

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, and
the system-context / permissive mint bypass included. The block sentence keeps the two halves
and the bypass clause and then points at enabled; it deliberately does not repeat the rest
of 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 resolveToken null-cause path — holds up. In
packages/plugins/plugin-sharing/src/share-link-service.ts, resolveToken reads the object's
current policy and refuses on it, with no bypass parameter in scope:

    const schema = this.engine.getSchema?.(row.object_name);
    const policy = getPolicy(schema);
    
    if (!policy.enabled) {
      this.logger?.warn?.(  reason: 'SHARING_NOT_ENABLED'  );
      return null;
    }

Three readings support each clause of the new sentence:

  1. Held at every redemption, on the current block. The gate re-reads 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.
  2. Existing links stop resolving. getPolicy returns enabled: false whenever
    isPublicSharingEnabled(schema) is false, which covers an absent block as well as
    enabled: false — so an object with no publicSharing block refuses its already-minted rows
    too. The package's own suite pins this under a [#14033] REVERSAL REGISTER, including
    REVERSED by #14033 — reverse control: a block-less object is switched off, its seeded link refuses
    and a standing policy, not a revocation — turning the block back ON restores the SAME token.
  3. The system-context / permissive bypass is mint-only. The bypass appears at exactly one
    decision point, in createLink: if (!policy.enabled && !this.permissive && !context.isSystem).
    The redemption gate quoted above tests policy.enabled alone. The option's own doc comment says
    so in the same words: "[[Decision] What does turning publicSharing.enabled off 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 is
    off does not resolve until the block is enabled."

Design note 7 in packages/spec/src/contracts/share-link-service.ts states 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's files[]
carries src/**/*.zod.ts, and object.zod.ts matches. Not left on the glob's word:
npm pack --dry-run --json enumerates 2073 files in the tarball and src/data/object.zod.ts is
one of them, so the edited source ships verbatim.

Half 2 — is the changed text present in the published dist? Yes, in 22 files. packages/spec
builds unminified bundles that carry source comments through, so the new sentence appears in
dist/index.js, dist/data/index.js, the .mjs twins and the browser/ and api/ variants.

reading grep over packages/spec/dist/ files
target the new sentence, AND refuses to 22
positive control a .describe() string from the same block, Allow records of this object to be published via share link 22
negative control a string that exists nowhere in the repo 0
neighbour control #16196's landed enabled TSDoc, Master switch — a STANDING policy held at every redemption, not a 22

The 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 nothing and 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 never
match it. It was re-run against a non-wrapping phrase and reads 22, in agreement with the rest.

Both halves say yes, so skip-changeset would 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.

check verdict
pnpm lint (whole repo, eslint . --no-inline-config) exit 0, at 7be4c2b3b with a clean tree
pnpm --filter @objectstack/spec typecheck exit 0 — check:test-typecheck: OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json
pnpm --filter @objectstack/spec test exit 0 — Test Files 467 passed (467) / Tests 13100 passed (13100)
pnpm --filter @objectstack/spec build exit 0
pnpm --filter @objectstack/spec run check:api-surface exit 0 — @objectstack/spec public API surface + factory signatures unchanged ✓
pnpm --filter @objectstack/spec run check:authorable-surface exit 0 — Successfully generated 1588 schemas.
pnpm --filter @objectstack/spec run check:docs exit 0 — 228 generated files in sync with packages/spec
check: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) exit 0 each
pnpm check:nul-bytes · check:published-files · check:doc-authoring · check:dts-closure · check:spec-parsed-alias · check:cross-package-test-inputs exit 0 each
pnpm check:objectui-changeset · check-changeset-no-major --base origin/main · check-adr-0087-registration --base origin/main · check-empty-changeset --base origin/main exit 0 each
check-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-shape exit 0 each

The gate list was derived mechanically rather than guessed:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, re-derived after
the 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 is
    re-run against this PR's real body and commit list below.
  • The derivation reports five families whose argv takes a value only a CI run supplies
    (check-shard-attestation, check-test-completeness), ten that declare a population too wide to
    place, and a whole-tree always-runs tail. Those are CI's, not this branch's.
  • The 42 "artifact roster" families the derivation scores silent are not read as a clearance in
    either 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

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

…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
@github-actions github-actions Bot added the size/s label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 1 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-driven.mdx (via ObjectSchemaBase (symbol, a top-level const object))
What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 07c56b5388a214b9cf8bcf4c3d60fab14c91ced3packageMentionDocs.

Which tree this was computed on

This run read content/docs from 240ac34797b7a9f690cadf71ec204db3120e196c — the merge of head 7be4c2b3b26cda831490a9dd43c98257c87d2a3e into base 07c56b5388a214b9cf8bcf4c3d60fab14c91ced3, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 07c56b5388a214b9cf8bcf4c3d60fab14c91ced3 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/s tooling

Projects

None yet

2 participants