From a31ae8a12f45e2e37216956c913755bd835adc8b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 18:59:22 +0000 Subject: [PATCH 1/3] fix(verify, plugin-dev, plugin-hono-server): runtime strings state each decision in words instead of a tracker number (stage 4) The verify RLS runner's report, persona provisioning errors and probe metadata, the verify harness's organizations remedy, the dev plugin's tenancy refusals and no-auth warning, and the Hono server's no-API warning pointed at tracker numbers. The numbers go; where a sentence leaned on one, it now says the decision in words. Two pins that asserted a number now assert the decision sentence. The prose-id ledger is recomputed with --census-ledger: the four rows leave, nothing else moves. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude --- .../src/dev-plugin-optional-load-failure.test.ts | 2 +- packages/plugins/plugin-dev/src/dev-plugin.ts | 7 ++++--- .../plugin-hono-server/src/hono-plugin.ts | 2 +- .../src/hono-transport-only.test.ts | 2 +- packages/verify/src/harness.ts | 2 +- packages/verify/src/rls.ts | 16 ++++++++-------- scripts/doc-authoring-prose-id.baseline.json | 15 --------------- 7 files changed, 16 insertions(+), 30 deletions(-) diff --git a/packages/plugins/plugin-dev/src/dev-plugin-optional-load-failure.test.ts b/packages/plugins/plugin-dev/src/dev-plugin-optional-load-failure.test.ts index 77cb76cb9ec..93a72cac476 100644 --- a/packages/plugins/plugin-dev/src/dev-plugin-optional-load-failure.test.ts +++ b/packages/plugins/plugin-dev/src/dev-plugin-optional-load-failure.test.ts @@ -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); diff --git a/packages/plugins/plugin-dev/src/dev-plugin.ts b/packages/plugins/plugin-dev/src/dev-plugin.ts index 708af69f9b2..3c4378c9347 100644 --- a/packages/plugins/plugin-dev/src/dev-plugin.ts +++ b/packages/plugins/plugin-dev/src/dev-plugin.ts @@ -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)`); @@ -931,7 +931,8 @@ 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 ' + + '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.', ); @@ -997,7 +998,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}`); diff --git a/packages/plugins/plugin-hono-server/src/hono-plugin.ts b/packages/plugins/plugin-hono-server/src/hono-plugin.ts index 201c5866935..04ad7d5d0bc 100644 --- a/packages/plugins/plugin-hono-server/src/hono-plugin.ts +++ b/packages/plugins/plugin-hono-server/src/hono-plugin.ts @@ -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.', ); diff --git a/packages/plugins/plugin-hono-server/src/hono-transport-only.test.ts b/packages/plugins/plugin-hono-server/src/hono-transport-only.test.ts index f7987742ec6..9d28f178280 100644 --- a/packages/plugins/plugin-hono-server/src/hono-transport-only.test.ts +++ b/packages/plugins/plugin-hono-server/src/hono-transport-only.test.ts @@ -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 () => { diff --git a/packages/verify/src/harness.ts b/packages/verify/src/harness.ts index 47a921ad0bc..01fd2633232 100644 --- a/packages/verify/src/harness.ts +++ b/packages/verify/src/harness.ts @@ -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})`, diff --git a/packages/verify/src/rls.ts b/packages/verify/src/rls.ts index f1073bb9d9f..26b3e57cf11 100644 --- a/packages/verify/src/rls.ts +++ b/packages/verify/src/rls.ts @@ -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', @@ -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 } }; @@ -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: '[]', @@ -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 } }; @@ -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, ); @@ -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({ @@ -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 ?? ''}`); } @@ -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) { diff --git a/scripts/doc-authoring-prose-id.baseline.json b/scripts/doc-authoring-prose-id.baseline.json index ad4f4a7a56e..3d8b2879b8b 100644 --- a/scripts/doc-authoring-prose-id.baseline.json +++ b/scripts/doc-authoring-prose-id.baseline.json @@ -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 }, @@ -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 @@ -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 } } From 03a490204bddb703c96dd519dcca9754026fcccd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 19:00:26 +0000 Subject: [PATCH 2/3] fix(plugin-dev): keep the no-auth warning in its four literals Re-wraps the rewritten sentence across the warning's existing literals instead of adding a fifth, so the change stays text-only at the AST level. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude --- packages/plugins/plugin-dev/src/dev-plugin.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/plugins/plugin-dev/src/dev-plugin.ts b/packages/plugins/plugin-dev/src/dev-plugin.ts index 3c4378c9347..08b6c510791 100644 --- a/packages/plugins/plugin-dev/src/dev-plugin.ts +++ b/packages/plugins/plugin-dev/src/dev-plugin.ts @@ -932,9 +932,8 @@ export class DevPlugin implements Plugin { 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, 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.', + + '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 { From 29a50b21009095970767af3e08f7afe3fd8d9937 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 19:01:51 +0000 Subject: [PATCH 3/3] docs(changeset): stage 4 strings for verify, plugin-dev and plugin-hono-server Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude --- ...erify-plugin-strings-state-the-decision.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .changeset/20752-verify-plugin-strings-state-the-decision.md diff --git a/.changeset/20752-verify-plugin-strings-state-the-decision.md b/.changeset/20752-verify-plugin-strings-state-the-decision.md new file mode 100644 index 00000000000..19ec3b6a8d6 --- /dev/null +++ b/.changeset/20752-verify-plugin-strings-state-the-decision.md @@ -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) — ===`, 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.