Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/20751-services-strings-stage6-state-the-decision.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 3 additions & 2 deletions packages/plugins/plugin-audit/src/audit-writers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion packages/plugins/plugin-sharing/src/record-orphan-cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-sharing/src/share-link-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/plugin-sharing/src/sharing-rule-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
);
Expand Down Expand Up @@ -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.',
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
13 changes: 8 additions & 5 deletions packages/plugins/plugin-sharing/src/sharing-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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 },
);
Expand Down
16 changes: 0 additions & 16 deletions scripts/doc-authoring-prose-id.baseline.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,4 @@
{
"packages/plugins/plugin-audit/src/audit-writers.ts": {
"#5226": 1
},
"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,
Expand Down
Loading