diff --git a/.changeset/20596-plugin-sharing-provenance-anchors.md b/.changeset/20596-plugin-sharing-provenance-anchors.md new file mode 100644 index 00000000000..9dc01cd6532 --- /dev/null +++ b/.changeset/20596-plugin-sharing-provenance-anchors.md @@ -0,0 +1,10 @@ +--- +'@objectstack/plugin-sharing': patch +--- + +Provenance comments in `plugin-sharing` were re-anchored + +Comment and docblock lines under `src/` that cited tracker numbers which no +longer resolve on GitHub now cite the commit in this repository's history that +decided the matter, and say in their own words what was decided. Comments +only: no type, schema, export, log or refusal text, or runtime behaviour changes. diff --git a/packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.test.ts b/packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.test.ts index 80135bfac53..ee74accec26 100644 --- a/packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.test.ts +++ b/packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.test.ts @@ -1,6 +1,6 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. // -// #14484 — the backfill half of "A: tenant-scoped, writer-repaired, backfilled". +// commit 3f64fe6c6 — the backfill half of "A: tenant-scoped, writer-repaired, backfilled". // // These pin the properties the maintainer ruling names, as behaviour rather // than as prose: diff --git a/packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.ts b/packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.ts index 63253253d27..d19bfaf2bc3 100644 --- a/packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.ts +++ b/packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.ts @@ -2,16 +2,16 @@ /** * backfill-sys-record-share-organizations — the ONE-OFF repair sweep for the - * `sys_record_share` rows the pre-#14484 `SharingService.grant` stranded with + * `sys_record_share` rows that `SharingService.grant`, before commit 3f64fe6c6, stranded with * no organization. * * ## What this repairs, and why it is not optional * - * #14484 fixed the WRITER: `SharingService.grant` now stamps + * Commit 3f64fe6c6 fixed the WRITER: `SharingService.grant` now stamps * `organization_id` on every insert and update of `sys_record_share` — a * rule-materialised grant carries the granting rule's organization, a direct * grant the shared record's. It wrote nothing to existing rows, and on a WALLED - * deployment that asymmetry is the cliff the card names: + * deployment that asymmetry is the cliff that commit pins: * * - the SQL driver's own tenant predicate is NULL-TOLERANT — * `(organization_id = :tenantId OR organization_id IS NULL)` — so an @@ -33,7 +33,7 @@ * The tree's precedents for this shape are * `plugin-approvals/src/backfill-platform-row-organizations.ts` and * `service-storage/src/backfill-sys-file-organizations.ts`, and both require a - * MAINTAINER ORDER PER TABLE. The 2026-09-02 ruling on #14484 (decision batch + * MAINTAINER ORDER PER TABLE. The 2026-09-02 ruling commit 3f64fe6c6 applies (decision batch * #11 item 3, maintainer verbatim 「#13564 转维护者处理;其他同意」 — "其他同意" * adopts A: tenant-scoped, writer-repaired, existing rows backfilled from the * record they grant access to) IS that order, and it is the order for @@ -121,7 +121,7 @@ import { resolveTenantFieldName } from '@objectstack/objectql'; /** * The ONE object this sweep repairs. ⛔ Scope-pinned by the 2026-09-02 ruling - * on #14484 — a second table needs its own maintainer order (see the module + * applied by commit 3f64fe6c6 — a second table needs its own maintainer order (see the module * doc). */ export const SYS_RECORD_SHARE_BACKFILL_OBJECT = 'sys_record_share'; diff --git a/packages/plugins/plugin-sharing/src/exec-context-annotation.pin.ts b/packages/plugins/plugin-sharing/src/exec-context-annotation.pin.ts index bd523560e6a..07db9d44c4b 100644 --- a/packages/plugins/plugin-sharing/src/exec-context-annotation.pin.ts +++ b/packages/plugins/plugin-sharing/src/exec-context-annotation.pin.ts @@ -4,8 +4,8 @@ * #7136 — compile-time pin for the CONTEXT type this plugin's enforcement * methods accept. * - * #6523 converged 36 contract signatures onto the full `ExecutionContext` (the - * #6206 ruling: enforcement adjudicates on the whole `resolveAuthzContext` + * Commit aa4b90d9a converged 36 contract signatures onto the full `ExecutionContext` (the + * full-envelope ruling: enforcement adjudicates on the whole `resolveAuthzContext` * envelope, never a per-site subset). #7136 is the consumer half — the * implementations here now annotate their own parameters with that same * envelope instead of the six-field shape they used to name. diff --git a/packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts b/packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts index c9dc9459842..c7151e3a7f1 100644 --- a/packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts +++ b/packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts @@ -153,7 +153,7 @@ export const SysRecordShare = ObjectSchema.create({ // // ⚠️ ORDERING CONSTRAINT — this id half is `required: true`, and that // makes it ORDER-DEPENDENT in seeds even though a pointer pair - // contributes no static ordering edge (#11674, measured against the real + // contributes no static ordering edge (commit 1cba33f16, measured against the real // engine in `packages/objectql/src/engine-seed-required-deferral.test.ts`): // the seed loader defers an unresolvable reference by DELETING the column // from the pass-1 insert, required-validation rejects that row, and pass 2 @@ -161,7 +161,7 @@ export const SysRecordShare = ObjectSchema.create({ // an OPTIONAL id half order-independent (`sys_audit_log`) does not reach // this one. ⇒ SEED THE TARGET DATASET FIRST. The failure if you do not is // loud in three places — a write error naming this column, a - // dropped-deferral error, and `success: false` — and since #11674 the + // dropped-deferral error, and `success: false` — and since commit 1cba33f16 the // loader also WARNS at load time, before the engine rejects the row. referenceVia: 'object_name', }), diff --git a/packages/plugins/plugin-sharing/src/objects/sys-share-link.object.ts b/packages/plugins/plugin-sharing/src/objects/sys-share-link.object.ts index eec45e8b884..ff6188f9dcc 100644 --- a/packages/plugins/plugin-sharing/src/objects/sys-share-link.object.ts +++ b/packages/plugins/plugin-sharing/src/objects/sys-share-link.object.ts @@ -144,7 +144,7 @@ export const SysShareLink = ObjectSchema.create({ // // ⚠️ ORDERING CONSTRAINT — this id half is `required: true`, and that // makes it ORDER-DEPENDENT in seeds even though a pointer pair - // contributes no static ordering edge (#11674, measured against the real + // contributes no static ordering edge (commit 1cba33f16, measured against the real // engine in `packages/objectql/src/engine-seed-required-deferral.test.ts`): // the seed loader defers an unresolvable reference by DELETING the column // from the pass-1 insert, required-validation rejects that row, and pass 2 @@ -152,7 +152,7 @@ export const SysShareLink = ObjectSchema.create({ // an OPTIONAL id half order-independent (`sys_audit_log`) does not reach // this one. ⇒ SEED THE TARGET DATASET FIRST. The failure if you do not is // loud in three places — a write error naming this column, a - // dropped-deferral error, and `success: false` — and since #11674 the + // dropped-deferral error, and `success: false` — and since commit 1cba33f16 the // loader also WARNS at load time, before the engine rejects the row. referenceVia: 'object_name', }), diff --git a/packages/plugins/plugin-sharing/src/position-graph.ts b/packages/plugins/plugin-sharing/src/position-graph.ts index 95744d45c7f..2227af83c21 100644 --- a/packages/plugins/plugin-sharing/src/position-graph.ts +++ b/packages/plugins/plugin-sharing/src/position-graph.ts @@ -39,7 +39,7 @@ export interface PositionGraphOptions { * * ⚠️ A SECOND implementation of this question lives in `plugin-approvals` * (`ApprovalService.expandPositionUsers`), and it is deliberately NOT identical - * — do not unify them without reading #8613 / #8710 first. Approval routing is + * — do not unify them without reading #8613 / commit 04d03c3a0 first. Approval routing is * an ADDRESSING path, so it reads the directory raw and applies no ADR-0091 D2 * window: dropping a lapsed holder there is fail-OPEN (a step routing to * nobody). Note also that the `sys_position.active` gate for THIS path is not diff --git a/packages/plugins/plugin-sharing/src/reconcile-refused-grant-continues.test.ts b/packages/plugins/plugin-sharing/src/reconcile-refused-grant-continues.test.ts index 36a7dbf5013..2734cbe29c8 100644 --- a/packages/plugins/plugin-sharing/src/reconcile-refused-grant-continues.test.ts +++ b/packages/plugins/plugin-sharing/src/reconcile-refused-grant-continues.test.ts @@ -6,7 +6,7 @@ * * ## The shape, and which half of it is the security half * - * After #14484 `sys_record_share` is `tenant-scoped` in the #13491 ledger, so + * After commit 3f64fe6c6 `sys_record_share` is `tenant-scoped` in the #13491 ledger, so * on a walled install an organization-less system insert on it is refused * loudly with `ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED` (#8844). * `SharingService.grant` resolves the organization on every path that can; a @@ -34,7 +34,7 @@ * the deployment posture. A fake engine would have to imitate the very thing * whose behaviour decides the case. So these cases run a real `SqlDriver` on * better-sqlite3 `:memory:` behind a real `ObjectQL` on an `isolated` posture, - * the way `record-share-organization-stamp.test.ts` does for the #14484 stamp. + * the way `record-share-organization-stamp.test.ts` does for the commit 3f64fe6c6 stamp. * * ## Why the fixture's organization-less record is in the MIDDLE * @@ -347,7 +347,7 @@ describe('[#14754] reconcile: a refused grant is counted and the pass CONTINUES' const rule = await platformGlobalRule(rules, 'os14754_update_half', 'read'); // A pre-existing rule grant on the organization-less record, carrying no - // organization (which is how it got there before #14484). The pass wants + // organization (which is how it got there before commit 3f64fe6c6). The pass wants // to raise it to `edit`. await driver.create('sys_record_share', { id: 'shr_orgless_old', object_name: OBJECT, record_id: 'rec_orgless', recipient_type: 'user', diff --git a/packages/plugins/plugin-sharing/src/record-share-organization-stamp.test.ts b/packages/plugins/plugin-sharing/src/record-share-organization-stamp.test.ts index 084cb03bbca..2b86b51e8eb 100644 --- a/packages/plugins/plugin-sharing/src/record-share-organization-stamp.test.ts +++ b/packages/plugins/plugin-sharing/src/record-share-organization-stamp.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#14484] Every `sys_record_share` row carries `organization_id` — on a REAL + * [commit 3f64fe6c6] Every `sys_record_share` row carries `organization_id` — on a REAL * engine over a REAL driver, both write paths, plus the backfill and the * engine rule the ledger flip switches on. * diff --git a/packages/plugins/plugin-sharing/src/rule-criteria-org-scope.test.ts b/packages/plugins/plugin-sharing/src/rule-criteria-org-scope.test.ts index aa3c3f6e5b0..66189547c6a 100644 --- a/packages/plugins/plugin-sharing/src/rule-criteria-org-scope.test.ts +++ b/packages/plugins/plugin-sharing/src/rule-criteria-org-scope.test.ts @@ -95,7 +95,7 @@ const DEAL_FIELDS: Record> = { const SHARE_FIELDS: Record> = { id: { type: 'text', name: 'id', label: 'Id', primary: true }, - // [#14484] The tenant column the registry provisions on every platform + // [commit 3f64fe6c6] The tenant column the registry provisions on every platform // object (`applySystemFields`); this hand-built table must declare it too, // now that the writer stamps it — the fixture was never spec-faithful // without it, the omission just had no reader. diff --git a/packages/plugins/plugin-sharing/src/share-link-eligibility.test.ts b/packages/plugins/plugin-sharing/src/share-link-eligibility.test.ts index 2d61c765604..a9fbcb8135f 100644 --- a/packages/plugins/plugin-sharing/src/share-link-eligibility.test.ts +++ b/packages/plugins/plugin-sharing/src/share-link-eligibility.test.ts @@ -38,7 +38,7 @@ * the ablation meaningful: with the new predicate removed, the eligibility * cases must flip red and these five must stay green. * - * ## [#13608] The second seam: the same policy, held again at REDEMPTION + * ## [commit fc9ba76a5] The second seam: the same policy, held again at REDEMPTION * * Enforcing at mint alone left the adjacent half open, and the state the * predicate reads is exactly the state an editor changes: publish an article @@ -69,7 +69,7 @@ import type { DriverQuery } from '@objectstack/spec/contracts'; import { assertEngineUpdateDispatch, assertEngineFindOnePredicate } from '@objectstack/objectql'; import type { IHttpServer, IHttpRequest, IHttpResponse, RouteHandler } from '@objectstack/spec/contracts'; import { ShareLinkService } from './share-link-service.js'; -// [#13608] The PUBLIC seam an anonymous holder actually reaches. The refusal's +// [commit fc9ba76a5] The PUBLIC seam an anonymous holder actually reaches. The refusal's // shape is a claim about what that caller can observe, so it is measured there // and not only on the service's return value. import { registerShareLinkRoutes } from './share-link-routes.js'; @@ -139,7 +139,7 @@ interface BootOptions { */ shapeRow?: (row: any) => any; /** - * [#13608] Collect the service's server-side log. The redemption refusal is + * [commit fc9ba76a5] Collect the service's server-side log. The redemption refusal is * deliberately silent on the wire, so this is where the REASON it refused * becomes assertable — and where the ruling says the reason belongs. */ @@ -165,7 +165,7 @@ async function boot(article: any = ARTICLE, options: BootOptions = {}) { const schemas: Record = { article, sys_share_link: SysShareLink }; /** - * [#13608] Every read the service issues, in order. Two claims are measured + * [commit fc9ba76a5] Every read the service issues, in order. Two claims are measured * off it: the `id`-only probe is UNCHANGED for an object with no predicate, * and the eligibility path widens that same read rather than adding a second. */ @@ -595,7 +595,7 @@ describe('[#7861] publicSharing.eligibility is enforced at createLink', () => { /** - * [#13608] The redemption seam. + * [commit fc9ba76a5] The redemption seam. * * `resolveToken` checked `revoked_at`, `expires_at`, the audience gates, the * password and record EXISTENCE — and served whatever survived, under @@ -923,7 +923,7 @@ describe('[#13608] publicSharing.eligibility is enforced again at REDEMPTION', ( * `publicSharing.enabled` governed MINTING only: `getPolicy()` collapsed to an * empty policy when the block was off, and `resolveToken` read nothing off * `policy.enabled`. So the platform held this shape — the predicate INSIDE - * the block was re-evaluated at every redemption (#13608, above) while turning + * the block was re-evaluated at every redemption (commit fc9ba76a5, above) while turning * the ENTIRE block off did not stop a single existing link. Maintainer ruling * of 2026-09-01 (quoted verbatim in `share-link-service.test.ts`'s reversal * register): the switch is a standing policy held at every redemption, @@ -983,7 +983,7 @@ describe('[#14033] publicSharing.enabled is a standing policy — the switch is /** * The HTTP seam, driven end-to-end on the real service through the real * route, with the route's SECURE default context — every request below is - * anonymous. Same reading as the #13608 pin above, for the same reason: the + * anonymous. Same reading as the commit fc9ba76a5 pin above, for the same reason: the * switched-off link lands in the generic "invalid / expired / revoked" * answer, byte-for-byte what a token that NEVER EXISTED gets — not the 410 * bucket, which would confirm the token was real, and not a 422 naming the @@ -1268,7 +1268,7 @@ describe('[#14637] the route probe reads the standing policy before it answers f // Back-dated on the stored row, not minted: `createLink` refuses a past // `expiresAt` outright (`422 EXPIRY_IN_PAST`), so this is the only way to // reach an ALREADY-EXPIRED link — and it is exactly what the passage of - // time does to a live one. Same stamp the #13608 pins above use. + // time does to a live one. Same stamp the commit fc9ba76a5 pins above use. await driver.update('sys_share_link', link.id, { expires_at: new Date(Date.now() - 60_000).toISOString(), }); @@ -1306,7 +1306,7 @@ describe('[#14637] the route probe reads the standing policy before it answers f }); /** - * [#13608] Mount the real PUBLIC resolve route on the real service. + * [commit fc9ba76a5] Mount the real PUBLIC resolve route on the real service. * * Only the verbs `registerShareLinkRoutes` calls are implemented, and the * SECURE default `contextFromRequest` is deliberately left in place: it reads @@ -1318,7 +1318,7 @@ describe('[#14637] the route probe reads the standing policy before it answers f * the `audience: 'signed_in'` arm from the serving side), and the returned * driver takes the request query (the only way to reach the `WRONG_PASSWORD` * arm). Omit both and this is byte-for-byte the anonymous, query-less harness - * the #13608 and #14033 pins above drive. + * the commit fc9ba76a5 and #14033 pins above drive. */ function mountResolveRoute(service: ShareLinkService, engine: unknown, signedInUserId?: string) { const routes = new Map(); diff --git a/packages/plugins/plugin-sharing/src/share-link-enforcement-context.test.ts b/packages/plugins/plugin-sharing/src/share-link-enforcement-context.test.ts index 4d049075b07..d4390a7da7e 100644 --- a/packages/plugins/plugin-sharing/src/share-link-enforcement-context.test.ts +++ b/packages/plugins/plugin-sharing/src/share-link-enforcement-context.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#6206 / #6430 ruling A] What the share-link routes hand to ENFORCEMENT. + * [commit 8e13ca876 / #6430 ruling A] What the share-link routes hand to ENFORCEMENT. * * ## The defect * diff --git a/packages/plugins/plugin-sharing/src/share-link-routes.ts b/packages/plugins/plugin-sharing/src/share-link-routes.ts index 7110833ad6f..714291c4f0e 100644 --- a/packages/plugins/plugin-sharing/src/share-link-routes.ts +++ b/packages/plugins/plugin-sharing/src/share-link-routes.ts @@ -60,7 +60,7 @@ export interface ShareLinkRoutesOptions { * trusted `x-user-id` / `x-tenant-id`, which let a client forge attribution * and enumerate/revoke other users' links. * - * [#6206 / #6430] It returns the FULL {@link ExecutionContext} — the whole + * [commit 8e13ca876 / #6430] It returns the FULL {@link ExecutionContext} — the whole * `resolveAuthzContext` envelope — because this module forwards it unchanged * into `createLink` / `listLinks` / `revokeLink`, every one of which * ADJUDICATES access. A resolver that rebuilds a subset here silently changes @@ -78,7 +78,7 @@ export interface ShareLinkRoutesOptions { const defaultContext = (_req: IHttpRequest): ExecutionContext => ({}); /** - * [#6206] The routes' own 401 gate — authenticated vs anonymous, and nothing + * [commit 8e13ca876, full-envelope ruling] The routes' own 401 gate — authenticated vs anonymous, and nothing * more. * * Typed to {@link ShareLinkExecutionContext} deliberately: that is the shape diff --git a/packages/plugins/plugin-sharing/src/share-link-service.test.ts b/packages/plugins/plugin-sharing/src/share-link-service.test.ts index 41daaaa9146..f88aae1d786 100644 --- a/packages/plugins/plugin-sharing/src/share-link-service.test.ts +++ b/packages/plugins/plugin-sharing/src/share-link-service.test.ts @@ -669,7 +669,7 @@ describe('[#13856] declared redactFields survive publicSharing opt-out', () => { // `publicSharing` block was absent or `enabled !== true`, and nothing in // `resolveToken()` read `policy.enabled` — the opt-in was checked at MINT only // (`createLink` → 422 `SHARING_NOT_ENABLED`). So the platform held this shape: -// the predicate INSIDE the block (`eligibility`, #13608) was re-evaluated on +// the predicate INSIDE the block (`eligibility`, commit fc9ba76a5) was re-evaluated on // every redemption, while turning the WHOLE block off did not stop a single // link already handed out. An author who wanted anonymous serving to stop had // to narrow the predicate rather than switch the feature off. Measured before @@ -680,7 +680,7 @@ describe('[#13856] declared redactFields survive publicSharing opt-out', () => { // // Quoted verbatim in the reversal register above. In one line each: (1) the // switch is a standing policy, re-read at every redemption; (2) retroactive on -// deploy, as #13608 was; (3) how a link was minted — system context, the +// deploy, as commit fc9ba76a5 was; (3) how a link was minted — system context, the // `permissive` bypass, ledger row 37 — buys it nothing at redemption; (4) // switch OFF ⇒ nothing inside the block is evaluated; switch ON ⇒ the sibling // keys keep their redemption-time behaviour. @@ -692,7 +692,7 @@ describe('[#13856] declared redactFields survive publicSharing opt-out', () => { // read the service issued was the token lookup (no record probe — read off a // recording engine); and the usage counters did not move. The HTTP-seam // shape, the server-side log line and the real-driver readings live beside -// the #13608 pins in `share-link-eligibility.test.ts`. +// the commit fc9ba76a5 pins in `share-link-eligibility.test.ts`. describe('[#14033] publicSharing.enabled is a standing policy — held again at redemption', () => { /** Every `find` the service issues, so "no record read" is a measurement rather than an assumption. */ function recordingService(engine: any, opts: Record = {}) { diff --git a/packages/plugins/plugin-sharing/src/share-link-service.ts b/packages/plugins/plugin-sharing/src/share-link-service.ts index ef7ca45a691..ba7fba2e819 100644 --- a/packages/plugins/plugin-sharing/src/share-link-service.ts +++ b/packages/plugins/plugin-sharing/src/share-link-service.ts @@ -10,7 +10,7 @@ import type { ShareLinkAudience, } from '@objectstack/spec/contracts'; /** - * [#6206 / #6430 — maintainer ruling A] Every method here that adjudicates + * [commit 8e13ca876 / #6430 — maintainer ruling A] Every method here that adjudicates * access takes the FULL envelope. The route-local `ShareLinkExecutionContext` * is the HTTP layer's 401 vocabulary and is deliberately not named in this * file: the contexts this file receives are forwarded into `engine.find`, where @@ -259,7 +259,7 @@ async function defaultVerifyPassword(password: string, hash: string): Promise { - // [#13279] The failure this asserts against: degrading the outage to + // [commit 6a180e42d] The failure this asserts against: degrading the outage to // `{}` answers 401, byte-identical to a genuine anonymous caller. const h = await boot({ tenancy: 'factory-throws' }); const res = await post(h, {}); diff --git a/packages/plugins/plugin-sharing/src/sharing-plugin.ts b/packages/plugins/plugin-sharing/src/sharing-plugin.ts index 59c1fb8c80e..026d033cfc5 100644 --- a/packages/plugins/plugin-sharing/src/sharing-plugin.ts +++ b/packages/plugins/plugin-sharing/src/sharing-plugin.ts @@ -26,7 +26,7 @@ import type { IMetadataService, IObjectQLEngine, } from '@objectstack/spec/contracts'; -// [#6206] The share-link routes' context is the FULL authorization envelope — +// [commit 8e13ca876] The share-link routes' context is the FULL authorization envelope — // it feeds enforcement (`engine.find`), so it is an `ExecutionContext`, never // the route-local `ShareLinkExecutionContext`. import type { ExecutionContext } from '@objectstack/spec/kernel'; @@ -38,7 +38,7 @@ import { markFilterSubtreeProvenance } from '@objectstack/spec/data'; // [#12260] The SANCTIONED renderer for OPERATION-level refusal copy. The // Operation Message Catalog is the ONE seat for these sentences — its own // header bars both a package-local string table and a second rendering -// mechanism for a second producer, and #12493 landed this middleware's key +// mechanism for a second producer, and commit aa5994e17 landed this middleware's key // (`record_write_denied`) into it ahead of this consumer half. import { renderOperationMessage, type ValidationMessageTranslator } from '@objectstack/spec/system'; import { SysRecordShare, SysSharingRule, SysShareLink } from './objects/index.js'; @@ -538,7 +538,7 @@ export class SharingServicePlugin implements Plugin { * arm: an embedding with no `plugin-auth` is a SUPPORTED composition and its * behaviour here is exactly what it was. Loud arm: * `verifiedContextFromRequest`'s `catch` already re-raises this brand rather - * than laundering it into a 401 (#13279), and the routes' own `catch` answers + * than laundering it into a 401 (commit 6a180e42d), and the routes' own `catch` answers * `err.status` — so the outage reaches the wire as a 503. * * ⚠️ The brand exists only on the ASYNC resolution path: `PluginContext.getService` @@ -892,7 +892,7 @@ export class SharingServicePlugin implements Plugin { // `x-user-id` headers. An unresolvable request → anonymous (the // authed routes then 401). // - // [#6206 / #6430 — maintainer ruling A, 2026-08-07] The envelope is + // [commit 8e13ca876 / #6430 — maintainer ruling A, 2026-08-07] The envelope is // handed on WHOLE. This assembly used to name four fields // (`userId`/`tenantId`/`positions`/`permissions`) and the resulting // object was passed straight into `engine.find` as the [Finding-2] @@ -949,7 +949,7 @@ export class SharingServicePlugin implements Plugin { // `runtime/src/security/resolve-execution-context.ts`. return { ...authz, isSystem: false }; } catch (err) { - // [#13279] Degrading an outage to `{}` answers 401 — the same + // [commit 6a180e42d] Degrading an outage to `{}` answers 401 — the same // answer a genuine anonymous caller gets. Re-raised so the // outage is not laundered into an authentication verdict. if (isAuthzStoreUnavailableError(err)) throw err; @@ -1352,7 +1352,7 @@ export function buildSharingMiddleware( // through so a bulk DELETE scopes to owned rows alone (no share widening), // while a bulk UPDATE keeps the edit-share widening (ADR-0111 D3). // - // [#8792, maintainer ruling 2026-08-15] ⛔ This merge carries NO + // [commit 83c661d97, maintainer ruling 2026-08-15] ⛔ This merge carries NO // `markFilterSubtreeProvenance` and NO `vouchCallerWhereBeforeRewrite`, // and that is RULED, not an oversight. #8220 declares the mark for // READ-scope merge boundaries; write-scope refusal semantics stay @@ -1383,7 +1383,7 @@ export function buildSharingMiddleware( // a real report of an author unable to diagnose a bulk-write refusal on // their own predicate. That makes the extension a pulled feature with a // consumer attached, and it returns as a decision — with write-scope - // refusal semantics to specify and pin at a real driver, and #8836's + // refusal semantics to specify and pin at a real driver, and commit 1850ebbb0's // request-scoped invariant to carry (no filter object that can be // vouched `'author'` may outlive the request that vouched it). let writeFilter = await service.buildWriteFilter(ctx.object, exec ?? {}, verb); @@ -1395,7 +1395,7 @@ export function buildSharingMiddleware( onBehalfOf: undefined, __writeScope: exec.__delegatorWriteScope, }, verb); - // [#8792] Unmarked deliberately too — same ruling, same reasons as + // [commit 83c661d97] Unmarked deliberately too — same ruling, same reasons as // above, not repeated here. Its read-path twin (the delegator's // `buildReadFilter`) IS marked `'policy'`; that difference is the ruled // read/write boundary, not drift between two copies of one pattern. diff --git a/packages/plugins/plugin-sharing/src/sharing-rule-service.ts b/packages/plugins/plugin-sharing/src/sharing-rule-service.ts index 51317797c0d..e7966db943f 100644 --- a/packages/plugins/plugin-sharing/src/sharing-rule-service.ts +++ b/packages/plugins/plugin-sharing/src/sharing-rule-service.ts @@ -9,15 +9,15 @@ import type { SharingRuleRecipientType, } from '@objectstack/spec/contracts'; // [#7136] The full `resolveAuthzContext` envelope — what `ISharingRuleService` -// has declared for every one of these context parameters since #6523 (the -// #6206 ruling: no per-site subset contracts). +// has declared for every one of these context parameters since commit aa4b90d9a (the +// full-envelope ruling: no per-site subset contracts). import type { ExecutionContext } from '@objectstack/spec/kernel'; // [#15981] The built-in platform-operator position NAME is deliberately NOT // imported here any more. It used to spell the second half of // {@link SharingRuleService.hasPlatformAuthority}, and reading it as authority // became an escalation channel once `positions[]` started carrying ADR-0057 D4 // `sys_user_position` names; that predicate now reads the ADR-0095 rung. -// [#8710] The ONE predicate for `sys_position.active` / `sys_permission_set.active` +// [commit 04d03c3a0] The ONE predicate for `sys_position.active` / `sys_permission_set.active` // (#8613). Reused rather than re-spelled: two notions of "is this row active" // — one honouring the 1/0 and 'false' storage shapes, one not — is how the // enforcement hole this closes gets re-opened one seam over. @@ -191,7 +191,7 @@ export interface SharingRuleServiceOptions { } /** - * [#8710] Memo for the catalogue reads a recipient expansion adds, scoped to + * [commit 04d03c3a0] Memo for the catalogue reads a recipient expansion adds, scoped to * ONE evaluator pass. * * The lifetime is the whole design. It is the same lifetime the graph services @@ -916,7 +916,7 @@ export class SharingRuleService implements ISharingRuleService { const rules = await this.listRules({ object }, context); if (rules.length === 0) return []; const results: SharingRuleReconcilePassResult[] = []; - // [#8710] ONE pass, so N rules naming the same position pay ONE catalogue + // [commit 04d03c3a0] ONE pass, so N rules naming the same position pay ONE catalogue // read — and the memo dies with this call, so the next pass re-reads and a // deactivation is honoured immediately. const pass: RuleEvaluationPass = {}; @@ -1189,7 +1189,7 @@ export class SharingRuleService implements ISharingRuleService { * the evaluator must still see rows no individual recipient could, it must * just stop seeing rows the RULE has no business in. * - * ## [#14484] The same context is what the grant is WRITTEN under + * ## [commit 3f64fe6c6] The same context is what the grant is WRITTEN under * * `reconcile` / `reconcileForRecord` hand this context to * `SharingService.grant`, which stamps `sys_record_share.organization_id` @@ -1445,7 +1445,7 @@ export class SharingRuleService implements ISharingRuleService { return members; } if (rule.recipient_type === 'position') { - // [#8710] A DEACTIVATED position confers NOTHING — checked before the + // [commit 04d03c3a0] A DEACTIVATED position confers NOTHING — checked before the // expansion, not after it, so the rule's desired grant set is empty and // the reconcilers' existing revoke-the-remainder branches retract what it // already materialised. See {@link positionConfersAccess}. @@ -1537,7 +1537,7 @@ export class SharingRuleService implements ISharingRuleService { } /** - * [#8710] Does `positionName` still CONFER access in this rule's + * [commit 04d03c3a0] Does `positionName` still CONFER access in this rule's * organization? Memoised for the pass; the extra read is accepted. * * Maintainer ruling, 2026-08-15, verbatim: @@ -1579,7 +1579,7 @@ export class SharingRuleService implements ISharingRuleService { } /** - * [#8710] The catalogue verdict for one position name. Three fallbacks, each + * [commit 04d03c3a0] The catalogue verdict for one position name. Three fallbacks, each * of them a way this could otherwise have become a silent mass revocation: * * 1. **A name with no `sys_position` row is untouched.** Position names @@ -1647,7 +1647,7 @@ export class SharingRuleService implements ISharingRuleService { * * ## The defect this closes, and which half of it is the security half * - * `sys_record_share` is tenant-scoped in the #13491 ledger (#14484), so on a + * `sys_record_share` is tenant-scoped in the #13491 ledger (commit 3f64fe6c6), so on a * walled install an organization-less system insert on it is refused loudly * with {@link SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE}. `SharingService.grant` * resolves the organization on every path that can; a platform-global rule diff --git a/packages/plugins/plugin-sharing/src/sharing-rule.test.ts b/packages/plugins/plugin-sharing/src/sharing-rule.test.ts index 8185bcf4644..57abaa031f8 100644 --- a/packages/plugins/plugin-sharing/src/sharing-rule.test.ts +++ b/packages/plugins/plugin-sharing/src/sharing-rule.test.ts @@ -9,7 +9,7 @@ import { SharingService } from './sharing-service.js'; import { SharingRuleService } from './sharing-rule-service.js'; import { TeamGraphService, expandPrincipal } from './team-graph.js'; import { BusinessUnitGraphService } from './business-unit-graph.js'; -// [#8710] The ADDRESSING primitive, imported so the negative half of the +// [commit 04d03c3a0] The ADDRESSING primitive, imported so the negative half of the // ruling can be pinned in the same suite as the positive half: the filter // belongs to the sharing CALL SITE, never to the expansion helper. import { PositionGraphService } from './position-graph.js'; @@ -1866,7 +1866,7 @@ describe('[#8158] a non-system caller with NO organization does not get the syst }); // --------------------------------------------------------------------------- -// #8710 — a DEACTIVATED `sys_position` confers no sharing-rule shares +// commit 04d03c3a0 — a DEACTIVATED `sys_position` confers no sharing-rule shares // // Maintainer ruling, 2026-08-15, the recitable line: // diff --git a/packages/plugins/plugin-sharing/src/sharing-service.test.ts b/packages/plugins/plugin-sharing/src/sharing-service.test.ts index 53fc452680f..6010d59fb35 100644 --- a/packages/plugins/plugin-sharing/src/sharing-service.test.ts +++ b/packages/plugins/plugin-sharing/src/sharing-service.test.ts @@ -1167,7 +1167,7 @@ describe('[ADR-0111 D5] sys_record_share read self-scope (middleware)', () => { // [#5859 / #5852] The hierarchy resolver's tenancy authority. // // `HierarchyScopeContext.organizationId` is the AUTHORITATIVE tenancy field -// (#5858 / PR #5973) and the only one an enterprise resolver scopes its owner +// (#5858 / commit abeb3751f) and the only one an enterprise resolver scopes its owner // query by. This producer used to fill it from `(context as any).organizationId` // — a key NO transport ever sets — so every resolver ran unscoped and the whole // DEPTH tenant isolation was inert: in #5852 an ordinary member of org_a @@ -1788,7 +1788,7 @@ describe('[#13551] the record-share `$in` drops nullish `record_id` rows', () => }); // ───────────────────────────────────────────────────────────────────── -// [#14484] Every sys_record_share write carries organization_id +// [commit 3f64fe6c6] Every sys_record_share write carries organization_id // ───────────────────────────────────────────────────────────────────── // // Ruled 2026-09-02 (decision batch #11 item 3, A adopted): a rule-materialised diff --git a/packages/plugins/plugin-sharing/src/sharing-service.ts b/packages/plugins/plugin-sharing/src/sharing-service.ts index 4b7024507f4..2833b9a1616 100644 --- a/packages/plugins/plugin-sharing/src/sharing-service.ts +++ b/packages/plugins/plugin-sharing/src/sharing-service.ts @@ -17,11 +17,11 @@ import { } from '@objectstack/spec/security'; // [#7136] Every enforcement method below takes the FULL `resolveAuthzContext` // envelope — the same type `ISharingService` declares for these parameters -// since #6523 (the #6206 ruling: no per-site subset contracts). Annotating the +// since commit aa4b90d9a (the full-envelope ruling: no per-site subset contracts). Annotating the // implementations with a narrower shape is what forced this file to cast its // way out of its own contract to read fields the caller had already supplied. import type { ExecutionContext } from '@objectstack/spec/kernel'; -// [#14484] The engine's own answer to "which column is this object walled +// [commit 3f64fe6c6] The engine's own answer to "which column is this object walled // by?" — the twin of `SqlDriver.computeTenantField`, so the organization a grant // is stamped from is read off the SAME column the wall scopes the record by. import { resolveTenantFieldName } from '@objectstack/objectql'; @@ -113,7 +113,7 @@ export function effectiveSharingModel(schema: any): 'private' | 'read' | 'public /** * [#5859 / #5852] The caller's ACTIVE ORGANIZATION as carried by an execution * context — the value `HierarchyScopeContext.organizationId` (the authoritative - * tenancy field since #5858 / PR #5973) must be filled with. + * tenancy field since #5858 / commit abeb3751f) must be filled with. * * Every transport puts it on `tenantId`: both HTTP entry points build their * context from the ONE shared authorization resolver @@ -144,7 +144,7 @@ function activeOrganizationId(context: ExecutionContext): string | null { } /** - * [#14484] What `SharingService.recordOrganization` found — three answers the + * [commit 3f64fe6c6] What `SharingService.recordOrganization` found — three answers the * direct-grant path treats differently, so they are typed apart rather than * collapsed into one `null`. `none` (no tenant column, record gone, or an * organization-less row) earns the acting session's organization as the @@ -1228,7 +1228,7 @@ export class SharingService implements ISharingService { // best-effort — the boot backfill, the object-wide re-grant and the // bu-tree re-grant queue log and continue, and the write hooks catch so a // user's insert/update is never failed by it. - // [#14484] The organization this grant belongs to, resolved here so BOTH + // [commit 3f64fe6c6] The organization this grant belongs to, resolved here so BOTH // halves of the upsert carry it. A rule-materialised grant carries the // rule's organization (the evaluator threads it — see // `SharingRuleService.criteriaContext`); a direct grant carries the shared @@ -1265,7 +1265,7 @@ export class SharingService implements ISharingService { const row: any = existing[0]; const patch: any = { id: row.id, - // [#14484] The update half stamps too: a row written before the writer + // [commit 3f64fe6c6] The update half stamps too: a row written before the writer // was repaired carries NULL, and the next grant that touches it is the // cheapest repair there is. A resolution of `null` leaves the stored // value alone rather than clearing one the backfill already wrote. @@ -1276,7 +1276,7 @@ export class SharingService implements ISharingService { reason: input.reason ?? row.reason ?? null, updated_at: now, }; - // [#14484] The organization rides the write context as well as the row — + // [commit 3f64fe6c6] The organization rides the write context as well as the row — // `{ isSystem, tenantId }` is the shape #8844's refusal prescribes for a // system write, the same chokepoint a session write goes through // (`ObjectQLEngine.buildDriverOptions` → `DriverOptions.tenantId`), and @@ -1297,7 +1297,7 @@ export class SharingService implements ISharingService { const id = makeShareId(); const row: any = { id, - // [#14484] Carried on the row literal itself, explicitly `null` when + // [commit 3f64fe6c6] Carried on the row literal itself, explicitly `null` when // nothing resolved: `sys_record_share` is tenant-scoped in the #13491 // ledger, so an organization-less system insert is the engine's to // decide — derived on a `single` install, REFUSED loudly on a walled one @@ -1316,7 +1316,7 @@ export class SharingService implements ISharingService { created_at: now, updated_at: now, }; - // [#14484] Same write context as the update half — see the note there. + // [commit 3f64fe6c6] Same write context as the update half — see the note there. await this.engine.insert('sys_record_share', row, { context: { ...SYSTEM_CTX, tenantId: organizationId ?? undefined }, }); @@ -1324,7 +1324,7 @@ export class SharingService implements ISharingService { } /** - * [#14484] The organization a SYSTEM caller's grant belongs to. + * [commit 3f64fe6c6] The organization a SYSTEM caller's grant belongs to. * * First the organization the caller THREADS: `SharingRuleService.reconcile` * / `reconcileForRecord` pass the rule's own `criteriaContext`, so a @@ -1351,7 +1351,7 @@ export class SharingService implements ISharingService { } /** - * [#14484] The organization a DIRECT grant belongs to: the organization of + * [commit 3f64fe6c6] The organization a DIRECT grant belongs to: the organization of * the record being shared (the ruling's second pin), read from the record * itself — never the caller's active organization first, which under a * `single` posture holding several organizations may not be the record's. @@ -1392,7 +1392,7 @@ export class SharingService implements ISharingService { } /** - * [#14484] The organization `(object, recordId)` is walled by, read off the + * [commit 3f64fe6c6] The organization `(object, recordId)` is walled by, read off the * column the object is actually walled by ({@link resolveTenantFieldName}: * ADR-0066 opt-out → declared `tenancy.tenantField` → injected * `organization_id`), under the system context so field-level masking cannot @@ -1425,7 +1425,7 @@ export class SharingService implements ISharingService { ? { kind: 'organization', organizationId: value } : NO_RECORD_ORGANIZATION; } catch (err: any) { - // [#14484] The id stays out of the string: it reaches operators. The + // [commit 3f64fe6c6] The id stays out of the string: it reaches operators. The // text says what the failed read does NOT do — substitute the acting // session's organization — because that is the one thing a reader of // this line needs to know the row was spared. @@ -1440,7 +1440,7 @@ export class SharingService implements ISharingService { } } - /** [#14484] The tenant column of `object`, or `null` when it has none / the engine cannot say. */ + /** [commit 3f64fe6c6] The tenant column of `object`, or `null` when it has none / the engine cannot say. */ private tenantFieldOf(object: string): string | null { if (typeof this.engine.getSchema !== 'function') return null; let schema: unknown; @@ -1682,7 +1682,7 @@ export class SharingService implements ISharingService { const ids = await resolver.resolveOwnerIds( { userId: me, - // AUTHORITATIVE (#5858 / PR #5973). Never `(context as any).organizationId`: + // AUTHORITATIVE (#5858 / commit abeb3751f). Never `(context as any).organizationId`: // no execution context in this repo carries that key. organizationId, // [#6139] What a `null` organizationId MEANS. Under `single` it is diff --git a/packages/plugins/plugin-sharing/src/translations/index.ts b/packages/plugins/plugin-sharing/src/translations/index.ts index 56f2885739e..b3c914c0208 100644 --- a/packages/plugins/plugin-sharing/src/translations/index.ts +++ b/packages/plugins/plugin-sharing/src/translations/index.ts @@ -23,7 +23,7 @@ import { esESGeneratedSourceHashes } from './es-ES.source-hashes.generated.js'; * ## The provenance companions are READ here, not merely recorded * * `os i18n extract --source-hashes` writes `.source-hashes.generated.ts` - * beside these bundles (maintainer ruling #12069 Option A, #11671). A record + * beside these bundles (maintainer ruling #12069 Option A, commit 09b4f4e4e). A record * says: "this locale's leaf at that path is still a byte copy of THAT source * revision". Recording alone changes nothing a user sees — the substitution is * what {@link withSourceFallback} does, and until it was wired here this set diff --git a/packages/plugins/plugin-sharing/src/translations/serving-seam.test.ts b/packages/plugins/plugin-sharing/src/translations/serving-seam.test.ts index 3eb466dea7e..71220809dcf 100644 --- a/packages/plugins/plugin-sharing/src/translations/serving-seam.test.ts +++ b/packages/plugins/plugin-sharing/src/translations/serving-seam.test.ts @@ -5,7 +5,7 @@ // ## What this file pins, and what it deliberately does not // // `os i18n extract --source-hashes` writes `.source-hashes.generated.ts` -// (maintainer ruling #12069 Option A, #11671) and `withSourceFallback` +// (maintainer ruling #12069 Option A, commit 09b4f4e4e) and `withSourceFallback` // substitutes the current source for a leaf whose record disagrees with it. // Those two halves landed apart: recording rolled out to all nine bundle sets // and the reading half stayed in `@objectstack/platform-objects`. Eight sets diff --git a/packages/plugins/plugin-sharing/src/write-denial-user-copy.test.ts b/packages/plugins/plugin-sharing/src/write-denial-user-copy.test.ts index 77d979a4b49..15fdf8723b5 100644 --- a/packages/plugins/plugin-sharing/src/write-denial-user-copy.test.ts +++ b/packages/plugins/plugin-sharing/src/write-denial-user-copy.test.ts @@ -19,7 +19,7 @@ * * The refusal now renders through the shared Operation Message Catalog * (`@objectstack/spec/system`, key `record_write_denied`, landed ahead of this - * consumer half by #12493) instead of a package-local string. + * consumer half by commit aa5994e17) instead of a package-local string. * * ⚠️ These tests assert the SENTENCE A USER READS, in zh-CN specifically, as a * LITERAL. Asserting only that a catalog key was passed — or comparing the