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
22 changes: 22 additions & 0 deletions .changeset/20752-verify-plugin-strings-state-the-decision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@objectstack/verify': patch
'@objectstack/plugin-dev': patch
'@objectstack/plugin-hono-server': patch
---

The `verify --rls` report and messages, the dev plugin's tenancy and no-auth messages and the Hono server's no-API warning no longer cite tracker numbers; each one states the decision behind it in words

Clause-②: no

Strings these three packages show to operators, and print in verification reports, sent the reader to 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/verify`, the `objectstack verify --rls` report: the header reads `=== objectstack verify (RLS / cross-owner by-id-write invariant) — <app> ===`, and the position-persona line reads `── position personas (each holds one declared position and nothing else) — N of M declared position(s) probed`.
- `@objectstack/verify`, an `rls-hole` verdict's detail: it says the by-id write bypassed RLS, and that a caller that cannot read a record must not be able to write it.
- `@objectstack/verify`, the refusal when the RLS probe persona cannot be provisioned (no ObjectQL engine): it says a by-id write that bypasses RLS is what becomes unreachable. The matching position-persona refusal drops its citation.
- `@objectstack/verify`, the records the probe writes: the probe permission set's row-level-security policy description, the probe `sys_permission_set` row's description and the position persona's `sys_user_position` reason drop their citations. Each already said what it is for.
- `@objectstack/verify`, the `bootStack` refusal for `multiTenant: true` when the app does not declare `@objectstack/organizations`: the citation beside "a package merely reachable through NODE_PATH or a hoisted workspace store is not accepted" goes.
- `@objectstack/plugin-dev`, the `REST API NOT enabled` warning for a stack that mounts no auth: it says anonymous access to object data is always denied, with no setting that turns that off.
- `@objectstack/plugin-dev`, the two refusals for an `OrganizationsPlugin` that refused to be constructed or failed to initialize: they drop their citations. Each already says `OS_ALLOW_DEGRADED_TENANCY` covers only an absent multi-org runtime, not a present one that declined.
- `@objectstack/plugin-hono-server`, the boot warning for a server with no data or discovery API mounted: it drops its citation. It already says the plugin is a transport adapter that serves neither.

Text only: no status, error code, exit code, route, field, export, verdict or count moves. A log filter or script that matched the old text (for example the report header's `RLS / #NNNN` spelling) needs the new spelling.
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ describe('DevPlugin — an optional service that is installed and fails to const
const line = allLines(ctx).find((l) => l.includes('REST API NOT enabled'));
expect(line, 'the no-auth refusal is reported on its own terms').toBeDefined();
expect(line).toContain('no auth is mounted');
expect(line).toContain('#3963');
expect(line).toContain('always denied, with no setting that turns that off');
expect(line).toContain('NOT a missing-package problem');
// And the false claim it used to emit instead is gone.
expect(allLines(ctx).some((l) => l.includes('@objectstack/rest not installed'))).toBe(false);
Expand Down
10 changes: 5 additions & 5 deletions packages/plugins/plugin-dev/src/dev-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,7 @@ export class DevPlugin implements Plugin {
+ (mountCode !== undefined ? `code: ${String(mountCode)} — ` : '')
+ `${mountMessage}. OS_ALLOW_DEGRADED_TENANCY does NOT apply to this failure and will `
+ 'not get past it: it covers an ABSENT multi-org runtime the operator accepts doing '
+ 'without, not a present one that declined. (#4818)',
+ 'without, not a present one that declined.',
);
}
ctx.logger.info(` ✔ Organizations plugin enabled (posture '${tenancyPosture}': organization_id auto-stamp, per-org seed)`);
Expand Down Expand Up @@ -931,9 +931,9 @@ export class DevPlugin implements Plugin {
if (!authMounted) {
ctx.logger.warn(
' ✘ REST API NOT enabled: no auth is mounted in this stack, so no caller could ever '
+ 'authenticate and anonymous access to object data is always denied (#3963). This is NOT a '
+ 'missing-package problem — @objectstack/rest was never consulted. Install/enable '
+ 'plugin-auth (or the `auth` tier), or drop the REST API from this dev stack.',
+ 'authenticate and anonymous access to object data is always denied, with no setting that '
+ 'turns that off. This is NOT a missing-package problem — @objectstack/rest was never '
+ 'consulted. Install/enable plugin-auth (or the `auth` tier), or drop the REST API from this dev stack.',
);
} else {
try {
Expand Down Expand Up @@ -997,7 +997,7 @@ export class DevPlugin implements Plugin {
+ 'requested multi-organization isolation must not serve traffic without it (ADR-0093 D5). '
+ 'The plugin reported (verbatim — the framework does not interpret it): '
+ `${err?.message ?? String(err)}. OS_ALLOW_DEGRADED_TENANCY does NOT apply: it covers an `
+ 'ABSENT multi-org runtime, not a present one that declined. (#4818)',
+ 'ABSENT multi-org runtime, not a present one that declined.',
);
}
ctx.logger.error(`Failed to init child plugin ${plugin.name}: ${err.message}`);
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-hono-server/src/hono-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ export class HonoServerPlugin implements Plugin {
if (!hasPlugin(REST_API_PLUGIN) && !hasPlugin(RUNTIME_DISPATCHER_PLUGIN)) {
ctx.logger.warn(
'No data or discovery API is mounted on this server. HonoServerPlugin is a '
+ 'transport adapter and serves neither (#4073). Mount `createRestApiPlugin` '
+ 'transport adapter and serves neither. Mount `createRestApiPlugin` '
+ 'from @objectstack/rest for full CRUD behind the gate stack, or '
+ '`createDispatcherPlugin` from @objectstack/runtime.',
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('#4073 end state — the plugin serves transport and /me/* only', () =>
// The message must carry the remedy, not just the diagnosis.
expect(hit[0]).toContain('@objectstack/rest');
expect(hit[0]).toContain('@objectstack/runtime');
expect(hit[0]).toContain('#4073');
expect(hit[0]).toContain('transport adapter and serves neither');
});

it('a REST-composed boot stays quiet', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/verify/src/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ export async function bootStack(
'authority on what it has to publish.'
: `Install/link it in THIS APP (${hostRoot}) — and DECLARE it in that app's ` +
'package.json, which is what is actually checked: a package merely reachable through ' +
'NODE_PATH or a hoisted workspace store is not accepted (#4719) — to run multi-org fixtures.';
'NODE_PATH or a hoisted workspace store is not accepted — to run multi-org fixtures.';
throw new Error(
'verify: multiTenant=true requires the enterprise @objectstack/organizations package (migrated from plugin-org-scoping, ADR-0105 D12). ' +
`${remedy} (${(e as Error).message})`,
Expand Down
16 changes: 8 additions & 8 deletions packages/verify/src/rls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ export function rlsProbePermissionSet(config: any): PermissionSet {
label: `RLS probe scope for ${o.name}`,
description:
'Verifier-authored owner narrowing (select only) — puts the probe persona outside the ' +
'scope of every record it did not create, so the by-id-write class is reachable (#7685).',
'scope of every record it did not create, so the by-id-write class is reachable.',
object: o.name,
operation: 'select',
using: 'created_by == current_user.id',
Expand Down Expand Up @@ -428,7 +428,7 @@ export async function provisionRlsProbePersona(
throw new Error(
'verify: cannot provision the RLS probe persona — no ObjectQL engine on this stack. ' +
'The probe needs object-level read+edit grants, without which every by-id probe is ' +
'masked by the object gate and the #1994 class is unreachable (#7685).',
'masked by the object gate and a by-id write that bypasses RLS is unreachable.',
);
}
const sysCtx = { context: { isSystem: true } };
Expand Down Expand Up @@ -461,7 +461,7 @@ export async function provisionRlsProbePersona(
description:
'Ephemeral persona minted by `objectstack verify --rls`: object-level read+edit on every ' +
'declared object plus an owner-scoped SELECT narrowing, so a by-id refusal is attributable ' +
'to the record gate rather than the object gate (#7685).',
'to the record gate rather than the object gate.',
object_permissions: JSON.stringify(probeSet.objects ?? {}),
field_permissions: '{}',
system_permissions: '[]',
Expand Down Expand Up @@ -550,7 +550,7 @@ export async function provisionRlsPositionPersona(
throw new Error(
`verify: cannot provision the RLS position persona for '${position}' — no ObjectQL engine on ` +
'this stack. Without the position assignment the app\'s position-gated policies are not ' +
'applicable to the persona, so the app-authored narrowing is unreachable (#7978).',
'applicable to the persona, so the app-authored narrowing is unreachable.',
);
}
const sysCtx = { context: { isSystem: true } };
Expand All @@ -572,7 +572,7 @@ export async function provisionRlsPositionPersona(
business_unit_id: null,
organization_id: null,
granted_by: null,
reason: `Ephemeral persona minted by \`objectstack verify --rls\`: holds '${position}' and nothing else, so the app's own position-gated RLS narrowing is exercised (#7978).`,
reason: `Ephemeral persona minted by \`objectstack verify --rls\`: holds '${position}' and nothing else, so the app's own position-gated RLS narrowing is exercised.`,
},
sysCtx,
);
Expand Down Expand Up @@ -765,7 +765,7 @@ async function probeAsPersona(
object,
status: 'rls-hole',
target: origin,
detail: `the probe cannot read it (GET ${bRead.status}) yet MUTATED it by id (PATCH ${bWrite.status}) — by-id write bypassed RLS (#1994 class)${via}`,
detail: `the probe cannot read it (GET ${bRead.status}) yet MUTATED it by id (PATCH ${bWrite.status}) — by-id write bypassed RLS, and a caller that cannot read a record must not be able to write it${via}`,
});
} else {
results.push({
Expand Down Expand Up @@ -905,7 +905,7 @@ function summaryLine(s: RlsSummary): string {
}

export function formatRlsReport(report: RlsReport): string {
const lines: string[] = [`\n=== objectstack verify (RLS / #1994) — ${report.app} ===`];
const lines: string[] = [`\n=== objectstack verify (RLS / cross-owner by-id-write invariant) — ${report.app} ===`];
for (const r of report.results) {
lines.push(` ${statusMark(r.status)} ${r.object} [${r.status}] ${r.detail ?? ''}`);
}
Expand Down Expand Up @@ -940,7 +940,7 @@ export function formatRlsReport(report: RlsReport): string {
// would claim N× the reach the fan-out actually has.
const cov = report.positionCoverage;
lines.push(
`\n ── position personas (#7978) — ${cov.ran.length} of ${cov.declared.length} declared position(s) probed`,
`\n ── position personas (each holds one declared position and nothing else) — ${cov.ran.length} of ${cov.declared.length} declared position(s) probed`,
);
if (cov.note) lines.push(` · ${cov.note}`);
for (const run of report.positionRuns) {
Expand Down
15 changes: 0 additions & 15 deletions scripts/doc-authoring-prose-id.baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,6 @@
"packages/plugins/plugin-audit/src/translations/zh-CN.objects.generated.ts": {
"#11507": 1
},
"packages/plugins/plugin-dev/src/dev-plugin.ts": {
"#3963": 1,
"#4818": 2
},
"packages/plugins/plugin-email/src/email-service.ts": {
"#5172": 1
},
Expand All @@ -189,9 +185,6 @@
"packages/plugins/plugin-email/src/templates/auth-templates.ts": {
"#8019": 4
},
"packages/plugins/plugin-hono-server/src/hono-plugin.ts": {
"#4073": 1
},
"packages/plugins/plugin-security/src/bootstrap-system-capabilities.ts": {
"#5876": 1,
"#8552": 2
Expand Down Expand Up @@ -483,13 +476,5 @@
},
"packages/triggers/trigger-record-change/src/record-change-trigger.ts": {
"#3457": 1
},
"packages/verify/src/harness.ts": {
"#4719": 1
},
"packages/verify/src/rls.ts": {
"#1994": 3,
"#7685": 3,
"#7978": 3
}
}
Loading