From 9d36b55c9fb14993a31051d64a0a390614bf88e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 23:53:23 +0000 Subject: [PATCH 1/2] docs(sharing): say why getPolicy's disabled-branch redactFields read is kept MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `getPolicy()`'s `enabled !== true` branch reads `raw.redactFields`. Its comment justified that read by a case #14033 removed: it spoke of "tokens that still serve" on a switched-off block, and after #14033's redemption gate no such token exists. The comment was describing an impossible case, which is how a read outlives the reason anyone can still read for it. Measured before rewriting it, on `origin/main` 7a17f3bf1: - `policy.redactFields` has exactly one reader in the file — the union in `resolveToken` — and the `[#14033]` gate returns `null` 75 lines above it. - `createLink` never reads it on any branch; the row it writes carries the caller's `redactFields`. - `getPolicy` is module-private with exactly two callers, both in-file. So the read is unreachable, as the card says. Collapsing the branch back to `redactFields: []` was also measured: the whole `@objectstack/plugin-sharing` suite stays green, 726/726, unchanged. That is the reason the read is KEPT rather than removed — no pin distinguishes the two shapes, so a future regression of the gate would restore #13856's fail-open widening uncaught, and the read is the only thing that fails closed behind it. Comment only. Non-comment content of the file is byte-identical. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8 --- .../plugin-sharing/src/share-link-service.ts | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/plugins/plugin-sharing/src/share-link-service.ts b/packages/plugins/plugin-sharing/src/share-link-service.ts index 726dace8f7..29c9508847 100644 --- a/packages/plugins/plugin-sharing/src/share-link-service.ts +++ b/packages/plugins/plugin-sharing/src/share-link-service.ts @@ -100,17 +100,38 @@ function getPolicy(schema: any): { enabled: false, allowedAudiences: [], allowedPermissions: [], - // [#13856] The declared redaction set is read REGARDLESS of `enabled`. - // This branch used to return `redactFields: []`, so a link minted while - // the object was opted IN and redeemed after it was opted OUT kept - // resolving AND started serving the very fields the object declares - // redacted — turning the feature off WIDENED what the anonymous - // endpoint serves. Opting out gates MINTING (`createLink`'s 422 reads - // `enabled`, not this list) and whatever #14033 rules for standing - // links; it must never strip the object's declared redactions from - // tokens that still serve. An object with no `publicSharing` block at - // all keeps `[]` — nothing declared, nothing redacted — exactly as - // before. + // [#13856 -> #14033] The declared redaction set is read REGARDLESS of + // `enabled`. Kept deliberately, as DEFENCE IN DEPTH — not because any + // caller reaches it today. + // + // Why it exists (#13856): this branch used to return `redactFields: []`, + // so a link minted while the object was opted IN and redeemed after it + // was opted OUT kept resolving AND started serving the very fields the + // object declares redacted — turning the feature OFF WIDENED what the + // anonymous endpoint serves. Fail-open, and the reason this read was + // added. + // + // Why nothing reaches it now (#14033): `publicSharing.enabled` is a + // standing policy re-read at every redemption, and `resolveToken`'s gate + // returns `null` for a switched-off block BEFORE the + // `policy.redactFields` union below — so no token on such a block serves + // at all, and the sibling keys under it are MOOT. `createLink` never + // reads this list on any branch (its 422 reads `enabled`; the row it + // writes carries the CALLER's `redactFields`). The ruling is quoted + // verbatim in the #14033 reversal register in `share-link-service.test.ts`. + // + // Why it stays anyway (#14581): "moot" is not "must not be read". The + // read is free, it fails CLOSED, and it is the only thing between a + // future change to that gate and a silent repeat of #13856 — measured + // on that card: collapsing this branch back to `[]` leaves the entire + // `plugin-sharing` suite green, so NO pin distinguishes the two shapes + // and a regression of the gate would land uncaught. #14637 is the live + // reminder that a property this service states can be defeated one + // layer up. Do not tidy this into `[]` on the strength of the gate + // above it alone. + // + // An object with no `publicSharing` block at all keeps `[]` — nothing + // declared, nothing redacted — exactly as before. redactFields: Array.isArray(raw?.redactFields) ? (raw.redactFields as string[]) : [], }; } From d2b749a92d72ec0e0ff72b33edab7feb60166a03 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 23:58:16 +0000 Subject: [PATCH 2/2] docs(sharing): hold the rewritten comment at line-count parity with the base The first pass wrote the same explanation over 32 lines where the comment it replaced used 11. That is not free: `check-system-context-census` anchors the `context.isSystem` read sites in this file by ABSOLUTE LINE NUMBER, so +21 lines rotted 10 of them and turned the gate red. Measured both ways on `origin/main` 7a17f3bf1: - base file, unmodified -> `check-system-context-census: OK ... 145 anchors resolve` (exit 0) - base + the 32-line comment -> `10 problem(s) over 145 anchors`, five `[site-without-a-row]` and five `[anchor-is-not-a-read-site]` (exit 1) - `--fix` repairs it by rewriting 5 anchors, all on ONE line (`content/docs/permissions/system-context.mdx:138`) One line of churn is small, but that page is one of the two hottest generated files in the repo and is contended by two other open PRs on this branchline. A comment-only change that carries no behaviour should not need a census regenerated to land, so the comment now says the same five things in exactly the 11 lines it replaced. The file is 1006 lines before and after, no anchor moves, and the census stays green without being touched. Non-comment content remains byte-identical to the base. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8 --- .../plugin-sharing/src/share-link-service.ts | 41 +++++-------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/packages/plugins/plugin-sharing/src/share-link-service.ts b/packages/plugins/plugin-sharing/src/share-link-service.ts index 29c9508847..3d5215c37f 100644 --- a/packages/plugins/plugin-sharing/src/share-link-service.ts +++ b/packages/plugins/plugin-sharing/src/share-link-service.ts @@ -101,37 +101,16 @@ function getPolicy(schema: any): { allowedAudiences: [], allowedPermissions: [], // [#13856 -> #14033] The declared redaction set is read REGARDLESS of - // `enabled`. Kept deliberately, as DEFENCE IN DEPTH — not because any - // caller reaches it today. - // - // Why it exists (#13856): this branch used to return `redactFields: []`, - // so a link minted while the object was opted IN and redeemed after it - // was opted OUT kept resolving AND started serving the very fields the - // object declares redacted — turning the feature OFF WIDENED what the - // anonymous endpoint serves. Fail-open, and the reason this read was - // added. - // - // Why nothing reaches it now (#14033): `publicSharing.enabled` is a - // standing policy re-read at every redemption, and `resolveToken`'s gate - // returns `null` for a switched-off block BEFORE the - // `policy.redactFields` union below — so no token on such a block serves - // at all, and the sibling keys under it are MOOT. `createLink` never - // reads this list on any branch (its 422 reads `enabled`; the row it - // writes carries the CALLER's `redactFields`). The ruling is quoted - // verbatim in the #14033 reversal register in `share-link-service.test.ts`. - // - // Why it stays anyway (#14581): "moot" is not "must not be read". The - // read is free, it fails CLOSED, and it is the only thing between a - // future change to that gate and a silent repeat of #13856 — measured - // on that card: collapsing this branch back to `[]` leaves the entire - // `plugin-sharing` suite green, so NO pin distinguishes the two shapes - // and a regression of the gate would land uncaught. #14637 is the live - // reminder that a property this service states can be defeated one - // layer up. Do not tidy this into `[]` on the strength of the gate - // above it alone. - // - // An object with no `publicSharing` block at all keeps `[]` — nothing - // declared, nothing redacted — exactly as before. + // `enabled` — DEFENCE IN DEPTH, not a live read. #13856: this branch + // returned `[]`, so opting an object OUT WIDENED what an already-minted + // token served (fail-open). #14033 then made `enabled` a standing + // policy — `resolveToken`'s gate returns `null` before the only reader + // of this list (the union below) and `createLink` never reads it, so + // nothing reaches this today and the sibling keys are MOOT. But moot is + // not "must not be read": #14581 measured that collapsing this back to + // `[]` leaves the whole package suite green, so NO pin would catch a + // regression of that gate — this is what fails CLOSED behind it. An + // object with no `publicSharing` block keeps `[]`, exactly as before. redactFields: Array.isArray(raw?.redactFields) ? (raw.redactFields as string[]) : [], }; }