From 90db7d4e6914cc26f71dec48703a45ffd2684227 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 22:45:19 +0000 Subject: [PATCH] fix(plugin-sharing,plugin-audit): runtime strings state the decision instead of citing a tracker number Stage 6 of the services lane's share of the runtime-string burn-down: every ledgered tracker-number occurrence in plugin-sharing/src and the one in plugin-audit's audit-writers.ts. Each rewritten string states what the cited card decided, in words, or drops a citation the sentence already explained. Text only: no status, error code, field, route or control flow moves. The doc-authoring prose-id ledger is recomputed with --census-ledger: four file entries leave it, nothing else moves. Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude --- ...services-strings-stage6-state-the-decision.md | 15 +++++++++++++++ .../plugins/plugin-audit/src/audit-writers.ts | 5 +++-- .../plugin-sharing/src/record-orphan-cleanup.ts | 5 ++++- .../plugin-sharing/src/share-link-service.ts | 2 +- .../plugin-sharing/src/sharing-rule-service.ts | 4 ++-- .../plugin-sharing/src/sharing-service.test.ts | 2 +- .../plugin-sharing/src/sharing-service.ts | 13 ++++++++----- scripts/doc-authoring-prose-id.baseline.json | 16 ---------------- 8 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 .changeset/20751-services-strings-stage6-state-the-decision.md diff --git a/.changeset/20751-services-strings-stage6-state-the-decision.md b/.changeset/20751-services-strings-stage6-state-the-decision.md new file mode 100644 index 00000000000..0860b5d3af9 --- /dev/null +++ b/.changeset/20751-services-strings-stage6-state-the-decision.md @@ -0,0 +1,15 @@ +--- +'@objectstack/plugin-sharing': patch +'@objectstack/plugin-audit': patch +--- + +Sharing refusals and log lines, and the audit write-failure line, no longer cite tracker numbers; each one states the decision behind it in words + +Clause-②: no + +Some strings these two packages show to administrators and operators pointed at an issue-tracker number for the reason behind them. The number goes; where the sentence did not already say what was decided, it now does. + +- `@objectstack/plugin-sharing`: the orphan-sweep line for record shares says every share on a deleted record goes, whatever its source, so a reused record id cannot inherit it; the same line for share links says a share link is a bearer token, so a reused record id must not inherit it; the write-gate failure line says a failed lookup is a refusal, never an abstention, because an abstention would hand the row to the other write authorities, which may admit it; the authored-row-write probe line says only an app-authored row-level policy that positively admits the row may lift the sharing refusal; the hierarchy-scope line says the resolver contract makes a resolver fail closed on a missing organization. The two sharing-rule refusals (no active organization; deleting a platform-global rule) drop their citations, since each sentence already says why. The `OrphanSweepSubject.issue` member's doc comment now says the member carries that reason in words. +- `@objectstack/plugin-audit`: the missing-table fix in the audit write-failure line says that on a fresh `os dev` boot the table exists in the sibling telemetry file and not in the primary one, so look there before concluding it was never created. + +Text only: no status, error code, field, route or control flow moves. A client or log filter that matches the old text (for example a tracker-number suffix) needs the new spelling. diff --git a/packages/plugins/plugin-audit/src/audit-writers.ts b/packages/plugins/plugin-audit/src/audit-writers.ts index dbb8419561e..c15f1a53c14 100644 --- a/packages/plugins/plugin-audit/src/audit-writers.ts +++ b/packages/plugins/plugin-audit/src/audit-writers.ts @@ -971,8 +971,9 @@ function auditWriteFailureLine(f: { '`OS_SKIP_SCHEMA_SYNC` creates it out-of-band instead). (2) Otherwise it was created on a DIFFERENT ' + 'datasource than the one this write reached: its ADR-0057 §3.6 lifecycle class routes it to the ' + 'dedicated `telemetry` datasource whenever one is registered (`os dev` provisions one by default as a ' + - 'SIBLING SQLite file) — see framework#5226. Set `OS_TELEMETRY_DB=0` to keep every lifecycle-classed ' + - 'object on the primary datasource.' + 'SIBLING SQLite file), so on a fresh `os dev` boot the table exists in that sibling file and not in the ' + + 'primary one; look there before concluding it was never created. Set `OS_TELEMETRY_DB=0` to keep ' + + 'every lifecycle-classed object on the primary datasource.' : 'Fix: resolve the driver fault quoted at the head of this line on the connection this write ran ' + 'on — every audited write that hits it loses its row until it is resolved.'; return consequence + once + fix; diff --git a/packages/plugins/plugin-sharing/src/record-orphan-cleanup.ts b/packages/plugins/plugin-sharing/src/record-orphan-cleanup.ts index 8d6ed8ec5a2..c894c264c9e 100644 --- a/packages/plugins/plugin-sharing/src/record-orphan-cleanup.ts +++ b/packages/plugins/plugin-sharing/src/record-orphan-cleanup.ts @@ -118,7 +118,10 @@ export interface OrphanSweepSubject { table: string; /** Noun for log messages: `share` → "orphan share sweep", "share rows". */ noun: string; - /** Issue reference appended to the "revoked N rows" warning. */ + /** + * Why the rows go, appended to the "revoked N rows" warning. Runtime text + * carries no tracker number, so this states the decision in words. + */ issue: string; } diff --git a/packages/plugins/plugin-sharing/src/share-link-service.ts b/packages/plugins/plugin-sharing/src/share-link-service.ts index 88b4b0e1895..288e11c2eb5 100644 --- a/packages/plugins/plugin-sharing/src/share-link-service.ts +++ b/packages/plugins/plugin-sharing/src/share-link-service.ts @@ -64,7 +64,7 @@ const SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] } as const; const SHARE_LINK_SWEEP_SUBJECT = { table: 'sys_share_link', noun: 'share-link', - issue: '#5190', + issue: 'a share link is a bearer token, so a reused record id must not inherit it', } as const; /** URL-safe alphabet (RFC 4648 base64url minus padding). 64 symbols. */ diff --git a/packages/plugins/plugin-sharing/src/sharing-rule-service.ts b/packages/plugins/plugin-sharing/src/sharing-rule-service.ts index e7966db943f..22fe52798d1 100644 --- a/packages/plugins/plugin-sharing/src/sharing-rule-service.ts +++ b/packages/plugins/plugin-sharing/src/sharing-rule-service.ts @@ -424,7 +424,7 @@ export class SharingRuleService implements ISharingRuleService { 'PERMISSION_DENIED: sharing-rule administration requires an active organization — this ' + 'session carries none. manage_sharing is an ORG-scoped capability (ADR-0111 D6), so with ' + 'no organization resolved there is no tenant whose rules it authorizes, and answering ' + - 'unscoped would expose every tenant’s rules (#8158). Select an active organization and ' + + 'unscoped would expose every tenant’s rules. Select an active organization and ' + 'retry. Platform operators (manage_platform_settings or the platform_admin position) and ' + 'system contexts are unaffected.', ); @@ -529,7 +529,7 @@ export class SharingRuleService implements ISharingRuleService { 'PERMISSION_DENIED: deleting a platform-global sharing rule requires platform authority — ' + 'the manage_platform_settings capability or the platform_admin position. Org-scoped ' + 'manage_sharing does not authorize it, because this rule belongs to no organization and ' + - 'deleting it revokes every tenant’s grants under it (#7795). It remains listable, ' + + 'deleting it revokes every tenant’s grants under it. It remains listable, ' + 'readable and evaluable.', ); } diff --git a/packages/plugins/plugin-sharing/src/sharing-service.test.ts b/packages/plugins/plugin-sharing/src/sharing-service.test.ts index 6010d59fb35..f38e449cef0 100644 --- a/packages/plugins/plugin-sharing/src/sharing-service.test.ts +++ b/packages/plugins/plugin-sharing/src/sharing-service.test.ts @@ -1613,7 +1613,7 @@ describe('[#6428] fail-closed: an unresolvable verdict is DENY, never abstain', expect(logged.length).toBeGreaterThan(0); expect(String(logged[0][0])).toContain('fail-closed'); - expect(String(logged[0][0])).toContain('#6428'); + expect(String(logged[0][0])).toContain('an abstention would hand the row to the other write authorities'); }); it('a throwing SHARE lookup denies too — the whole evaluation is covered, not just the first query', async () => { diff --git a/packages/plugins/plugin-sharing/src/sharing-service.ts b/packages/plugins/plugin-sharing/src/sharing-service.ts index a726d24d95f..b2e809eb036 100644 --- a/packages/plugins/plugin-sharing/src/sharing-service.ts +++ b/packages/plugins/plugin-sharing/src/sharing-service.ts @@ -309,7 +309,7 @@ export interface SharingSecurityProbe { const RECORD_SHARE_SWEEP_SUBJECT = { table: 'sys_record_share', noun: 'share', - issue: '#5103', + issue: 'every share on a deleted record goes, whatever its source, so a reused record id cannot inherit it', } as const; /** @@ -734,8 +734,9 @@ export class SharingService implements ISharingService { ): SharingWriteVerdict { this.logger?.error?.( `[sharing] the ${verb} gate could not resolve a verdict for '${object}' record ` - + `'${recordId}' (user ${context?.userId ?? 'unknown'}) — DENYING (fail-closed, #6428): ` - + 'a failed lookup is a refusal, never an abstention', + + `'${recordId}' (user ${context?.userId ?? 'unknown'}) — DENYING (fail-closed): ` + + 'a failed lookup is a refusal, never an abstention, because an abstention would hand the row ' + + 'to the other write authorities, which may admit it', err instanceof Error ? err : new Error(String(err)), ); return 'deny'; @@ -954,7 +955,8 @@ export class SharingService implements ISharingService { this.logger?.warn?.( `[sharing] the authored-row-write probe for '${object}' record '${recordId}' ` + `(${operation}, user ${context?.userId ?? 'unknown'}) could not be resolved — ` - + 'ABSTAINING, so the existing refusal stands (fail-closed, #5493)', + + 'ABSTAINING, so the existing refusal stands (fail-closed: only an app-authored row-level ' + + 'policy that positively admits this row may lift the sharing refusal)', err instanceof Error ? err : new Error(String(err)), ); return 'abstain'; @@ -1833,7 +1835,8 @@ export class SharingService implements ISharingService { this.logger?.warn?.( '[sharing] hierarchy scope NOT widened: an organization wall is in force but the caller ' + 'context carries no active organization — failing closed to owner-only. ' + - '"No org" is not "every org" (IHierarchyScopeResolver.resolveOwnerIds, #5973); ' + + '"No org" is not "every org": the IHierarchyScopeResolver.resolveOwnerIds contract makes a ' + + 'resolver fail closed on a missing organization; ' + 'the same rule walls Layer 0 (ADR-0095 D1 / ADR-0105 D1).', { userId: me, scope }, ); diff --git a/scripts/doc-authoring-prose-id.baseline.json b/scripts/doc-authoring-prose-id.baseline.json index 747d5d03c6a..7dafdd01100 100644 --- a/scripts/doc-authoring-prose-id.baseline.json +++ b/scripts/doc-authoring-prose-id.baseline.json @@ -70,9 +70,6 @@ "packages/lint/src/validate-widget-bindings.ts": { "#2501": 1 }, - "packages/plugins/plugin-audit/src/audit-writers.ts": { - "#5226": 1 - }, "packages/plugins/plugin-audit/src/objects/sys-activity.object.ts": { "#11507": 1 }, @@ -137,19 +134,6 @@ "#10424": 1, "#3545": 6 }, - "packages/plugins/plugin-sharing/src/share-link-service.ts": { - "#5190": 1 - }, - "packages/plugins/plugin-sharing/src/sharing-rule-service.ts": { - "#7795": 1, - "#8158": 1 - }, - "packages/plugins/plugin-sharing/src/sharing-service.ts": { - "#5103": 1, - "#5493": 1, - "#5973": 1, - "#6428": 1 - }, "packages/services/service-analytics/src/analytics-service.ts": { "#3867": 1, "#5222": 1,