From 7d6389ba0876188eb6ca4e17f21f26bfda2d0dad Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 04:51:12 +0000 Subject: [PATCH 01/13] wip(spec,rest,client,cli)!: retire the saved-report stack (#20102) Claude-Session: https://claude.ai/code/session_013RWUA7bNq5bRhehLPqXwMg Co-authored-by: Claude --- .changeset/config.json | 1 - packages/cli/package.json | 1 - packages/cli/src/commands/serve.ts | 22 +- .../test/serve-capability-identity.test.ts | 1 - packages/client/src/client.test.ts | 102 +- packages/client/src/index.ts | 112 +- .../client/src/return-type-precision.test.ts | 8 - packages/core/src/fallbacks/index.ts | 7 +- packages/lint/src/validate-sortable-fields.ts | 21 +- packages/metadata-protocol/src/protocol.ts | 20 +- packages/objectql/src/engine.ts | 6 +- .../objectql/src/filter-comparand-shape.ts | 9 +- .../scripts/i18n-extract.config.ts | 4 - .../translations/bundle-ownership.test.ts | 4 +- packages/platform-objects/src/audit/index.ts | 3 +- .../platform-iana-timezone-columns.test.ts | 85 +- .../src/audit/sys-job.object.ts | 7 +- .../src/audit/sys-report-schedule.object.ts | 218 - .../src/audit/sys-saved-report.object.ts | 127 - .../src/identity/sys-business-unit.object.ts | 6 +- packages/plugins/plugin-reports/CHANGELOG.md | 4368 ----------------- packages/plugins/plugin-reports/LICENSE | 202 - packages/plugins/plugin-reports/package.json | 52 - .../dispatcher-runs-on-object-kernel.test.ts | 126 - .../src/exec-context-annotation.pin.ts | 156 - packages/plugins/plugin-reports/src/index.ts | 34 - ...lugin-shutdown-releases-dispatcher.test.ts | 268 - .../src/report-export-axis.test.ts | 223 - ...rt-group-posture-scope.integration.test.ts | 250 - .../plugin-reports/src/report-service.test.ts | 945 ---- .../plugin-reports/src/report-service.ts | 964 ---- .../plugin-reports/src/reports-plugin.ts | 231 - packages/plugins/plugin-reports/tsconfig.json | 10 - .../plugins/plugin-reports/tsconfig.test.json | 72 - .../plugins/plugin-reports/vitest.config.ts | 51 - .../reports-delete-enumeration-oracle.test.ts | 252 - .../rest/src/reports-routes-retired.test.ts | 117 + .../src/rest-api-plugin-slot-lookups.test.ts | 18 +- packages/rest/src/rest-api-plugin.ts | 10 +- packages/rest/src/rest-route-ledger.ts | 11 +- packages/rest/src/rest-server.ts | 359 +- ...-response-internal-fields.tripwire.test.ts | 6 - packages/rest/src/rest.test.ts | 221 - ...schedule-delete-enumeration-oracle.test.ts | 267 - .../spec/src/api/error-code-ledger.zod.ts | 14 +- packages/spec/src/contracts/index.ts | 5 +- packages/spec/src/contracts/report-service.ts | 186 - .../src/contracts/sharing-service.test.ts | 12 +- .../spec/src/contracts/sharing-service.ts | 4 +- packages/spec/src/data/object.zod.ts | 2 +- packages/spec/src/data/query.zod.ts | 4 +- .../spec/src/kernel/platform-capabilities.ts | 30 +- .../semantic/18.saved-report-stack-retired.ts | 49 + packages/spec/src/stack.zod.ts | 12 +- .../system/constants/platform-object-names.ts | 2 - packages/spec/src/system/job.zod.ts | 4 +- pnpm-lock.yaml | 37 - scripts/check-test-source-alias.mjs | 4 - scripts/doc-authoring-prose-id.baseline.json | 4 - scripts/engine-double-contract.baseline.json | 32 - scripts/test-shard-timings.json | 1 - 61 files changed, 362 insertions(+), 10017 deletions(-) delete mode 100644 packages/platform-objects/src/audit/sys-report-schedule.object.ts delete mode 100644 packages/platform-objects/src/audit/sys-saved-report.object.ts delete mode 100644 packages/plugins/plugin-reports/CHANGELOG.md delete mode 100644 packages/plugins/plugin-reports/LICENSE delete mode 100644 packages/plugins/plugin-reports/package.json delete mode 100644 packages/plugins/plugin-reports/src/dispatcher-runs-on-object-kernel.test.ts delete mode 100644 packages/plugins/plugin-reports/src/exec-context-annotation.pin.ts delete mode 100644 packages/plugins/plugin-reports/src/index.ts delete mode 100644 packages/plugins/plugin-reports/src/plugin-shutdown-releases-dispatcher.test.ts delete mode 100644 packages/plugins/plugin-reports/src/report-export-axis.test.ts delete mode 100644 packages/plugins/plugin-reports/src/report-group-posture-scope.integration.test.ts delete mode 100644 packages/plugins/plugin-reports/src/report-service.test.ts delete mode 100644 packages/plugins/plugin-reports/src/report-service.ts delete mode 100644 packages/plugins/plugin-reports/src/reports-plugin.ts delete mode 100644 packages/plugins/plugin-reports/tsconfig.json delete mode 100644 packages/plugins/plugin-reports/tsconfig.test.json delete mode 100644 packages/plugins/plugin-reports/vitest.config.ts delete mode 100644 packages/rest/src/reports-delete-enumeration-oracle.test.ts create mode 100644 packages/rest/src/reports-routes-retired.test.ts delete mode 100644 packages/rest/src/schedule-delete-enumeration-oracle.test.ts delete mode 100644 packages/spec/src/contracts/report-service.ts create mode 100644 packages/spec/src/migrations/entries/semantic/18.saved-report-stack-retired.ts diff --git a/.changeset/config.json b/.changeset/config.json index bd5e166f97c..14d68f959cb 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -44,7 +44,6 @@ "@objectstack/organizations", "@objectstack/mcp", "@objectstack/plugin-pinyin-search", - "@objectstack/plugin-reports", "@objectstack/plugin-security", "@objectstack/plugin-sharing", "@objectstack/service-sms", diff --git a/packages/cli/package.json b/packages/cli/package.json index 0d8330cba3a..176024d636b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -83,7 +83,6 @@ "@objectstack/plugin-auth": "workspace:*", "@objectstack/plugin-email": "workspace:*", "@objectstack/plugin-hono-server": "workspace:*", - "@objectstack/plugin-reports": "workspace:*", "@objectstack/plugin-security": "workspace:*", "@objectstack/plugin-pinyin-search": "workspace:*", "@objectstack/plugin-sharing": "workspace:*", diff --git a/packages/cli/src/commands/serve.ts b/packages/cli/src/commands/serve.ts index 6b1c0140a0c..acef7693950 100644 --- a/packages/cli/src/commands/serve.ts +++ b/packages/cli/src/commands/serve.ts @@ -23,7 +23,7 @@ import { // the legacy boolean in as its unset-fallback. serve's last direct reader of the // boolean was the banner, and that was exactly the drift #4801 fixed. import { readEnvWithDeprecation, resolveTenancyPosture, resolveAllowDegradedTenancy, isMcpServerEnabled, stampSearchPinyinEnabled, isModuleNotFoundError } from '@objectstack/types'; -import { PLATFORM_CAPABILITY_TOKENS, PLATFORM_ALWAYS_ON_CAPABILITIES } from '@objectstack/spec/kernel'; +import { PLATFORM_CAPABILITY_TOKENS, PLATFORM_ALWAYS_ON_CAPABILITIES, RETIRED_PLATFORM_CAPABILITY_GUIDANCE } from '@objectstack/spec/kernel'; // The posture vocabulary, read from the package that DEFINES it (#5359) — the // boot gate's fix list enumerates the accepted values, and a second literal // list would be free to drift the day a posture is added. @@ -2001,11 +2001,6 @@ export default class Serve extends Command { export: 'PinyinSearchPlugin', identities: ['com.objectstack.plugin.pinyin-search', 'PinyinSearchPlugin'], }, - reports: { - pkg: '@objectstack/plugin-reports', - export: 'ReportsServicePlugin', - identities: ['com.objectstack.service.reports', 'ReportsServicePlugin'], - }, approvals: { pkg: '@objectstack/plugin-approvals', export: 'ApprovalsServicePlugin', @@ -2818,13 +2813,13 @@ export default class Serve extends Command { if (!requires.includes(cap)) requires.push(cap); } } - // The email + approvals + reports services schedule background work - // (durable retries, SLA escalation, scheduled digests). Auto-pull + // The email + approvals services schedule background work + // (durable retries, SLA escalation). Auto-pull // 'job' and 'queue' so plugins can opt into durable scheduling. // IMPORTANT: prepend, so their plugins load (and their kernel:ready // hooks fire) BEFORE consumers like email/approvals that subscribe // to queues during their own kernel:ready phase. - const NEEDS_JOB_OR_QUEUE = ['email', 'approvals', 'reports', 'auth']; + const NEEDS_JOB_OR_QUEUE = ['email', 'approvals', 'auth']; if (NEEDS_JOB_OR_QUEUE.some((c) => requires.includes(c))) { if (!requires.includes('queue')) requires.unshift('queue'); if (!requires.includes('job')) requires.unshift('job'); @@ -4617,8 +4612,15 @@ export default class Serve extends Command { // (`serve-capability-vocabulary.test.ts`), and `@objectstack/spec` and // this package release in one fixed version group. if (declaredRequires.has(cap) && !PLATFORM_CAPABILITY_TOKENS.includes(cap)) { + // A RETIRED token (e.g. `reports`, #20102) is not a typo: say what + // replaced it, in the same words `defineStack` refuses it with. + const retired = Object.hasOwn(RETIRED_PLATFORM_CAPABILITY_GUIDANCE, cap) + ? RETIRED_PLATFORM_CAPABILITY_GUIDANCE[cap] + : undefined; console.warn(chalk.yellow( - ` ⚠ requires: "${cap}" is not a known platform capability — check for a typo. It was ignored.`, + retired + ? ` ⚠ ${retired} It was ignored.` + : ` ⚠ requires: "${cap}" is not a known platform capability — check for a typo. It was ignored.`, )); } continue; diff --git a/packages/cli/test/serve-capability-identity.test.ts b/packages/cli/test/serve-capability-identity.test.ts index ccdd30a5d5d..1fe47ad9cfd 100644 --- a/packages/cli/test/serve-capability-identity.test.ts +++ b/packages/cli/test/serve-capability-identity.test.ts @@ -113,7 +113,6 @@ const EXPECTED_PROVIDER_NAME: Record = { sms: 'com.objectstack.service.sms', sharing: 'com.objectstack.service.sharing', 'pinyin-search': 'com.objectstack.plugin.pinyin-search', - reports: 'com.objectstack.service.reports', approvals: 'com.objectstack.service.approvals', settings: 'com.objectstack.service.settings', webhooks: 'com.objectstack.plugin-webhook-outbox', diff --git a/packages/client/src/client.test.ts b/packages/client/src/client.test.ts index daf3312c6a4..91feb61557f 100644 --- a/packages/client/src/client.test.ts +++ b/packages/client/src/client.test.ts @@ -390,93 +390,21 @@ describe('ObjectStackClient', () => { }); }); -describe('Reports namespace (#3587 gap closure)', () => { - it('reports.list pins GET /reports with filters and unwraps {data}', async () => { - const { client, fetchMock } = createMockClient({ data: [{ id: 'r1' }] }); - const rows = await client.reports.list({ object: 'lead', ownerId: 'u1' }); - expect(String(fetchMock.mock.calls[0][0])).toBe( - 'http://localhost:3000/api/v1/reports?object=lead&ownerId=u1', - ); - expect(rows).toEqual([{ id: 'r1' }]); - }); - - it('reports.save pins POST /reports', async () => { - const { client, fetchMock } = createMockClient({ id: 'r1' }); - await client.reports.save({ name: 'Pipeline', object: 'lead', query: { fields: ['id'] } }); - const [url, init] = fetchMock.mock.calls[0]; - expect(String(url)).toBe('http://localhost:3000/api/v1/reports'); - expect(init.method).toBe('POST'); - expect(JSON.parse(init.body)).toEqual({ name: 'Pipeline', object: 'lead', query: { fields: ['id'] } }); - }); - - // [#11926] The literal below is the ORIGINAL fixture of the test above, - // preserved verbatim rather than repaired. For as long as `reports.save` - // took `any` it sat there constructing an input the service contract - // REFUSES — `SaveReportInput.query` is required — against a mock transport - // that never reaches a service, so no run could ever have failed on it. It - // is evidence, and giving it a `query` would have silenced the evidence - // without closing anything. So it moves here, and the compiler asserts the - // refusal instead. - // - // This is a bidirectional pin, not a comment. `client.test.ts` is compiled - // by `tsconfig.test.json` — named by this package's `typecheck` script — - // and holds no `test-typecheck-debt.json` entry, so an unlisted file must - // have zero errors. Widen the parameter back to `any` and the directive - // below stops matching an error: tsc reds with TS2578, "unused - // '@ts-expect-error' directive". It cannot rot into a phantom check. - it('[#11926] reports.save refuses a query-less report at the type level', async () => { - const { client, fetchMock } = createMockClient({ id: 'r1' }); - // @ts-expect-error — `query` is required by `SaveReportInput`. - await client.reports.save({ name: 'Pipeline', object: 'lead' }); - // The SDK is a transport, not a second validator: the request still - // goes out unaltered. The refusal ON THE WIRE belongs to the route and - // is pinned in packages/rest/src/rest.test.ts — see - // 'POST /reports refuses a body the service contract requires more of'. - expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toEqual({ name: 'Pipeline', object: 'lead' }); - }); - - it('reports.get / delete pin /reports/:id and delete tolerates 204', async () => { - const { client, fetchMock } = createMockClient({ id: 'r1' }); - await client.reports.get('r1'); - expect(String(fetchMock.mock.calls[0][0])).toBe('http://localhost:3000/api/v1/reports/r1'); - - const del = createMockClient(undefined, 204); - // A 204 has no JSON body — the method must not try to parse one. - del.fetchMock.mockResolvedValue({ ok: true, status: 204, statusText: 'No Content', json: async () => { throw new Error('no body'); }, headers: new Headers() }); - const out = await del.client.reports.delete('r1'); - expect(String(del.fetchMock.mock.calls[0][0])).toBe('http://localhost:3000/api/v1/reports/r1'); - expect(del.fetchMock.mock.calls[0][1].method).toBe('DELETE'); - expect(out).toEqual({ deleted: true }); - }); - - it('reports.run pins POST /reports/:id/run', async () => { - const { client, fetchMock } = createMockClient({ rows: [] }); - await client.reports.run('r1'); - const [url, init] = fetchMock.mock.calls[0]; - expect(String(url)).toBe('http://localhost:3000/api/v1/reports/r1/run'); - expect(init.method).toBe('POST'); - }); - - it('reports.schedule pins POST /reports/:id/schedule with the schedule body', async () => { - const { client, fetchMock } = createMockClient({ id: 's1' }); - await client.reports.schedule('r1', { recipients: ['a@example.com'], cronExpression: '0 8 * * 1' }); - const [url, init] = fetchMock.mock.calls[0]; - expect(String(url)).toBe('http://localhost:3000/api/v1/reports/r1/schedule'); - expect(JSON.parse(init.body)).toEqual({ recipients: ['a@example.com'], cronExpression: '0 8 * * 1' }); - }); - - it('reports.listSchedules / unschedule pin the schedule routes', async () => { - const { client, fetchMock } = createMockClient({ data: [{ id: 's1' }] }); - const rows = await client.reports.listSchedules('r1'); - expect(String(fetchMock.mock.calls[0][0])).toBe('http://localhost:3000/api/v1/reports/r1/schedules'); - expect(rows).toEqual([{ id: 's1' }]); - - const del = createMockClient(undefined, 204); - del.fetchMock.mockResolvedValue({ ok: true, status: 204, statusText: 'No Content', json: async () => { throw new Error('no body'); }, headers: new Headers() }); - const out = await del.client.reports.unschedule('s1'); - expect(String(del.fetchMock.mock.calls[0][0])).toBe('http://localhost:3000/api/v1/reports/schedules/s1'); - expect(del.fetchMock.mock.calls[0][1].method).toBe('DELETE'); - expect(out).toEqual({ deleted: true }); +describe('[#20102] the saved-report namespace is retired', () => { + // The `reports` namespace left with the `/api/v1/reports` routes it + // called. Both halves are pinned: a caller that still writes + // `client.reports` is a compile error (tsc checks this file through + // `tsconfig.test.json`, so an unused directive reds as TS2578 the moment + // the namespace comes back), and at runtime there is no such member to + // reach for. + it('has no `reports` member, at the type level or at runtime', () => { + const { client, fetchMock } = createMockClient({ data: [] }); + // @ts-expect-error — `reports` was retired; a report is `report` metadata. + const retired = client.reports; + expect(retired).toBeUndefined(); + expect('reports' in client).toBe(false); + // Nothing was sent: the member is absent, not a stub that still calls out. + expect(fetchMock).not.toHaveBeenCalled(); }); }); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 6f056822de8..4f334d3148a 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -165,10 +165,6 @@ import type { ObjectDraft, RecordShare, RemoteTable, - ReportRunResult, - ReportSchedule, - SaveReportInput, - SavedReport, SchemaValidationReport, ScreenSpec, SendEmailResult, @@ -6353,109 +6349,11 @@ export class ObjectStackClient { return this.unwrapResponse(res); }; - /** - * Saved reports (#3587 gap closure) - * - * Tenant-wide report definitions, execution, and recurring email - * schedules, served by `@objectstack/plugin-reports` behind the REST - * surface. Every route 501s [NOT_IMPLEMENTED] on deployments without the - * reports service. Fixed path — `reports` is not in `ApiRoutesSchema`. - */ - reports = { - /** List saved reports, optionally filtered by object or owner. */ - list: async (opts?: { object?: string; ownerId?: string }): Promise => { - const params = new URLSearchParams(); - if (opts?.object) params.set('object', opts.object); - if (opts?.ownerId) params.set('ownerId', opts.ownerId); - const qs = params.toString(); - const res = await this.fetch(`${this.baseUrl}/api/v1/reports${qs ? `?${qs}` : ''}`); - const body = await this.unwrapResponse<{ data?: SavedReport[] } | SavedReport[]>(res); - return Array.isArray(body) ? body : (body?.data ?? []); - }, - - /** - * Create or update a saved report definition. - * - * [#11926] The parameter is the service contract's own `SaveReportInput`, - * not `any`: `name`, `object` and `query` are required, and omitting one is - * a compile error here rather than a surprise from whichever reports - * implementation the deployment mounts. The wire refusal is the route's — - * `POST /reports` answers 400 [VALIDATION_FAILED] for the same three keys, - * so a JavaScript caller that never sees this type is refused too. - */ - save: async (report: SaveReportInput): Promise => { - const res = await this.fetch(`${this.baseUrl}/api/v1/reports`, { - method: 'POST', - body: JSON.stringify(report ?? {}), - }); - return this.unwrapResponse(res); - }, - - /** Get a saved report by id. 404 [REPORT_NOT_FOUND] when absent. */ - get: async (id: string): Promise => { - const res = await this.fetch(`${this.baseUrl}/api/v1/reports/${encodeURIComponent(id)}`); - return this.unwrapResponse(res); - }, - - /** Delete a saved report; its schedules cascade. */ - delete: async (id: string): Promise<{ deleted: boolean }> => { - const res = await this.fetch(`${this.baseUrl}/api/v1/reports/${encodeURIComponent(id)}`, { - method: 'DELETE', - }); - if (res.status === 204) return { deleted: true }; - return this.unwrapResponse<{ deleted: boolean }>(res); - }, - - /** Execute a saved report and return its rendered output. */ - run: async (id: string): Promise => { - const res = await this.fetch(`${this.baseUrl}/api/v1/reports/${encodeURIComponent(id)}/run`, { - method: 'POST', - body: JSON.stringify({}), - }); - return this.unwrapResponse(res); - }, - - /** - * Create a recurring email schedule for a report. Provide either - * `intervalMinutes` or `cronExpression`; `recipients` is required. - */ - schedule: async ( - id: string, - opts: { - recipients: string[]; - name?: string; - intervalMinutes?: number; - cronExpression?: string; - timezone?: string; - format?: string; - subjectTemplate?: string; - ownerId?: string; - active?: boolean; - }, - ): Promise => { - const res = await this.fetch(`${this.baseUrl}/api/v1/reports/${encodeURIComponent(id)}/schedule`, { - method: 'POST', - body: JSON.stringify(opts), - }); - return this.unwrapResponse(res); - }, - - /** List the recurring schedules attached to a report. */ - listSchedules: async (id: string): Promise => { - const res = await this.fetch(`${this.baseUrl}/api/v1/reports/${encodeURIComponent(id)}/schedules`); - const body = await this.unwrapResponse<{ data?: ReportSchedule[] } | ReportSchedule[]>(res); - return Array.isArray(body) ? body : (body?.data ?? []); - }, - - /** Delete a schedule by its id (report-independent path). */ - unschedule: async (scheduleId: string): Promise<{ deleted: boolean }> => { - const res = await this.fetch(`${this.baseUrl}/api/v1/reports/schedules/${encodeURIComponent(scheduleId)}`, { - method: 'DELETE', - }); - if (res.status === 204) return { deleted: true }; - return this.unwrapResponse<{ deleted: boolean }>(res); - }, - }; + // The former `reports` namespace (saved-report definitions, runs and + // e-mail schedules) was retired with its server routes in #20102 — the + // saved-report stack had no consumer. A report is `report` metadata, read + // through `meta.*` and run through `analytics.*`; a saved ad-hoc object + // query is a ListView. // The former `views` CRUD namespace was removed in #3612 — no server // surface mounts /ui/views (both surfaces serve only /ui/view/:object…). diff --git a/packages/client/src/return-type-precision.test.ts b/packages/client/src/return-type-precision.test.ts index 8439bd817db..b204de42bc8 100644 --- a/packages/client/src/return-type-precision.test.ts +++ b/packages/client/src/return-type-precision.test.ts @@ -87,8 +87,6 @@ import type { ImportObjectResult, RecordShare, RemoteTable, - ReportSchedule, - SavedReport, SearchResult, ShareLink, SharingRuleRow, @@ -154,8 +152,6 @@ export async function returnTypePrecisionPins(): Promise { // ── shape class 3: array ELEMENT typed through the client's own unwrap ─ // These routes answer `{ data: rows }` with no `success` flag, so // `unwrapResponse` passes it through and the method folds it to an array. - expectTypeOf(await client.reports.list()).toEqualTypeOf(); - expectTypeOf(await client.reports.listSchedules('rep_1')).toEqualTypeOf(); expectTypeOf(await client.shares.list('lead', 'rec_1')).toEqualTypeOf(); expectTypeOf(await client.shares.rules.list()).toEqualTypeOf(); expectTypeOf(await client.shareLinks.list()).toEqualTypeOf(); @@ -239,9 +235,6 @@ export async function returnTypePrecisionPins(): Promise { // @ts-expect-error the route answers `{ tables }`, not a bare array const wrongTables: RemoteTable[] = await client.datasources.external.listTables('ds'); - // @ts-expect-error `reports.list` answers SavedReport[], not a single row - const wrongReport: SavedReport = await client.reports.list(); - // @ts-expect-error a flow definition is not an execution log const wrongFlow: ExecutionLog = await client.automation.getFlow('flow_a'); @@ -259,7 +252,6 @@ export async function returnTypePrecisionPins(): Promise { void wrongScope; void wrongTables; - void wrongReport; void wrongFlow; void wrongSearch; void wrongClone; diff --git a/packages/core/src/fallbacks/index.ts b/packages/core/src/fallbacks/index.ts index 114db6567c8..db631c9fcff 100644 --- a/packages/core/src/fallbacks/index.ts +++ b/packages/core/src/fallbacks/index.ts @@ -25,9 +25,10 @@ export { * (maintainer ruling 2026-08-22). `createMemoryJob()`'s `schedule()` records a * job and never fires it, so pre-injecting it made every "prefer the platform * job service, else own a timer" consumer take the job-service branch and then - * silently never run: `plugin-reports` logged `dispatcher registered with job - * service` and dispatched nothing, ever (measured: 0 reads of - * `sys_report_schedule` in 5600 ms with the success line present). With no + * silently never run: the saved-report plugin (since retired, #20102) logged + * `dispatcher registered with job service` and dispatched nothing, ever + * (measured: 0 reads of its schedule table in 5600 ms with the success line + * present). With no * entry here, `getService('job')` throws when no job plugin is installed, * every consumer's documented no-job-service path becomes reachable (they all * already run on `LiteKernel`, which injects no fallbacks), and the kernel diff --git a/packages/lint/src/validate-sortable-fields.ts b/packages/lint/src/validate-sortable-fields.ts index 2c899d1e7b8..47b255663c3 100644 --- a/packages/lint/src/validate-sortable-fields.ts +++ b/packages/lint/src/validate-sortable-fields.ts @@ -101,18 +101,15 @@ * * NOT walked, each verified against the schema rather than assumed: * - * - **A saved report's `query.orderBy`.** Verified: it is not an authoring - * surface at all. `sys_saved_report` is a platform OBJECT and the envelope - * lives in its `query_json` COLUMN (`packages/platform-objects/src/audit/ - * sys-saved-report.object.ts`, `contracts/report-service.ts`) — a runtime - * record written through the reports API, never a key in stack metadata. - * The stack's own `reports[]` is `ReportSchema`, whose ADR-0021 single-form - * cutover REMOVED the inline query; what it declares instead is - * `order[].by`, naming a dataset dimension or measure, and `checkReportOrder` - * already refines that against what the report selects. So there is no - * authored `query.orderBy` for a stack rule to reach; the engine door - * (#7095) is the only door that surface has, which is precisely why #7095 - * added it. + * - **A report's `query.orderBy`.** Verified: no such authoring surface + * exists. The one place a raw report query ever lived was a runtime record + * (the saved-report stack's `query_json` column), never a key in stack + * metadata — and that stack was retired whole in #20102. The stack's own + * `reports[]` is `ReportSchema`, whose ADR-0021 single-form cutover REMOVED + * the inline query; what it declares instead is `order[].by`, naming a + * dataset dimension or measure, and `checkReportOrder` already refines that + * against what the report selects. So there is no authored `query.orderBy` + * for a stack rule to reach. * - **Flow node sort config.** Verified: none exists. * `automation/builtin-node-config.zod.ts`'s record-reading node declares * `limit` and no ordering key at all, and no schema under diff --git a/packages/metadata-protocol/src/protocol.ts b/packages/metadata-protocol/src/protocol.ts index 35f178907b5..7b789e919ae 100644 --- a/packages/metadata-protocol/src/protocol.ts +++ b/packages/metadata-protocol/src/protocol.ts @@ -3382,8 +3382,8 @@ function invalidSortError( * This is the one unknown key on this axis that has a KNOWN right answer, so it * gets a rejection that carries the translation rather than a generic refusal. * `direction` is not a typo — it is the live vocabulary of a neighbouring - * contract (`IReportService.orderBy`, `spec/src/contracts/report-service.ts`), - * which `plugin-auth/objectql-adapter.ts` already translates to `order` by hand. + * contract (better-auth's adapter `sortBy`), which + * `plugin-auth/objectql-adapter.ts` already translates to `order` by hand. * A necessary translation nothing enforced is exactly ADR-0049's shape. * * Measured on `main` before this rejection existed, on the schema side of the @@ -3410,7 +3410,7 @@ function invalidSortDirectionKeyError(param: string, field: string): Error { { hint: ` Write \`{ field: '${field}', order: 'desc' }\`. \`direction\` is` - + " `IReportService.orderBy`'s vocabulary, a genuinely different contract; on this" + + " the better-auth adapter's `sortBy` vocabulary, a genuinely different contract; on this" + ' axis it was silently dropped and `order` fell back to `asc`, so a descending' + ' request came back ascending — and with `limit`, a different set of rows.', extra: { field, key: 'direction' }, @@ -8991,8 +8991,8 @@ export class ObjectStackProtocolImplementation implements }; // `order`, NOT `direction`: the QueryAST sort shape is // `SortNodeSchema` = `{ field, order }`, and both drivers normalize - // off `.order` with no fallback. `direction` is `IReportService`'s - // vocabulary and is silently DROPPED here (the schema is not + // off `.order` with no fallback. `direction` is another contract's + // vocabulary (better-auth's adapter `sortBy`) and is silently DROPPED here (the schema is not // `.strict()`), which left this query running ascending — the // OLDEST `limit` audit events, i.e. the beginning of an object's // life and never its recent changes (#4674). The `as any` is gone @@ -9455,8 +9455,8 @@ export class ObjectStackProtocolImplementation implements * SCOPE: this is an INGRESS gate, so it covers what reaches {@link * findData}. The half it cannot reach — a caller handing a `where` straight * to `engine.find` / `findOne` / `count` / `aggregate` / `update` / - * `delete`, which is how a saved report's `query.filter` travels - * (`plugin-reports` forwards it verbatim) — is closed at the engine's own + * `delete`, which is how a flow node's `config.filter` travels — is closed + * at the engine's own * filter seam by `assertFilterIsMaterializable` (`@objectstack/objectql`, * `filter-comparand-shape.ts`), with the same `400 INVALID_FIELD` and the * same remedy sentence. Same two-door shape, and same reason, as the sort @@ -9732,9 +9732,9 @@ export class ObjectStackProtocolImplementation implements * sentence this gate emits, ruled on #7095 (an ORDER BY the engine cannot * apply is a refusal with guidance prose, never a silent drop). What made * leaving it at ingress untenable is that the direct path is AUTHOR- - * reachable, not merely internal: a saved report's `query.orderBy` is - * forwarded verbatim into `engine.find` (`plugin-reports`), and it never - * passes through here. + * reachable, not merely internal: a saved report's `query.orderBy` was + * forwarded verbatim into `engine.find` (by the saved-report stack, since + * retired in #20102), and it never passed through here. * * ONE EDGE, measured and deliberately left: a nested `expand` sort is also * forwarded into the expansion sub-read (`expandRelatedRecords`), and the diff --git a/packages/objectql/src/engine.ts b/packages/objectql/src/engine.ts index 1b4bd88376d..6403908d068 100644 --- a/packages/objectql/src/engine.ts +++ b/packages/objectql/src/engine.ts @@ -1245,9 +1245,9 @@ function assertOrderByIsMaterializable( * SQL renders `"account"."name"` against a table that was never joined, the DB * answers `no such column`, and the #3821 recovery ladder retries `select('*')`. * The caller asked to narrow and silently received EVERY field, byte-identical - * to no projection at all. A saved report's `query.fields` reaches this the - * same way (`plugin-reports` forwards it verbatim), as does every hook and - * internal caller. + * to no projection at all. Every hook and internal caller reaches this the + * same way (a saved report's `query.fields` did too, until the saved-report + * stack was retired in #20102). * * WHY A REFUSAL: ruled 2026-08-12 on #7589 (adopting the drivers seat's * Option B) — a dotted entry the engine cannot resolve is refused loudly at diff --git a/packages/objectql/src/filter-comparand-shape.ts b/packages/objectql/src/filter-comparand-shape.ts index 3b09a0ee563..0eab708c29c 100644 --- a/packages/objectql/src/filter-comparand-shape.ts +++ b/packages/objectql/src/filter-comparand-shape.ts @@ -147,10 +147,11 @@ export function assertListComparandShapes( * * WHY AT THIS SEAM AND NOT ONLY AT INGRESS: #7095 had to add * `assertOrderByIsMaterializable` inside this package because a saved report's - * `query.orderBy` is forwarded verbatim into `engine.find` and never passes the - * REST door. Filters travel the SAME path — `plugin-reports`' `executeReport` - * calls `this.engine.find(report.object_name, { where: q.filter, … })` — so an - * ingress-only fix would have left the author-reachable half open. This gate + * `query.orderBy` was forwarded verbatim into `engine.find` and never passed the + * REST door. Filters traveled the SAME path — the saved-report executor (the + * stack was retired in #20102) handed `q.filter` to the engine's `find` as its + * `where`, and a flow node's `config.filter` still does — so an ingress-only fix + * would have left the author-reachable half open. This gate * runs inside `lowerWhereFilterArray`, the one seam EVERY caller-supplied * `where` passes through (`find` / `findOne` / `count` / `aggregate` / `update` * / `delete`), which is what makes a new verb unable to miss it by omission. diff --git a/packages/platform-objects/scripts/i18n-extract.config.ts b/packages/platform-objects/scripts/i18n-extract.config.ts index 138c1401d93..b1613326be8 100644 --- a/packages/platform-objects/scripts/i18n-extract.config.ts +++ b/packages/platform-objects/scripts/i18n-extract.config.ts @@ -167,8 +167,6 @@ import { SysAttachment, SysEmail, SysEmailTemplate, - SysSavedReport, - SysReportSchedule, // sys_approval_* moved to @objectstack/plugin-approvals (ADR-0029 K2.b / D8). SysJob, SysJobRun, @@ -302,8 +300,6 @@ const config: ObjectStackDefinition = defineStack({ SysAttachment, SysEmail, SysEmailTemplate, - SysSavedReport, - SysReportSchedule, // sys_approval_* moved to @objectstack/plugin-approvals (ADR-0029 K2.b / D8). SysJob, SysJobRun, diff --git a/packages/platform-objects/src/apps/translations/bundle-ownership.test.ts b/packages/platform-objects/src/apps/translations/bundle-ownership.test.ts index 12de4a2861a..f26bb215155 100644 --- a/packages/platform-objects/src/apps/translations/bundle-ownership.test.ts +++ b/packages/platform-objects/src/apps/translations/bundle-ownership.test.ts @@ -33,8 +33,8 @@ const OWNED_OBJECTS = new Set([ 'sys_scim_subject', 'sys_scim_user', // audit / messaging-adjacent (still owned here) 'sys_notification', 'sys_attachment', 'sys_email', 'sys_email_template', - 'sys_saved_report', 'sys_report_schedule', 'sys_job', 'sys_job_run', 'sys_job_queue', - 'sys_import_job', + // (sys_saved_report / sys_report_schedule retired with the saved-report stack, #20102) + 'sys_job', 'sys_job_run', 'sys_job_queue', 'sys_import_job', // metadata 'sys_metadata', 'sys_metadata_history', 'sys_view_definition', 'sys_metadata_audit', // system diff --git a/packages/platform-objects/src/audit/index.ts b/packages/platform-objects/src/audit/index.ts index c474dad81d2..937efe826b4 100644 --- a/packages/platform-objects/src/audit/index.ts +++ b/packages/platform-objects/src/audit/index.ts @@ -13,8 +13,7 @@ export { SysNotification } from './sys-notification.object.js'; export { SysAttachment } from './sys-attachment.object.js'; export { SysEmail } from './sys-email.object.js'; export { SysEmailTemplate } from './sys-email-template.object.js'; -export { SysSavedReport } from './sys-saved-report.object.js'; -export { SysReportSchedule } from './sys-report-schedule.object.js'; +// sys_saved_report / sys_report_schedule retired with the saved-report stack (#20102). // sys_approval_request / sys_approval_action moved to @objectstack/plugin-approvals (ADR-0029 K2.b). export { SysJob } from './sys-job.object.js'; export { SysJobRun } from './sys-job-run.object.js'; diff --git a/packages/platform-objects/src/audit/platform-iana-timezone-columns.test.ts b/packages/platform-objects/src/audit/platform-iana-timezone-columns.test.ts index c96f9948d28..9c30e7a3e57 100644 --- a/packages/platform-objects/src/audit/platform-iana-timezone-columns.test.ts +++ b/packages/platform-objects/src/audit/platform-iana-timezone-columns.test.ts @@ -1,44 +1,34 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * #15872 — the platform's two OLDER IANA columns, `sys_job.timezone` and - * `sys_report_schedule.timezone`, predate `valueDomain` and disagreed with each - * other in three dimensions at once (length 100 vs 64, default none vs `'UTC'`, - * validation none vs none). This file pins what that card actually changed and, - * just as deliberately, what it did NOT. + * #15872 — `sys_job.timezone`, the platform's OLDER IANA column, predates + * `valueDomain`. It had a sibling, `sys_report_schedule.timezone`, and the two + * disagreed in three dimensions at once (length 100 vs 64, default none vs + * `'UTC'`, validation none vs none); that sibling was retired with the + * saved-report stack (#20102), so what this file pins is the surviving column — + * what #15872 changed on it and, just as deliberately, what it did NOT. * - * CLOSED here — validation. Both columns now declare - * `valueDomain: 'iana_time_zone'`, the same declaration the ruled pair - * `sys_business_unit.timezone` / `sys_organization.timezone` carries (#14238, - * pinned in `identity/org-hierarchy-timezone.test.ts`). Four columns, one - * membership predicate. + * CLOSED here — validation. The column declares `valueDomain: 'iana_time_zone'`, + * the same declaration the ruled pair `sys_business_unit.timezone` / + * `sys_organization.timezone` carries (#14238, pinned in + * `identity/org-hierarchy-timezone.test.ts`). Three columns, one membership + * predicate. * * LEFT ALONE, and pinned so that staying alone is a decision rather than a * drift someone repairs by reflex: * - * - the DEFAULTS still differ, because a default here is a CONSUMER semantic, - * not a shape question. `sys_report_schedule` documents "default UTC" and its - * reader falls back to `'UTC'`; `sys_job` says nothing, and giving it one - * would change what an unset row means. The ruled pair, for its own reasons, - * has none on either column — so "all four agree" is NOT the invariant, and - * a test asserting it would be asserting a bug. - * - the BOUNDS still differ (100 vs 64). `maxLength` is not only a write bound: - * it reaches DDL, and narrowing a physical `varchar(100)` is `driver-sql`'s + * - NO DEFAULT. A default here is a CONSUMER semantic, not a shape question: + * `sys_job.timezone` is written and never read, and giving it a default + * would change what an unset row means. + * - the BOUND stays 100. `maxLength` is not only a write bound: it reaches + * DDL, and narrowing a physical `varchar(100)` is `driver-sql`'s * `narrow_varchar` op at severity `error`, category destructive. What the - * column physically holds in a deployment is not readable from the repo, so - * the convergence is a separate decision and #15872 stays open on it. - * - * The reader measurement that decided the card's severity is recorded beside - * each declaration, not here: the `sys_job` column is written and never read, - * while the `sys_report_schedule` column is read back into croner by - * `ReportService.nextRunAt`, whose catch turned a non-member zone into a silent - * fall back to `interval_minutes` — the wrong instant, forever. + * column physically holds in a deployment is not readable from the repo. */ import { describe, it, expect } from 'vitest'; import { isValueDomainMember } from '@objectstack/spec/shared'; import { SysJob } from './sys-job.object'; -import { SysReportSchedule } from './sys-report-schedule.object'; type ColumnShape = { type?: unknown; @@ -49,23 +39,17 @@ type ColumnShape = { }; const jobColumn = () => (SysJob.fields as Record).timezone; -const scheduleColumn = () => (SysReportSchedule.fields as Record).timezone; -describe('#15872 — the platform\'s two older IANA time-zone columns', () => { - it('reads the real declarations, not an empty probe', () => { +describe('#15872 — the platform\'s older IANA time-zone column', () => { + it('reads the real declaration, not an empty probe', () => { // Vacuity control: a renamed column or a changed export would otherwise let // every assertion below pass over `undefined`. expect(SysJob.name).toBe('sys_job'); - expect(SysReportSchedule.name).toBe('sys_report_schedule'); expect(jobColumn()).toBeTypeOf('object'); - expect(scheduleColumn()).toBeTypeOf('object'); }); - it.each([ - ['sys_job', jobColumn], - ['sys_report_schedule', scheduleColumn], - ])('%s.timezone is an optional text column validated against the IANA domain', (_object, column) => { - const c = column(); + it('sys_job.timezone is an optional text column validated against the IANA domain', () => { + const c = jobColumn(); // `VALUE_DOMAIN_FIELD_TYPES` is `{text}`, so the declaration below is also // the reason the type must stay `text`. expect(c.type).toBe('text'); @@ -73,49 +57,42 @@ describe('#15872 — the platform\'s two older IANA time-zone columns', () => { expect(c.valueDomain).toBe('iana_time_zone'); }); - it('the DEFAULTS deliberately still differ — a default here is a consumer semantic', () => { - // ⛔ Not a tidy-up target. `sys_report_schedule`'s reader documents and - // implements a UTC default; `sys_job` has no reader at all, and minting one - // would give "unset" a new meaning on rows that predate it. - expect(scheduleColumn().defaultValue).toBe('UTC'); + it('declares NO default — a default here is a consumer semantic', () => { + // ⛔ Not a tidy-up target. `sys_job` has no reader at all, and minting a + // default would give "unset" a new meaning on rows that predate it. expect('defaultValue' in jobColumn()).toBe(false); }); - it('the BOUNDS deliberately still differ — converging them is a DDL question, not a shape one', () => { - // If someone converges these, they owe the reading #15872 could not take: + it('keeps its 100-character bound — narrowing it is a DDL question, not a shape one', () => { + // If someone narrows this, they owe the reading #15872 could not take: // what the physical column holds. Red here is the prompt to go and take it. expect(jobColumn().maxLength).toBe(100); - expect(scheduleColumn().maxLength).toBe(64); }); it('the declared domain refuses every non-member this card was filed over', () => { // Asked of the predicate the write path calls (`isValueDomainMember`) under - // the domain the columns actually declare — never a re-implementation. + // the domain the column actually declares — never a re-implementation. const domain = jobColumn().valueDomain as 'iana_time_zone'; - expect(domain).toBe(scheduleColumn().valueDomain); // The card's own three examples. `Mars/Olympus` is shape-valid and // nonexistent, `UTC+8` and `China Standard Time` are the two spellings a // human reaches for that the tzdb does not carry. expect(isValueDomainMember(domain, 'Mars/Olympus')).toBe(false); expect(isValueDomainMember(domain, 'UTC+8')).toBe(false); expect(isValueDomainMember(domain, 'China Standard Time')).toBe(false); - // …and still admits what both columns must keep taking, `UTC` included — + // …and still admits what the column must keep taking, `UTC` included — // which `Intl.supportedValuesOf('timeZone')` omits, so a column judged - // against the enumeration would refuse `sys_report_schedule`'s own default. + // against the enumeration would refuse the platform's own default zone. expect(isValueDomainMember(domain, 'UTC')).toBe(true); expect(isValueDomainMember(domain, 'Asia/Shanghai')).toBe(true); - expect(isValueDomainMember(domain, scheduleColumn().defaultValue as string)).toBe(true); }); - it('both bounds admit every zone the runtime enumerates, so neither refuses a legal value', () => { - // The smaller bound is the one that could bite; assert against both so a - // future ICU that enumerates a longer name reds here rather than silently + it('the bound admits every zone the runtime enumerates, so it refuses no legal value', () => { + // A future ICU that enumerates a longer name reds here rather than silently // refusing a legal zone at the write seam. // `Intl.supportedValuesOf` is ES2022; the package's `lib` predates it, so // the call is typed here rather than the whole program's lib widened. const intl = Intl as unknown as { supportedValuesOf(key: 'timeZone'): string[] }; const longest = Math.max(...intl.supportedValuesOf('timeZone').map((z) => z.length)); - expect(longest).toBeLessThanOrEqual(scheduleColumn().maxLength as number); expect(longest).toBeLessThanOrEqual(jobColumn().maxLength as number); }); }); diff --git a/packages/platform-objects/src/audit/sys-job.object.ts b/packages/platform-objects/src/audit/sys-job.object.ts index f2db10eac5f..a7dcf605f9b 100644 --- a/packages/platform-objects/src/audit/sys-job.object.ts +++ b/packages/platform-objects/src/audit/sys-job.object.ts @@ -72,7 +72,8 @@ export const SysJob = ObjectSchema.create({ // added, because it decides what the declaration is worth: NOTHING does. // `DbJobAdapter` writes it (`upsertJobRow`, `schedule.timezone ?? null`) and // its three `sys_job` read sites take `id` / `run_count` / `failure_count` - // only — the tree's one `row.timezone` read belongs to `sys_report_schedule`. + // only — the tree's one other `row.timezone` read belonged to + // `sys_report_schedule`, retired with the saved-report stack (#20102). // The value the scheduler actually honours travels in memory // (`toBoundaryJobSchedule` -> `CronJobAdapter.schedule` -> croner), and // `DbJobAdapter.schedule` awaits that call BEFORE `upsertJobRow`, so a @@ -83,8 +84,8 @@ export const SysJob = ObjectSchema.create({ // The door this declaration actually closes is the OTHER one: a direct write // to the object (Studio, REST, a script), which had no validation whatever. // - // ⚠️ `maxLength` deliberately still says 100 while `sys_report_schedule` - // says 64. Converging it is the card's third dimension and is NOT landed + // ⚠️ `maxLength` deliberately still says 100 where `sys_report_schedule` + // (retired since, #20102) said 64. Converging it is the card's third dimension and is NOT landed // here: `maxLength` is not only a write bound, it reaches DDL — narrowing a // physical `varchar(100)` produces `driver-sql`'s `narrow_varchar` op at // severity `error`, category destructive ("narrowing may truncate", diff --git a/packages/platform-objects/src/audit/sys-report-schedule.object.ts b/packages/platform-objects/src/audit/sys-report-schedule.object.ts deleted file mode 100644 index cb7114fa843..00000000000 --- a/packages/platform-objects/src/audit/sys-report-schedule.object.ts +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { ObjectSchema, Field } from '@objectstack/spec/data'; - -/** - * sys_report_schedule — Recurring Report Delivery - * - * Joins a `sys_saved_report` to an interval and a recipient list so - * the reports plugin can deliver "daily pipeline digest" / "weekly - * lead summary" without a separate workflow. - * - * Scheduling: `interval_minutes` (1440 = daily, 10080 = weekly) or a - * `cron_expression`. When a `cron_expression` is set it wins over - * `interval_minutes` and is evaluated in `timezone` (default UTC) via - * croner — so "every weekday 09:00 local" is expressible. `next_run_at` - * is computed from whichever applies. - * - * Delivery: when the master dispatch job ticks (every minute by - * default), every schedule with `next_run_at <= now` is loaded, - * its report is executed, the result is rendered into the report's - * `format`, and the rendered body is emailed to each address in - * `recipients`. `next_run_at` is then advanced by `interval_minutes`. - * - * Conventions: - * - `recipients` is a comma-separated list of RFC-5322 addresses to - * keep the schema driver-agnostic. The reports plugin splits and - * trims before handing the list to IEmailService. - * - `active=false` disables the schedule without losing its history. - * - * @namespace sys - */ -export const SysReportSchedule = ObjectSchema.create({ - name: 'sys_report_schedule', - label: 'Report Schedule', - pluralLabel: 'Report Schedules', - icon: 'clock', - isSystem: true, - managedBy: 'platform', - description: 'Recurring delivery of a sys_saved_report via email', - titleFormat: '{report_id} → {recipients}', - highlightFields: ['report_id', 'recipients', 'interval_minutes', 'active', 'next_run_at'], - - fields: { - id: Field.text({ - label: 'Schedule ID', - required: true, - readonly: true, - group: 'System', - }), - - report_id: Field.lookup('sys_saved_report', { - label: 'Report', - required: true, - group: 'Schedule', - }), - - name: Field.text({ - label: 'Name', - required: false, - maxLength: 200, - description: 'Optional label for the digest — used in the email subject', - group: 'Schedule', - }), - - interval_minutes: Field.number({ - label: 'Interval (minutes)', - required: false, - defaultValue: 1440, - description: 'How often to send (1440 = daily, 10080 = weekly)', - group: 'Schedule', - }), - - cron_expression: Field.text({ - label: 'Cron Expression', - required: false, - maxLength: 100, - description: 'Optional 5/6-field cron — overrides interval_minutes when present', - group: 'Schedule', - }), - - // [#15872] Validated on write by `valueDomain: 'iana_time_zone'` — the same - // declaration `sys_business_unit.timezone` / `sys_organization.timezone` - // carry (#14238), and the same shared `Intl.DateTimeFormat` probe, never the - // `Intl.supportedValuesOf('timeZone')` enumeration (which omits `UTC`, this - // column's own default). Written values only (the `min`/`max`/`maxLength` - // transition-gate class), so a stored non-member survives and no migration - // is owed. - // - // WHY THIS COLUMN IS THE SHARP ONE, measured on #15872: unlike - // `sys_job.timezone`, this value IS read back and handed to a scheduler. - // `ReportService.rowFromSchedule` lifts it off the row and `nextRunAt` calls - // `new Cron(cron, { timezone }).nextRun(from)`. croner (10.0.1) does not - // reject a non-member zone when it is constructed WITHOUT a callback — it - // throws from `nextRun()` — and `nextRunAt` CATCHES that throw and falls - // back to `from + interval_minutes`. So before this line, a typo'd zone on a - // cron schedule silently discarded the cron: an admin's "every weekday 09:00 - // Asia/Shanghai" became "every 1440 minutes, forever", logged only as - // `invalid cron ''` — a warning that names the wrong input, since the - // expression was fine. Neither a throw nor a fall back to UTC: the wrong - // instant, permanently, which is the outcome this card was told to escalate - // on. `scheduleReport`'s eager create-time guard did not catch it either; - // it constructed a callback-less `Cron` and so was blind to exactly this - // half of its own input. Refusing the write is what closes it HERE. - // - // [#16291] The reader's two halves are closed separately, and this line does - // not stand in for either: `scheduleReport` now consults - // `isValueDomainMember('iana_time_zone', …)` itself — this declaration's own - // predicate, so neither door can accept what the other refuses — and the - // sweep quarantines a row that was STORED before this line existed (it does - // not run it and does not advance `next_run_at`, and says so in - // `last_status` / `last_error`) rather than re-deriving a cadence from - // `interval_minutes` that nobody asked for. - // - // `maxLength: 64` and `defaultValue: 'UTC'` are BOTH unchanged. The bound is - // already the value #14238 justified (twice the domain's real ceiling: the - // enumeration's longest name is 30 characters on this Node baseline, the - // longest tzdb link 32). The default is a consumer semantic — this reader - // documents "default UTC" and falls back to `'UTC'` in four places — and is - // deliberately NOT converged with `sys_job`, which has none. - timezone: Field.text({ - label: 'Timezone', - required: false, - maxLength: 64, - defaultValue: 'UTC', - valueDomain: 'iana_time_zone', - group: 'Schedule', - }), - - active: Field.boolean({ - label: 'Active', - required: true, - defaultValue: true, - group: 'Schedule', - }), - - recipients: Field.text({ - label: 'Recipients', - required: true, - maxLength: 4000, - description: 'Comma-separated email addresses', - group: 'Delivery', - }), - - format: Field.select( - ['csv', 'html_table'], - { - label: 'Format', - required: false, - defaultValue: 'html_table', - description: 'Render format — csv is attached, html_table is inlined', - group: 'Delivery', - }, - ), - - subject_template: Field.text({ - label: 'Subject Template', - required: false, - maxLength: 200, - description: 'Email subject; {{name}} / {{date}} / {{rows}} are substituted', - group: 'Delivery', - }), - - owner_id: Field.lookup('sys_user', { - label: 'Owner', - required: false, - group: 'Provenance', - }), - - next_run_at: Field.datetime({ - label: 'Next Run', - required: false, - description: 'Dispatcher loads schedules where next_run_at <= now', - group: 'State', - }), - - last_sent_at: Field.datetime({ - label: 'Last Sent', - required: false, - group: 'State', - }), - - last_status: Field.select( - ['ok', 'failed', 'skipped'], - { - label: 'Last Status', - required: false, - group: 'State', - }, - ), - - last_error: Field.textarea({ - label: 'Last Error', - required: false, - group: 'State', - }), - - created_at: Field.datetime({ - label: 'Created At', - required: true, - defaultValue: 'NOW()', - readonly: true, - group: 'System', - }), - - updated_at: Field.datetime({ - label: 'Updated At', - required: false, - group: 'System', - }), - }, - - indexes: [ - // Hot path for the dispatch loop. - { fields: ['active', 'next_run_at'] }, - { fields: ['report_id'] }, - { fields: ['owner_id'] }, - ], -}); diff --git a/packages/platform-objects/src/audit/sys-saved-report.object.ts b/packages/platform-objects/src/audit/sys-saved-report.object.ts deleted file mode 100644 index 3962207ddf2..00000000000 --- a/packages/platform-objects/src/audit/sys-saved-report.object.ts +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { ObjectSchema, Field } from '@objectstack/spec/data'; - -/** - * sys_saved_report — Reusable Report Definition - * - * A persisted query against a single object that can be re-executed - * on demand or on a schedule. Acts as the bridge between ad-hoc - * filtering in the UI and the structured report library administrators - * curate. - * - * The query envelope (`query_json`) is the same shape ObjectQL accepts - * — `{ filter, fields, orderBy, limit, groupBy }` — so a report can be - * round-tripped between the list view and the report definition - * without re-translation. - * - * Conventions: - * - `object_name` is the short object the report queries. - * - `format` controls how `IReportService.run()` renders the rows - * (`csv` for downloads, `html_table` for email digests, `json` - * for raw API consumption). - * - * @namespace sys - */ -export const SysSavedReport = ObjectSchema.create({ - name: 'sys_saved_report', - label: 'Saved Report', - pluralLabel: 'Saved Reports', - icon: 'bar-chart', - isSystem: true, - managedBy: 'platform', - description: 'Persisted ObjectQL report definition — re-runnable and schedulable', - displayNameField: 'name', - nameField: 'name', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField) - titleFormat: '{name}', - highlightFields: ['name', 'object_name', 'format', 'owner_id', 'updated_at'], - - fields: { - id: Field.text({ - label: 'Report ID', - required: true, - readonly: true, - group: 'System', - }), - - name: Field.text({ - label: 'Name', - required: true, - maxLength: 200, - searchable: true, - group: 'Definition', - }), - - description: Field.textarea({ - label: 'Description', - required: false, - group: 'Definition', - }), - - object_name: Field.text({ - label: 'Object', - required: true, - maxLength: 100, - description: 'Short object name the report queries', - group: 'Definition', - }), - - query_json: Field.textarea({ - label: 'Query', - required: true, - description: 'ObjectQL query envelope — { filter, fields, orderBy, limit, groupBy }', - group: 'Definition', - }), - - format: Field.select( - ['csv', 'json', 'html_table'], - { - label: 'Format', - required: true, - defaultValue: 'csv', - description: 'Rendering used by IReportService.run() and email digests', - group: 'Definition', - }, - ), - - owner_id: Field.lookup('sys_user', { - label: 'Owner', - required: false, - description: 'User that owns the report definition (drives sharing)', - group: 'Provenance', - }), - - last_run_at: Field.datetime({ - label: 'Last Run', - required: false, - description: 'Stamped by IReportService.run() on successful execution', - group: 'State', - }), - - last_row_count: Field.number({ - label: 'Last Row Count', - required: false, - group: 'State', - }), - - created_at: Field.datetime({ - label: 'Created At', - required: true, - defaultValue: 'NOW()', - readonly: true, - group: 'System', - }), - - updated_at: Field.datetime({ - label: 'Updated At', - required: false, - group: 'System', - }), - }, - - indexes: [ - { fields: ['object_name'] }, - { fields: ['owner_id'] }, - { fields: ['name'] }, - ], -}); diff --git a/packages/platform-objects/src/identity/sys-business-unit.object.ts b/packages/platform-objects/src/identity/sys-business-unit.object.ts index 19d606670fb..82b3ff9066e 100644 --- a/packages/platform-objects/src/identity/sys-business-unit.object.ts +++ b/packages/platform-objects/src/identity/sys-business-unit.object.ts @@ -162,9 +162,9 @@ export const SysBusinessUnit = ObjectSchema.create({ // the ruling's own precondition, 「rather than shipping an unvalidated text // column」): membership is the shared `Intl.DateTimeFormat` probe, never // the `Intl.supportedValuesOf('timeZone')` enumeration, which omits `UTC` - // — the very fallback this contract names. `maxLength: 64` follows - // `sys_report_schedule.timezone`, the platform's other IANA column that - // pairs a bound with the `UTC` default; the enumeration's longest name on + // — the very fallback this contract names. `maxLength: 64` followed + // `sys_report_schedule.timezone` (retired since, #20102), then the + // platform's other IANA column pairing a bound with the `UTC` default; the enumeration's longest name on // the repo's Node baseline is 30 characters and the tzdb caps each path // component at 14, so 64 is twice the domain's real ceiling and the smaller // of the two precedents (`sys_job.timezone` still says 100 — #15872 gave diff --git a/packages/plugins/plugin-reports/CHANGELOG.md b/packages/plugins/plugin-reports/CHANGELOG.md deleted file mode 100644 index ba4c503ddc5..00000000000 --- a/packages/plugins/plugin-reports/CHANGELOG.md +++ /dev/null @@ -1,4368 +0,0 @@ -# @objectstack/plugin-reports - -## 17.4.0 - -### Minor Changes - -- afa3a26: fix(plugin-reports)!: a non-member schedule `timezone` no longer discards the cron expression, and a schedule already holding one stops instead of firing on a cadence nobody asked for (#16291) - - **BREAKING** for a deployment that already stores a report schedule with a cron expression and a `timezone` that is not an IANA member. Such a schedule is delivering today, on the wrong cadence; after this change it does not deliver at all until a human corrects the zone. It ships as `minor` under the lockstep launch-window convention (`scripts/check-changeset-no-major.mjs` refuses `major`); the version number is not the signal here, this entry is. - - - - ## What an upgrading operator has to do, and how to find out - - If `sys_report_schedule` holds a row whose `timezone` is not a real IANA zone **and** whose `cron_expression` is set, the sweep now marks it `last_status: 'failed'` with a `last_error` naming the zone, and stops running it. Correct the `timezone` on that row; the schedule resumes on the next sweep with no re-enable and no second action, because `active` and the past `next_run_at` are deliberately left alone. - - Only rows written **before** `valueDomain: 'iana_time_zone'` landed on that column can be in this state, and the set cannot grow: measured on a real kernel with a real SQLite driver, `insert` into `sys_report_schedule` with `timezone: 'Mars/Olympus'` is already refused today — `VALIDATION_FAILED · Timezone must be a valid IANA time zone identifier, e.g. Europe/Zurich (got "Mars/Olympus")`. A set that cannot grow is still not an empty one, which is why this carries a banner rather than a shrug. - - ## The defect - - croner (10.0.1) answers a non-member zone in three different ways, and only the middle one was ever reached here: `new Cron(expr, { timezone })` **without a callback** validates the expression and lets any zone through, `nextRun()` on that instance then throws a `CronDate` conversion `TypeError`, and the callback form throws at construction. `scheduleReport`'s eager guard used the callback-less form, so the timezone half of its own input passed straight under a guard whose stated purpose was "a clear error at schedule time instead of a schedule that silently falls back to interval on sweep" — and `nextRunAt` caught that deferred throw and returned `from + interval_minutes`. A schedule authored as "every weekday 09:00 Asia/Shanghai" became "every 1440 minutes, forever", re-derived on every sweep, logged only as a complaint about a cron expression that was perfectly good. - - ## What changed - - - **The create-time guard now asks the right question.** `scheduleReport` consults `isValueDomainMember('iana_time_zone', …)` from `@objectstack/spec/shared` — the same predicate `sys_report_schedule.timezone`'s `valueDomain` declaration enforces on write — and refuses a non-member with `VALIDATION_FAILED: invalid timezone '': not a member of the 'iana_time_zone' value domain`. One answer at both doors, so this one cannot accept what the storage door refuses; it says so earlier and names the input that is actually wrong. It applies whether or not a `cron_expression` is set, because the storage gate does too. **This is not what makes the change breaking:** the storage door already refuses the same value today, so no reachable accept set narrows — what moves is which door answers and how clearly. - - **The row now stores the string the scheduler evaluates.** An empty `timezone` was stored verbatim while every `new Cron` call site read it as `UTC`; it is normalised to `UTC` on the way in. - - **A schedule already holding an unusable zone is stopped, not rescheduled.** It is not run and its `next_run_at` is not advanced; `last_status` / `last_error` carry the reason. Repairing the value automatically was rejected: the intended zone is not recoverable from a typo, and rewriting it to `UTC` would deliver at yet another set of wrong instants while the row looked healthy. Interval-only schedules are untouched — interval arithmetic never consults the zone, so a legacy bad value there still delivers on the cadence its author asked for. - - **Both fall-back warnings name both inputs.** The "no next occurrence" and the former "invalid cron" lines each mentioned only the expression, so either of them on a timezone fault sent an investigator to audit the half that was fine. They now carry the expression *and* the zone, and the second no longer asserts the expression is the broken one. - -### Patch Changes - -- be92d46: These fourteen packages now declare `repository.directory`, so their npm pages carry a working "source" deep link to their own directory in the monorepo. - - npm renders that field by concatenating it onto `repository.url`. None of these fourteen manifests carried a `repository` block at all, so every one of their npm pages offered no route from the package back to its code — not a broken link, no link. That is what this publishes: the block those pages read, naming each package's own directory. - - Nothing else about these packages changes. No export, no runtime behaviour, no dependency and no file in the tarball other than the manifest's own `repository` key. The version bump exists because the fix is only real once it is published: the field lives in the manifest npm serves, so a corrected manifest sitting in the repository leaves the package page exactly as wrong as it was. - - The rule behind it is now mechanical rather than remembered — `check:manifest-repository-directory` makes a publishable (non-private) workspace manifest declare the field naming its own directory, so a package added or moved after this cannot quietly go back to having no source link. -- Updated dependencies [fe0d9a4] -- Updated dependencies [ecd2158] -- Updated dependencies [f2b5e46] -- Updated dependencies [2ed6be6] -- Updated dependencies [ed7243d] -- Updated dependencies [6ba0db4] -- Updated dependencies [625b0c3] -- Updated dependencies [233222e] -- Updated dependencies [07f40e5] -- Updated dependencies [ceb4877] -- Updated dependencies [e9fcd6b] -- Updated dependencies [90e7e6d] -- Updated dependencies [2bdabe6] -- Updated dependencies [ca326b5] -- Updated dependencies [8f404a5] -- Updated dependencies [159dbad] -- Updated dependencies [68437d4] -- Updated dependencies [abb140c] -- Updated dependencies [8333a6c] -- Updated dependencies [3e3ecb0] -- Updated dependencies [3030369] -- Updated dependencies [d5d8d50] -- Updated dependencies [e08892d] -- Updated dependencies [ae05f2e] -- Updated dependencies [b548e43] -- Updated dependencies [c463d03] -- Updated dependencies [64bd6a3] -- Updated dependencies [13c48c2] -- Updated dependencies [b0529e1] -- Updated dependencies [66dc6ab] -- Updated dependencies [6f94458] -- Updated dependencies [6e67b86] -- Updated dependencies [132742f] -- Updated dependencies [85a2459] -- Updated dependencies [50dc214] -- Updated dependencies [e89fa92] -- Updated dependencies [e9fcd6b] -- Updated dependencies [8976ea1] -- Updated dependencies [56fe8c2] -- Updated dependencies [acabd24] -- Updated dependencies [ab50c8f] -- Updated dependencies [6491463] -- Updated dependencies [89cf4d6] -- Updated dependencies [21c5dcb] -- Updated dependencies [6d4d5d3] -- Updated dependencies [ed5d557] -- Updated dependencies [bca21f7] -- Updated dependencies [e9fcd6b] -- Updated dependencies [2025b1f] -- Updated dependencies [1a7a7c9] -- Updated dependencies [e9fcd6b] -- Updated dependencies [cbca47d] -- Updated dependencies [acf4d38] -- Updated dependencies [ef3a138] -- Updated dependencies [68d5dfd] -- Updated dependencies [3e21cf0] -- Updated dependencies [4cfc93b] -- Updated dependencies [efd6b43] -- Updated dependencies [859ded3] -- Updated dependencies [fa125f3] -- Updated dependencies [74628d9] -- Updated dependencies [a646120] -- Updated dependencies [6f1ce7d] -- Updated dependencies [7778115] -- Updated dependencies [2c753fe] -- Updated dependencies [52804cd] -- Updated dependencies [3f89967] -- Updated dependencies [53cf263] -- Updated dependencies [21aabbc] -- Updated dependencies [9c270bb] -- Updated dependencies [76c8c5a] -- Updated dependencies [a84e1ce] -- Updated dependencies [bf1054a] -- Updated dependencies [d8d2776] -- Updated dependencies [222dc0f] -- Updated dependencies [e9fcd6b] -- Updated dependencies [32c917d] -- Updated dependencies [f9a3c32] -- Updated dependencies [f502898] -- Updated dependencies [51ae731] -- Updated dependencies [af7edfe] -- Updated dependencies [b60f48b] -- Updated dependencies [c78c918] -- Updated dependencies [4ca358d] -- Updated dependencies [cf9bda4] -- Updated dependencies [784cb92] -- Updated dependencies [7629f4d] -- Updated dependencies [51df9fd] -- Updated dependencies [a7da4de] -- Updated dependencies [de0bcdd] -- Updated dependencies [70f7d6d] -- Updated dependencies [c677cda] -- Updated dependencies [6acb37e] -- Updated dependencies [7797102] -- Updated dependencies [554a160] -- Updated dependencies [f7da71e] -- Updated dependencies [7f745c3] -- Updated dependencies [0a038cc] -- Updated dependencies [e9fcd6b] -- Updated dependencies [97adce2] -- Updated dependencies [a83482c] -- Updated dependencies [5eb24f8] -- Updated dependencies [2a3decc] -- Updated dependencies [cc00df2] -- Updated dependencies [cc00df2] -- Updated dependencies [f4e6adf] -- Updated dependencies [ee4a59b] -- Updated dependencies [4db3c61] -- Updated dependencies [5ca314a] -- Updated dependencies [e0af1a8] -- Updated dependencies [4771bd9] -- Updated dependencies [414c1fc] -- Updated dependencies [22c0279] -- Updated dependencies [c930f85] -- Updated dependencies [0db2947] -- Updated dependencies [92b5d7f] -- Updated dependencies [613bfbd] -- Updated dependencies [abae16a] -- Updated dependencies [094b8fd] -- Updated dependencies [c7aca0d] -- Updated dependencies [c1d8f98] -- Updated dependencies [8e0b297] -- Updated dependencies [d4f9b2a] -- Updated dependencies [5f7fa1d] -- Updated dependencies [87f0ccc] -- Updated dependencies [aedbaef] -- Updated dependencies [a727043] -- Updated dependencies [c5d6803] -- Updated dependencies [10d05bb] -- Updated dependencies [69602e5] -- Updated dependencies [c3ce76c] -- Updated dependencies [7936b29] -- Updated dependencies [46803fa] -- Updated dependencies [c2a336c] -- Updated dependencies [9f890d3] -- Updated dependencies [0bb2318] -- Updated dependencies [f7db8f4] -- Updated dependencies [1ecee3e] -- Updated dependencies [9408b7f] -- Updated dependencies [2bb0614] -- Updated dependencies [b3820c3] -- Updated dependencies [e9fcd6b] -- Updated dependencies [9bcd9be] -- Updated dependencies [b398ad2] -- Updated dependencies [99261a7] -- Updated dependencies [81b426f] -- Updated dependencies [001af1c] -- Updated dependencies [fb77aa5] -- Updated dependencies [581d8f8] -- Updated dependencies [f81afe3] -- Updated dependencies [40a44b9] -- Updated dependencies [f89812e] -- Updated dependencies [7a7fb03] -- Updated dependencies [8fd246d] -- Updated dependencies [021a735] -- Updated dependencies [7bdb163] - - @objectstack/spec@17.4.0 - - @objectstack/core@17.4.0 - - @objectstack/platform-objects@17.4.0 - -## 17.3.0 - -### Patch Changes - -- ad54eb3: feat(tooling): onboard all 14 `packages/plugins/**` packages into `check:test-typecheck` (#14062) - - Every plugin package now has a `tsconfig.test.json` compiled by the shared - `check:test-typecheck` gate, and its `typecheck` script names it. Before this, - the shrink-only `test-typecheck-debt.json` ratchet said **nothing** about a - third of the repo's runtime surface: 14 packages, 1 `tsconfig.test.json` - (`plugin-security`, wired directly to `tsc` rather than to the instrument), and - 0 `check:test-typecheck` scripts. - - Onboarded as a family by the director ruling of 2026-09-01 on #14062 - (maintainer verbatim: 「同意」), which also carries the #5286 maintainer - authority the starting ledgers need. The smaller branch triage recommended — - declare the instrument's scope and re-site the two compile-time pins — was - recorded as considered and not taken: an instrument silent over a third of the - runtime surface is a hole readers generalise across, and that costs more than - fourteen tsconfigs. - - **Measured, not assumed** (at `e80889095`, workspace closure built first). Four - packages carry residue and therefore a starting ledger — plugin-approvals 324 - over 8 files, plugin-auth 94 over 10, plugin-sharing 3 over 2, - knowledge-ragflow 3 over 1. The other ten measure **zero** and deliberately get - no ledger file at all: the gate reads a missing ledger as `{ entries: {} }`, so - any error there is red immediately with no entry to be added to — strictly - stronger than a ledger holding nothing, and the call `plugin-security` had - already recorded for itself. - - ⛔ **This does not repair 345 type errors.** Per ruling item 3 it makes the - ratchet able to *see* them; paydown follows the ratchet's own shrink-only - discipline on its own cards. No test file is edited here. - - Two corrections to the finding's own prose, both measured: the exclusion is - narrower than "no plugin package compiles its tests" — 9 of the 14 already - compiled their tests inside the `typecheck`-invoked build config, at zero - errors — and `exec-context-annotation.pin.ts` is a `.pin.ts`, which - `**/*.test.ts` never excluded, so its directives were already live. The pin - this change genuinely makes real is - `plugin-approvals/src/manager-org-screen-parity.contract.test.ts`, which no tsc - program had ever read. -- a1c804b: Report CSV and HTML exports now infer their columns from **every** row of the result set, not from the first 50. Column inference sampled `rows.slice(0, 50)` while the projection it produced was applied to all rows, so when a report declared no explicit `query.fields`, any key whose first occurrence fell at row 51 or later was absent from the header *and* dropped from every row that carried it. The export gave no signal: the CSV was well-formed, every row had the same arity, and nothing marked a column as inferred rather than declared, so a recipient of a scheduled report attachment could not tell. Sparse columns are the normal shape of report output — an optional field, a formula only some records satisfy, a lookup that resolves for a subset — and the sampled prefix is the query's first page in its own `orderBy`, so for a report sorted by status or created date the sample correlated with exactly the column it dropped. Both affected renderers are fixed (`csv`, which is also the `default:` format branch, and `html_table`); `json` was never affected. Already-inferred columns keep their position and late-appearing ones are appended, so an export that was correct before is byte-identical now. -- Updated dependencies [809d417] -- Updated dependencies [387e231] -- Updated dependencies [f794e4e] -- Updated dependencies [cae2169] -- Updated dependencies [b812a54] -- Updated dependencies [2d4fa75] -- Updated dependencies [0e4e51b] -- Updated dependencies [e84bbf6] -- Updated dependencies [effae80] -- Updated dependencies [efb3513] -- Updated dependencies [d62f990] -- Updated dependencies [c45d8e6] -- Updated dependencies [2e3e8c7] -- Updated dependencies [e621291] -- Updated dependencies [655b106] -- Updated dependencies [40a93b5] -- Updated dependencies [d5b330d] -- Updated dependencies [dda969c] -- Updated dependencies [1f45690] -- Updated dependencies [277948f] -- Updated dependencies [8bdd955] -- Updated dependencies [f3bbbef] -- Updated dependencies [4f24e9d] -- Updated dependencies [e27583e] -- Updated dependencies [4bd6faa] -- Updated dependencies [86cbe37] -- Updated dependencies [6a180e4] -- Updated dependencies [474242f] -- Updated dependencies [63cd487] -- Updated dependencies [bd4aa4e] -- Updated dependencies [803eaab] -- Updated dependencies [f8e8f03] -- Updated dependencies [983edf1] -- Updated dependencies [eae824e] -- Updated dependencies [f6fa22c] -- Updated dependencies [8a483b3] -- Updated dependencies [3bc2e38] -- Updated dependencies [97bcd99] -- Updated dependencies [df59de0] -- Updated dependencies [96e25a8] -- Updated dependencies [f75a38a] -- Updated dependencies [7a25e7d] -- Updated dependencies [1fa05a6] -- Updated dependencies [c85a265] -- Updated dependencies [dcb10a5] -- Updated dependencies [773a999] -- Updated dependencies [35dffea] -- Updated dependencies [d8024f0] -- Updated dependencies [8120808] -- Updated dependencies [776a098] -- Updated dependencies [5060877] -- Updated dependencies [4f6325d] -- Updated dependencies [52954c0] -- Updated dependencies [2aa8456] -- Updated dependencies [93809a3] -- Updated dependencies [7c0d0c3] -- Updated dependencies [daae7aa] -- Updated dependencies [8dc22d6] -- Updated dependencies [a392dbf] -- Updated dependencies [279431e] -- Updated dependencies [948dd6b] -- Updated dependencies [3b4c56c] -- Updated dependencies [ae8edd2] -- Updated dependencies [e25403c] -- Updated dependencies [64baa68] -- Updated dependencies [9fa70d7] -- Updated dependencies [09db64a] -- Updated dependencies [92916e7] -- Updated dependencies [a84f3ea] -- Updated dependencies [f2eaae8] -- Updated dependencies [c09451b] -- Updated dependencies [ba64877] -- Updated dependencies [7345308] -- Updated dependencies [79b6a22] -- Updated dependencies [30d96ab] -- Updated dependencies [f658793] -- Updated dependencies [c95ad19] -- Updated dependencies [e58ea8b] -- Updated dependencies [4a17645] -- Updated dependencies [3795c5f] -- Updated dependencies [8ab926b] -- Updated dependencies [7317cf2] -- Updated dependencies [e25e839] -- Updated dependencies [5997207] -- Updated dependencies [8b13cc8] -- Updated dependencies [4a4a35d] -- Updated dependencies [86e765a] -- Updated dependencies [1d7e76a] -- Updated dependencies [53dc739] -- Updated dependencies [fd289be] -- Updated dependencies [03bf7b1] -- Updated dependencies [f90e820] -- Updated dependencies [18d816a] -- Updated dependencies [e8bd715] -- Updated dependencies [b91c351] -- Updated dependencies [a28a3c0] -- Updated dependencies [daeaaf9] -- Updated dependencies [c459da6] -- Updated dependencies [e914733] -- Updated dependencies [f887e52] -- Updated dependencies [881f8d8] -- Updated dependencies [3bfa1e6] -- Updated dependencies [0a8ebf3] -- Updated dependencies [901355c] -- Updated dependencies [34ce8e7] -- Updated dependencies [33681ea] -- Updated dependencies [4635f3e] -- Updated dependencies [fd289be] -- Updated dependencies [ee3595c] -- Updated dependencies [09b4f4e] -- Updated dependencies [b2eab95] -- Updated dependencies [93940d4] -- Updated dependencies [3a04b01] -- Updated dependencies [45b9051] -- Updated dependencies [3954fb7] -- Updated dependencies [4805b56] -- Updated dependencies [b9e9227] -- Updated dependencies [d395692] -- Updated dependencies [5894d30] -- Updated dependencies [a3765f6] -- Updated dependencies [2d5cee3] -- Updated dependencies [e22158f] -- Updated dependencies [7404925] -- Updated dependencies [0c2334f] -- Updated dependencies [778c59f] -- Updated dependencies [d2619fd] -- Updated dependencies [af56546] -- Updated dependencies [6acb11a] -- Updated dependencies [33c5fd3] -- Updated dependencies [20b0fdb] -- Updated dependencies [905019b] -- Updated dependencies [a286411] -- Updated dependencies [98c0d33] -- Updated dependencies [368a82e] -- Updated dependencies [a3d5724] -- Updated dependencies [93ea19b] -- Updated dependencies [9ee2dcf] -- Updated dependencies [8cb96ec] -- Updated dependencies [8f10a79] -- Updated dependencies [6269a55] -- Updated dependencies [a17da05] -- Updated dependencies [a8c00e2] -- Updated dependencies [0fb8760] -- Updated dependencies [e5ce2ed] -- Updated dependencies [be21955] -- Updated dependencies [bc56e18] -- Updated dependencies [be21955] -- Updated dependencies [a9ee989] -- Updated dependencies [4d0d944] -- Updated dependencies [15d58db] -- Updated dependencies [d63b014] -- Updated dependencies [9abe4e4] -- Updated dependencies [2cc7122] -- Updated dependencies [50d6c92] -- Updated dependencies [9e0ba21] -- Updated dependencies [311433f] -- Updated dependencies [3e5ad08] -- Updated dependencies [9abe4e4] -- Updated dependencies [b7131f3] -- Updated dependencies [e5812fa] -- Updated dependencies [7085f90] -- Updated dependencies [dee4dd4] -- Updated dependencies [ce7e497] -- Updated dependencies [51ecb2f] -- Updated dependencies [9086761] -- Updated dependencies [f6344e7] -- Updated dependencies [42a117b] -- Updated dependencies [1401ae7] -- Updated dependencies [4297fe7] -- Updated dependencies [e398863] -- Updated dependencies [d16df74] -- Updated dependencies [d79c602] -- Updated dependencies [f11fc61] -- Updated dependencies [e808890] -- Updated dependencies [8f79379] -- Updated dependencies [e6ca40e] -- Updated dependencies [0c77ea4] -- Updated dependencies [52954c0] -- Updated dependencies [89eb997] -- Updated dependencies [7131f12] -- Updated dependencies [aa5994e] -- Updated dependencies [be93457] -- Updated dependencies [a65db76] -- Updated dependencies [15eb2c9] -- Updated dependencies [5691b07] -- Updated dependencies [2a6122b] -- Updated dependencies [225e769] -- Updated dependencies [8af88dd] -- Updated dependencies [fb5fbb8] -- Updated dependencies [d7b3963] -- Updated dependencies [33184fd] -- Updated dependencies [7c41693] -- Updated dependencies [b72db01] -- Updated dependencies [dce5cd4] -- Updated dependencies [9688f58] -- Updated dependencies [556ebc1] -- Updated dependencies [177ebdc] -- Updated dependencies [8d237b4] -- Updated dependencies [2d2e6f0] -- Updated dependencies [2d8dd8d] -- Updated dependencies [22d573e] -- Updated dependencies [b5a2398] -- Updated dependencies [348860c] -- Updated dependencies [5383fa6] -- Updated dependencies [5b3ff63] -- Updated dependencies [1a6a19c] -- Updated dependencies [064d484] -- Updated dependencies [527e050] -- Updated dependencies [dd33bf9] -- Updated dependencies [4cb2a90] -- Updated dependencies [74a7804] -- Updated dependencies [53d3689] -- Updated dependencies [b3a63d3] -- Updated dependencies [49f0dcf] -- Updated dependencies [033a34c] -- Updated dependencies [4d25d22] -- Updated dependencies [1ffee51] -- Updated dependencies [5ae4303] -- Updated dependencies [ece4dad] -- Updated dependencies [e9b377e] -- Updated dependencies [146f448] -- Updated dependencies [735f5c7] -- Updated dependencies [a7e18de] -- Updated dependencies [366f895] -- Updated dependencies [dc75ba8] -- Updated dependencies [cce0aa9] -- Updated dependencies [e764507] -- Updated dependencies [cff17af] -- Updated dependencies [39404f3] -- Updated dependencies [ca1965f] -- Updated dependencies [8619f95] -- Updated dependencies [b706af9] -- Updated dependencies [add4360] -- Updated dependencies [e0abc38] -- Updated dependencies [fc9ba76] -- Updated dependencies [0f94cc7] -- Updated dependencies [a11c1a5] -- Updated dependencies [71f9cd1] -- Updated dependencies [ee17d86] -- Updated dependencies [cdbd920] -- Updated dependencies [18c432e] -- Updated dependencies [3c418c4] -- Updated dependencies [fa8715a] -- Updated dependencies [a933ed7] -- Updated dependencies [b3ca463] -- Updated dependencies [a933ed7] -- Updated dependencies [0d4a6a8] -- Updated dependencies [518d5e5] -- Updated dependencies [6643ba1] -- Updated dependencies [eeba2ef] -- Updated dependencies [ec4c4d2] -- Updated dependencies [424f73c] -- Updated dependencies [cccbe51] -- Updated dependencies [a8d6b1d] -- Updated dependencies [e4a7695] -- Updated dependencies [87075b1] -- Updated dependencies [fc58a99] -- Updated dependencies [14cfc00] -- Updated dependencies [1c6f7b4] -- Updated dependencies [e854a53] -- Updated dependencies [dfebfc8] -- Updated dependencies [598b7ec] -- Updated dependencies [d028b37] -- Updated dependencies [f7b25c5] -- Updated dependencies [122ef38] -- Updated dependencies [4a37870] -- Updated dependencies [428f9b2] -- Updated dependencies [aa7ff56] -- Updated dependencies [811a3c2] -- Updated dependencies [1401ae7] -- Updated dependencies [2fd3f1c] -- Updated dependencies [c41b42e] -- Updated dependencies [c4db311] -- Updated dependencies [750fff5] -- Updated dependencies [c19035e] -- Updated dependencies [ececf7a] -- Updated dependencies [d173125] -- Updated dependencies [8eeca27] -- Updated dependencies [8425c17] -- Updated dependencies [a5ef1d8] -- Updated dependencies [772d5de] -- Updated dependencies [ce80ec2] -- Updated dependencies [b372318] -- Updated dependencies [97a2263] -- Updated dependencies [29d0676] -- Updated dependencies [0169d49] -- Updated dependencies [6bd3231] -- Updated dependencies [d2b5ba8] -- Updated dependencies [b799ac5] -- Updated dependencies [8f74307] -- Updated dependencies [d23dc08] -- Updated dependencies [644ad50] -- Updated dependencies [0da7cd2] -- Updated dependencies [28a5c3e] -- Updated dependencies [4bc18e5] -- Updated dependencies [9f57f1e] - - @objectstack/spec@17.3.0 - - @objectstack/platform-objects@17.3.0 - - @objectstack/core@17.3.0 - -## 17.2.0 - -### Minor Changes - -- e222a53: **BREAKING** (compile-time only): twelve logger sink types that declared an - optional `error` now declare a **non-optional** `warn`, so a durability report - always has somewhere to land (#9754, #10556). - - `minor`, not `major`: during the launch window this stack ships breaking changes - as `minor` — every publishable package versions in lockstep, so a `major` would - promote the whole release. `patch` would be wrong in the other direction, because - this *can* break a consumer's build. - - `error` stays optional on every one of these types — hosts legitimately inject - reduced sinks, and requiring `error` was measured and rejected as #9754 option C. - What changes is that its *absence* now has a declared, guaranteed destination. - Call sites keep the `logger?.warn?.(…)` spelling as the backstop for hosts the - type cannot reach, so **no runtime behaviour changes**: nothing that printed - before stops printing, and nothing silent starts printing. - - ### Who has to change, and what to do - - Only a caller that hands one of these sinks an object with **no `warn` method** — - for example `{ info }` or `{ error }` alone. Add a `warn` member; there is no - rename, no removal, and no stored value or metadata key to rewrite. Every - construction site inside this repo already supplied one, so the in-repo cost was - zero; the compile error is reserved for the callers that were silently discarding - these reports. - - The affected types, by package: - - - `@objectstack/cloud-connection` — the internal `PluginContext['logger']` - - `@objectstack/metadata-protocol` — `IndexMigrationLogger` - - `@objectstack/plugin-approvals` — the internal `MinimalLogger` of `lifecycle-hooks` - - `@objectstack/plugin-audit` — `AuthEventAuditLogger`, `ReadAuditLogger` - - `@objectstack/plugin-auth` — `ReconcileMembershipDeps['logger']`, the internal - `LoggerLike` of `member-role-canonical`, and `AuthManagerOptions['logger']` - - `@objectstack/plugin-email` — `ReclaimLogger`, via `ReclaimAttachmentContentOptions` - - `@objectstack/plugin-reports` — `ReportServiceOptions['logger']` - - `@objectstack/plugin-sharing` — the internal `MinimalLogger` of `bulk-recompute`, - `rule-hooks` and `record-share-cascade` - - `@objectstack/plugin-webhooks` — `OptionalLogger`, via `AutoEnqueuerOptions` - - `@objectstack/service-knowledge` — `KnowledgeLogger` - - `AuthManagerOptions['logger']` is the one most likely to be reached from outside: - `AuthManager` is public surface, its `logger` option stays optional, and a logger - that *is* supplied must now carry `warn`. The only non-test construction site in - this repo passes the kernel `Logger`, whose `warn` is already required. - - `ReportService` and `AutoEnqueuer` additionally stopped defaulting their logger - field to `{}`. The field is now honestly optional rather than holding an empty - object that declared it could report and discarded everything. Behaviour is - unchanged in both directions. - - - -### Patch Changes - -- 6d5c4fa: Release these plugins' resources from `destroy()`, the teardown hook the kernel - actually calls (#10371). `Plugin` declares `init()`, `start?(ctx)` and - `destroy?()` — and no `stop()` — so `ObjectKernel.performShutdown()` and - `LiteKernel.destroy()`, which walk the plugins in reverse calling - `plugin.destroy()`, walked straight past every plugin whose teardown was spelled - `stop()`. `await kernel.shutdown()` resolved with the reports dispatcher still - armed, the REST/OpenAPI/Slack connectors still registered on the automation - engine, the approvals SLA escalation job still scheduled, and the knowledge - event-sync subscription still open. - - Each teardown body now lives in `destroy()`. `stop()` is retained as a - delegating alias with its parameter made optional, so an embedder that learned - to call it directly — precisely because the kernel never did — keeps working - unchanged. No export is removed and the `Plugin` interface is untouched. - - Same defect as #9371 in `@objectstack/service-messaging`, which surfaced as - fully green test runs exiting 1 on `EnvironmentTeardownError` and being evicted - from the merge queue. -- Updated dependencies [8f04d9a] -- Updated dependencies [6936d07] -- Updated dependencies [59eb04d] -- Updated dependencies [9f05b7d] -- Updated dependencies [3b2af5e] -- Updated dependencies [7d2d112] -- Updated dependencies [5fa0d72] -- Updated dependencies [02b3b07] -- Updated dependencies [914c413] -- Updated dependencies [55809a0] -- Updated dependencies [ee2ff45] -- Updated dependencies [47cd3ec] -- Updated dependencies [52db1d1] -- Updated dependencies [5649efb] -- Updated dependencies [9d7d2de] -- Updated dependencies [c815c50] -- Updated dependencies [795ea05] -- Updated dependencies [2306a76] -- Updated dependencies [e5ea701] -- Updated dependencies [a40dcc1] -- Updated dependencies [def0d3e] -- Updated dependencies [8d0bb79] -- Updated dependencies [5acb58d] -- Updated dependencies [2e3cf95] -- Updated dependencies [4c93387] -- Updated dependencies [504c8d5] -- Updated dependencies [a037f7c] -- Updated dependencies [3ee8ddf] -- Updated dependencies [16cef97] -- Updated dependencies [a79bd35] -- Updated dependencies [6ceaa4b] -- Updated dependencies [15ea214] -- Updated dependencies [de19489] -- Updated dependencies [c684d00] -- Updated dependencies [923c424] -- Updated dependencies [0ab81d1] -- Updated dependencies [1ec36b7] -- Updated dependencies [5f2e54c] -- Updated dependencies [189373b] -- Updated dependencies [35ad101] -- Updated dependencies [ceb33a9] -- Updated dependencies [dccbcec] -- Updated dependencies [73d9795] -- Updated dependencies [8012960] -- Updated dependencies [266654d] -- Updated dependencies [f34f56b] -- Updated dependencies [f399618] -- Updated dependencies [75e9301] -- Updated dependencies [2810695] - - @objectstack/platform-objects@17.2.0 - - @objectstack/spec@17.2.0 - - @objectstack/core@17.2.0 - -## 17.1.0 - -### Patch Changes - -- Updated dependencies [56656aa] -- Updated dependencies [c9f5950] -- Updated dependencies [d6e80b2] -- Updated dependencies [07e630e] -- Updated dependencies [66beee0] -- Updated dependencies [2f65b1b] -- Updated dependencies [720ee95] -- Updated dependencies [f287435] -- Updated dependencies [2782805] -- Updated dependencies [e43d63a] -- Updated dependencies [9aa8890] -- Updated dependencies [7c9c1dd] -- Updated dependencies [03520eb] -- Updated dependencies [75b7c24] -- Updated dependencies [d5552ca] -- Updated dependencies [d9813a9] -- Updated dependencies [8640fb2] -- Updated dependencies [2420641] -- Updated dependencies [2ad91c3] -- Updated dependencies [f57fb38] -- Updated dependencies [00777a0] -- Updated dependencies [d491625] -- Updated dependencies [420804d] -- Updated dependencies [716ac9b] -- Updated dependencies [a38408a] -- Updated dependencies [62b1427] -- Updated dependencies [7ea1372] -- Updated dependencies [23abe27] -- Updated dependencies [985a9cd] -- Updated dependencies [5f5e234] -- Updated dependencies [a8189ae] -- Updated dependencies [26e70fb] -- Updated dependencies [42b05af] -- Updated dependencies [2b292ce] -- Updated dependencies [abcf853] -- Updated dependencies [8b9eba5] -- Updated dependencies [d575779] -- Updated dependencies [94f7ef8] -- Updated dependencies [c5ac5e4] -- Updated dependencies [a777944] -- Updated dependencies [dd88e1c] -- Updated dependencies [856527c] -- Updated dependencies [870f710] -- Updated dependencies [79c46da] -- Updated dependencies [7ff3975] -- Updated dependencies [29d055b] -- Updated dependencies [65589d6] -- Updated dependencies [2c86fe3] -- Updated dependencies [e196c6a] -- Updated dependencies [24173e9] -- Updated dependencies [4ab7523] -- Updated dependencies [19539b4] -- Updated dependencies [f8eb736] -- Updated dependencies [11b779e] -- Updated dependencies [739fe5b] -- Updated dependencies [4bfe1a5] -- Updated dependencies [2065e31] -- Updated dependencies [b69d0f5] -- Updated dependencies [4d47afe] -- Updated dependencies [e4e5c6e] -- Updated dependencies [9a56784] -- Updated dependencies [d00d2f6] -- Updated dependencies [df0c12d] -- Updated dependencies [d31785f] -- Updated dependencies [c308a4f] -- Updated dependencies [e2899f6] -- Updated dependencies [3851f87] -- Updated dependencies [2a29caa] -- Updated dependencies [09a6eee] -- Updated dependencies [1a7f907] -- Updated dependencies [cd455c8] -- Updated dependencies [e1bb0ca] -- Updated dependencies [30d3752] -- Updated dependencies [c80e7ae] -- Updated dependencies [09a9a8a] -- Updated dependencies [07026cf] -- Updated dependencies [5d4f3d5] -- Updated dependencies [4d80e8b] -- Updated dependencies [30b1c63] -- Updated dependencies [079b457] -- Updated dependencies [e43b211] -- Updated dependencies [890b38f] -- Updated dependencies [8bee54b] -- Updated dependencies [04f8fdb] -- Updated dependencies [7a537ce] -- Updated dependencies [593c4bf] -- Updated dependencies [6158146] -- Updated dependencies [84cb121] -- Updated dependencies [ca19ee8] -- Updated dependencies [a675b4d] -- Updated dependencies [b887013] -- Updated dependencies [ff08691] -- Updated dependencies [60e0f90] -- Updated dependencies [90c5285] -- Updated dependencies [402c125] -- Updated dependencies [7901b2d] -- Updated dependencies [56bca91] -- Updated dependencies [b3f9831] -- Updated dependencies [79394d7] -- Updated dependencies [730fd9a] -- Updated dependencies [44bc51d] -- Updated dependencies [73cfddf] -- Updated dependencies [d634e66] - - @objectstack/spec@17.1.0 - - @objectstack/platform-objects@17.1.0 - - @objectstack/core@17.1.0 - -## 17.0.0 - -### Major Changes - -- 4ed7ed4: feat(security)!: the export axis is now OPT-IN, explainable, and covers reports (#3544, #3710) - - **BREAKING — `allowExport` unset no longer means "inherit read".** Reading a - record and taking a bulk machine-readable copy of the whole table are different - privileges (Salesforce "Export Reports", Dynamics "Export to Excel", NetSuite - "Export Lists", SAP `S_GUI` 61 all separate them). The axis now says so. - - ### Migration — FROM → TO - - | | before | after | - | -------------------- | ----------------------------------- | -------------------------- | - | `allowExport` unset | export **allowed** (inherited read) | export **denied** | - | `allowExport: false` | export denied | export denied (unchanged) | - | `allowExport: true` | export allowed | export allowed (unchanged) | - - **The one-line fix:** add `allowExport: true` to the object entry (or the `'*'` - wildcard) of every permission set whose holders should keep exporting. - - ```ts - objects: { - deal: { allowRead: true, allowExport: true }, // ← add the grant - } - ``` - - Nothing else changes: read, CRUD, RLS, FLS and sharing are untouched, and a set - that never exported is unaffected. - - **Who is affected.** Package-shipped sets are re-seeded on upgrade, so the - built-ins are handled for you — `admin_full_access` and `organization_admin` now - carry `allowExport: true` explicitly. **Environment-authored sets are not**: any - custom set whose users export must be edited. `member_default` deliberately does - NOT carry the grant, so ordinary authenticated users lose export until an admin - grants it — that is the point of the flip, not an oversight. - - **Merge semantics.** Most-permissive, exactly like the CRUD bits: any set - granting `true` grants export. `false` and unset are the same outcome; `false` - is authoring intent, not a veto, because permission sets are additive capability - containers (ADR-0090). - - **Not implied by super-user bits.** `viewAllRecords` / `modifyAllRecords` no - longer confer export. Separating "may see all data" from "may take a bulk copy" - is the segregation-of-duties case the axis exists for. - - ### Also in this change - - - **spec** — a set carrying `allowExport` is now **high-privilege** - (`describeHighPrivilegeBits`), so it cannot be bound to the `everyone` / - `guest` audience anchors. Without this the opt-in was defeatable by binding an - export-granting set to `everyone`. One predicate, so the runtime anchor gate, - the `@objectstack/lint` security-posture rule and the install-time suggestion - surface all pick it up together. - - **spec / plugin-security** — `ExplainOperationSchema` gains `export`, so - `explain` can answer _why_ a caller got `403 EXPORT_NOT_PERMITTED`. It - explains as `read ∧ the export grant`: `object_crud` reports the conjunction - and attributes the granting set, while every data-shaped layer - (requiredPermissions, OWD/depth/sharing, RLS, record attribution) is computed - as the `find` the export actually performs — asking the RLS compiler about an - `export` operation would match no policy and wrongly report "no RLS applies". - `readFilter` is surfaced for `export` as it is for `read`. - - **plugin-reports** — closes the reports side door (#3710). A report rendered - as `csv`/`json` is the same bulk copy of the same object, so it is gated by - the same `ISecurityService.canExport`. Enforced in `executeReport`, which the - interactive run, the ad-hoc run and the scheduled dispatch all funnel through; - `scheduleReport` additionally refuses at create time so an author is not told - at 3am. A schedule created while granted stops delivering once the grant is - revoked. `html_table` stays a read — it is a rendered view, not a bulk copy. - Deployments without `plugin-security` are unaffected (no permission sets - exist, so the axis does not apply). - - - -### Patch Changes - -- f40c5b4: refactor(plugin-approvals,plugin-reports): enforcement implementations annotate the full `ExecutionContext` (#7135) - - The services half of #7070, mirroring what PR #7140 did for - `plugin-sharing` / `plugin-audit`. #6523 converged 36 contract signatures onto - the complete `resolveAuthzContext` envelope, applying the #6206 ruling — - enforcement adjudicates on the whole envelope, never a per-site subset. The - implementations behind those contracts still annotated their own parameters - with the six-field shape the contracts used to name, so nothing they could - _read_ had widened. - - `ApprovalService`, the approval flow-node provider and `ReportService` now - declare `ExecutionContext` on all 43 of those positions, and the casts the - narrow annotation forced are gone: - - - `isOverrideActor()` read the derived `posture` (ADR-0095) through an - unchecked `(context as any)`. That gate decides whether a platform or tenant - admin may release a STUCK approval — one routed to an unstaffed position, the - only in-product recovery from a permanently locked record — so an erasure sat - directly on an enforcement input: a mistyped rung would have compiled and - silently denied every override. It is a declared read now. - - Both services' `SYSTEM_CTX` is typed as the envelope and passed as itself, - retiring the `SYSTEM_CTX as unknown as …` double casts at the three sites - that hand it to a contract method. - - The `(context as any).userId` / `.tenantId` reads in `ApprovalService` now - read declared fields. - - `OwnerContextResolver` returns the envelope, which is what a scheduled report - actually resolves for its owner (#2849 / #2980). - - **No runtime behaviour changes.** The values were always complete — this - family's damage was type-side — so every gate answers exactly what it answered - before. Method parameters only WIDEN what they accept, so no caller is - affected, and no public export changes shape. - - Casts deliberately kept, and now documented where they sit: `organizationId` - is not a field of the envelope at all — that spelling has its own history - (#5858 / `check:org-identifier`) and was held out of this change by #7070. In - `approval-node.ts` the single remaining assertion exists only because the - literal names that key; it was reduced from `as unknown as …` to a single - `as ExecutionContext`, which still requires the literal to be comparable to - the envelope. - - Because a re-narrowed annotation would compile, ship and pass every test in - these packages, the convergence is pinned by a new compile-time module per - package, `exec-context-annotation.pin.ts`: it hands each parameter a fresh - literal naming envelope-only fields (`posture`, `accessible_org_ids`, - `org_user_ids`), which TypeScript's excess-property check rejects the moment a - parameter narrows back, plus negative cases so a parameter erased to `any` - cannot pass either. - - The exported `SharingExecutionContext` type itself is NOT removed here: it is - defined in `packages/spec`, which is single-owner, so its retirement is a - separate follow-up. - -- 2e836de: chore(packaging): CHANGELOG.md ships in every npm tarball (#4261) - - The AGENTS.md post-task checklist requires breaking changesets to carry their - FROM → TO migration because "this text ships to consumers as `CHANGELOG.md` - inside the npm package and is what an upgrading agent greps after the tombstone - error." That delivery path was severed for 68 of the 69 publishable packages: - npm packs `package.json` / `README*` / `LICENSE*` unconditionally but — unlike - older npm versions — not `CHANGELOG.md`, and the canonical - `"files": ["dist", "README.md"]` whitelist never named it. Measured on npm - 10.9.7: `npm pack --dry-run` on `@objectstack/types` shipped 3 files while its - 70KB `CHANGELOG.md` stayed behind. Only `@objectstack/spec` listed it - explicitly. - - The tombstone-error scenario is precisely the one where the repo is out of - reach — the upgrading agent has `node_modules` and nothing else — so the - migration text has to ride in the tarball. Every publishable package now - declares `CHANGELOG.md` in `files`, and the canonical whitelist is - `["dist", "README.md", "CHANGELOG.md"]`. - - The other half is the gate: `check:published-files` gains a fifth invariant, - COMPLETE — a whitelist that fails to cover `CHANGELOG.md` fails the - always-required lint job, so the next package cannot silently sever the path - again. `@objectstack/spec`'s per-package EXTRA_ENTRIES exemption dissolves - into the canonical set. - - Consumer-visible change: one more file per install (the package's changelog, - e.g. 70.8KB for `@objectstack/types`), and `grep -r "removed key" -node_modules/@objectstack/*/CHANGELOG.md` now finds the migration it was - promised. - -- b5f9397: fix(sharing,runtime): a `sort` passed straight to the engine never ordered anything; migrate every in-repo engine call to canonical QueryAST keys (#4346) - - Two changes with different weights, from one sweep of every in-repo engine - call site that still speaks a deprecated alias. - - **The bug — three dropped sorts.** #4346 made the engine fold `filter`→`where` - and `top`→`limit` on all six methods. The other four pairs in - `RPC_QUERY_ALIAS_SLOTS` (`select`, `sort`, `skip`, `populate`) are folded at - the RPC/wire layer only — their values need shape lowering that belongs to - those layers — and a **direct `engine.find()` never crosses that layer**. Three - call sites passed `sort` there, so it rode onto the AST untouched, every - driver's `Array.isArray(query.orderBy)` guard declined to emit an ORDER BY, and - the query returned an ordinary-looking, arbitrarily-ordered result: - - | call site | asked for | actually got | - | ----------------------------------- | ------------------------------------------------- | --------------------------- | - | `share-link-routes.ts` | shared AI conversation messages, `created_at asc` | messages in arbitrary order | - | `runtime/domains/share-links.ts` | same route, runtime-domain copy | same | - | `share-link-service.ts` `listLinks` | the 200 most recent share links | an arbitrary 200 | - - All three combine the dropped sort with a `limit` — the "latest N" shape whose - failure #4226 spelled out: an unapplied sort returns rows in arbitrary order, - which `limit` then slices into an arbitrary page. #4226 fixed that in the wire - normalizer; these calls sit one layer below it. `listLinks` had no test at all, - which is why it went unnoticed. Now pinned — on the option bag the engine - receives, not on row order, because the failure is that the key never becomes - `orderBy` and a fake engine honouring either spelling would pass either way. - - **The cleanup — 27 no-op renames.** Every remaining in-repo engine call passing - `filter` now passes `where` (approvals 5, auth 2, reports 6, sharing 11, - webhooks 2, plus the one `filters` in a spec doc example). These are strict - no-ops since #4346 folds the alias — the point is that the framework stops - depending on a spelling it asks users to migrate off, which is a prerequisite - for ever retiring the aliases. Service-level `filter` PARAMETERS (each - service's own public API, e.g. `listRequests(filter)`) are deliberately - untouched — those are not engine option bags. - - Two of the renamed calls were live victims of the #4346 bug rather than - cosmetic: `auth-manager`'s `stampIdentitySource` read the table's first row via - `findOne({filter})` and counted the whole table via `count({filter})`, so a - federated sign-in never stamped `source: 'idp_provisioned'`. #4346 already - corrected the behaviour; this makes the call say what it means. - -- d0d5205: refactor(core,plugin-audit,service-storage,plugin-reports): give the `__` operation-private-key convention a single owner (#7284) - - `withoutOperationPrivateKeys` — the rule that a consumer forwarding a caller's - execution envelope to a question about a DIFFERENT object must first drop the - `__`-prefixed keys plugin-security stamped for the operation in flight — had been - hand-copied into three packages: `plugin-audit`'s comment access hooks (#7141), - `service-storage`'s attachment access hooks (#7145) and `plugin-reports`' report - service (#7204). Each carried its own `OPERATION_PRIVATE_KEY_PREFIX` and its own - doc block, and the prose had already diverged while the code still agreed — the - shape that makes a later divergence in behaviour hard to notice. - - The helper now lives once, in `@objectstack/core` - (`security/operation-private-keys.ts`), exported from the package root. Core is - the only candidate all three consumers already depend on: `plugin-security` is - the producer of the convention and the most honest owner, but none of the three - depends on it and a string-prefix filter does not justify three new dependency - edges onto a plugin; `@objectstack/spec` is fenced off by Prime Directive #2. The - new home sits beside `assemble-execution-context.ts`, which owns the other end of - the same lifecycle — that file is where an `ExecutionContext` is built at a - transport entry point, this one is where it is stripped back down before being - forwarded. - - The full reasoning moved with the code rather than being thinned: which keys the - middleware stamps and why each is a widening input, why they are dropped by - PREFIX and never by a name list, and why the fresh copy is load-bearing in both - directions. Each consumer keeps only its own local half — which object _its_ - gates actually ask about — and points at the shared home. - - No behaviour change: the three copies were byte-equivalent, and all three - packages' suites pass unchanged. Two new pins at the home cover it — the rule's - own behaviour, which no package-level test had ever asserted directly, and a - repository-shape pin that turns red if a fourth file declares its own copy. - -- cc2de0e: chore(packaging): 20 packages stop publishing their sources, tests and build tooling (#4248) - - These 20 packages declared no `files` field, so npm fell back to packing the - whole package directory. `npm pack --dry-run` on `@objectstack/plugin-webhooks` - listed **21 files** — 15 under `src/`, three of them unit tests - (`auto-enqueuer.test.ts`, `bootstrap-declared-webhooks.test.ts`, …), plus the - build-time `scripts/i18n-extract.config.ts`. `dist/` lands on top of that at - publish time rather than instead of it, so consumers were installing the - TypeScript sources and the test suite alongside the artifact they asked for. - - Each now declares `"files": ["dist", "README.md"]`, matching the 29 packages - that already did. Nothing a consumer imports moves: every `main` / `types` / - `exports` target in all 20 already resolved inside `dist/`, which the new - `check:published-files` guard verifies rather than assumes. The visible change - is a smaller install and a smaller dependency-scanning surface — `npm pack` on - `@objectstack/plugin-webhooks` now yields 2 files plus `dist/`. - - The other half of the fix is the gate. Half the packages declaring `files` and - half not was the #3786 shape — a hand-copied convention with nothing enforcing - it, where whoever forgets the line gets no signal at all. `check:published-files` - (new, wired into the always-required `lint` job) holds every non-private - workspace package to four invariants: `files` is **declared**; it is - **sufficient** (covers every entry point, so tightening a whitelist cannot ship - a package that fails to resolve); it is **minimal** (admits no test, test-harness - config or build script); and anything beyond `dist` + `README.md` is - **registered** with a reason, reconciled in both directions so a stale exemption - is an error rather than dead text. `@objectstack/spec` is the one package with - registered extras — its `.zod.ts` sources, JSON Schemas, liveness ledgers and - `CHANGELOG.md` are product, not build input. - - This also closes an assumption #4206 was resting on. Excluding `/scripts/**` - from the docs-drift implementation test is sound only while no package publishes - `scripts/` as runtime code; that held, but it held because someone read all three - offenders by hand. It is now checked on every PR. - -- cb466aa: Reports read with the caller's whole execution envelope, so a `group`-posture report no longer under-reports - - `executeReport` rebuilt a five-field projection of the caller's `ExecutionContext` - (`userId` / `tenantId` / `positions` / `permissions` / `isSystem`) before handing it to - the engine read that produces the report — while the method's own comment promised - "reports execute with the caller's identity". - - **Before.** `accessible_org_ids` was not in that projection, and the engine reads it by - name (`buildDriverOptions`, ADR-0105 D2 / #3623) to widen the driver's native tenant - scope to the caller's whole membership set under the `group` tenancy posture. Absent, the - drivers fall back to active-org equality — "fail toward isolation". So the identical query - returned the membership union in an interactive list view and collapsed to the active org - inside a **saved or scheduled** report: silently short rows, no error, nothing in the - output saying so. Measured end-to-end on a real kernel + SQL driver: three rows across two - member orgs came back as three interactively and two in the report, and a scheduled CSV - digest emailed the owner the same two. `timezone` went the same way, so a read-time - formula field resolved its calendar day in UTC instead of the caller's business timezone; - `posture`, `org_user_ids`, `systemPermissions` and `onBehalfOf` were dropped too. - - **After.** The read receives the caller's envelope whole (the #6206 ruling — enforcement - adjudicates on the whole `resolveAuthzContext` envelope, never a per-site subset), minus - the `__`-prefixed keys plugin-security stamps for the operation in flight, and as a fresh - object so a callee's stamp cannot write back into the caller's request context. The same - shape `plugin-audit` (#7141) and `service-storage` (#7145) landed. Direction is unchanged - outside `group`: the `isolated` posture, a deployment with no posture provider, and a - `group` caller with an empty accessible set all still read at active-org equality. - -- 2c2a212: fix(reports): owner-gate the saved-report schedule routes (#2980) - - The report read/run/delete routes are owner-isolated (a caller may only touch a - report they own, denied as `REPORT_NOT_FOUND` to avoid leaking that the id - exists), but the two schedule routes bypassed that gate: `unscheduleReport` and - `listSchedules` took the caller `context` as `_context` and never consulted it, - querying under the system context (RLS-bypassing). Any authenticated caller - could therefore delete another owner's report schedule — a cross-owner - destructive write — or list another owner's schedules (leaking recipient - addresses and cron), by supplying an id. - - Both now resolve the schedule's parent report and require the caller to own it, - mirroring the sibling routes: - - - **`unscheduleReport`** loads the schedule, then its report, and deletes only - when `canAccessReport` holds; a cross-owner attempt throws `REPORT_NOT_FOUND` - (mapped to `404` by the REST layer, deny-as-404 anti-enumeration), while a - genuinely-absent schedule stays idempotent. `scheduleReport` (create) was - already gated via `getReport`, so only the delete/list doors were open. - - **`listSchedules`** returns an empty list to any non-system caller who cannot - access the report it is scoped to — the same non-leaking posture as - `listReports`. The scheduler's system context still sees every schedule. - - No authoring-surface or metadata change; existing owner-path behavior is - unchanged. - -- dadd1ad: refactor(spec,plugin-sharing): retire the exported `SharingExecutionContext` type (#7218) - - - - **BREAKING — public surface removal.** `SharingExecutionContext` is deleted from - `@objectstack/spec` (`contracts/sharing-service`) and from - `@objectstack/plugin-sharing`, which re-exported it. Both `api-surface/` and - `export-origins/` snapshots are regenerated accordingly. - - This is the deferred deletion recorded when #7070 split the convergence in two. - #6523 / PR #7068 converged 36 contract signatures onto the full - `resolveAuthzContext` envelope (`ExecutionContext`), applying the #6206 ruling — - enforcement adjudicates on the whole envelope, never a per-site subset. The - consumer halves then re-annotated the implementations: PR #7140 (identity: - `plugin-sharing`, `plugin-audit`) and PR #7206 (services: `plugin-approvals`, - `plugin-reports`). Both landed with the type still exported, because it is - DEFINED in `packages/spec` and that package's retirement is the spec seat's to - make. Nothing declares it any more, so it goes. - - **Migration.** Anyone who imported `SharingExecutionContext` from either package - should import `ExecutionContext` from `@objectstack/spec` instead — the type the - contracts have declared since #7068. The old shape was six optional fields, all - of which exist on the envelope with the same names and types, so a value that - satisfied the retired type already satisfies `ExecutionContext`; only the - spelling of the annotation changes. - - **No runtime behaviour changes.** The type was erased at compile time and no - signature's accepted shape moved: the contracts already took the wide envelope. - - **What the retirement did NOT remove — the reason to read the pins.** Deleting - the type does not make re-narrowing a compile error. Structural subtyping still - accepts a six-field context where the envelope is expected, so the boundary is - held by the declared parameter type plus the pins, exactly as before. The three - `exec-context-annotation.pin.ts` files (`plugin-sharing`, `plugin-approvals`, - `plugin-reports`) told their failure story as "the parameter narrows back to - `SharingExecutionContext`", which a deletion would have quietly hollowed out. - Each now keeps the retired six-field shape as a local, non-exported SPECIMEN - type and refutes every enforcement parameter against it by type identity, so a - re-narrowing under ANY name is red — alongside the fresh-literal - excess-property checks they already carried. `sharing-service.test.ts` in - `packages/spec` is re-anchored the same way, and its "twin unchanged in shape" - case becomes a "twin stays retired" case. The narrative the retired type's doc - block carried (the measured `(context as any).posture` specimen, and why tsc - cannot police this) moves to the module doc of `contracts/sharing-service`, - which the contracts and pins now point at. - -- ea936f3: fix(plugin-reports): `DELETE /api/v1/reports/schedules/:scheduleId` stops telling a caller whether a schedule id exists - - `DELETE /api/v1/reports/schedules/:scheduleId` answered differently depending on - whether the target id **existed**, which let any authenticated caller enumerate - other owners' report schedules by probing ids and reading the status code: - - | Target | Before | After | - | ----------------------------------- | ---------------------- | ---------------------------------- | - | Another owner's schedule id | `404 REPORT_NOT_FOUND` | `404 REPORT_NOT_FOUND` (unchanged) | - | A schedule id that does not exist | `204 No Content` | `404 REPORT_NOT_FOUND` | - | A schedule whose report row is gone | `404 REPORT_NOT_FOUND` | `404 REPORT_NOT_FOUND` (unchanged) | - | Your own schedule | `204 No Content` | `204 No Content` (unchanged) | - - This is the same defect #7523 closed on the sibling `DELETE /reports/:id`, in the - costume that card explicitly warned about: there the split was 500-vs-204 and - loud, here it was 404-vs-204 and read as correct. The route was in fact cited by - #7523's investigation as the example of the _right_ shape, because it does route - its catch through `handleValidation` — which is why the cross-owner arm is a - clean 404 rather than a 500. Only the cross-owner arm was ever probed (QA run - #7515); the unknown-id arm was not, so the surviving half went unseen and - `packages/rest/src/rest.test.ts` pinned its `204` green. - - `ReportService.unscheduleReport()` carried the intent — _"others get a not-found - so the delete neither fires nor reveals the schedule's existence"_ — and a hole - one line wide above it: `if (!schedule) return; // idempotent`. Idempotence is - only harmless where every caller may see the row; with a cross-owner arm that - throws, resolving quietly _is_ the tell. - - Both deny arms are now one decision, taken before the delete fires, by the - predicate already blind to the difference between them: `canAccessReport` is - false for a schedule that does not exist, for one whose report is gone, and for - one owned by somebody else alike. A single throw site means a single message, so - the route's single `handleValidation` call emits a single response — status and - body cannot drift apart. - - Unlike `deleteReport`, this could not be pre-empted in the route. That one - collapses its arms with `getReport()`, which is already blind to the same - difference (#2980); the caller here presents a `scheduleId`, and `IReportService` - exposes no by-id schedule read to be blind with (`listSchedules` is keyed by - `reportId`). The blinding therefore lives in the service, and - `IReportService.unscheduleReport` now states it as a contract obligation rather - than leaving each implementation to rediscover it. - - Deleting a schedule you own still answers `204`. Deleting one you cannot see is - now `404` instead of a silent `204` — the cost of closing the oracle, and in line - with the cross-owner GET / run / upsert-overwrite / delete arms, which all - already answer 404. A system/dispatcher context deleting an id with no row now - gets `REPORT_NOT_FOUND` too, where it previously resolved; no caller in the repo - relies on that (the route is the only production caller). - - Tests assert the two deny arms' responses are **EQUAL** rather than pinning each - arm's status separately, so the plausible half-fix cannot pass through them — a - mutation that answers both arms 404 with different bodies leaves every per-arm - status assertion green and turns the equality assertions red. - -- 2465133: fix(plugins): sweep the service-lookup erasures out of the plugin composition roots, and fix the two alias-only HTTP reads it exposed (#4251 B5) - - Batch B5 of the #4251 sweep: the seven remaining `packages/plugins/*` composition - roots. 35 lookup sites that had been erased to `any` now carry the slot's - contract, so the compiler checks what each plugin actually calls on the service - it resolved. The ratchet drops 143 sites / 32 files to 108 / 25. - - **Two real defects, both of the shape this sweep exists to find.** Approvals' - actionable-link pages (ADR-0043) and sharing's public share-link REST routes each - read the HTTP server under `http-server` _only_ — the deprecated alias. The - ledger records `http.server` as canonical and as the only name present on every - provider path: `runtime.ts`'s `config.server` path registers no alias at all. On - that path both lookups threw, the surrounding `catch` swallowed it, and the - routes silently never mounted — approval e-mail action links 404'd and the - share-link surface was absent, with nothing in the log to say so. Both reads are - now canonical-first with the alias as fallback, each name in its own `try` - because `getService` throws on an empty slot (so `a() ?? b()` inside one `try` - never reaches `b` — the same correction #4393 made in metadata and - cloud-connection). - - Typing choices follow the batch method: pure data-plane consumers take the - narrow contract (`IDataEngine` in reports), consumers that bind hook or - middleware seams take the engine seen whole (`IObjectQLEngine` in approvals, - sharing and pinyin-search), and slots with no contract get a **named** local - surface rather than `any` — plugin-email's `MailSettingsSurface`, and the - surfaces the consuming packages already declared (`ApprovalMessagingSurface`, - `SharingSecurityProbe`, `ReportEmail`). A named surface that omits a member - still makes the compiler name every call site; `any` says nothing. - - No behaviour change beyond the two alias reads. No contract changes. - -- Updated dependencies [50616d9] -- Updated dependencies [430dcc2] -- Updated dependencies [690ccf2] -- Updated dependencies [6a67d7a] -- Updated dependencies [098f4bb] -- Updated dependencies [333a374] -- Updated dependencies [9fe9c1d] -- Updated dependencies [3d5c090] -- Updated dependencies [e5bd768] -- Updated dependencies [08b5a3d] -- Updated dependencies [e027b3e] -- Updated dependencies [e6ac4bd] -- Updated dependencies [c2429b0] -- Updated dependencies [445a0c2] -- Updated dependencies [d99aeb3] -- Updated dependencies [f6609e6] -- Updated dependencies [4727eb8] -- Updated dependencies [a70358a] -- Updated dependencies [0ecc656] -- Updated dependencies [06772eb] -- Updated dependencies [d4e0809] -- Updated dependencies [80334c7] -- Updated dependencies [f63cd09] -- Updated dependencies [97e7e3c] -- Updated dependencies [ce5242c] -- Updated dependencies [a7163ea] -- Updated dependencies [e6e9379] -- Updated dependencies [5823d59] -- Updated dependencies [3140f9c] -- Updated dependencies [9500ba4] -- Updated dependencies [fa3d0cf] -- Updated dependencies [af5a224] -- Updated dependencies [71f76e1] -- Updated dependencies [37b1346] -- Updated dependencies [99736a0] -- Updated dependencies [fe67e34] -- Updated dependencies [fdb4f50] -- Updated dependencies [270650f] -- Updated dependencies [3aef718] -- Updated dependencies [1bd5652] -- Updated dependencies [14252d3] -- Updated dependencies [7fb436c] -- Updated dependencies [879ea13] -- Updated dependencies [8828b9e] -- Updated dependencies [1ea6bce] -- Updated dependencies [c1dcacd] -- Updated dependencies [ad303ed] -- Updated dependencies [32ccb23] -- Updated dependencies [f5a4ef0] -- Updated dependencies [2d3e255] -- Updated dependencies [a8940e4] -- Updated dependencies [7d7521f] -- Updated dependencies [5dc4d02] -- Updated dependencies [f724f69] -- Updated dependencies [98877c9] -- Updated dependencies [98877c9] -- Updated dependencies [53068c1] -- Updated dependencies [ee58392] -- Updated dependencies [f16e54e] -- Updated dependencies [c44dd5e] -- Updated dependencies [06be54e] -- Updated dependencies [28ad90e] -- Updated dependencies [76d74ec] -- Updated dependencies [201b31f] -- Updated dependencies [e6b1b69] -- Updated dependencies [259459d] -- Updated dependencies [3f7f14e] -- Updated dependencies [e2616e0] -- Updated dependencies [6fdc5c6] -- Updated dependencies [8b9d71e] -- Updated dependencies [05154a1] -- Updated dependencies [33f5e23] -- Updated dependencies [259af21] -- Updated dependencies [f8644c7] -- Updated dependencies [306ca50] -- Updated dependencies [978fed2] -- Updated dependencies [cfc293f] -- Updated dependencies [587fc91] -- Updated dependencies [de70b42] -- Updated dependencies [9b6fe7c] -- Updated dependencies [fb3d99b] -- Updated dependencies [1986594] -- Updated dependencies [6968885] -- Updated dependencies [eaed61f] -- Updated dependencies [52200b4] -- Updated dependencies [cdfbee2] -- Updated dependencies [ad4af62] -- Updated dependencies [debe2f6] -- Updated dependencies [d44dbfa] -- Updated dependencies [29c6c9d] -- Updated dependencies [d21c001] -- Updated dependencies [ad047d2] -- Updated dependencies [8c711fb] -- Updated dependencies [f1cc3a3] -- Updated dependencies [09e4547] -- Updated dependencies [97b0798] -- Updated dependencies [474fe39] -- Updated dependencies [0bc685a] -- Updated dependencies [b949059] -- Updated dependencies [2826d1e] -- Updated dependencies [be1c52c] -- Updated dependencies [c5ff96d] -- Updated dependencies [5a84d41] -- Updated dependencies [84e7be9] -- Updated dependencies [91f4c78] -- Updated dependencies [ddc2527] -- Updated dependencies [820eff9] -- Updated dependencies [a6c3f38] -- Updated dependencies [5fa04fb] -- Updated dependencies [debc23a] -- Updated dependencies [0f8ad09] -- Updated dependencies [553a47f] -- Updated dependencies [43a7a8d] -- Updated dependencies [a98085f] -- Updated dependencies [20b1a9e] -- Updated dependencies [344a22a] -- Updated dependencies [4827e91] -- Updated dependencies [8d895ff] -- Updated dependencies [86f7a20] -- Updated dependencies [a3a884d] -- Updated dependencies [cfed092] -- Updated dependencies [203a449] -- Updated dependencies [8f9689f] -- Updated dependencies [73f69dc] -- Updated dependencies [04c56aa] -- Updated dependencies [f6472d7] -- Updated dependencies [57a3bb3] -- Updated dependencies [b3efeb7] -- Updated dependencies [ddd075a] -- Updated dependencies [88154be] -- Updated dependencies [e8dc61e] -- Updated dependencies [9c82146] -- Updated dependencies [5f9a987] -- Updated dependencies [744b8f5] -- Updated dependencies [ac37fc6] -- Updated dependencies [9f060e5] -- Updated dependencies [bc17d39] -- Updated dependencies [2f3e793] -- Updated dependencies [4820f55] -- Updated dependencies [462d9c4] -- Updated dependencies [78caf51] -- Updated dependencies [7d21581] -- Updated dependencies [37785ed] -- Updated dependencies [62a789b] -- Updated dependencies [2e284b2] -- Updated dependencies [d8e8d9c] -- Updated dependencies [789ad63] -- Updated dependencies [f2445c9] -- Updated dependencies [94e749b] -- Updated dependencies [ea1d916] -- Updated dependencies [2af1988] -- Updated dependencies [0af50a3] -- Updated dependencies [1b49eaf] -- Updated dependencies [ae31a19] -- Updated dependencies [2e836de] -- Updated dependencies [e0f300b] -- Updated dependencies [0161c7f] -- Updated dependencies [e900015] -- Updated dependencies [db02d47] -- Updated dependencies [b5bdf48] -- Updated dependencies [23338c3] -- Updated dependencies [12a19a8] -- Updated dependencies [5b843fb] -- Updated dependencies [62b6a2f] -- Updated dependencies [7e5af5c] -- Updated dependencies [5b4780b] -- Updated dependencies [a933452] -- Updated dependencies [9d1d9c7] -- Updated dependencies [8140915] -- Updated dependencies [a019e52] -- Updated dependencies [e8f8f6c] -- Updated dependencies [41dcda3] -- Updated dependencies [7b48cf9] -- Updated dependencies [b5404f4] -- Updated dependencies [64fc6d5] -- Updated dependencies [b746aa0] -- Updated dependencies [b4487aa] -- Updated dependencies [1007379] -- Updated dependencies [65ca83a] -- Updated dependencies [0bfdf46] -- Updated dependencies [947d4f9] -- Updated dependencies [f764691] -- Updated dependencies [e120a5a] -- Updated dependencies [e5bd2f6] -- Updated dependencies [e650d67] -- Updated dependencies [04476e7] -- Updated dependencies [67bf2e2] -- Updated dependencies [eaaf03c] -- Updated dependencies [d17df80] -- Updated dependencies [7d0e7b5] -- Updated dependencies [c6d1cb4] -- Updated dependencies [6513c17] -- Updated dependencies [36030ff] -- Updated dependencies [79228cd] -- Updated dependencies [6117f7b] -- Updated dependencies [e533b0b] -- Updated dependencies [cdf4d9a] -- Updated dependencies [aee1806] -- Updated dependencies [c13350b] -- Updated dependencies [c13350b] -- Updated dependencies [2c1988c] -- Updated dependencies [9ca2d85] -- Updated dependencies [c13350b] -- Updated dependencies [891d345] -- Updated dependencies [c8124e5] -- Updated dependencies [a52e2ef] -- Updated dependencies [5293114] -- Updated dependencies [376a061] -- Updated dependencies [c142ced] -- Updated dependencies [211abdb] -- Updated dependencies [b3363e9] -- Updated dependencies [eda599e] -- Updated dependencies [a1a4140] -- Updated dependencies [7c7e246] -- Updated dependencies [2ef1807] -- Updated dependencies [f35cdc5] -- Updated dependencies [d03fe25] -- Updated dependencies [217e2e6] -- Updated dependencies [2672f85] -- Updated dependencies [20bc357] -- Updated dependencies [11066f6] -- Updated dependencies [916af17] -- Updated dependencies [84c86fb] -- Updated dependencies [2a2a9fb] -- Updated dependencies [86a71d1] -- Updated dependencies [c001422] -- Updated dependencies [77022a9] -- Updated dependencies [d5c75e2] -- Updated dependencies [03d26f7] -- Updated dependencies [5966c2a] -- Updated dependencies [2382580] -- Updated dependencies [9ea2bc5] -- Updated dependencies [a2e157c] -- Updated dependencies [95c4227] -- Updated dependencies [2a61116] -- Updated dependencies [52760bf] -- Updated dependencies [5543020] -- Updated dependencies [880d343] -- Updated dependencies [6e82972] -- Updated dependencies [d4df105] -- Updated dependencies [4615a18] -- Updated dependencies [f505689] -- Updated dependencies [d9fa683] -- Updated dependencies [606d577] -- Updated dependencies [4384921] -- Updated dependencies [e2798fa] -- Updated dependencies [3c628ce] -- Updated dependencies [c2d9098] -- Updated dependencies [0fd8556] -- Updated dependencies [3c7bcc0] -- Updated dependencies [4b6cac7] -- Updated dependencies [7631964] -- Updated dependencies [ac471a0] -- Updated dependencies [60ae58e] -- Updated dependencies [7f62706] -- Updated dependencies [667fa44] -- Updated dependencies [37e38d1] -- Updated dependencies [e906126] -- Updated dependencies [ce92674] -- Updated dependencies [08363a0] -- Updated dependencies [444de5b] -- Updated dependencies [a227ed7] -- Updated dependencies [7cb922e] -- Updated dependencies [1d22114] -- Updated dependencies [1eb13a0] -- Updated dependencies [c52e608] -- Updated dependencies [9613396] -- Updated dependencies [3f7b4ff] -- Updated dependencies [74155c7] -- Updated dependencies [b5f9397] -- Updated dependencies [ed77493] -- Updated dependencies [6908830] -- Updated dependencies [8b06bba] -- Updated dependencies [58a03d2] -- Updated dependencies [2bacd1a] -- Updated dependencies [e47b342] -- Updated dependencies [4c54037] -- Updated dependencies [dc530b4] -- Updated dependencies [9f601e8] -- Updated dependencies [6a9dec6] -- Updated dependencies [0f7157b] -- Updated dependencies [4dc1c7d] -- Updated dependencies [d9bef45] -- Updated dependencies [4dfd002] -- Updated dependencies [f549a0d] -- Updated dependencies [51c5227] -- Updated dependencies [82da264] -- Updated dependencies [f586f1a] -- Updated dependencies [77be690] -- Updated dependencies [4ed7ed4] -- Updated dependencies [9b9b70f] -- Updated dependencies [f5a9bc2] -- Updated dependencies [e59786e] -- Updated dependencies [2fa4ca1] -- Updated dependencies [bcf1112] -- Updated dependencies [baeb4f0] -- Updated dependencies [29488cc] -- Updated dependencies [881a3cc] -- Updated dependencies [f5a2320] -- Updated dependencies [ad6317b] -- Updated dependencies [811c30c] -- Updated dependencies [a4a85c8] -- Updated dependencies [859cb83] -- Updated dependencies [07a4e26] -- Updated dependencies [9774b78] -- Updated dependencies [8a88885] -- Updated dependencies [deb538f] -- Updated dependencies [b49ccfd] -- Updated dependencies [5b89711] -- Updated dependencies [85d95e7] -- Updated dependencies [08cd163] -- Updated dependencies [0c8a22f] -- Updated dependencies [5f7669e] -- Updated dependencies [becbe53] -- Updated dependencies [b127c8b] -- Updated dependencies [763931e] -- Updated dependencies [ec975f1] -- Updated dependencies [168f60f] -- Updated dependencies [b07d829] -- Updated dependencies [de9af8a] -- Updated dependencies [eb4204b] -- Updated dependencies [a80302a] -- Updated dependencies [a648e96] -- Updated dependencies [a47ac06] -- Updated dependencies [e4c61a7] -- Updated dependencies [cc60165] -- Updated dependencies [474f131] -- Updated dependencies [081aa6f] -- Updated dependencies [91f4c78] -- Updated dependencies [050cd82] -- Updated dependencies [4d552af] -- Updated dependencies [44d677c] -- Updated dependencies [c32944d] -- Updated dependencies [1dd780f] -- Updated dependencies [e8d0c21] -- Updated dependencies [244ca86] -- Updated dependencies [546ab3c] -- Updated dependencies [c4df271] -- Updated dependencies [c8d6f6e] -- Updated dependencies [0b51bb6] -- Updated dependencies [d9971d3] -- Updated dependencies [7dc1067] -- Updated dependencies [4f13be2] -- Updated dependencies [a41ba5c] -- Updated dependencies [189854c] -- Updated dependencies [0e3a226] -- Updated dependencies [92a67f2] -- Updated dependencies [9136327] -- Updated dependencies [bf0ae99] -- Updated dependencies [eb3e650] -- Updated dependencies [abeb375] -- Updated dependencies [cb3b6cd] -- Updated dependencies [73b7234] -- Updated dependencies [d2b97c3] -- Updated dependencies [61cc079] -- Updated dependencies [45dc446] -- Updated dependencies [0e96e46] -- Updated dependencies [c1d44f7] -- Updated dependencies [59b794f] -- Updated dependencies [ef4efa8] -- Updated dependencies [cbb6a5c] -- Updated dependencies [fc3a36a] -- Updated dependencies [ab9fb5c] -- Updated dependencies [69787f0] -- Updated dependencies [5d022a1] -- Updated dependencies [042b9ee] -- Updated dependencies [f985b3f] -- Updated dependencies [795b6e1] -- Updated dependencies [d52d4fe] -- Updated dependencies [742cebb] -- Updated dependencies [175d789] -- Updated dependencies [f549a0d] -- Updated dependencies [524151c] -- Updated dependencies [427344c] -- Updated dependencies [8af76ae] -- Updated dependencies [1d4756e] -- Updated dependencies [720c5ad] -- Updated dependencies [a8d1e24] -- Updated dependencies [b85cc54] -- Updated dependencies [a36db28] -- Updated dependencies [7a8476f] -- Updated dependencies [518ca7a] -- Updated dependencies [d1cabaa] -- Updated dependencies [41642b0] -- Updated dependencies [4cca74c] -- Updated dependencies [88ef03e] -- Updated dependencies [9a4932a] -- Updated dependencies [3f8817a] -- Updated dependencies [a2443e3] -- Updated dependencies [e1554b1] -- Updated dependencies [9e2caf3] -- Updated dependencies [4856789] -- Updated dependencies [81ce41a] -- Updated dependencies [85e1e4e] -- Updated dependencies [c3f4916] -- Updated dependencies [55dbbba] -- Updated dependencies [33e0385] -- Updated dependencies [dac6a08] -- Updated dependencies [72c3c86] -- Updated dependencies [2d8dba3] -- Updated dependencies [7f1a635] -- Updated dependencies [2205363] -- Updated dependencies [09fe58d] -- Updated dependencies [f9fc874] -- Updated dependencies [d62f8eb] -- Updated dependencies [d0a5ceb] -- Updated dependencies [a7586cd] -- Updated dependencies [4c5e80e] -- Updated dependencies [4b5702a] -- Updated dependencies [011b386] -- Updated dependencies [e18a162] -- Updated dependencies [e98fb14] -- Updated dependencies [394b7a1] -- Updated dependencies [ce92674] -- Updated dependencies [0f2fdcd] -- Updated dependencies [d6d1a50] -- Updated dependencies [cf2c9b7] -- Updated dependencies [8ffa8b9] -- Updated dependencies [d127ff0] -- Updated dependencies [674ac99] -- Updated dependencies [833b512] -- Updated dependencies [1b9a53b] -- Updated dependencies [36d90fc] -- Updated dependencies [7777e8f] -- Updated dependencies [9b86cf6] -- Updated dependencies [d063a96] -- Updated dependencies [8825a06] -- Updated dependencies [5087ac6] -- Updated dependencies [677b591] -- Updated dependencies [cf7c694] -- Updated dependencies [ddd0f06] -- Updated dependencies [d77d1b7] -- Updated dependencies [0f9faa2] -- Updated dependencies [2d1ddf0] -- Updated dependencies [354b00f] -- Updated dependencies [3de535b] -- Updated dependencies [fe2e15a] -- Updated dependencies [5b79a34] -- Updated dependencies [502564d] -- Updated dependencies [603cab8] -- Updated dependencies [c757854] -- Updated dependencies [471839d] -- Updated dependencies [507b92a] -- Updated dependencies [46365ab] -- Updated dependencies [b508244] -- Updated dependencies [df95346] -- Updated dependencies [3dede58] -- Updated dependencies [c6b6bb4] -- Updated dependencies [594508e] -- Updated dependencies [7cf42fe] -- Updated dependencies [5966c2a] -- Updated dependencies [59c544d] -- Updated dependencies [0045682] -- Updated dependencies [7309c81] -- Updated dependencies [2f59da0] -- Updated dependencies [d56012f] -- Updated dependencies [f78dd83] -- Updated dependencies [a2cd18a] -- Updated dependencies [9051802] -- Updated dependencies [20bc1ec] -- Updated dependencies [1c625ca] -- Updated dependencies [2f8328c] -- Updated dependencies [2a6c279] -- Updated dependencies [9319586] -- Updated dependencies [8c8f0df] -- Updated dependencies [8ad609c] -- Updated dependencies [bbee302] -- Updated dependencies [90c2b15] -- Updated dependencies [4638aaa] -- Updated dependencies [0222d3c] -- Updated dependencies [08863dd] -- Updated dependencies [071d0dc] -- Updated dependencies [f293d45] -- Updated dependencies [56664f5] -- Updated dependencies [71f205d] -- Updated dependencies [f067930] -- Updated dependencies [414395b] -- Updated dependencies [42eeb7d] -- Updated dependencies [31cbe90] -- Updated dependencies [6b7129a] -- Updated dependencies [c5adfe1] -- Updated dependencies [97ace2a] -- Updated dependencies [26e1029] -- Updated dependencies [0a936ea] -- Updated dependencies [90bbf25] -- Updated dependencies [023c00b] -- Updated dependencies [eb91eba] -- Updated dependencies [42da73d] -- Updated dependencies [01e124d] -- Updated dependencies [ef7b5ef] -- Updated dependencies [9514767] -- Updated dependencies [8f20201] -- Updated dependencies [155507e] -- Updated dependencies [643b7c7] -- Updated dependencies [7bba90b] -- Updated dependencies [8813b90] -- Updated dependencies [108ba8d] -- Updated dependencies [2a5f04a] -- Updated dependencies [4f740b0] -- Updated dependencies [7ce02eb] -- Updated dependencies [b4ad984] -- Updated dependencies [e7a7506] -- Updated dependencies [a9f32df] -- Updated dependencies [aeb9b27] -- Updated dependencies [7d27da0] -- Updated dependencies [d0d5205] -- Updated dependencies [1a15893] -- Updated dependencies [b70e534] -- Updated dependencies [7e05d8e] -- Updated dependencies [8f1851e] -- Updated dependencies [61ea810] -- Updated dependencies [2233a85] -- Updated dependencies [67452d1] -- Updated dependencies [089767f] -- Updated dependencies [a13827e] -- Updated dependencies [66d99ec] -- Updated dependencies [cb43296] -- Updated dependencies [b61afc1] -- Updated dependencies [79021fc] -- Updated dependencies [7733604] -- Updated dependencies [4921a95] -- Updated dependencies [40e420f] -- Updated dependencies [62dd69a] -- Updated dependencies [d13004a] -- Updated dependencies [be7360c] -- Updated dependencies [e15e679] -- Updated dependencies [2ab1257] -- Updated dependencies [0fc6219] -- Updated dependencies [061406d] -- Updated dependencies [e4c8b6c] -- Updated dependencies [acb10f6] -- Updated dependencies [605e190] -- Updated dependencies [c6c59f1] -- Updated dependencies [b0e78a8] -- Updated dependencies [f31cc8d] -- Updated dependencies [f343dc4] -- Updated dependencies [8269e32] -- Updated dependencies [74f7339] -- Updated dependencies [a6c35a2] -- Updated dependencies [c2f1002] -- Updated dependencies [4cc4fb7] -- Updated dependencies [97b6658] -- Updated dependencies [28d1eb7] -- Updated dependencies [06770c0] -- Updated dependencies [2c26040] -- Updated dependencies [f758cec] -- Updated dependencies [5b47ab5] -- Updated dependencies [b09d8d9] -- Updated dependencies [b09d8d9] -- Updated dependencies [8675db6] -- Updated dependencies [b09d8d9] -- Updated dependencies [27358d5] -- Updated dependencies [1c3da1f] -- Updated dependencies [c1f344b] -- Updated dependencies [3eb1b2b] -- Updated dependencies [9c93465] -- Updated dependencies [a34fd2e] -- Updated dependencies [ebb209c] -- Updated dependencies [76bcb83] -- Updated dependencies [59b85c0] -- Updated dependencies [889ae47] -- Updated dependencies [4f4c3fb] -- Updated dependencies [78f0be8] -- Updated dependencies [6e357ed] -- Updated dependencies [d6938bf] -- Updated dependencies [35f7fb4] -- Updated dependencies [0410522] -- Updated dependencies [63b33e6] -- Updated dependencies [f163028] -- Updated dependencies [814db6d] -- Updated dependencies [a5302c7] -- Updated dependencies [31e0be9] -- Updated dependencies [4bfd455] -- Updated dependencies [ffd2ce2] -- Updated dependencies [2a44c1d] -- Updated dependencies [7084313] -- Updated dependencies [f07808c] -- Updated dependencies [7ffc3d3] -- Updated dependencies [88346ba] -- Updated dependencies [4631592] -- Updated dependencies [62f8017] -- Updated dependencies [32ff033] -- Updated dependencies [a831df1] -- Updated dependencies [f752ee3] -- Updated dependencies [a1b61e0] -- Updated dependencies [cd6b9f2] -- Updated dependencies [2cb6d3c] -- Updated dependencies [af2a095] -- Updated dependencies [5ac93d4] -- Updated dependencies [695cfbd] -- Updated dependencies [0e043d8] -- Updated dependencies [93f267f] -- Updated dependencies [7445149] -- Updated dependencies [ec796d5] -- Updated dependencies [071d0dc] -- Updated dependencies [0024abf] -- Updated dependencies [8dd98bf] -- Updated dependencies [e87fea1] -- Updated dependencies [c65e529] -- Updated dependencies [0848bea] -- Updated dependencies [d51bed2] -- Updated dependencies [dadd1ad] -- Updated dependencies [acbf364] -- Updated dependencies [3ca34c1] -- Updated dependencies [7adc841] -- Updated dependencies [239c3a3] -- Updated dependencies [b8b3c64] -- Updated dependencies [2f2e63c] -- Updated dependencies [4845f85] -- Updated dependencies [486d526] -- Updated dependencies [94a0bbc] -- Updated dependencies [d6bfb3d] -- Updated dependencies [8a9c079] -- Updated dependencies [7b005b4] -- Updated dependencies [cc3555e] -- Updated dependencies [a2266a6] -- Updated dependencies [d25a0ec] -- Updated dependencies [89d7b35] -- Updated dependencies [94f7b6a] -- Updated dependencies [5c94f83] -- Updated dependencies [ea936f3] -- Updated dependencies [0c0fbd9] -- Updated dependencies [667b83e] -- Updated dependencies [f3141d8] -- Updated dependencies [5487c20] -- Updated dependencies [aa8b847] -- Updated dependencies [7687f7b] -- Updated dependencies [5a84d41] -- Updated dependencies [fd3013a] -- Updated dependencies [85ec26d] -- Updated dependencies [73e576f] -- Updated dependencies [f6476fc] -- Updated dependencies [69ac82c] -- Updated dependencies [4ac12ef] -- Updated dependencies [833ed84] -- Updated dependencies [a18abf3] -- Updated dependencies [c6a4eeb] -- Updated dependencies [1659072] -- Updated dependencies [f450ae7] -- Updated dependencies [abceb0d] -- Updated dependencies [627b188] -- Updated dependencies [8d4eae7] -- Updated dependencies [c5a5996] -- Updated dependencies [0c302a7] -- Updated dependencies [b88f5e8] -- Updated dependencies [857a6cf] -- Updated dependencies [65a3a84] -- Updated dependencies [6633337] -- Updated dependencies [21676eb] -- Updated dependencies [3f296bf] -- Updated dependencies [e474853] -- Updated dependencies [e9cb9ab] -- Updated dependencies [42cc219] -- Updated dependencies [d42a92f] -- Updated dependencies [569611f] -- Updated dependencies [51d74ad] -- Updated dependencies [d7e0b42] -- Updated dependencies [3510e4a] -- Updated dependencies [f00d8d4] -- Updated dependencies [5326b36] -- Updated dependencies [aa4b90d] -- Updated dependencies [ccd9397] -- Updated dependencies [503be86] -- Updated dependencies [54299ca] -- Updated dependencies [ae490ef] -- Updated dependencies [e124711] -- Updated dependencies [dc61def] -- Updated dependencies [bca935b] -- Updated dependencies [d92c72d] -- Updated dependencies [c54c822] -- Updated dependencies [8dcc0f5] -- Updated dependencies [75b9e51] -- Updated dependencies [f61c8cf] -- Updated dependencies [e3ef52b] -- Updated dependencies [0a2f233] -- Updated dependencies [8621cdd] -- Updated dependencies [251e888] -- Updated dependencies [07f1822] -- Updated dependencies [e336549] -- Updated dependencies [3bb9340] -- Updated dependencies [1e604c4] -- Updated dependencies [04fab5e] -- Updated dependencies [183b4c4] -- Updated dependencies [7f713b6] -- Updated dependencies [d40f43a] -- Updated dependencies [2fdb36e] -- Updated dependencies [e787608] -- Updated dependencies [6f23667] -- Updated dependencies [cde1975] -- Updated dependencies [0bc685a] -- Updated dependencies [20526f5] -- Updated dependencies [efedd28] -- Updated dependencies [5d21a48] -- Updated dependencies [5278e11] -- Updated dependencies [c5eef1d] -- Updated dependencies [e5e7ee0] -- Updated dependencies [23dba62] -- Updated dependencies [e0f300b] -- Updated dependencies [761a0ba] -- Updated dependencies [c960170] -- Updated dependencies [19365b7] -- Updated dependencies [ba98e26] -- Updated dependencies [b7ed26d] -- Updated dependencies [a2ebea2] -- Updated dependencies [800bdb0] -- Updated dependencies [9d4dfc4] -- Updated dependencies [1059965] -- Updated dependencies [def5919] -- Updated dependencies [ee264b2] -- Updated dependencies [60b672e] -- Updated dependencies [f104bab] -- Updated dependencies [68dea0b] -- Updated dependencies [6b441a8] -- Updated dependencies [64f8cbe] -- Updated dependencies [6cb81c7] -- Updated dependencies [61282f9] -- Updated dependencies [ce0cfe9] -- Updated dependencies [04f1182] -- Updated dependencies [3a2dde7] -- Updated dependencies [8c20f75] -- Updated dependencies [be87153] -- Updated dependencies [dd0f681] -- Updated dependencies [60f0dd8] -- Updated dependencies [a87c5cd] -- Updated dependencies [a47f338] -- Updated dependencies [b3a3d83] -- Updated dependencies [7a55913] -- Updated dependencies [35accbf] -- Updated dependencies [6038de7] -- Updated dependencies [fc5f536] -- Updated dependencies [5647006] -- Updated dependencies [e654bfd] -- Updated dependencies [01a7337] -- Updated dependencies [b45c71e] -- Updated dependencies [d71ff32] -- Updated dependencies [f8cfbb4] -- Updated dependencies [6e6c872] -- Updated dependencies [2598216] -- Updated dependencies [11949fc] -- Updated dependencies [2c7e62d] -- Updated dependencies [eb95d97] -- Updated dependencies [b098b0e] -- Updated dependencies [4d00b13] -- Updated dependencies [1363084] -- Updated dependencies [fa5758e] -- Updated dependencies [38f7e4f] -- Updated dependencies [eb7613c] -- Updated dependencies [c57f3cf] -- Updated dependencies [ecc9110] -- Updated dependencies [9aa5510] -- Updated dependencies [e4c2dc8] -- Updated dependencies [97faca3] -- Updated dependencies [57bab76] -- Updated dependencies [c89d18c] -- Updated dependencies [1bd2795] -- Updated dependencies [f7bd4e2] -- Updated dependencies [361bd5b] -- Updated dependencies [aac90a5] -- Updated dependencies [3da3da5] -- Updated dependencies [1e6ab15] -- Updated dependencies [b90086a] -- Updated dependencies [8186a70] -- Updated dependencies [a329cca] -- Updated dependencies [c87ef70] -- Updated dependencies [3cb0618] -- Updated dependencies [32a0874] -- Updated dependencies [6eec18c] -- Updated dependencies [4d7bebf] -- Updated dependencies [821ac7a] -- Updated dependencies [8f81731] -- Updated dependencies [7055c22] -- Updated dependencies [785a748] -- Updated dependencies [3af0354] -- Updated dependencies [866ff16] -- Updated dependencies [5a85e67] -- Updated dependencies [8b50cb3] -- Updated dependencies [a0fdc56] -- Updated dependencies [b95577a] -- Updated dependencies [0dcbc11] -- Updated dependencies [d88f3e9] -- Updated dependencies [ad5fe25] -- Updated dependencies [c183a12] -- Updated dependencies [83c161f] -- Updated dependencies [d8c4957] -- Updated dependencies [b9f930b] -- Updated dependencies [f24cb83] -- Updated dependencies [5dbbb92] -- Updated dependencies [ea90179] -- Updated dependencies [1818998] -- Updated dependencies [ce92674] -- Updated dependencies [5ef0b5b] -- Updated dependencies [8c2db68] -- Updated dependencies [22b5e54] -- Updated dependencies [0166bd5] -- Updated dependencies [8064b07] -- Updated dependencies [09ee21c] -- Updated dependencies [4a56dbd] -- Updated dependencies [289d04a] -- Updated dependencies [f549a0d] -- Updated dependencies [48fbacb] -- Updated dependencies [06df4fa] -- Updated dependencies [3fc2e48] -- Updated dependencies [c9b809f] -- Updated dependencies [e8f435c] -- Updated dependencies [32386f8] -- Updated dependencies [9b702dc] -- Updated dependencies [ab16331] -- Updated dependencies [41610f6] -- Updated dependencies [69f1dfd] -- Updated dependencies [bbe05de] -- Updated dependencies [355e951] -- Updated dependencies [a1dd1e4] -- Updated dependencies [dadb43f] -- Updated dependencies [3556b67] - - @objectstack/spec@17.0.0 - - @objectstack/core@17.0.0 - - @objectstack/platform-objects@17.0.0 - -## 17.0.0-rc.6 - -### Patch Changes - -- f40c5b4: refactor(plugin-approvals,plugin-reports): enforcement implementations annotate the full `ExecutionContext` (#7135) - - The services half of #7070, mirroring what PR #7140 did for - `plugin-sharing` / `plugin-audit`. #6523 converged 36 contract signatures onto - the complete `resolveAuthzContext` envelope, applying the #6206 ruling — - enforcement adjudicates on the whole envelope, never a per-site subset. The - implementations behind those contracts still annotated their own parameters - with the six-field shape the contracts used to name, so nothing they could - _read_ had widened. - - `ApprovalService`, the approval flow-node provider and `ReportService` now - declare `ExecutionContext` on all 43 of those positions, and the casts the - narrow annotation forced are gone: - - - `isOverrideActor()` read the derived `posture` (ADR-0095) through an - unchecked `(context as any)`. That gate decides whether a platform or tenant - admin may release a STUCK approval — one routed to an unstaffed position, the - only in-product recovery from a permanently locked record — so an erasure sat - directly on an enforcement input: a mistyped rung would have compiled and - silently denied every override. It is a declared read now. - - Both services' `SYSTEM_CTX` is typed as the envelope and passed as itself, - retiring the `SYSTEM_CTX as unknown as …` double casts at the three sites - that hand it to a contract method. - - The `(context as any).userId` / `.tenantId` reads in `ApprovalService` now - read declared fields. - - `OwnerContextResolver` returns the envelope, which is what a scheduled report - actually resolves for its owner (#2849 / #2980). - - **No runtime behaviour changes.** The values were always complete — this - family's damage was type-side — so every gate answers exactly what it answered - before. Method parameters only WIDEN what they accept, so no caller is - affected, and no public export changes shape. - - Casts deliberately kept, and now documented where they sit: `organizationId` - is not a field of the envelope at all — that spelling has its own history - (#5858 / `check:org-identifier`) and was held out of this change by #7070. In - `approval-node.ts` the single remaining assertion exists only because the - literal names that key; it was reduced from `as unknown as …` to a single - `as ExecutionContext`, which still requires the literal to be comparable to - the envelope. - - Because a re-narrowed annotation would compile, ship and pass every test in - these packages, the convergence is pinned by a new compile-time module per - package, `exec-context-annotation.pin.ts`: it hands each parameter a fresh - literal naming envelope-only fields (`posture`, `accessible_org_ids`, - `org_user_ids`), which TypeScript's excess-property check rejects the moment a - parameter narrows back, plus negative cases so a parameter erased to `any` - cannot pass either. - - The exported `SharingExecutionContext` type itself is NOT removed here: it is - defined in `packages/spec`, which is single-owner, so its retirement is a - separate follow-up. - -- d0d5205: refactor(core,plugin-audit,service-storage,plugin-reports): give the `__` operation-private-key convention a single owner (#7284) - - `withoutOperationPrivateKeys` — the rule that a consumer forwarding a caller's - execution envelope to a question about a DIFFERENT object must first drop the - `__`-prefixed keys plugin-security stamped for the operation in flight — had been - hand-copied into three packages: `plugin-audit`'s comment access hooks (#7141), - `service-storage`'s attachment access hooks (#7145) and `plugin-reports`' report - service (#7204). Each carried its own `OPERATION_PRIVATE_KEY_PREFIX` and its own - doc block, and the prose had already diverged while the code still agreed — the - shape that makes a later divergence in behaviour hard to notice. - - The helper now lives once, in `@objectstack/core` - (`security/operation-private-keys.ts`), exported from the package root. Core is - the only candidate all three consumers already depend on: `plugin-security` is - the producer of the convention and the most honest owner, but none of the three - depends on it and a string-prefix filter does not justify three new dependency - edges onto a plugin; `@objectstack/spec` is fenced off by Prime Directive #2. The - new home sits beside `assemble-execution-context.ts`, which owns the other end of - the same lifecycle — that file is where an `ExecutionContext` is built at a - transport entry point, this one is where it is stripped back down before being - forwarded. - - The full reasoning moved with the code rather than being thinned: which keys the - middleware stamps and why each is a widening input, why they are dropped by - PREFIX and never by a name list, and why the fresh copy is load-bearing in both - directions. Each consumer keeps only its own local half — which object _its_ - gates actually ask about — and points at the shared home. - - No behaviour change: the three copies were byte-equivalent, and all three - packages' suites pass unchanged. Two new pins at the home cover it — the rule's - own behaviour, which no package-level test had ever asserted directly, and a - repository-shape pin that turns red if a fourth file declares its own copy. - -- cb466aa: Reports read with the caller's whole execution envelope, so a `group`-posture report no longer under-reports - - `executeReport` rebuilt a five-field projection of the caller's `ExecutionContext` - (`userId` / `tenantId` / `positions` / `permissions` / `isSystem`) before handing it to - the engine read that produces the report — while the method's own comment promised - "reports execute with the caller's identity". - - **Before.** `accessible_org_ids` was not in that projection, and the engine reads it by - name (`buildDriverOptions`, ADR-0105 D2 / #3623) to widen the driver's native tenant - scope to the caller's whole membership set under the `group` tenancy posture. Absent, the - drivers fall back to active-org equality — "fail toward isolation". So the identical query - returned the membership union in an interactive list view and collapsed to the active org - inside a **saved or scheduled** report: silently short rows, no error, nothing in the - output saying so. Measured end-to-end on a real kernel + SQL driver: three rows across two - member orgs came back as three interactively and two in the report, and a scheduled CSV - digest emailed the owner the same two. `timezone` went the same way, so a read-time - formula field resolved its calendar day in UTC instead of the caller's business timezone; - `posture`, `org_user_ids`, `systemPermissions` and `onBehalfOf` were dropped too. - - **After.** The read receives the caller's envelope whole (the #6206 ruling — enforcement - adjudicates on the whole `resolveAuthzContext` envelope, never a per-site subset), minus - the `__`-prefixed keys plugin-security stamps for the operation in flight, and as a fresh - object so a callee's stamp cannot write back into the caller's request context. The same - shape `plugin-audit` (#7141) and `service-storage` (#7145) landed. Direction is unchanged - outside `group`: the `isolated` posture, a deployment with no posture provider, and a - `group` caller with an empty accessible set all still read at active-org equality. - -- 2c2a212: fix(reports): owner-gate the saved-report schedule routes (#2980) - - The report read/run/delete routes are owner-isolated (a caller may only touch a - report they own, denied as `REPORT_NOT_FOUND` to avoid leaking that the id - exists), but the two schedule routes bypassed that gate: `unscheduleReport` and - `listSchedules` took the caller `context` as `_context` and never consulted it, - querying under the system context (RLS-bypassing). Any authenticated caller - could therefore delete another owner's report schedule — a cross-owner - destructive write — or list another owner's schedules (leaking recipient - addresses and cron), by supplying an id. - - Both now resolve the schedule's parent report and require the caller to own it, - mirroring the sibling routes: - - - **`unscheduleReport`** loads the schedule, then its report, and deletes only - when `canAccessReport` holds; a cross-owner attempt throws `REPORT_NOT_FOUND` - (mapped to `404` by the REST layer, deny-as-404 anti-enumeration), while a - genuinely-absent schedule stays idempotent. `scheduleReport` (create) was - already gated via `getReport`, so only the delete/list doors were open. - - **`listSchedules`** returns an empty list to any non-system caller who cannot - access the report it is scoped to — the same non-leaking posture as - `listReports`. The scheduler's system context still sees every schedule. - - No authoring-surface or metadata change; existing owner-path behavior is - unchanged. - -- dadd1ad: refactor(spec,plugin-sharing): retire the exported `SharingExecutionContext` type (#7218) - - - - **BREAKING — public surface removal.** `SharingExecutionContext` is deleted from - `@objectstack/spec` (`contracts/sharing-service`) and from - `@objectstack/plugin-sharing`, which re-exported it. Both `api-surface/` and - `export-origins/` snapshots are regenerated accordingly. - - This is the deferred deletion recorded when #7070 split the convergence in two. - #6523 / PR #7068 converged 36 contract signatures onto the full - `resolveAuthzContext` envelope (`ExecutionContext`), applying the #6206 ruling — - enforcement adjudicates on the whole envelope, never a per-site subset. The - consumer halves then re-annotated the implementations: PR #7140 (identity: - `plugin-sharing`, `plugin-audit`) and PR #7206 (services: `plugin-approvals`, - `plugin-reports`). Both landed with the type still exported, because it is - DEFINED in `packages/spec` and that package's retirement is the spec seat's to - make. Nothing declares it any more, so it goes. - - **Migration.** Anyone who imported `SharingExecutionContext` from either package - should import `ExecutionContext` from `@objectstack/spec` instead — the type the - contracts have declared since #7068. The old shape was six optional fields, all - of which exist on the envelope with the same names and types, so a value that - satisfied the retired type already satisfies `ExecutionContext`; only the - spelling of the annotation changes. - - **No runtime behaviour changes.** The type was erased at compile time and no - signature's accepted shape moved: the contracts already took the wide envelope. - - **What the retirement did NOT remove — the reason to read the pins.** Deleting - the type does not make re-narrowing a compile error. Structural subtyping still - accepts a six-field context where the envelope is expected, so the boundary is - held by the declared parameter type plus the pins, exactly as before. The three - `exec-context-annotation.pin.ts` files (`plugin-sharing`, `plugin-approvals`, - `plugin-reports`) told their failure story as "the parameter narrows back to - `SharingExecutionContext`", which a deletion would have quietly hollowed out. - Each now keeps the retired six-field shape as a local, non-exported SPECIMEN - type and refutes every enforcement parameter against it by type identity, so a - re-narrowing under ANY name is red — alongside the fresh-literal - excess-property checks they already carried. `sharing-service.test.ts` in - `packages/spec` is re-anchored the same way, and its "twin unchanged in shape" - case becomes a "twin stays retired" case. The narrative the retired type's doc - block carried (the measured `(context as any).posture` specimen, and why tsc - cannot police this) moves to the module doc of `contracts/sharing-service`, - which the contracts and pins now point at. - -- 2465133: fix(plugins): sweep the service-lookup erasures out of the plugin composition roots, and fix the two alias-only HTTP reads it exposed (#4251 B5) - - Batch B5 of the #4251 sweep: the seven remaining `packages/plugins/*` composition - roots. 35 lookup sites that had been erased to `any` now carry the slot's - contract, so the compiler checks what each plugin actually calls on the service - it resolved. The ratchet drops 143 sites / 32 files to 108 / 25. - - **Two real defects, both of the shape this sweep exists to find.** Approvals' - actionable-link pages (ADR-0043) and sharing's public share-link REST routes each - read the HTTP server under `http-server` _only_ — the deprecated alias. The - ledger records `http.server` as canonical and as the only name present on every - provider path: `runtime.ts`'s `config.server` path registers no alias at all. On - that path both lookups threw, the surrounding `catch` swallowed it, and the - routes silently never mounted — approval e-mail action links 404'd and the - share-link surface was absent, with nothing in the log to say so. Both reads are - now canonical-first with the alias as fallback, each name in its own `try` - because `getService` throws on an empty slot (so `a() ?? b()` inside one `try` - never reaches `b` — the same correction #4393 made in metadata and - cloud-connection). - - Typing choices follow the batch method: pure data-plane consumers take the - narrow contract (`IDataEngine` in reports), consumers that bind hook or - middleware seams take the engine seen whole (`IObjectQLEngine` in approvals, - sharing and pinyin-search), and slots with no contract get a **named** local - surface rather than `any` — plugin-email's `MailSettingsSurface`, and the - surfaces the consuming packages already declared (`ApprovalMessagingSurface`, - `SharingSecurityProbe`, `ReportEmail`). A named surface that omits a member - still makes the compiler name every call site; `any` says nothing. - - No behaviour change beyond the two alias reads. No contract changes. - -- Updated dependencies [3d5c090] -- Updated dependencies [e5bd768] -- Updated dependencies [e027b3e] -- Updated dependencies [c2429b0] -- Updated dependencies [445a0c2] -- Updated dependencies [f6609e6] -- Updated dependencies [a70358a] -- Updated dependencies [97e7e3c] -- Updated dependencies [8828b9e] -- Updated dependencies [53068c1] -- Updated dependencies [ee58392] -- Updated dependencies [f16e54e] -- Updated dependencies [06be54e] -- Updated dependencies [259459d] -- Updated dependencies [3f7f14e] -- Updated dependencies [6968885] -- Updated dependencies [eaed61f] -- Updated dependencies [debe2f6] -- Updated dependencies [97b0798] -- Updated dependencies [5fa04fb] -- Updated dependencies [43a7a8d] -- Updated dependencies [73f69dc] -- Updated dependencies [04c56aa] -- Updated dependencies [b3efeb7] -- Updated dependencies [ddd075a] -- Updated dependencies [88154be] -- Updated dependencies [e8dc61e] -- Updated dependencies [2f3e793] -- Updated dependencies [d8e8d9c] -- Updated dependencies [94e749b] -- Updated dependencies [ea1d916] -- Updated dependencies [ae31a19] -- Updated dependencies [e0f300b] -- Updated dependencies [62b6a2f] -- Updated dependencies [5b4780b] -- Updated dependencies [a933452] -- Updated dependencies [8140915] -- Updated dependencies [7b48cf9] -- Updated dependencies [b5404f4] -- Updated dependencies [f764691] -- Updated dependencies [e120a5a] -- Updated dependencies [e650d67] -- Updated dependencies [04476e7] -- Updated dependencies [79228cd] -- Updated dependencies [b3363e9] -- Updated dependencies [2ef1807] -- Updated dependencies [d03fe25] -- Updated dependencies [2672f85] -- Updated dependencies [11066f6] -- Updated dependencies [916af17] -- Updated dependencies [84c86fb] -- Updated dependencies [2a2a9fb] -- Updated dependencies [a2e157c] -- Updated dependencies [95c4227] -- Updated dependencies [2a61116] -- Updated dependencies [d4df105] -- Updated dependencies [e2798fa] -- Updated dependencies [0fd8556] -- Updated dependencies [74155c7] -- Updated dependencies [6908830] -- Updated dependencies [8b06bba] -- Updated dependencies [4c54037] -- Updated dependencies [0f7157b] -- Updated dependencies [d9bef45] -- Updated dependencies [f549a0d] -- Updated dependencies [82da264] -- Updated dependencies [f586f1a] -- Updated dependencies [9b9b70f] -- Updated dependencies [f5a9bc2] -- Updated dependencies [881a3cc] -- Updated dependencies [ad6317b] -- Updated dependencies [8a88885] -- Updated dependencies [5f7669e] -- Updated dependencies [becbe53] -- Updated dependencies [b127c8b] -- Updated dependencies [a80302a] -- Updated dependencies [474f131] -- Updated dependencies [050cd82] -- Updated dependencies [4d552af] -- Updated dependencies [44d677c] -- Updated dependencies [c32944d] -- Updated dependencies [1dd780f] -- Updated dependencies [c8d6f6e] -- Updated dependencies [92a67f2] -- Updated dependencies [9136327] -- Updated dependencies [bf0ae99] -- Updated dependencies [cb3b6cd] -- Updated dependencies [73b7234] -- Updated dependencies [d2b97c3] -- Updated dependencies [59b794f] -- Updated dependencies [fc3a36a] -- Updated dependencies [69787f0] -- Updated dependencies [5d022a1] -- Updated dependencies [042b9ee] -- Updated dependencies [f549a0d] -- Updated dependencies [a36db28] -- Updated dependencies [3f8817a] -- Updated dependencies [a2443e3] -- Updated dependencies [e1554b1] -- Updated dependencies [4856789] -- Updated dependencies [c3f4916] -- Updated dependencies [33e0385] -- Updated dependencies [2205363] -- Updated dependencies [09fe58d] -- Updated dependencies [d0a5ceb] -- Updated dependencies [e18a162] -- Updated dependencies [d6d1a50] -- Updated dependencies [d127ff0] -- Updated dependencies [9b86cf6] -- Updated dependencies [8825a06] -- Updated dependencies [5087ac6] -- Updated dependencies [2d1ddf0] -- Updated dependencies [354b00f] -- Updated dependencies [3de535b] -- Updated dependencies [fe2e15a] -- Updated dependencies [c6b6bb4] -- Updated dependencies [59c544d] -- Updated dependencies [2f59da0] -- Updated dependencies [8ad609c] -- Updated dependencies [bbee302] -- Updated dependencies [08863dd] -- Updated dependencies [56664f5] -- Updated dependencies [31cbe90] -- Updated dependencies [90bbf25] -- Updated dependencies [eb91eba] -- Updated dependencies [42da73d] -- Updated dependencies [643b7c7] -- Updated dependencies [d0d5205] -- Updated dependencies [1a15893] -- Updated dependencies [b70e534] -- Updated dependencies [2233a85] -- Updated dependencies [62dd69a] -- Updated dependencies [e15e679] -- Updated dependencies [2ab1257] -- Updated dependencies [4cc4fb7] -- Updated dependencies [28d1eb7] -- Updated dependencies [2c26040] -- Updated dependencies [f758cec] -- Updated dependencies [78f0be8] -- Updated dependencies [35f7fb4] -- Updated dependencies [a5302c7] -- Updated dependencies [7084313] -- Updated dependencies [0e043d8] -- Updated dependencies [dadd1ad] -- Updated dependencies [2f2e63c] -- Updated dependencies [486d526] -- Updated dependencies [89d7b35] -- Updated dependencies [85ec26d] -- Updated dependencies [f6476fc] -- Updated dependencies [4ac12ef] -- Updated dependencies [b88f5e8] -- Updated dependencies [42cc219] -- Updated dependencies [d42a92f] -- Updated dependencies [51d74ad] -- Updated dependencies [d7e0b42] -- Updated dependencies [3510e4a] -- Updated dependencies [aa4b90d] -- Updated dependencies [54299ca] -- Updated dependencies [dc61def] -- Updated dependencies [251e888] -- Updated dependencies [183b4c4] -- Updated dependencies [2fdb36e] -- Updated dependencies [e787608] -- Updated dependencies [20526f5] -- Updated dependencies [c5eef1d] -- Updated dependencies [e0f300b] -- Updated dependencies [761a0ba] -- Updated dependencies [61282f9] -- Updated dependencies [be87153] -- Updated dependencies [60f0dd8] -- Updated dependencies [a87c5cd] -- Updated dependencies [a47f338] -- Updated dependencies [2598216] -- Updated dependencies [2c7e62d] -- Updated dependencies [eb7613c] -- Updated dependencies [ecc9110] -- Updated dependencies [f7bd4e2] -- Updated dependencies [361bd5b] -- Updated dependencies [1818998] -- Updated dependencies [09ee21c] -- Updated dependencies [f549a0d] -- Updated dependencies [3fc2e48] -- Updated dependencies [e8f435c] -- Updated dependencies [41610f6] - - @objectstack/spec@17.0.0-rc.6 - - @objectstack/platform-objects@17.0.0-rc.6 - - @objectstack/core@17.0.0-rc.6 - -## 17.0.0-rc.5 - -### Patch Changes - -- Updated dependencies [e8f8f6c] -- Updated dependencies [7f713b6] -- Updated dependencies [c960170] -- Updated dependencies [def5919] -- Updated dependencies [ce0cfe9] -- Updated dependencies [1363084] - - @objectstack/spec@17.0.0-rc.5 - - @objectstack/core@17.0.0-rc.5 - - @objectstack/platform-objects@17.0.0-rc.5 - -## 17.0.0-rc.4 - -### Patch Changes - -- Updated dependencies [9fe9c1d] -- Updated dependencies [d4e0809] -- Updated dependencies [f724f69] -- Updated dependencies [28ad90e] -- Updated dependencies [f8644c7] -- Updated dependencies [306ca50] -- Updated dependencies [978fed2] -- Updated dependencies [cfc293f] -- Updated dependencies [de70b42] -- Updated dependencies [fb3d99b] -- Updated dependencies [cdfbee2] -- Updated dependencies [29c6c9d] -- Updated dependencies [d21c001] -- Updated dependencies [f1cc3a3] -- Updated dependencies [ddc2527] -- Updated dependencies [553a47f] -- Updated dependencies [a3a884d] -- Updated dependencies [cfed092] -- Updated dependencies [2e284b2] -- Updated dependencies [1b49eaf] -- Updated dependencies [0161c7f] -- Updated dependencies [e900015] -- Updated dependencies [b5bdf48] -- Updated dependencies [a019e52] -- Updated dependencies [64fc6d5] -- Updated dependencies [b746aa0] -- Updated dependencies [947d4f9] -- Updated dependencies [eaaf03c] -- Updated dependencies [d17df80] -- Updated dependencies [7d0e7b5] -- Updated dependencies [6513c17] -- Updated dependencies [c142ced] -- Updated dependencies [eda599e] -- Updated dependencies [c001422] -- Updated dependencies [77022a9] -- Updated dependencies [52760bf] -- Updated dependencies [5543020] -- Updated dependencies [880d343] -- Updated dependencies [6e82972] -- Updated dependencies [4615a18] -- Updated dependencies [7f62706] -- Updated dependencies [667fa44] -- Updated dependencies [37e38d1] -- Updated dependencies [1eb13a0] -- Updated dependencies [c52e608] -- Updated dependencies [4dfd002] -- Updated dependencies [77be690] -- Updated dependencies [811c30c] -- Updated dependencies [b49ccfd] -- Updated dependencies [85d95e7] -- Updated dependencies [168f60f] -- Updated dependencies [244ca86] -- Updated dependencies [546ab3c] -- Updated dependencies [0b51bb6] -- Updated dependencies [d9971d3] -- Updated dependencies [eb3e650] -- Updated dependencies [abeb375] -- Updated dependencies [ef4efa8] -- Updated dependencies [cbb6a5c] -- Updated dependencies [795b6e1] -- Updated dependencies [175d789] -- Updated dependencies [55dbbba] -- Updated dependencies [72c3c86] -- Updated dependencies [7f1a635] -- Updated dependencies [e98fb14] -- Updated dependencies [0f2fdcd] -- Updated dependencies [8ffa8b9] -- Updated dependencies [674ac99] -- Updated dependencies [1b9a53b] -- Updated dependencies [502564d] -- Updated dependencies [471839d] -- Updated dependencies [46365ab] -- Updated dependencies [b508244] -- Updated dependencies [594508e] -- Updated dependencies [1c625ca] -- Updated dependencies [71f205d] -- Updated dependencies [414395b] -- Updated dependencies [c5adfe1] -- Updated dependencies [26e1029] -- Updated dependencies [108ba8d] -- Updated dependencies [b4ad984] -- Updated dependencies [a9f32df] -- Updated dependencies [aeb9b27] -- Updated dependencies [7d27da0] -- Updated dependencies [089767f] -- Updated dependencies [e4c8b6c] -- Updated dependencies [acb10f6] -- Updated dependencies [1c3da1f] -- Updated dependencies [a34fd2e] -- Updated dependencies [889ae47] -- Updated dependencies [4f4c3fb] -- Updated dependencies [7adc841] -- Updated dependencies [4845f85] -- Updated dependencies [7b005b4] -- Updated dependencies [94f7b6a] -- Updated dependencies [5c94f83] -- Updated dependencies [73e576f] -- Updated dependencies [c5a5996] -- Updated dependencies [ae490ef] -- Updated dependencies [f61c8cf] -- Updated dependencies [e3ef52b] -- Updated dependencies [07f1822] -- Updated dependencies [04fab5e] -- Updated dependencies [efedd28] -- Updated dependencies [5278e11] -- Updated dependencies [23dba62] -- Updated dependencies [ba98e26] -- Updated dependencies [f104bab] -- Updated dependencies [fc5f536] -- Updated dependencies [f8cfbb4] -- Updated dependencies [c89d18c] -- Updated dependencies [aac90a5] -- Updated dependencies [1e6ab15] -- Updated dependencies [c87ef70] -- Updated dependencies [3cb0618] -- Updated dependencies [32a0874] -- Updated dependencies [7055c22] -- Updated dependencies [785a748] -- Updated dependencies [3af0354] -- Updated dependencies [866ff16] -- Updated dependencies [5a85e67] -- Updated dependencies [c183a12] -- Updated dependencies [8064b07] -- Updated dependencies [4a56dbd] -- Updated dependencies [06df4fa] - - @objectstack/spec@17.0.0-rc.4 - - @objectstack/core@17.0.0-rc.4 - - @objectstack/platform-objects@17.0.0-rc.4 - -## 17.0.0-rc.2 - -### Patch Changes - -- Updated dependencies [430dcc2] -- Updated dependencies [e6ac4bd] -- Updated dependencies [80334c7] -- Updated dependencies [ce5242c] -- Updated dependencies [a7163ea] -- Updated dependencies [e6e9379] -- Updated dependencies [98877c9] -- Updated dependencies [98877c9] -- Updated dependencies [c44dd5e] -- Updated dependencies [e6b1b69] -- Updated dependencies [ad047d2] -- Updated dependencies [2826d1e] -- Updated dependencies [5a84d41] -- Updated dependencies [20b1a9e] -- Updated dependencies [203a449] -- Updated dependencies [ac37fc6] -- Updated dependencies [4820f55] -- Updated dependencies [462d9c4] -- Updated dependencies [7d21581] -- Updated dependencies [f2445c9] -- Updated dependencies [23338c3] -- Updated dependencies [5b843fb] -- Updated dependencies [b4487aa] -- Updated dependencies [65ca83a] -- Updated dependencies [67bf2e2] -- Updated dependencies [c6d1cb4] -- Updated dependencies [36030ff] -- Updated dependencies [6117f7b] -- Updated dependencies [e533b0b] -- Updated dependencies [cdf4d9a] -- Updated dependencies [aee1806] -- Updated dependencies [c13350b] -- Updated dependencies [c13350b] -- Updated dependencies [9ca2d85] -- Updated dependencies [c13350b] -- Updated dependencies [891d345] -- Updated dependencies [a52e2ef] -- Updated dependencies [5293114] -- Updated dependencies [20bc357] -- Updated dependencies [5966c2a] -- Updated dependencies [2382580] -- Updated dependencies [d9fa683] -- Updated dependencies [3c7bcc0] -- Updated dependencies [4b6cac7] -- Updated dependencies [7631964] -- Updated dependencies [ac471a0] -- Updated dependencies [60ae58e] -- Updated dependencies [ce92674] -- Updated dependencies [9f601e8] -- Updated dependencies [51c5227] -- Updated dependencies [a4a85c8] -- Updated dependencies [07a4e26] -- Updated dependencies [ec975f1] -- Updated dependencies [eb4204b] -- Updated dependencies [4f13be2] -- Updated dependencies [61cc079] -- Updated dependencies [0e96e46] -- Updated dependencies [d52d4fe] -- Updated dependencies [742cebb] -- Updated dependencies [ce92674] -- Updated dependencies [cf2c9b7] -- Updated dependencies [833b512] -- Updated dependencies [0f9faa2] -- Updated dependencies [7cf42fe] -- Updated dependencies [5966c2a] -- Updated dependencies [f78dd83] -- Updated dependencies [a2cd18a] -- Updated dependencies [4638aaa] -- Updated dependencies [0222d3c] -- Updated dependencies [071d0dc] -- Updated dependencies [0a936ea] -- Updated dependencies [023c00b] -- Updated dependencies [155507e] -- Updated dependencies [7bba90b] -- Updated dependencies [7e05d8e] -- Updated dependencies [061406d] -- Updated dependencies [c1f344b] -- Updated dependencies [9c93465] -- Updated dependencies [ebb209c] -- Updated dependencies [63b33e6] -- Updated dependencies [2a44c1d] -- Updated dependencies [695cfbd] -- Updated dependencies [7445149] -- Updated dependencies [071d0dc] -- Updated dependencies [0848bea] -- Updated dependencies [d51bed2] -- Updated dependencies [b8b3c64] -- Updated dependencies [0c0fbd9] -- Updated dependencies [f3141d8] -- Updated dependencies [5a84d41] -- Updated dependencies [fd3013a] -- Updated dependencies [21676eb] -- Updated dependencies [e336549] -- Updated dependencies [d40f43a] -- Updated dependencies [e5e7ee0] -- Updated dependencies [a2ebea2] -- Updated dependencies [800bdb0] -- Updated dependencies [04f1182] -- Updated dependencies [5647006] -- Updated dependencies [38f7e4f] -- Updated dependencies [c57f3cf] -- Updated dependencies [97faca3] -- Updated dependencies [ad5fe25] -- Updated dependencies [ea90179] -- Updated dependencies [ce92674] -- Updated dependencies [5ef0b5b] -- Updated dependencies [48fbacb] -- Updated dependencies [355e951] -- Updated dependencies [dadb43f] - - @objectstack/spec@17.0.0-rc.2 - - @objectstack/platform-objects@17.0.0-rc.2 - - @objectstack/core@17.0.0-rc.2 - -## 17.0.0-rc.1 - -### Patch Changes - -- 2e836de: chore(packaging): CHANGELOG.md ships in every npm tarball (#4261) - - The AGENTS.md post-task checklist requires breaking changesets to carry their - FROM → TO migration because "this text ships to consumers as `CHANGELOG.md` - inside the npm package and is what an upgrading agent greps after the tombstone - error." That delivery path was severed for 68 of the 69 publishable packages: - npm packs `package.json` / `README*` / `LICENSE*` unconditionally but — unlike - older npm versions — not `CHANGELOG.md`, and the canonical - `"files": ["dist", "README.md"]` whitelist never named it. Measured on npm - 10.9.7: `npm pack --dry-run` on `@objectstack/types` shipped 3 files while its - 70KB `CHANGELOG.md` stayed behind. Only `@objectstack/spec` listed it - explicitly. - - The tombstone-error scenario is precisely the one where the repo is out of - reach — the upgrading agent has `node_modules` and nothing else — so the - migration text has to ride in the tarball. Every publishable package now - declares `CHANGELOG.md` in `files`, and the canonical whitelist is - `["dist", "README.md", "CHANGELOG.md"]`. - - The other half is the gate: `check:published-files` gains a fifth invariant, - COMPLETE — a whitelist that fails to cover `CHANGELOG.md` fails the - always-required lint job, so the next package cannot silently sever the path - again. `@objectstack/spec`'s per-package EXTRA_ENTRIES exemption dissolves - into the canonical set. - - Consumer-visible change: one more file per install (the package's changelog, - e.g. 70.8KB for `@objectstack/types`), and `grep -r "removed key" -node_modules/@objectstack/*/CHANGELOG.md` now finds the migration it was - promised. - -- b5f9397: fix(sharing,runtime): a `sort` passed straight to the engine never ordered anything; migrate every in-repo engine call to canonical QueryAST keys (#4346) - - Two changes with different weights, from one sweep of every in-repo engine - call site that still speaks a deprecated alias. - - **The bug — three dropped sorts.** #4346 made the engine fold `filter`→`where` - and `top`→`limit` on all six methods. The other four pairs in - `RPC_QUERY_ALIAS_SLOTS` (`select`, `sort`, `skip`, `populate`) are folded at - the RPC/wire layer only — their values need shape lowering that belongs to - those layers — and a **direct `engine.find()` never crosses that layer**. Three - call sites passed `sort` there, so it rode onto the AST untouched, every - driver's `Array.isArray(query.orderBy)` guard declined to emit an ORDER BY, and - the query returned an ordinary-looking, arbitrarily-ordered result: - - | call site | asked for | actually got | - | ----------------------------------- | ------------------------------------------------- | --------------------------- | - | `share-link-routes.ts` | shared AI conversation messages, `created_at asc` | messages in arbitrary order | - | `runtime/domains/share-links.ts` | same route, runtime-domain copy | same | - | `share-link-service.ts` `listLinks` | the 200 most recent share links | an arbitrary 200 | - - All three combine the dropped sort with a `limit` — the "latest N" shape whose - failure #4226 spelled out: an unapplied sort returns rows in arbitrary order, - which `limit` then slices into an arbitrary page. #4226 fixed that in the wire - normalizer; these calls sit one layer below it. `listLinks` had no test at all, - which is why it went unnoticed. Now pinned — on the option bag the engine - receives, not on row order, because the failure is that the key never becomes - `orderBy` and a fake engine honouring either spelling would pass either way. - - **The cleanup — 27 no-op renames.** Every remaining in-repo engine call passing - `filter` now passes `where` (approvals 5, auth 2, reports 6, sharing 11, - webhooks 2, plus the one `filters` in a spec doc example). These are strict - no-ops since #4346 folds the alias — the point is that the framework stops - depending on a spelling it asks users to migrate off, which is a prerequisite - for ever retiring the aliases. Service-level `filter` PARAMETERS (each - service's own public API, e.g. `listRequests(filter)`) are deliberately - untouched — those are not engine option bags. - - Two of the renamed calls were live victims of the #4346 bug rather than - cosmetic: `auth-manager`'s `stampIdentitySource` read the table's first row via - `findOne({filter})` and counted the whole table via `count({filter})`, so a - federated sign-in never stamped `source: 'idp_provisioned'`. #4346 already - corrected the behaviour; this makes the call say what it means. - -- cc2de0e: chore(packaging): 20 packages stop publishing their sources, tests and build tooling (#4248) - - These 20 packages declared no `files` field, so npm fell back to packing the - whole package directory. `npm pack --dry-run` on `@objectstack/plugin-webhooks` - listed **21 files** — 15 under `src/`, three of them unit tests - (`auto-enqueuer.test.ts`, `bootstrap-declared-webhooks.test.ts`, …), plus the - build-time `scripts/i18n-extract.config.ts`. `dist/` lands on top of that at - publish time rather than instead of it, so consumers were installing the - TypeScript sources and the test suite alongside the artifact they asked for. - - Each now declares `"files": ["dist", "README.md"]`, matching the 29 packages - that already did. Nothing a consumer imports moves: every `main` / `types` / - `exports` target in all 20 already resolved inside `dist/`, which the new - `check:published-files` guard verifies rather than assumes. The visible change - is a smaller install and a smaller dependency-scanning surface — `npm pack` on - `@objectstack/plugin-webhooks` now yields 2 files plus `dist/`. - - The other half of the fix is the gate. Half the packages declaring `files` and - half not was the #3786 shape — a hand-copied convention with nothing enforcing - it, where whoever forgets the line gets no signal at all. `check:published-files` - (new, wired into the always-required `lint` job) holds every non-private - workspace package to four invariants: `files` is **declared**; it is - **sufficient** (covers every entry point, so tightening a whitelist cannot ship - a package that fails to resolve); it is **minimal** (admits no test, test-harness - config or build script); and anything beyond `dist` + `README.md` is - **registered** with a reason, reconciled in both directions so a stale exemption - is an error rather than dead text. `@objectstack/spec` is the one package with - registered extras — its `.zod.ts` sources, JSON Schemas, liveness ledgers and - `CHANGELOG.md` are product, not build input. - - This also closes an assumption #4206 was resting on. Excluding `/scripts/**` - from the docs-drift implementation test is sound only while no package publishes - `scripts/` as runtime code; that held, but it held because someone read all three - offenders by hand. It is now checked on every PR. - -- Updated dependencies [6a67d7a] -- Updated dependencies [0ecc656] -- Updated dependencies [06772eb] -- Updated dependencies [270650f] -- Updated dependencies [3aef718] -- Updated dependencies [1ea6bce] -- Updated dependencies [c1dcacd] -- Updated dependencies [ad303ed] -- Updated dependencies [32ccb23] -- Updated dependencies [f5a4ef0] -- Updated dependencies [2d3e255] -- Updated dependencies [7d7521f] -- Updated dependencies [5dc4d02] -- Updated dependencies [05154a1] -- Updated dependencies [9b6fe7c] -- Updated dependencies [8c711fb] -- Updated dependencies [09e4547] -- Updated dependencies [91f4c78] -- Updated dependencies [820eff9] -- Updated dependencies [8d895ff] -- Updated dependencies [f6472d7] -- Updated dependencies [78caf51] -- Updated dependencies [62a789b] -- Updated dependencies [789ad63] -- Updated dependencies [2af1988] -- Updated dependencies [0af50a3] -- Updated dependencies [2e836de] -- Updated dependencies [12a19a8] -- Updated dependencies [41dcda3] -- Updated dependencies [c8124e5] -- Updated dependencies [a1a4140] -- Updated dependencies [217e2e6] -- Updated dependencies [86a71d1] -- Updated dependencies [d5c75e2] -- Updated dependencies [03d26f7] -- Updated dependencies [4384921] -- Updated dependencies [3c628ce] -- Updated dependencies [7cb922e] -- Updated dependencies [1d22114] -- Updated dependencies [b5f9397] -- Updated dependencies [ed77493] -- Updated dependencies [58a03d2] -- Updated dependencies [dc530b4] -- Updated dependencies [e59786e] -- Updated dependencies [bcf1112] -- Updated dependencies [9774b78] -- Updated dependencies [b07d829] -- Updated dependencies [a648e96] -- Updated dependencies [a47ac06] -- Updated dependencies [e4c61a7] -- Updated dependencies [cc60165] -- Updated dependencies [081aa6f] -- Updated dependencies [91f4c78] -- Updated dependencies [e8d0c21] -- Updated dependencies [45dc446] -- Updated dependencies [c1d44f7] -- Updated dependencies [ab9fb5c] -- Updated dependencies [f985b3f] -- Updated dependencies [9a4932a] -- Updated dependencies [f9fc874] -- Updated dependencies [011b386] -- Updated dependencies [7777e8f] -- Updated dependencies [507b92a] -- Updated dependencies [7309c81] -- Updated dependencies [20bc1ec] -- Updated dependencies [90c2b15] -- Updated dependencies [42eeb7d] -- Updated dependencies [01e124d] -- Updated dependencies [7ce02eb] -- Updated dependencies [a13827e] -- Updated dependencies [7733604] -- Updated dependencies [40e420f] -- Updated dependencies [d13004a] -- Updated dependencies [be7360c] -- Updated dependencies [5b47ab5] -- Updated dependencies [b09d8d9] -- Updated dependencies [b09d8d9] -- Updated dependencies [8675db6] -- Updated dependencies [b09d8d9] -- Updated dependencies [3eb1b2b] -- Updated dependencies [59b85c0] -- Updated dependencies [6e357ed] -- Updated dependencies [d6938bf] -- Updated dependencies [31e0be9] -- Updated dependencies [4bfd455] -- Updated dependencies [ffd2ce2] -- Updated dependencies [62f8017] -- Updated dependencies [a831df1] -- Updated dependencies [f752ee3] -- Updated dependencies [a1b61e0] -- Updated dependencies [cd6b9f2] -- Updated dependencies [2cb6d3c] -- Updated dependencies [af2a095] -- Updated dependencies [ec796d5] -- Updated dependencies [e87fea1] -- Updated dependencies [c65e529] -- Updated dependencies [3ca34c1] -- Updated dependencies [239c3a3] -- Updated dependencies [94a0bbc] -- Updated dependencies [d6bfb3d] -- Updated dependencies [a2266a6] -- Updated dependencies [d25a0ec] -- Updated dependencies [667b83e] -- Updated dependencies [627b188] -- Updated dependencies [8d4eae7] -- Updated dependencies [857a6cf] -- Updated dependencies [65a3a84] -- Updated dependencies [ccd9397] -- Updated dependencies [bca935b] -- Updated dependencies [d92c72d] -- Updated dependencies [c54c822] -- Updated dependencies [8dcc0f5] -- Updated dependencies [75b9e51] -- Updated dependencies [0a2f233] -- Updated dependencies [8621cdd] -- Updated dependencies [6f23667] -- Updated dependencies [5d21a48] -- Updated dependencies [19365b7] -- Updated dependencies [b7ed26d] -- Updated dependencies [68dea0b] -- Updated dependencies [64f8cbe] -- Updated dependencies [b3a3d83] -- Updated dependencies [7a55913] -- Updated dependencies [35accbf] -- Updated dependencies [6038de7] -- Updated dependencies [eb95d97] -- Updated dependencies [e4c2dc8] -- Updated dependencies [1bd2795] -- Updated dependencies [8186a70] -- Updated dependencies [a329cca] -- Updated dependencies [6eec18c] -- Updated dependencies [4d7bebf] -- Updated dependencies [821ac7a] -- Updated dependencies [8f81731] -- Updated dependencies [8b50cb3] -- Updated dependencies [8c2db68] -- Updated dependencies [22b5e54] -- Updated dependencies [0166bd5] -- Updated dependencies [9b702dc] -- Updated dependencies [ab16331] - - @objectstack/spec@17.0.0-rc.1 - - @objectstack/platform-objects@17.0.0-rc.1 - - @objectstack/core@17.0.0-rc.1 - -## 17.0.0-rc.0 - -### Major Changes - -- 4ed7ed4: feat(security)!: the export axis is now OPT-IN, explainable, and covers reports (#3544, #3710) - - **BREAKING — `allowExport` unset no longer means "inherit read".** Reading a - record and taking a bulk machine-readable copy of the whole table are different - privileges (Salesforce "Export Reports", Dynamics "Export to Excel", NetSuite - "Export Lists", SAP `S_GUI` 61 all separate them). The axis now says so. - - ### Migration — FROM → TO - - | | before | after | - | -------------------- | ----------------------------------- | -------------------------- | - | `allowExport` unset | export **allowed** (inherited read) | export **denied** | - | `allowExport: false` | export denied | export denied (unchanged) | - | `allowExport: true` | export allowed | export allowed (unchanged) | - - **The one-line fix:** add `allowExport: true` to the object entry (or the `'*'` - wildcard) of every permission set whose holders should keep exporting. - - ```ts - objects: { - deal: { allowRead: true, allowExport: true }, // ← add the grant - } - ``` - - Nothing else changes: read, CRUD, RLS, FLS and sharing are untouched, and a set - that never exported is unaffected. - - **Who is affected.** Package-shipped sets are re-seeded on upgrade, so the - built-ins are handled for you — `admin_full_access` and `organization_admin` now - carry `allowExport: true` explicitly. **Environment-authored sets are not**: any - custom set whose users export must be edited. `member_default` deliberately does - NOT carry the grant, so ordinary authenticated users lose export until an admin - grants it — that is the point of the flip, not an oversight. - - **Merge semantics.** Most-permissive, exactly like the CRUD bits: any set - granting `true` grants export. `false` and unset are the same outcome; `false` - is authoring intent, not a veto, because permission sets are additive capability - containers (ADR-0090). - - **Not implied by super-user bits.** `viewAllRecords` / `modifyAllRecords` no - longer confer export. Separating "may see all data" from "may take a bulk copy" - is the segregation-of-duties case the axis exists for. - - ### Also in this change - - - **spec** — a set carrying `allowExport` is now **high-privilege** - (`describeHighPrivilegeBits`), so it cannot be bound to the `everyone` / - `guest` audience anchors. Without this the opt-in was defeatable by binding an - export-granting set to `everyone`. One predicate, so the runtime anchor gate, - the `@objectstack/lint` security-posture rule and the install-time suggestion - surface all pick it up together. - - **spec / plugin-security** — `ExplainOperationSchema` gains `export`, so - `explain` can answer _why_ a caller got `403 EXPORT_NOT_PERMITTED`. It - explains as `read ∧ the export grant`: `object_crud` reports the conjunction - and attributes the granting set, while every data-shaped layer - (requiredPermissions, OWD/depth/sharing, RLS, record attribution) is computed - as the `find` the export actually performs — asking the RLS compiler about an - `export` operation would match no policy and wrongly report "no RLS applies". - `readFilter` is surfaced for `export` as it is for `read`. - - **plugin-reports** — closes the reports side door (#3710). A report rendered - as `csv`/`json` is the same bulk copy of the same object, so it is gated by - the same `ISecurityService.canExport`. Enforced in `executeReport`, which the - interactive run, the ad-hoc run and the scheduled dispatch all funnel through; - `scheduleReport` additionally refuses at create time so an author is not told - at 3am. A schedule created while granted stops delivering once the grant is - revoked. `html_table` stays a read — it is a rendered view, not a bulk copy. - Deployments without `plugin-security` are unaffected (no permission sets - exist, so the axis does not apply). - -### Patch Changes - -- Updated dependencies [50616d9] -- Updated dependencies [08b5a3d] -- Updated dependencies [d99aeb3] -- Updated dependencies [4727eb8] -- Updated dependencies [f63cd09] -- Updated dependencies [fa3d0cf] -- Updated dependencies [af5a224] -- Updated dependencies [71f76e1] -- Updated dependencies [37b1346] -- Updated dependencies [99736a0] -- Updated dependencies [fe67e34] -- Updated dependencies [fdb4f50] -- Updated dependencies [1bd5652] -- Updated dependencies [14252d3] -- Updated dependencies [7fb436c] -- Updated dependencies [879ea13] -- Updated dependencies [201b31f] -- Updated dependencies [e2616e0] -- Updated dependencies [6fdc5c6] -- Updated dependencies [8b9d71e] -- Updated dependencies [33f5e23] -- Updated dependencies [259af21] -- Updated dependencies [587fc91] -- Updated dependencies [1986594] -- Updated dependencies [ad4af62] -- Updated dependencies [d44dbfa] -- Updated dependencies [474fe39] -- Updated dependencies [0bc685a] -- Updated dependencies [b949059] -- Updated dependencies [be1c52c] -- Updated dependencies [c5ff96d] -- Updated dependencies [84e7be9] -- Updated dependencies [a6c3f38] -- Updated dependencies [debc23a] -- Updated dependencies [0f8ad09] -- Updated dependencies [8f9689f] -- Updated dependencies [57a3bb3] -- Updated dependencies [5f9a987] -- Updated dependencies [9f060e5] -- Updated dependencies [bc17d39] -- Updated dependencies [db02d47] -- Updated dependencies [0bfdf46] -- Updated dependencies [376a061] -- Updated dependencies [7c7e246] -- Updated dependencies [f35cdc5] -- Updated dependencies [9ea2bc5] -- Updated dependencies [c2d9098] -- Updated dependencies [a227ed7] -- Updated dependencies [9613396] -- Updated dependencies [e47b342] -- Updated dependencies [4ed7ed4] -- Updated dependencies [2fa4ca1] -- Updated dependencies [f5a2320] -- Updated dependencies [deb538f] -- Updated dependencies [5b89711] -- Updated dependencies [0c8a22f] -- Updated dependencies [763931e] -- Updated dependencies [de9af8a] -- Updated dependencies [c4df271] -- Updated dependencies [a41ba5c] -- Updated dependencies [189854c] -- Updated dependencies [0e3a226] -- Updated dependencies [524151c] -- Updated dependencies [1d4756e] -- Updated dependencies [720c5ad] -- Updated dependencies [a8d1e24] -- Updated dependencies [d1cabaa] -- Updated dependencies [41642b0] -- Updated dependencies [4cca74c] -- Updated dependencies [88ef03e] -- Updated dependencies [9e2caf3] -- Updated dependencies [81ce41a] -- Updated dependencies [85e1e4e] -- Updated dependencies [dac6a08] -- Updated dependencies [394b7a1] -- Updated dependencies [677b591] -- Updated dependencies [d77d1b7] -- Updated dependencies [5b79a34] -- Updated dependencies [c757854] -- Updated dependencies [0045682] -- Updated dependencies [2a5f04a] -- Updated dependencies [4f740b0] -- Updated dependencies [67452d1] -- Updated dependencies [4921a95] -- Updated dependencies [0fc6219] -- Updated dependencies [605e190] -- Updated dependencies [c6c59f1] -- Updated dependencies [b0e78a8] -- Updated dependencies [f31cc8d] -- Updated dependencies [f343dc4] -- Updated dependencies [8269e32] -- Updated dependencies [74f7339] -- Updated dependencies [a6c35a2] -- Updated dependencies [c2f1002] -- Updated dependencies [f163028] -- Updated dependencies [f07808c] -- Updated dependencies [7ffc3d3] -- Updated dependencies [88346ba] -- Updated dependencies [4631592] -- Updated dependencies [32ff033] -- Updated dependencies [5ac93d4] -- Updated dependencies [93f267f] -- Updated dependencies [0024abf] -- Updated dependencies [acbf364] -- Updated dependencies [5487c20] -- Updated dependencies [aa8b847] -- Updated dependencies [7687f7b] -- Updated dependencies [1659072] -- Updated dependencies [abceb0d] -- Updated dependencies [0c302a7] -- Updated dependencies [6633337] -- Updated dependencies [f00d8d4] -- Updated dependencies [503be86] -- Updated dependencies [cde1975] -- Updated dependencies [0bc685a] -- Updated dependencies [11949fc] -- Updated dependencies [b098b0e] -- Updated dependencies [4d00b13] -- Updated dependencies [9aa5510] -- Updated dependencies [57bab76] -- Updated dependencies [b90086a] -- Updated dependencies [b95577a] -- Updated dependencies [83c161f] -- Updated dependencies [d8c4957] -- Updated dependencies [f24cb83] -- Updated dependencies [5dbbb92] -- Updated dependencies [69f1dfd] - - @objectstack/spec@17.0.0-rc.0 - - @objectstack/platform-objects@17.0.0-rc.0 - - @objectstack/core@17.0.0-rc.0 - -## 16.1.0 - -### Patch Changes - -- Updated dependencies [212b66a] -- Updated dependencies [d10c4dc] -- Updated dependencies [9e45b63] -- Updated dependencies [b20201f] - - @objectstack/platform-objects@16.1.0 - - @objectstack/spec@16.1.0 - - @objectstack/core@16.1.0 - -## 16.0.0 - -### Patch Changes - -- Updated dependencies [f972574] -- Updated dependencies [6289ec3] -- Updated dependencies [22013aa] -- Updated dependencies [3ad3dd5] -- Updated dependencies [8efa395] -- Updated dependencies [3a18b60] -- Updated dependencies [a8aa34c] -- Updated dependencies [e057f42] -- Updated dependencies [a3823b2] -- Updated dependencies [bc65105] -- Updated dependencies [43a3efb] -- Updated dependencies [524696a] -- Updated dependencies [bfa3c3f] -- Updated dependencies [5e3301d] -- Updated dependencies [dd9f223] -- Updated dependencies [46e876c] -- Updated dependencies [5f05de2] -- Updated dependencies [021ba4c] -- Updated dependencies [158aa14] -- Updated dependencies [62a2117] -- Updated dependencies [d2723e2] -- Updated dependencies [fefcd54] -- Updated dependencies [beaf2de] -- Updated dependencies [369eb6e] -- Updated dependencies [06ff734] -- Updated dependencies [b659111] -- Updated dependencies [5754a23] -- Updated dependencies [6c270a6] -- Updated dependencies [290e2f0] -- Updated dependencies [668dd17] -- Updated dependencies [8abf133] -- Updated dependencies [e0859b1] -- Updated dependencies [04ecd4e] -- Updated dependencies [4d5a892] -- Updated dependencies [16cebeb] -- Updated dependencies [86d30af] -- Updated dependencies [8923843] -- Updated dependencies [a2795f6] -- Updated dependencies [f16b492] -- Updated dependencies [4b6fde8] -- Updated dependencies [2018df9] -- Updated dependencies [fc5a3a2] -- Updated dependencies [8ff9210] - - @objectstack/spec@16.0.0 - - @objectstack/platform-objects@16.0.0 - - @objectstack/core@16.0.0 - -## 16.0.0-rc.1 - -### Patch Changes - -- Updated dependencies [6289ec3] -- Updated dependencies [8efa395] -- Updated dependencies [bfa3c3f] -- Updated dependencies [62a2117] -- Updated dependencies [06ff734] - - @objectstack/spec@16.0.0-rc.1 - - @objectstack/platform-objects@16.0.0-rc.1 - - @objectstack/core@16.0.0-rc.1 - -## 16.0.0-rc.0 - -### Patch Changes - -- Updated dependencies [f972574] -- Updated dependencies [22013aa] -- Updated dependencies [3ad3dd5] -- Updated dependencies [3a18b60] -- Updated dependencies [a8aa34c] -- Updated dependencies [e057f42] -- Updated dependencies [a3823b2] -- Updated dependencies [bc65105] -- Updated dependencies [43a3efb] -- Updated dependencies [524696a] -- Updated dependencies [5e3301d] -- Updated dependencies [dd9f223] -- Updated dependencies [46e876c] -- Updated dependencies [5f05de2] -- Updated dependencies [021ba4c] -- Updated dependencies [158aa14] -- Updated dependencies [d2723e2] -- Updated dependencies [fefcd54] -- Updated dependencies [beaf2de] -- Updated dependencies [369eb6e] -- Updated dependencies [b659111] -- Updated dependencies [5754a23] -- Updated dependencies [6c270a6] -- Updated dependencies [290e2f0] -- Updated dependencies [668dd17] -- Updated dependencies [8abf133] -- Updated dependencies [e0859b1] -- Updated dependencies [04ecd4e] -- Updated dependencies [4d5a892] -- Updated dependencies [16cebeb] -- Updated dependencies [86d30af] -- Updated dependencies [8923843] -- Updated dependencies [a2795f6] -- Updated dependencies [f16b492] -- Updated dependencies [4b6fde8] -- Updated dependencies [2018df9] -- Updated dependencies [fc5a3a2] - - @objectstack/spec@16.0.0-rc.0 - - @objectstack/platform-objects@16.0.0-rc.0 - - @objectstack/core@16.0.0-rc.0 - -## 15.1.1 - -### Patch Changes - -- @objectstack/spec@15.1.1 -- @objectstack/core@15.1.1 -- @objectstack/platform-objects@15.1.1 - -## 15.1.0 - -### Patch Changes - -- f531a26: fix(security): close three execution-surface authz holes surfaced by the #2849 class sweep (#2980, #2981, #2982) - - Three independent, confirmed-exploitable defects where an execution surface - ignored the caller's identity or fell open on a missing one. Each is fixed at - its own enforcement point; none change behaviour for correctly-scoped callers. - - - **#2980 — reports IDOR + scheduled-report RLS bypass.** `ReportService` - discarded the caller's context and read/wrote `sys_saved_report` with a system - context, so any authenticated user could read, delete, or overwrite any saved - report by id (cross-owner / cross-tenant), and `listReports` enumerated all - owners. `getReport`/`deleteReport`/`saveReport`/`listReports` are now - owner-scoped (system read of the protection-locked metadata object, but - authorization enforced by owner match); create/overwrite can no longer spoof - ownership. Scheduled dispatch no longer runs `isSystem` (which emailed the - target object's entire table past the owner's RLS): it resolves the owner to a - real RLS-bearing context via a new `resolveOwnerContext` seam and **fails - closed** (skips + marks the schedule failed) when the owner can't be resolved, - rather than running elevated. Wiring that resolver is the reports-surface - consumer of ADR-0073's user-less identity resolution. - - - **#2981 — knowledge/RAG retrieval fall-open.** `applyPermissionFilter` returned - every hit when the context was missing _or_ system. A missing identity is no - longer treated as a grant: object-backed hits fail closed (dropped, keeping - ACL-less file/http hits), and only an **explicit** system context passes - through. Closes the agent path where an omitted `ToolExecutionContext.actor` - yielded unfiltered semantic search over the whole corpus. - - - **#2982 — bulk-write OWD gap.** `update({multi:true})` / `deleteMany` had no - single id to `canEdit`-gate, so owner scoping was skipped on private (and - public_read) objects. A new `SharingService.buildWriteFilter` (the edit-set - analogue of `buildReadFilter`) is AND-ed into the write AST for multi writes, - constraining them to rows the caller may edit — including the on-behalf-of - delegator intersection. - - Tracked as the motivating evidence of ADR-0096 (execution-surface identity - admission); the mechanism that would prevent the class structurally is separate. - -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [3fe9df1] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [4109153] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [627f225] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] -- Updated dependencies [f531a26] - - @objectstack/spec@15.1.0 - - @objectstack/platform-objects@15.1.0 - - @objectstack/core@15.1.0 - -## 15.0.0 - -### Patch Changes - -- Updated dependencies [02a014b] -- Updated dependencies [28b7c28] -- Updated dependencies [13749ec] -- Updated dependencies [e62c233] -- Updated dependencies [ed61c9b] -- Updated dependencies [31d04d4] - - @objectstack/platform-objects@15.0.0 - - @objectstack/spec@15.0.0 - - @objectstack/core@15.0.0 - -## 14.8.0 - -### Patch Changes - -- 607aaf4: 导出文件名本地化 + 系统字段标签内置多语言回退。 - - **`@objectstack/rest` — 导出下载文件名**:`GET /data/:object/export` 的 `Content-Disposition` 不再是裸的 `<对象名>.<扩展名>`,改为「对象显示名-时间戳」:ASCII 兜底用 API 名(`filename="contracts-20260714-153045.xlsx"`),本地化标签(如中文)按 RFC 5987/6266 编码进 `filename*=UTF-8''…`(浏览器直接下载得到 `合同-20260714-153045.xlsx`)。新增导出 `exportContentDisposition(objectName, label, ext, now?)`。 - - **`@objectstack/spec` — 系统字段标签回退**:ObjectQL 注册表给每个对象注入的系统字段(`owner_id`/`created_at`/`created_by`/`updated_at`/`updated_by`)只带英文标签,自定义对象又没有对应的翻译条目,导致中文界面的列表表头、导出文件、导入模板里漏出 "Owner"/"Created At" 等英文。`translateObject` 现内置这五个字段的 en/zh-CN/ja-JP/es-ES 标签表(措辞与平台生成的翻译包一致),仅当字段仍是注入的英文默认值时套用——作者自定义的标签绝不覆盖;无翻译包时也生效(`translateObject` 不再因缺 bundle 而提前返回,REST 元数据翻译路径同步放宽,缓存 ETag 本就按 locale 分键,无缓存串味风险)。 - - **`@objectstack/plugin-reports` — 附件文件名**:定时报表附件的文件名清洗从「非 ASCII 全部替换成 `_`」改为按 Unicode 字母/数字保留(`\p{L}\p{N}`),中文计划名不再变成一串下划线。 - - **`@objectstack/rest` — 导入接受翻译后的选项标签(导出 ↔ 导入闭环)**:导出与导入模板写出的是*翻译后*的选项标签(如 `待规划`),但导入强制转换只认作者原始 schema 的标签/值,导致用户把自己刚导出的本地化文件原样导回时 select 字段全部报 `invalid_option`。`prepareImportRequest` 新增 `localizeSchema` 钩子(REST 导入路由传入 `translateMetaItem`),把当前 locale 的翻译标签合并进字段选项作为匹配同义词——作者标签与选项 code 照常匹配,非法值照常报错,翻译失败时降级为仅作者标签匹配。新增导出 `mergeLocalizedOptionSynonyms(metaMap, localizedMetaMap)`。 - -- Updated dependencies [16b4bf6] -- Updated dependencies [16b4bf6] -- Updated dependencies [10e8983] -- Updated dependencies [607aaf4] -- Updated dependencies [bb71321] - - @objectstack/spec@14.8.0 - - @objectstack/platform-objects@14.8.0 - - @objectstack/core@14.8.0 - -## 14.7.0 - -### Patch Changes - -- Updated dependencies [d6a72eb] - - @objectstack/spec@14.7.0 - - @objectstack/core@14.7.0 - - @objectstack/platform-objects@14.7.0 - -## 14.6.0 - -### Patch Changes - -- Updated dependencies [609cb13] -- Updated dependencies [ce6d151] - - @objectstack/spec@14.6.0 - - @objectstack/platform-objects@14.6.0 - - @objectstack/core@14.6.0 - -## 14.5.0 - -### Patch Changes - -- Updated dependencies [526805e] -- Updated dependencies [d79ca07] -- Updated dependencies [33ebd34] -- Updated dependencies [c044f08] -- Updated dependencies [01274eb] -- Updated dependencies [8f23746] -- Updated dependencies [b97af7e] -- Updated dependencies [6da03ee] - - @objectstack/spec@14.5.0 - - @objectstack/platform-objects@14.5.0 - - @objectstack/core@14.5.0 - -## 14.4.0 - -### Patch Changes - -- Updated dependencies [7953832] -- Updated dependencies [82e745e] -- Updated dependencies [f3035bd] -- Updated dependencies [82c0d94] -- Updated dependencies [7449476] - - @objectstack/spec@14.4.0 - - @objectstack/platform-objects@14.4.0 - - @objectstack/core@14.4.0 - -## 14.3.0 - -### Patch Changes - -- Updated dependencies [2a71f48] -- Updated dependencies [02f6af4] -- Updated dependencies [c1064f1] - - @objectstack/platform-objects@14.3.0 - - @objectstack/spec@14.3.0 - - @objectstack/core@14.3.0 - -## 14.2.0 - -### Patch Changes - -- Updated dependencies [ac8f029] -- Updated dependencies [4ab9958] - - @objectstack/spec@14.2.0 - - @objectstack/platform-objects@14.2.0 - - @objectstack/core@14.2.0 - -## 14.1.0 - -### Patch Changes - -- Updated dependencies [5a8465f] -- Updated dependencies [7f8620b] -- Updated dependencies [82ba3a6] - - @objectstack/spec@14.1.0 - - @objectstack/core@14.1.0 - - @objectstack/platform-objects@14.1.0 - -## 14.0.0 - -### Patch Changes - -- Updated dependencies [0a8e685] -- Updated dependencies [afa8115] -- Updated dependencies [80f12ca] -- Updated dependencies [332b711] -- Updated dependencies [e2fa074] -- Updated dependencies [23c8668] -- Updated dependencies [29f017d] -- Updated dependencies [216fa9a] -- Updated dependencies [6c22b12] -- Updated dependencies [d0531c4] -- Updated dependencies [cff5aac] - - @objectstack/spec@14.0.0 - - @objectstack/platform-objects@14.0.0 - - @objectstack/core@14.0.0 - -## 13.0.0 - -### Patch Changes - -- Updated dependencies [6d83431] -- Updated dependencies [01917c2] -- Updated dependencies [b271691] -- Updated dependencies [a5a1e41] -- Updated dependencies [466adf6] -- Updated dependencies [5be00c3] -- Updated dependencies [466adf6] -- Updated dependencies [2bee609] -- Updated dependencies [9fa84f9] -- Updated dependencies [fc7e7f7] - - @objectstack/spec@13.0.0 - - @objectstack/core@13.0.0 - - @objectstack/platform-objects@13.0.0 - -## 12.6.0 - -### Patch Changes - -- Updated dependencies [6cebf22] -- Updated dependencies [21420d9] - - @objectstack/spec@12.6.0 - - @objectstack/core@12.6.0 - - @objectstack/platform-objects@12.6.0 - -## 12.5.0 - -### Patch Changes - -- Updated dependencies [8b3d363] - - @objectstack/spec@12.5.0 - - @objectstack/core@12.5.0 - - @objectstack/platform-objects@12.5.0 - -## 12.4.0 - -### Patch Changes - -- Updated dependencies [60dc3ba] - - @objectstack/spec@12.4.0 - - @objectstack/core@12.4.0 - - @objectstack/platform-objects@12.4.0 - -## 12.3.0 - -### Patch Changes - -- Updated dependencies [e7eceec] - - @objectstack/spec@12.3.0 - - @objectstack/core@12.3.0 - - @objectstack/platform-objects@12.3.0 - -## 12.2.0 - -### Patch Changes - -- Updated dependencies [fce8ff4] -- Updated dependencies [3962023] -- Updated dependencies [2bb193d] -- Updated dependencies [0426d27] -- Updated dependencies [da807f7] -- Updated dependencies [4f5b791] - - @objectstack/spec@12.2.0 - - @objectstack/core@12.2.0 - - @objectstack/platform-objects@12.2.0 - -## 12.1.0 - -### Patch Changes - -- Updated dependencies [93e6d02] - - @objectstack/spec@12.1.0 - - @objectstack/core@12.1.0 - - @objectstack/platform-objects@12.1.0 - -## 12.0.0 - -### Patch Changes - -- Updated dependencies [a8df396] -- Updated dependencies [e695fe0] -- Updated dependencies [07f055c] -- Updated dependencies [7c09621] -- Updated dependencies [7709db4] -- Updated dependencies [2082109] -- Updated dependencies [7c09621] -- Updated dependencies [9860de4] -- Updated dependencies [069c205] - - @objectstack/spec@12.0.0 - - @objectstack/platform-objects@12.0.0 - - @objectstack/core@12.0.0 - -## 11.10.0 - -### Patch Changes - -- Updated dependencies [6a9397e] -- Updated dependencies [c0efe5d] - - @objectstack/spec@11.10.0 - - @objectstack/core@11.10.0 - - @objectstack/platform-objects@11.10.0 - -## 11.9.0 - -### Patch Changes - -- Updated dependencies [d3595d9] - - @objectstack/spec@11.9.0 - - @objectstack/core@11.9.0 - - @objectstack/platform-objects@11.9.0 - -## 11.8.0 - -### Patch Changes - -- Updated dependencies [53d491a] -- Updated dependencies [b84726b] - - @objectstack/platform-objects@11.8.0 - - @objectstack/spec@11.8.0 - - @objectstack/core@11.8.0 - -## 11.7.0 - -### Patch Changes - -- Updated dependencies [5178906] - - @objectstack/spec@11.7.0 - - @objectstack/platform-objects@11.7.0 - - @objectstack/core@11.7.0 - -## 11.6.0 - -### Patch Changes - -- @objectstack/spec@11.6.0 -- @objectstack/core@11.6.0 -- @objectstack/platform-objects@11.6.0 - -## 11.5.0 - -### Patch Changes - -- Updated dependencies [6ee4f04] -- Updated dependencies [c1e3a65] - - @objectstack/spec@11.5.0 - - @objectstack/core@11.5.0 - - @objectstack/platform-objects@11.5.0 - -## 11.4.0 - -### Patch Changes - -- Updated dependencies [5821c51] -- Updated dependencies [a0fce3f] - - @objectstack/spec@11.4.0 - - @objectstack/core@11.4.0 - - @objectstack/platform-objects@11.4.0 - -## 11.3.0 - -### Patch Changes - -- Updated dependencies [58e8e31] -- Updated dependencies [b4a5df0] - - @objectstack/spec@11.3.0 - - @objectstack/core@11.3.0 - - @objectstack/platform-objects@11.3.0 - -## 11.2.0 - -### Patch Changes - -- Updated dependencies [d0f4b13] -- Updated dependencies [302bdab] - - @objectstack/spec@11.2.0 - - @objectstack/core@11.2.0 - - @objectstack/platform-objects@11.2.0 - -## 11.1.0 - -### Patch Changes - -- Updated dependencies [cbc8c02] -- Updated dependencies [07c2773] -- Updated dependencies [d7a88df] -- Updated dependencies [4f8f108] -- Updated dependencies [ce0b4f6] -- Updated dependencies [90bce88] -- Updated dependencies [3209ec6] -- Updated dependencies [e011d42] -- Updated dependencies [6e5bdd5] -- Updated dependencies [9ccfcd6] -- Updated dependencies [ecf193f] -- Updated dependencies [51bec81] -- Updated dependencies [3e593a7] -- Updated dependencies [63d5403] - - @objectstack/platform-objects@11.1.0 - - @objectstack/core@11.1.0 - - @objectstack/spec@11.1.0 - -## 11.0.0 - -### Patch Changes - -- Updated dependencies [9b5bf3d] -- Updated dependencies [cb5b393] -- Updated dependencies [ab5718a] -- Updated dependencies [4845c12] -- Updated dependencies [c1a754a] -- Updated dependencies [6fbe91f] -- Updated dependencies [715d667] -- Updated dependencies [5eef4cf] -- Updated dependencies [72759e1] -- Updated dependencies [6c4fbd9] -- Updated dependencies [ef3ed67] -- Updated dependencies [cd51229] -- Updated dependencies [7697a0e] -- Updated dependencies [e7e04f1] -- Updated dependencies [cfd5ac4] -- Updated dependencies [2be5c1f] -- Updated dependencies [ad143ce] -- Updated dependencies [5c4a8c8] -- Updated dependencies [3afaeed] -- Updated dependencies [5737261] -- Updated dependencies [a619a3a] -- Updated dependencies [f44c1bd] -- Updated dependencies [8801c02] -- Updated dependencies [3d04e06] -- Updated dependencies [4a84c98] -- Updated dependencies [c715d25] -- Updated dependencies [aa33b02] -- Updated dependencies [d980f0d] -- Updated dependencies [a658523] -- Updated dependencies [82ff91c] -- Updated dependencies [638f472] - - @objectstack/platform-objects@11.0.0 - - @objectstack/spec@11.0.0 - - @objectstack/core@11.0.0 - -## 10.3.0 - -### Patch Changes - -- @objectstack/spec@10.3.0 -- @objectstack/core@10.3.0 -- @objectstack/platform-objects@10.3.0 - -## 10.2.0 - -### Patch Changes - -- Updated dependencies [b496498] - - @objectstack/spec@10.2.0 - - @objectstack/core@10.2.0 - - @objectstack/platform-objects@10.2.0 - -## 10.1.0 - -### Patch Changes - -- Updated dependencies [49da36e] -- Updated dependencies [ac79f16] - - @objectstack/spec@10.1.0 - - @objectstack/core@10.1.0 - - @objectstack/platform-objects@10.1.0 - -## 10.0.0 - -### Patch Changes - -- Updated dependencies [d7ff626] -- Updated dependencies [2a1b16b] -- Updated dependencies [2256e93] -- Updated dependencies [7108ff3] -- Updated dependencies [30c0313] -- Updated dependencies [e16f2a8] -- Updated dependencies [e411a82] -- Updated dependencies [ae271d0] -- Updated dependencies [61ed5c7] -- Updated dependencies [a581385] -- Updated dependencies [d5f6d29] -- Updated dependencies [220ce5b] -- Updated dependencies [3efe334] -- Updated dependencies [0df063e] -- Updated dependencies [ce13bb8] -- Updated dependencies [feead7e] -- Updated dependencies [6ca20b3] -- Updated dependencies [5f875fe] -- Updated dependencies [b469950] -- Updated dependencies [47d978a] - - @objectstack/spec@10.0.0 - - @objectstack/platform-objects@10.0.0 - - @objectstack/core@10.0.0 - -## 9.11.0 - -### Patch Changes - -- Updated dependencies [e7f6539] -- Updated dependencies [2365d07] -- Updated dependencies [6595b53] -- Updated dependencies [fa8964d] -- Updated dependencies [36138c7] -- Updated dependencies [a8e4f3b] -- Updated dependencies [4c213c2] -- Updated dependencies [2afb612] - - @objectstack/spec@9.11.0 - - @objectstack/core@9.11.0 - - @objectstack/platform-objects@9.11.0 - -## 9.10.0 - -### Patch Changes - -- Updated dependencies [db02bd5] -- Updated dependencies [641675d] -- Updated dependencies [94e9040] -- Updated dependencies [4331adb] -- Updated dependencies [1f88fd9] -- Updated dependencies [1f88fd9] - - @objectstack/spec@9.10.0 - - @objectstack/platform-objects@9.10.0 - - @objectstack/core@9.10.0 - -## 9.9.1 - -### Patch Changes - -- @objectstack/spec@9.9.1 -- @objectstack/core@9.9.1 -- @objectstack/platform-objects@9.9.1 - -## 9.9.0 - -### Minor Changes - -- d42004b: feat(reports): report schedules honor `cron_expression` + `timezone` - - `sys_report_schedule` has long carried `cron_expression` and `timezone` fields, but `ReportService` only ever advanced `next_run_at` by `interval_minutes` — both fields were stored and ignored. Scheduling now computes `next_run_at` from the cron expression (when present) in the schedule's timezone via `croner` — the same library the job scheduler uses — so "every weekday at 09:00 local" is expressible. `interval_minutes` remains the fallback. - - - `cron_expression` wins over `interval_minutes` when set (the field's documented contract). - - Evaluated in `timezone` (default `UTC`). - - `scheduleReport` validates the cron expression eagerly and rejects an invalid one with `VALIDATION_FAILED`, rather than silently falling back at sweep time. A cron that becomes unschedulable later is logged and falls back to the interval — it never throws into the dispatch sweep. - - The DB-polling dispatch model is unchanged; only the next-run computation is cron-aware. Part of ADR-0053 Phase 2 (#1983) but self-contained — report schedules run under a system context, so the timezone source is the schedule's own field, independent of the reference-timezone resolver. - -### Patch Changes - -- Updated dependencies [84249a4] -- Updated dependencies [11af299] -- Updated dependencies [d5774b5] -- Updated dependencies [134043a] -- Updated dependencies [90108e0] -- Updated dependencies [9afeb2d] -- Updated dependencies [6bec07e] -- Updated dependencies [601cc11] -- Updated dependencies [575448d] - - @objectstack/spec@9.9.0 - - @objectstack/core@9.9.0 - - @objectstack/platform-objects@9.9.0 - -## 9.8.0 - -### Patch Changes - -- Updated dependencies [97c55b3] -- Updated dependencies [1b1f490] - - @objectstack/spec@9.8.0 - - @objectstack/core@9.8.0 - - @objectstack/platform-objects@9.8.0 - -## 9.7.0 - -### Patch Changes - -- @objectstack/spec@9.7.0 -- @objectstack/core@9.7.0 -- @objectstack/platform-objects@9.7.0 - -## 9.6.0 - -### Patch Changes - -- Updated dependencies [d1e930a] -- Updated dependencies [71578f2] -- Updated dependencies [5e3a301] -- Updated dependencies [5db2742] - - @objectstack/spec@9.6.0 - - @objectstack/core@9.6.0 - - @objectstack/platform-objects@9.6.0 - -## 9.5.1 - -### Patch Changes - -- Updated dependencies [ee72aae] - - @objectstack/spec@9.5.1 - - @objectstack/core@9.5.1 - - @objectstack/platform-objects@9.5.1 - -## 9.5.0 - -### Patch Changes - -- Updated dependencies [d08551c] -- Updated dependencies [5be7102] -- Updated dependencies [707aeed] -- Updated dependencies [7a103d4] -- Updated dependencies [4b01250] - - @objectstack/spec@9.5.0 - - @objectstack/platform-objects@9.5.0 - - @objectstack/core@9.5.0 - -## 9.4.0 - -### Patch Changes - -- Updated dependencies [060467a] -- Updated dependencies [0856476] -- Updated dependencies [b678d8c] -- Updated dependencies [b678d8c] -- Updated dependencies [b678d8c] - - @objectstack/spec@9.4.0 - - @objectstack/core@9.4.0 - - @objectstack/platform-objects@9.4.0 - -## 9.3.0 - -### Patch Changes - -- Updated dependencies [1ada658] -- Updated dependencies [3219191] -- Updated dependencies [290f631] -- Updated dependencies [50b7b47] -- Updated dependencies [f15d6f6] -- Updated dependencies [f8684ea] -- Updated dependencies [c802327] -- Updated dependencies [b4765be] - - @objectstack/spec@9.3.0 - - @objectstack/platform-objects@9.3.0 - - @objectstack/core@9.3.0 - -## 9.2.0 - -### Patch Changes - -- Updated dependencies [2f57b75] -- Updated dependencies [2f57b75] - - @objectstack/spec@9.2.0 - - @objectstack/core@9.2.0 - - @objectstack/platform-objects@9.2.0 - -## 9.1.0 - -### Patch Changes - -- Updated dependencies [b9062c9] - - @objectstack/spec@9.1.0 - - @objectstack/core@9.1.0 - - @objectstack/platform-objects@9.1.0 - -## 9.0.1 - -### Patch Changes - -- Updated dependencies [1817845] - - @objectstack/spec@9.0.1 - - @objectstack/core@9.0.1 - - @objectstack/platform-objects@9.0.1 - -## 9.0.0 - -### Patch Changes - -- Updated dependencies [4c3f693] -- Updated dependencies [0bf39f1] -- Updated dependencies [f533f42] -- Updated dependencies [1c83ee8] - - @objectstack/spec@9.0.0 - - @objectstack/core@9.0.0 - - @objectstack/platform-objects@9.0.0 - -## 8.0.1 - -### Patch Changes - -- @objectstack/spec@8.0.1 -- @objectstack/core@8.0.1 -- @objectstack/platform-objects@8.0.1 - -## 8.0.0 - -### Patch Changes - -- Updated dependencies [a46c017] -- Updated dependencies [b990b89] -- Updated dependencies [99111ec] -- Updated dependencies [d5a8161] -- Updated dependencies [5cf1f1b] -- Updated dependencies [9ef89d4] -- Updated dependencies [3306d2f] -- Updated dependencies [c262301] -- Updated dependencies [bc44195] -- Updated dependencies [9e2e229] - - @objectstack/spec@8.0.0 - - @objectstack/core@8.0.0 - - @objectstack/platform-objects@8.0.0 - -## 7.9.0 - -### Patch Changes - -- @objectstack/spec@7.9.0 -- @objectstack/core@7.9.0 -- @objectstack/platform-objects@7.9.0 - -## 7.8.0 - -### Patch Changes - -- Updated dependencies [06f2bbb] -- Updated dependencies [36719db] -- Updated dependencies [424ab26] - - @objectstack/spec@7.8.0 - - @objectstack/core@7.8.0 - - @objectstack/platform-objects@7.8.0 - -## 7.7.0 - -### Patch Changes - -- Updated dependencies [b391955] -- Updated dependencies [f06b64e] -- Updated dependencies [023bf93] -- Updated dependencies [764c747] - - @objectstack/spec@7.7.0 - - @objectstack/platform-objects@7.7.0 - - @objectstack/core@7.7.0 - -## 7.6.0 - -### Patch Changes - -- Updated dependencies [955d4c8] -- Updated dependencies [c4a4cbd] -- Updated dependencies [b046ec2] -- Updated dependencies [2170ad9] -- Updated dependencies [02d6359] -- Updated dependencies [7648242] -- Updated dependencies [8fa1e7f] -- Updated dependencies [7ae6abc] -- Updated dependencies [55866f5] -- Updated dependencies [60f9c45] - - @objectstack/spec@7.6.0 - - @objectstack/platform-objects@7.6.0 - - @objectstack/core@7.6.0 - -## 7.5.0 - -### Patch Changes - -- @objectstack/spec@7.5.0 -- @objectstack/core@7.5.0 -- @objectstack/platform-objects@7.5.0 - -## 7.4.1 - -### Patch Changes - -- @objectstack/spec@7.4.1 -- @objectstack/core@7.4.1 -- @objectstack/platform-objects@7.4.1 - -## 7.4.0 - -### Patch Changes - -- Updated dependencies [23c7107] -- Updated dependencies [c72daad] -- Updated dependencies [4404572] -- Updated dependencies [eea3f1b] -- Updated dependencies [e478e0c] -- Updated dependencies [4cc2ced] -- Updated dependencies [13632b1] -- Updated dependencies [f115182] -- Updated dependencies [2faf9f2] -- Updated dependencies [2faf9f2] -- Updated dependencies [2faf9f2] -- Updated dependencies [58b450b] -- Updated dependencies [82eb6cf] -- Updated dependencies [c381977] -- Updated dependencies [13d8653] -- Updated dependencies [ff3d006] -- Updated dependencies [5e831de] - - @objectstack/spec@7.4.0 - - @objectstack/platform-objects@7.4.0 - - @objectstack/core@7.4.0 - -## 7.3.0 - -### Patch Changes - -- Updated dependencies [5e7c554] - - @objectstack/spec@7.3.0 - - @objectstack/core@7.3.0 - - @objectstack/platform-objects@7.3.0 - -## 7.2.1 - -### Patch Changes - -- @objectstack/spec@7.2.1 -- @objectstack/core@7.2.1 -- @objectstack/platform-objects@7.2.1 - -## 7.2.0 - -### Patch Changes - -- @objectstack/spec@7.2.0 -- @objectstack/core@7.2.0 -- @objectstack/platform-objects@7.2.0 - -## 7.1.0 - -### Patch Changes - -- Updated dependencies [6228609] -- Updated dependencies [47a92f4] - - @objectstack/platform-objects@7.1.0 - - @objectstack/spec@7.1.0 - - @objectstack/core@7.1.0 - -## 7.0.0 - -### Patch Changes - -- Updated dependencies [74470ad] -- Updated dependencies [d29617e] -- Updated dependencies [dc72172] -- Updated dependencies [d29617e] -- Updated dependencies [010757b] -- Updated dependencies [257954d] - - @objectstack/spec@7.0.0 - - @objectstack/platform-objects@7.0.0 - - @objectstack/core@7.0.0 - -## 6.9.0 - -### Patch Changes - -- @objectstack/spec@6.9.0 -- @objectstack/core@6.9.0 -- @objectstack/platform-objects@6.9.0 - -## 6.8.1 - -### Patch Changes - -- @objectstack/spec@6.8.1 -- @objectstack/core@6.8.1 -- @objectstack/platform-objects@6.8.1 - -## 6.8.0 - -### Patch Changes - -- Updated dependencies [6e88f77] -- Updated dependencies [c8b9f57] -- Updated dependencies [45d27c5] - - @objectstack/spec@6.8.0 - - @objectstack/platform-objects@6.8.0 - - @objectstack/core@6.8.0 - -## 6.7.1 - -### Patch Changes - -- @objectstack/spec@6.7.1 -- @objectstack/core@6.7.1 -- @objectstack/platform-objects@6.7.1 - -## 6.7.0 - -### Patch Changes - -- Updated dependencies [430067b] -- Updated dependencies [4f9e9d4] -- Updated dependencies [4f9e9d4] - - @objectstack/spec@6.7.0 - - @objectstack/platform-objects@6.7.0 - - @objectstack/core@6.7.0 - -## 6.6.0 - -### Patch Changes - -- Updated dependencies [a49cfc2] - - @objectstack/spec@6.6.0 - - @objectstack/core@6.6.0 - - @objectstack/platform-objects@6.6.0 - -## 6.5.1 - -### Patch Changes - -- @objectstack/spec@6.5.1 -- @objectstack/core@6.5.1 -- @objectstack/platform-objects@6.5.1 - -## 6.5.0 - -### Patch Changes - -- @objectstack/spec@6.5.0 -- @objectstack/core@6.5.0 -- @objectstack/platform-objects@6.5.0 - -## 6.4.0 - -### Patch Changes - -- Updated dependencies [f8651cc] -- Updated dependencies [f8651cc] -- Updated dependencies [0bf6f9a] - - @objectstack/spec@6.4.0 - - @objectstack/core@6.4.0 - - @objectstack/platform-objects@6.4.0 - -## 6.3.0 - -### Patch Changes - -- @objectstack/spec@6.3.0 -- @objectstack/core@6.3.0 -- @objectstack/platform-objects@6.3.0 - -## 6.2.0 - -### Patch Changes - -- Updated dependencies [b4c74a9] - - @objectstack/spec@6.2.0 - - @objectstack/core@6.2.0 - - @objectstack/platform-objects@6.2.0 - -## 6.1.1 - -### Patch Changes - -- @objectstack/spec@6.1.1 -- @objectstack/core@6.1.1 -- @objectstack/platform-objects@6.1.1 - -## 6.1.0 - -### Patch Changes - -- Updated dependencies [93c0589] - - @objectstack/spec@6.1.0 - - @objectstack/core@6.1.0 - - @objectstack/platform-objects@6.1.0 - -## 6.0.0 - -### Patch Changes - -- Updated dependencies [629a716] -- Updated dependencies [dbc4f7d] -- Updated dependencies [944f187] - - @objectstack/spec@6.0.0 - - @objectstack/platform-objects@6.0.0 - - @objectstack/core@6.0.0 - -## 5.2.0 - -### Patch Changes - -- Updated dependencies [bab2b20] -- Updated dependencies [fa011d8] -- Updated dependencies [f0f7c27] -- Updated dependencies [b806f58] - - @objectstack/platform-objects@5.2.0 - - @objectstack/spec@5.2.0 - - @objectstack/core@5.2.0 - -## 5.1.0 - -### Patch Changes - -- Updated dependencies [75f4ee6] -- Updated dependencies [823d559] - - @objectstack/spec@5.1.0 - - @objectstack/platform-objects@5.1.0 - - @objectstack/core@5.1.0 - -## 5.0.0 - -### Patch Changes - -- Updated dependencies [888a5c1] -- Updated dependencies [2f9073a] - - @objectstack/platform-objects@5.0.0 - - @objectstack/spec@5.0.0 - - @objectstack/core@5.0.0 - -## 4.2.0 - -### Patch Changes - -- Updated dependencies [2869891] - - @objectstack/spec@4.2.0 - - @objectstack/core@4.2.0 - - @objectstack/platform-objects@4.2.0 - -## 4.1.1 - -### Patch Changes - -- @objectstack/spec@4.1.1 -- @objectstack/core@4.1.1 -- @objectstack/platform-objects@4.1.1 - -## 4.0.1 - -### Patch Changes - -- Updated dependencies [2108c30] -- Updated dependencies [23db640] - - @objectstack/spec@4.1.0 - - @objectstack/core@4.1.0 - - @objectstack/platform-objects@4.1.0 diff --git a/packages/plugins/plugin-reports/LICENSE b/packages/plugins/plugin-reports/LICENSE deleted file mode 100644 index 16bc23f4043..00000000000 --- a/packages/plugins/plugin-reports/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute - must include a readable copy of the attribution notices - contained within such NOTICE file, excluding those notices - that do not pertain to any part of the Derivative Works, - in at least one of the following places: within a NOTICE - text file distributed as part of the Derivative Works; within - the Source form or documentation, if provided along with - the Derivative Works; or, within a display generated by the - Derivative Works, if and wherever such third-party notices - normally appear. The contents of the NOTICE file are for - informational purposes only and do not modify the License. - You may add Your own attribution notices within Derivative - Works that You distribute, alongside or as an addendum to - the NOTICE text from the Work, provided that such additional - attribution notices cannot be construed as modifying the - License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2026 ObjectStack - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/plugins/plugin-reports/package.json b/packages/plugins/plugin-reports/package.json deleted file mode 100644 index 184649861d0..00000000000 --- a/packages/plugins/plugin-reports/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "@objectstack/plugin-reports", - "version": "17.4.0", - "license": "Apache-2.0", - "description": "Saved reports + scheduled email digests for ObjectStack — sys_saved_report + sys_report_schedule + IReportService.", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.js" - } - }, - "scripts": { - "build": "tsup --config ../../../tsup.config.ts && node ../../../scripts/check-dts-emitted.mjs", - "test": "vitest run --passWithNoTests", - "typecheck": "tsc --noEmit && pnpm check:test-typecheck", - "check:test-typecheck": "tsx ../../../scripts/check-test-typecheck.mts --self-test && tsx ../../../scripts/check-test-typecheck.mts --package packages/plugins/plugin-reports --project tsconfig.test.json" - }, - "dependencies": { - "@objectstack/core": "workspace:*", - "@objectstack/platform-objects": "workspace:*", - "@objectstack/spec": "workspace:*", - "croner": "^10.0.1" - }, - "devDependencies": { - "@objectstack/driver-sql": "workspace:*", - "@objectstack/objectql": "workspace:*", - "@types/node": "^26.2.0", - "tsx": "^4.23.12", - "typescript": "^6.0.3", - "vitest": "^4.1.11" - }, - "keywords": [ - "objectstack", - "plugin", - "reports", - "scheduling", - "email" - ], - "repository": { - "type": "git", - "url": "https://github.com/objectstack-ai/objectstack.git", - "directory": "packages/plugins/plugin-reports" - }, - "files": [ - "dist", - "README.md", - "CHANGELOG.md" - ] -} diff --git a/packages/plugins/plugin-reports/src/dispatcher-runs-on-object-kernel.test.ts b/packages/plugins/plugin-reports/src/dispatcher-runs-on-object-kernel.test.ts deleted file mode 100644 index 7106abd72aa..00000000000 --- a/packages/plugins/plugin-reports/src/dispatcher-runs-on-object-kernel.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * [#10746] Scheduled reports must actually RUN on `ObjectKernel` — the kernel - * real deployments use — when no job plugin is installed. - * - * THE DEFECT THIS PINS. `ReportsServicePlugin.start()` prefers the platform - * job service and only falls through to its own `setInterval` when - * `ctx.getService('job')` yields nothing with a `schedule` method. - * `ObjectKernel.preInjectCoreFallbacks()` used to register `createMemoryJob()` - * for every unprovided `core` service before Phase 2 — and `createMemoryJob()` - * is honest in its own docblock that a `schedule()`d job NEVER fires on its - * own (its `schedule()` is `jobs.set(...)` and nothing else). So on an - * `ObjectKernel` without `@objectstack/service-job` the plugin logged - * `dispatcher registered with job service` and then dispatched nothing, ever: - * measured as 0 reads of `sys_report_schedule` in 5600 ms with the success - * line present. The `setInterval` branch the plugin's docblock offers as the - * single-kernel answer was dead code on the kernel real deployments use. - * - * THE REPAIR (maintainer ruling 2026-08-22, Option A — a fallback must not - * fake capability). `job` came off the kernel's pre-injection list - * (`CORE_FALLBACK_FACTORIES` in `@objectstack/core`), so `getService('job')` - * now throws when no job plugin is installed, the plugin's existing catch - * falls through to `setInterval`, and single-kernel deployments actually - * dispatch scheduled reports. - * - * WHY THIS FILE EXISTS BESIDE `plugin-shutdown-releases-dispatcher.test.ts`. - * That file pins RELEASE at shutdown, and its running-dispatcher leg runs on - * `LiteKernel` — which injects no fallbacks, so it could never see this - * defect. Nothing pinned that the dispatcher RUNS on `ObjectKernel`; that gap - * is exactly why the defect shipped invisibly. This pin is the acceptance - * evidence for the fix: same composition a real single-kernel deployment - * boots, no job plugin anywhere, and the poll traffic itself is the assertion. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { ObjectKernel } from '@objectstack/core'; -import { ObjectQLPlugin } from '@objectstack/objectql'; -import type { ObjectQL } from '@objectstack/objectql'; -import { SqlDriver } from '@objectstack/driver-sql'; -import type { IDataEngine } from '@objectstack/spec/contracts'; -import { ReportsServicePlugin } from './reports-plugin.js'; - -/** - * The plugin floors its own interval at 5s (`Math.max(5_000, …)`), so this is - * the fastest REAL clock the dispatcher can be driven at. - */ -const DISPATCH_INTERVAL_MS = 5_000; -/** Comfortably past one tick boundary, so a window that sees zero is silence. */ -const OBSERVE_MS = 5_600; - -const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - -const openKernels: Array<{ shutdown(): Promise }> = []; -const openDrivers: Array<{ disconnect?: () => Promise }> = []; - -afterEach(async () => { - // Kernels first, drivers second: the kernel's own teardown still wants a - // live driver to drain against. - while (openKernels.length) { - try { await openKernels.pop()?.shutdown(); } catch { /* already stopped */ } - } - while (openDrivers.length) { - try { await openDrivers.pop()?.disconnect?.(); } catch { /* noop */ } - } -}); - -/** - * Installs the read counter on the engine instance the dispatcher captured at - * `kernel:ready` (`ctx.getService('objectql')` resolves to this same object), - * so the tally is of real `ReportService.dispatchDue()` traffic, not a - * stand-in. - */ -function countScheduleReads(engineHolder: { getService: (n: string) => T }): () => number { - type EngineCall = (name: string, ...rest: unknown[]) => unknown; - const engine = engineHolder.getService('objectql') as unknown as - Record<'find', EngineCall>; - let reads = 0; - const orig = engine.find.bind(engine); - engine.find = (name: string, ...rest: unknown[]) => { - if (String(name) === 'sys_report_schedule') reads++; - return orig(name, ...rest); - }; - return () => reads; -} - -/** A real in-memory SQL driver, connected and registered on `engine`. */ -async function attachSqlite(objectql: any): Promise { - const driver: any = new SqlDriver({ - client: 'better-sqlite3', - connection: { filename: ':memory:' }, - useNullAsDefault: true, - }); - await driver.connect(); - objectql.registerDriver(driver, true); - openDrivers.push(driver); - await objectql.syncSchemas(); -} - -describe('#10746 the dispatcher RUNS on ObjectKernel with no job plugin', () => { - it( - 'polls sys_report_schedule — the setInterval fallback is reachable on the production kernel', - { timeout: 60_000 }, - async () => { - // The composition a real single-kernel deployment boots: ObjectKernel - // (NOT LiteKernel), the engine, the reports plugin — and no job plugin. - const kernel = new ObjectKernel({ logger: { level: 'silent' } }); - openKernels.push(kernel); - - await kernel.use(new ObjectQLPlugin()); - await kernel.use(new ReportsServicePlugin({ dispatchIntervalMs: DISPATCH_INTERVAL_MS })); - await kernel.bootstrap(); - - await attachSqlite(kernel.getService('objectql')); - const scheduleReads = countScheduleReads(kernel as any); - - await sleep(OBSERVE_MS); - - // THE PIN. Before the fix this was 0 — the kernel pre-injected a `job` - // fallback whose `schedule()` recorded the dispatcher and never fired - // it, while the plugin logged success. One tick boundary has passed, so - // silence here is the defect, not timing. - expect(scheduleReads()).toBeGreaterThan(0); - }, - ); -}); diff --git a/packages/plugins/plugin-reports/src/exec-context-annotation.pin.ts b/packages/plugins/plugin-reports/src/exec-context-annotation.pin.ts deleted file mode 100644 index 0a7e0b11560..00000000000 --- a/packages/plugins/plugin-reports/src/exec-context-annotation.pin.ts +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * #7135 — compile-time pin for the CONTEXT type this plugin's report methods - * accept, and for what `OwnerContextResolver` is required to return. - * - * #6523 converged 36 contract signatures onto the full `ExecutionContext` (the - * #6206 ruling: enforcement adjudicates on the whole `resolveAuthzContext` - * envelope, never a per-site subset). #7135 is the services half of the #7070 - * consumer split — the implementations here now annotate their own parameters - * with that same envelope instead of the six-field shape they used to name. - * - * WHY THIS FILE EXISTS AT ALL. That convergence has no runtime behaviour and - * no compiler pressure in either direction: the values were always complete, - * and the narrow annotation is STRUCTURALLY ASSIGNABLE to the wide one, so - * re-narrowing any of these parameters compiles, ships, and passes every test - * in this package. Nothing would notice. This module is the one thing that - * does — every declaration below is red exactly when a parameter narrows back. - * - * HOW IT BITES, part 1: TypeScript's excess-property check on a FRESH object - * literal. `posture` (ADR-0095 D2), `accessible_org_ids` (ADR-0105 D2) and - * `org_user_ids` are fields of the envelope that the retired six-field shape - * did not carry, so a literal naming them is rejected the moment the parameter - * is annotated with anything that lacks them. Note this is the ONLY direction - * that works: a `@ts-expect-error` asserting the reverse would be unsatisfied - * and fail the build, because a narrow context IS assignable to a wide - * parameter — see item 3 of the module doc on - * `@objectstack/spec/contracts/sharing-service` for that boundary. - * - * HOW IT BITES, part 2 (#7218): type IDENTITY against the retired shape itself. - * #7135's failure story was "the parameter narrows back to the six-field - * `SharingExecutionContext`" — a type that no longer exists, since #7218 - * deleted it from the contract surface once all three implementations had been - * re-annotated. Deleting the type does NOT delete the failure mode: the six - * fields can be re-declared here under any name, and the literal checks above - * only fire on the fields a given literal happens to spell. So the retired - * shape is kept below as a local SPECIMEN and each parameter is refuted - * against it. A re-narrowing is then red twice over, and neither check depends - * on the retired export coming back. - * - * WHY A `.pin.ts` AND NOT A `*.test.ts`: unlike its sibling packages, - * `packages/plugins/plugin-reports/tsconfig.json` does NOT exclude - * `**\/*.test.ts` (measured on this card — `plugin-approvals` and - * `plugin-sharing` both do), so a pin in a test file here would in fact be - * read by `tsc --noEmit`. The `.pin.ts` convention is kept anyway: it does not - * depend on that exclusion staying absent, it survives a vitest-only run that - * never type-checks, and it keeps both halves of this card's pin identical in - * shape. It is imported by nothing, so tsup (entry `src/index.ts`) never - * bundles it into `dist`. - */ - -import type { ReportService, OwnerContextResolver } from './report-service.js'; - -type SaveReportContext = Parameters[1]; -type RunContext = Parameters[1]; -type GetReportContext = Parameters[1]; -type ScheduleReportContext = Parameters[1]; -type ListSchedulesContext = Parameters[1]; - -/** - * What a scheduled run executes AS. `resolveOwnerContext` resolves a saved - * report's owner into a real, RLS-bearing context so the digest sees the rows - * the owner would see interactively (#2849 / #2980); typing its result as the - * envelope is what lets `executeReport` read the whole thing it was handed. - */ -type ResolvedOwnerContext = NonNullable>>; - -/** - * [#7218] The RETIRED six-field shape, kept here as a SPECIMEN — a deliberate - * COPY of the type `@objectstack/spec` exported as `SharingExecutionContext` - * until #7218 deleted it. Copied rather than imported on purpose: nothing may - * depend on the retired name again, and a local copy is what lets this pin keep - * naming the shape it refuses after the export is gone. - * - * ⛔ Not a vocabulary to reach for, and not exported. - */ -type RetiredSharingContextSpecimen = { - userId?: string; - tenantId?: string; - positions?: string[]; - permissions?: string[]; - systemPermissions?: string[]; - isSystem?: boolean; -}; - -/** Type-level identity: true iff A and B are the same type. */ -type Eq = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) - ? true - : false; -/** Compile error when the argument is not `false`. */ -type Refute = T; - -/** - * NEGATIVE, at the type level: neither the method parameters nor what the owner - * resolver hands back IS the retired shape. Red the moment one is re-annotated - * with those six fields under any spelling — the failure #7135's pin told as - * "narrows back to `SharingExecutionContext`", restated so it no longer needs - * the deleted name to be checkable. `ResolvedOwnerContext` is included because a - * scheduled run adjudicates on whatever the resolver's RETURN type says it has. - */ -type _NotTheRetiredShape = [ - Refute>, - Refute>, - Refute>, - Refute>, - Refute>, - Refute>, -]; - -/** - * Never called — every line below is a type-level assertion evaluated by - * `tsc --noEmit`. The parameters are taken as arguments rather than read off a - * live service so the pin needs no instance and no import cycle. - */ -export function __pinReportsTakesTheFullEnvelope( - saveReport: (input: never, context: SaveReportContext) => unknown, - run: (reportId: string, context: RunContext) => unknown, - getReport: (reportId: string, context: GetReportContext) => unknown, - scheduleReport: (input: never, context: ScheduleReportContext) => unknown, - listSchedules: (filter: undefined, context: ListSchedulesContext) => unknown, - ownerContext: ResolvedOwnerContext, -): void { - // ── POSITIVE: fields that exist ONLY on the full envelope, no cast. ─────── - saveReport(undefined as never, { userId: 'u1', posture: 'MEMBER' }); - run('rep_1', { userId: 'u1', posture: 'TENANT_ADMIN', accessible_org_ids: ['org_a'] }); - getReport('rep_1', { userId: 'u1', org_user_ids: ['u1', 'u2'] }); - scheduleReport(undefined as never, { userId: 'u1', accessible_org_ids: ['org_a'] }); - listSchedules(undefined, { userId: 'u1', posture: 'PLATFORM_ADMIN' }); - - // The resolver hands back what it RESOLVED. Reading a field the six-field - // shape never had is what pins that: a scheduled run adjudicates on the - // whole envelope or it is not running as the owner at all. - const posture: ResolvedOwnerContext['posture'] = ownerContext.posture; - void posture; - - // ── NEGATIVE: none of these types IS the retired six-field shape. ──────── - // The tuple is all-`false` exactly when every `Refute` above holds; a - // parameter (or the resolver's return type) re-narrowed to the specimen makes - // its slot `true` and this assignment stops compiling. - const notTheRetiredShape: _NotTheRetiredShape = [false, false, false, false, false, false]; - void notTheRetiredShape; - - // ── NEGATIVE: widening must not have degenerated into `any`. ───────────── - // A parameter erased to `any` would swallow every positive above just as - // happily, so the pin is only worth its weight if wrong input still fails. - // @ts-expect-error 'SUPERUSER' is not an ADR-0095 posture rung - run('rep_1', { userId: 'u1', posture: 'SUPERUSER' }); - // @ts-expect-error `userId` is a string on the envelope, not a number - getReport('rep_1', { userId: 42 }); - // @ts-expect-error `accessible_org_ids` is a string[], not a bare string - listSchedules(undefined, { accessible_org_ids: 'org_a' }); - // @ts-expect-error `organizationId` is NOT a field of the envelope — that - // spelling has its own history (#5858 / `check:org-identifier`) and was held - // out of #7135 on purpose. - saveReport(undefined as never, { organizationId: 'org_a' }); -} diff --git a/packages/plugins/plugin-reports/src/index.ts b/packages/plugins/plugin-reports/src/index.ts deleted file mode 100644 index f06d91d6291..00000000000 --- a/packages/plugins/plugin-reports/src/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * @objectstack/plugin-reports - * - * Saved reports + scheduled email digests for ObjectStack. - * Persists `sys_saved_report` definitions and `sys_report_schedule` - * rows, then drives a dispatcher that runs due schedules and emails - * the rendered output via the configured `email` service. - */ - -export { SysSavedReport, SysReportSchedule } from '@objectstack/platform-objects/audit'; -export { - ReportService, - renderReport, - type ReportEngine, - type ReportEmail, - type ReportClock, - type ReportServiceOptions, -} from './report-service.js'; -export { - ReportsServicePlugin, - type ReportsPluginOptions, -} from './reports-plugin.js'; -export type { - IReportService, - SavedReport, - ReportSchedule, - ReportQuery, - ReportRunResult, - ReportFormat, - SaveReportInput, - ScheduleReportInput, -} from '@objectstack/spec/contracts'; diff --git a/packages/plugins/plugin-reports/src/plugin-shutdown-releases-dispatcher.test.ts b/packages/plugins/plugin-reports/src/plugin-shutdown-releases-dispatcher.test.ts deleted file mode 100644 index bc0b61f1b19..00000000000 --- a/packages/plugins/plugin-reports/src/plugin-shutdown-releases-dispatcher.test.ts +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * [#10371] `await kernel.shutdown()` must leave NOTHING of this plugin still - * running. - * - * THE DEFECT. `ReportsServicePlugin` arms its schedule dispatcher at - * `kernel:ready` — either a `setInterval` over `sys_report_schedule`, or, when - * `service-job` is installed, a scheduled `reports.dispatch` job. The teardown - * that released both was spelled `stop()`. The kernel's plugin teardown hook is - * `destroy()` (`Plugin.destroy?()` in `@objectstack/core`'s `types.ts` — the - * only teardown `ObjectKernel.performShutdown()` and `LiteKernel.destroy()` - * invoke), and `stop()` is not part of that interface, so nothing in the repo - * ever called it and the dispatcher went on ticking after shutdown resolved. - * - * THE ASYMMETRY THAT HID IT. `start?(ctx)` IS on the interface and does fire. - * A `start`/`stop` pair where only one half is wired reads as symmetric in - * review — which is why the identical shape survived in six packages at once, - * and why #9371 found it in `service-messaging` only after it had already cost - * something. - * - * WHY IT WENT UNNOTICED, AND WHERE THE BILL LANDED. `start()` `unref()`s the - * interval, so a long-lived host process still exits and the leak is silent in - * production. Under vitest the worker is alive throughout teardown, so a tick - * fires AFTER the test file is over, reads through a driver the suite has - * already disconnected, and the driver's console fallback warns. `console.*` - * inside a worker is an RPC to the main process (`onUserConsoleLog`); one - * issued after `rpcDone()` has snapshotted the pending set is rejected by - * `$rejectPendingCalls` as `EnvironmentTeardownError: [vitest-worker]: Closing - * rpc while "onUserConsoleLog" was pending`. Nothing awaits that promise, so it - * surfaces as an unhandled rejection and fails a run in which every test - * passed — twice measured on `examples/app-showcase` (334/334 and 337/337 - * green, exit 1, a merge-queue eviction each time). - * - * WHAT THIS PINS, AND WHY IN THIS SHAPE. The assertions are behavioural — - * "after shutdown the plugin issues no further schedule reads", "after shutdown - * the scheduled job has been cancelled" — and not - * `expect(plugin.destroy).toBeDefined()`, because the hook merely EXISTING is - * not the property that was missing; being REACHED BY THE KERNEL is. - * - * Every pre-shutdown leg is a POSITIVE CONTROL and load-bearing: without it a - * dispatcher that never started would satisfy the post-shutdown assertion - * vacuously, and this file would pass on a plugin that does nothing at all. - * - * The `stop()` legs are the other direction, and they are not decoration: the - * repair keeps `stop()` as a delegating alias because it is public API of an - * exported class and an embedder may have learned to call it directly PRECISELY - * BECAUSE the kernel never did. Pinning only the shutdown direction would go - * green on an implementation that simply deletes `stop()`, which breaks them. - */ - -import { describe, it, expect, afterEach } from 'vitest'; -import { ObjectKernel, LiteKernel } from '@objectstack/core'; -import type { Plugin, PluginContext } from '@objectstack/core'; -import { ObjectQLPlugin } from '@objectstack/objectql'; -import type { ObjectQL } from '@objectstack/objectql'; -import { SqlDriver } from '@objectstack/driver-sql'; -import type { IDataEngine } from '@objectstack/spec/contracts'; -import { ReportsServicePlugin } from './reports-plugin.js'; - -/** - * The plugin floors its own interval at 5s (`Math.max(5_000, …)`), so this is - * the fastest REAL clock the dispatcher can be driven at. Windows below are - * sized off it rather than off a wish. - */ -const DISPATCH_INTERVAL_MS = 5_000; -/** Comfortably past one tick boundary, so a window that sees zero is silence. */ -const OBSERVE_MS = 5_600; - -const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - -const openKernels: Array<{ shutdown(): Promise }> = []; -const openDrivers: Array<{ disconnect?: () => Promise }> = []; - -afterEach(async () => { - // Kernels first, drivers second: the kernel's own teardown still wants a live - // driver to drain against. - while (openKernels.length) { - try { await openKernels.pop()?.shutdown(); } catch { /* already stopped */ } - } - while (openDrivers.length) { - try { await openDrivers.pop()?.disconnect?.(); } catch { /* noop */ } - } -}); - -/** Records what the plugin asked the platform job service to run and cancel. */ -interface JobLog { - scheduled: string[]; - cancelled: string[]; -} - -/** - * Publishes a minimal `job` service so the plugin takes its job-service branch - * instead of the `setInterval` fallback. A plugin rather than a bare - * `registerService` call because the branch is only taken if the service is - * resolvable at `kernel:ready`, which is the kernel's business, not ours. - */ -class FakeJobServicePlugin implements Plugin { - name = 'test.fake.job'; - version = '1.0.0'; - type = 'standard' as const; - - constructor(private readonly log: JobLog) {} - - async init(ctx: PluginContext): Promise { - ctx.registerService('job', { - schedule: async (name: string) => { this.log.scheduled.push(name); }, - cancel: async (name: string) => { this.log.cancelled.push(name); }, - }); - } -} - -interface Booted { - kernel: { shutdown(): Promise }; - plugin: ReportsServicePlugin; - /** Reads the dispatcher has made against `sys_report_schedule`. */ - scheduleReads: () => number; -} - -/** - * Installs the read counter on the engine instance the dispatcher captured at - * `kernel:ready` (`ctx.getService('objectql')` resolves to this same object), so - * the tally is of real `ReportService.dispatchDue()` traffic and not of a - * stand-in. - */ -function countScheduleReads(engineHolder: { getService: (n: string) => T }): () => number { - type EngineCall = (name: string, ...rest: unknown[]) => unknown; - const engine = engineHolder.getService('objectql') as unknown as - Record<'find', EngineCall>; - let reads = 0; - const orig = engine.find.bind(engine); - engine.find = (name: string, ...rest: unknown[]) => { - if (String(name) === 'sys_report_schedule') reads++; - return orig(name, ...rest); - }; - return () => reads; -} - -/** A real in-memory SQL driver, connected and registered on `engine`. */ -async function attachSqlite(objectql: any): Promise { - const driver: any = new SqlDriver({ - client: 'better-sqlite3', - connection: { filename: ':memory:' }, - useNullAsDefault: true, - }); - await driver.connect(); - objectql.registerDriver(driver, true); - openDrivers.push(driver); - await objectql.syncSchemas(); -} - -/** - * The `setInterval` branch, and why it needs `LiteKernel` — MEASURED, not - * assumed. `ReportsServicePlugin.start()` prefers the platform job service and - * only falls through to `setInterval` when `ctx.getService('job')` yields - * nothing with a `schedule` method. `ObjectKernel.preInjectCoreFallbacks()` - * registers `createMemoryJob()` for every unprovided `core` service before - * Phase 2, so on an `ObjectKernel` a `job` service ALWAYS resolves and this - * branch is unreachable. `LiteKernel` injects no fallbacks, which is exactly - * the "single-kernel deployment without `service-job`" the plugin's own - * docblock names as the reason the `setInterval` path exists. - */ -async function bootReportsLiteKernel(): Promise { - const kernel = new LiteKernel({ logger: { level: 'silent' } }); - openKernels.push(kernel); - - kernel.use(new ObjectQLPlugin()); - const plugin = new ReportsServicePlugin({ dispatchIntervalMs: DISPATCH_INTERVAL_MS }); - kernel.use(plugin); - await kernel.bootstrap(); - - await attachSqlite(kernel.getService('objectql')); - return { kernel, plugin, scheduleReads: countScheduleReads(kernel as any) }; -} - -async function bootReportsKernel(extra?: Plugin): Promise { - const kernel = new ObjectKernel({ logger: { level: 'silent' } }); - openKernels.push(kernel); - - await kernel.use(new ObjectQLPlugin()); - if (extra) await kernel.use(extra); - const plugin = new ReportsServicePlugin({ dispatchIntervalMs: DISPATCH_INTERVAL_MS }); - await kernel.use(plugin); - await kernel.bootstrap(); - - await attachSqlite(kernel.getService('objectql')); - return { kernel, plugin, scheduleReads: countScheduleReads(kernel as any) }; -} - -describe('#10371 ReportsServicePlugin releases its dispatcher on kernel shutdown', () => { - it( - 'stops reading sys_report_schedule once shutdown() has resolved', - { timeout: 60_000 }, - async () => { - const { kernel, scheduleReads } = await bootReportsLiteKernel(); - - // POSITIVE CONTROL — the setInterval dispatcher really is ticking, so the - // post-shutdown assertion below measures silence and not absence. - await sleep(OBSERVE_MS); - expect(scheduleReads()).toBeGreaterThan(0); - - await kernel.shutdown(); - - const atShutdown = scheduleReads(); - await sleep(OBSERVE_MS); - - // THE PIN. shutdown() resolving means the plugin is done with the - // database. Before the fix this count kept climbing. - expect(scheduleReads()).toBe(atShutdown); - }, - ); - - it('cancels its scheduled job once shutdown() has resolved', async () => { - const log: JobLog = { scheduled: [], cancelled: [] }; - const { kernel } = await bootReportsKernel(new FakeJobServicePlugin(log)); - - // POSITIVE CONTROL — the job branch was taken and nothing has cancelled it. - expect(log.scheduled).toContain('reports.dispatch'); - expect(log.cancelled).toEqual([]); - - await kernel.shutdown(); - - // THE PIN. Before the fix the cancel lived in `stop()`, which the kernel - // never called, so the job outlived the kernel that scheduled it. - expect(log.cancelled).toContain('reports.dispatch'); - }); - - it('the retained stop() alias still tears down for an embedder that calls it directly', async () => { - const log: JobLog = { scheduled: [], cancelled: [] }; - const { plugin } = await bootReportsKernel(new FakeJobServicePlugin(log)); - - expect(log.scheduled).toContain('reports.dispatch'); - expect(log.cancelled).toEqual([]); - - // No argument — the shape an embedder writes today against a method whose - // parameter the repair made optional. - await plugin.stop(); - - expect(log.cancelled).toContain('reports.dispatch'); - }); - - it('the stop() alias still accepts the PluginContext argument it used to require', async () => { - const log: JobLog = { scheduled: [], cancelled: [] }; - const { plugin } = await bootReportsKernel(new FakeJobServicePlugin(log)); - - expect(log.scheduled).toContain('reports.dispatch'); - - // The pre-repair signature was `stop(ctx: PluginContext)`. An embedder - // holding that call shape must keep compiling AND keep working, which is - // the entire reason the alias was retained rather than deleted. - const ctx = { - logger: { info() {}, warn() {}, error() {}, debug() {} }, - } as unknown as PluginContext; - await plugin.stop(ctx); - - expect(log.cancelled).toContain('reports.dispatch'); - }); - - it('a teardown on a plugin that never started is a no-op rather than a throw', async () => { - // Idempotence matters because `destroy()` clears the handles it released; a - // teardown that only works once is a teardown that fails inside a suite, - // and the kernel calls it on every plugin it walks. - const plugin = new ReportsServicePlugin(); - await expect(plugin.destroy()).resolves.toBeUndefined(); - await expect(plugin.destroy()).resolves.toBeUndefined(); - await expect(plugin.stop()).resolves.toBeUndefined(); - }); -}); diff --git a/packages/plugins/plugin-reports/src/report-export-axis.test.ts b/packages/plugins/plugin-reports/src/report-export-axis.test.ts deleted file mode 100644 index cef177423b2..00000000000 --- a/packages/plugins/plugin-reports/src/report-export-axis.test.ts +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * #3710 — the reports side door around the user-level export axis (#3544). - * - * `GET /data/:object/export` answers 403 for a caller without the export grant. - * A report over the SAME object rendered as CSV is the same bulk copy of the - * same rows, so before this gate a refused caller could simply save a report, - * run it as CSV — or schedule one to their own inbox — and get the data anyway. - * - * The gate lives in `executeReport`, which every path funnels through (run, - * ad-hoc, and the scheduled dispatch), so these exercise all three rather than - * trusting one call site. - */ - -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { ReportService, type ReportEmail } from './report-service.js'; - -function makeFakeEngine() { - const tables: Record = {}; - const ensure = (n: string) => (tables[n] ??= []); - const matches = (row: any, filter: any): boolean => { - if (!filter || typeof filter !== 'object') return true; - for (const [k, v] of Object.entries(filter)) { - if (k.startsWith('$')) throw new Error(`fake driver: unsupported operator ${k}`); - if (row[k] !== v) return false; - } - return true; - }; - return { - _tables: tables, - async find(object: string, options?: any) { - return ensure(object) - .filter((r) => matches(r, options?.filter ?? options?.where)) - .slice(0, options?.limit ?? 1000); - }, - async insert(object: string, data: any) { ensure(object).push({ ...data }); return { ...data }; }, - async update(object: string, idOrData: any, _opts?: any) { - const data = typeof idOrData === 'object' ? idOrData : _opts; - const id = typeof idOrData === 'object' ? idOrData.id : idOrData; - const table = ensure(object); - const i = table.findIndex((r) => r.id === id); - if (i >= 0) table[i] = { ...table[i], ...data }; - return table[i]; - }, - async delete(object: string, options?: any) { - const table = ensure(object); - const i = table.findIndex((r) => r.id === (options?.where?.id ?? options?.id)); - if (i >= 0) table.splice(i, 1); - return {}; - }, - }; -} - -function makeFakeEmail() { - const sent: any[] = []; - const email: ReportEmail & { _sent: any[] } = { - _sent: sent, - async send(input) { sent.push(input); return { status: 'sent' }; }, - }; - return email; -} - -const CTX = { userId: 'u1', tenantId: 't1', positions: [], permissions: [] }; -const now = new Date('2026-01-15T10:00:00Z'); - -describe('reports × the user-level export axis (#3710)', () => { - let engine: ReturnType; - let email: ReturnType; - let canExport: ReturnType; - - const build = (opts: { canExport?: any } = {}) => - new ReportService({ - engine: engine as any, - email, - clock: { now: () => now }, - maxRows: 5000, - resolveOwnerContext: async (ownerId: string) => - ownerId ? { userId: ownerId, tenantId: 't1', positions: [], permissions: [] } : null, - ...('canExport' in opts ? { canExport: opts.canExport } : { canExport }), - }); - - beforeEach(() => { - engine = makeFakeEngine(); - email = makeFakeEmail(); - canExport = vi.fn().mockResolvedValue(true); - engine._tables['lead'] = [ - { id: 'l1', name: 'Acme', status: 'open' }, - { id: 'l2', name: 'Globex', status: 'won' }, - ]; - }); - - const saveReport = async (svc: ReportService, format: 'csv' | 'json' | 'html_table') => - svc.saveReport( - { name: 'leads', object: 'lead', query: { fields: ['id', 'name'] }, format }, - CTX, - ); - - describe('interactive run', () => { - it('denies a CSV report run when the caller may not export the object', async () => { - const svc = build({ canExport: vi.fn().mockResolvedValue(false) }); - const report = await saveReport(svc, 'csv'); - await expect(svc.run(report.id, CTX)).rejects.toThrow(/EXPORT_NOT_PERMITTED/); - }); - - it('denies a JSON report run too — json is a bulk machine-readable copy', async () => { - const svc = build({ canExport: vi.fn().mockResolvedValue(false) }); - const report = await saveReport(svc, 'json'); - await expect(svc.run(report.id, CTX)).rejects.toThrow(/EXPORT_NOT_PERMITTED/); - }); - - it('ALLOWS html_table — a rendered view is reading, not exporting', async () => { - // The axis must not become a second read permission: a caller holding - // allowRead may already see these rows on screen. - const svc = build({ canExport: vi.fn().mockResolvedValue(false) }); - const report = await saveReport(svc, 'html_table'); - const result = await svc.run(report.id, CTX); - expect(result.rowCount).toBe(2); - }); - - it('allows CSV when the grant is held, and asks about the REPORT object', async () => { - const svc = build(); - const report = await saveReport(svc, 'csv'); - const result = await svc.run(report.id, CTX); - expect(result.rowCount).toBe(2); - expect(canExport).toHaveBeenCalledWith('lead', expect.objectContaining({ userId: 'u1' })); - }); - - it('refuses BEFORE reading any row', async () => { - const svc = build({ canExport: vi.fn().mockResolvedValue(false) }); - const report = await saveReport(svc, 'csv'); - const findSpy = vi.spyOn(engine, 'find'); - await expect(svc.run(report.id, CTX)).rejects.toThrow(/EXPORT_NOT_PERMITTED/); - // `sys_saved_report` lookups are fine; the OBJECT must never be queried. - expect(findSpy.mock.calls.some(([obj]) => obj === 'lead')).toBe(false); - }); - - it('a throwing canExport denies (fail closed, ADR-0049)', async () => { - const svc = build({ canExport: vi.fn().mockRejectedValue(new Error('resolution failed')) }); - const report = await saveReport(svc, 'csv'); - await expect(svc.run(report.id, CTX)).rejects.toThrow(/EXPORT_NOT_PERMITTED/); - }); - - it('no canExport wired → axis does not apply (no plugin-security deployment)', async () => { - const svc = build({ canExport: undefined }); - const report = await saveReport(svc, 'csv'); - const result = await svc.run(report.id, CTX); - expect(result.rowCount).toBe(2); - }); - }); - - describe('scheduling', () => { - it('refuses to CREATE a csv schedule the author could not run', async () => { - const svc = build({ canExport: vi.fn().mockResolvedValue(false) }); - const report = await saveReport(svc, 'html_table'); - await expect( - svc.scheduleReport({ reportId: report.id, recipients: ['a@b.c'], format: 'csv' }, CTX), - ).rejects.toThrow(/EXPORT_NOT_PERMITTED/); - }); - - it('allows an html_table schedule for the same caller', async () => { - const svc = build({ canExport: vi.fn().mockResolvedValue(false) }); - const report = await saveReport(svc, 'html_table'); - const sched = await svc.scheduleReport( - { reportId: report.id, recipients: ['a@b.c'], format: 'html_table' }, - CTX, - ); - expect(sched.id).toBeTruthy(); - }); - }); - - describe('scheduled dispatch — the original side door', () => { - it('a csv schedule created while granted STOPS delivering once the grant is revoked', async () => { - // The reason the dispatch re-checks instead of trusting the create-time - // check: permissions change after a schedule exists. - const gate = vi.fn().mockResolvedValue(true); - const svc = build({ canExport: gate }); - const report = await saveReport(svc, 'html_table'); - await svc.scheduleReport( - { reportId: report.id, recipients: ['a@b.c'], format: 'csv', intervalMinutes: 1 }, - CTX, - ); - - gate.mockResolvedValue(false); // grant revoked - // Force the schedule due (mirrors the existing dispatch suite). - engine._tables['sys_report_schedule'][0].next_run_at = new Date(now.getTime() - 1000).toISOString(); - const out = await svc.dispatchDue(); - - expect(out.failed).toBe(1); - expect(email._sent).toHaveLength(0); - const sched = engine._tables['sys_report_schedule'][0]; - expect(String(sched.last_error)).toMatch(/EXPORT_NOT_PERMITTED/); - }); - - it('an html_table schedule still delivers for a caller without the grant', async () => { - const svc = build({ canExport: vi.fn().mockResolvedValue(false) }); - const report = await saveReport(svc, 'html_table'); - await svc.scheduleReport( - { reportId: report.id, recipients: ['a@b.c'], format: 'html_table', intervalMinutes: 1 }, - CTX, - ); - // Force the schedule due (mirrors the existing dispatch suite). - engine._tables['sys_report_schedule'][0].next_run_at = new Date(now.getTime() - 1000).toISOString(); - const out = await svc.dispatchDue(); - expect(out.fired).toBe(1); - expect(email._sent).toHaveLength(1); - }); - - it('a csv schedule delivers when the owner holds the grant', async () => { - const svc = build(); - const report = await saveReport(svc, 'html_table'); - await svc.scheduleReport( - { reportId: report.id, recipients: ['a@b.c'], format: 'csv', intervalMinutes: 1 }, - CTX, - ); - // Force the schedule due (mirrors the existing dispatch suite). - engine._tables['sys_report_schedule'][0].next_run_at = new Date(now.getTime() - 1000).toISOString(); - const out = await svc.dispatchDue(); - expect(out.fired).toBe(1); - expect(email._sent[0].attachments?.[0]?.contentType).toBe('text/csv'); - }); - }); -}); diff --git a/packages/plugins/plugin-reports/src/report-group-posture-scope.integration.test.ts b/packages/plugins/plugin-reports/src/report-group-posture-scope.integration.test.ts deleted file mode 100644 index 7566fe2c1d2..00000000000 --- a/packages/plugins/plugin-reports/src/report-group-posture-scope.integration.test.ts +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * #7204 — a `group`-posture report returns the caller's membership union, the - * same row set the caller sees interactively. - * - * `executeReport` used to rebuild a five-field projection of the caller's - * execution envelope (`userId` / `tenantId` / `positions` / `permissions` / - * `isSystem`) before handing it to the engine read that produces the report. - * `accessible_org_ids` was not in that projection, and `buildDriverOptions` - * reads it BY NAME (`engine.ts`, ADR-0105 D2 / #3623) to widen the driver's - * native tenant scope to the caller's whole membership set under the `group` - * posture. Absent, drivers "fall back to equality: fail toward isolation" — so - * the report silently returned FEWER rows than the identical interactive query, - * with no error and nothing in the output saying so. - * - * WHY THIS FILE REFUSES TO STUB THE ENGINE. The defect is invisible one layer - * up: a fake engine that records the context it was handed can only assert that - * a key is present, and "the key is on the object" is exactly what the previous - * shape of this bug looked like from inside the service. The consumer is the - * REAL `buildDriverOptions` → real `@objectstack/driver-sql` native scope, so - * the assertions below land on ROW SETS: the union of the rows in both orgs, or - * the equality subset. Backend is better-sqlite3 `:memory:`, the canonical - * in-repo integration stack (PR #5715). - * - * The posture matrix is load-bearing, not decoration. `group` is the only - * posture the widening applies to; `isolated`, "no provider wired" and "group - * with an empty accessible set" must all still collapse to active-org equality - * after the fix, or the change traded under-reporting for exposure. - */ - -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import { ObjectKernel } from '@objectstack/core'; -import { ObjectQLPlugin } from '@objectstack/objectql'; -import type { IDataEngine } from '@objectstack/spec/contracts'; -import { SqlDriver } from '@objectstack/driver-sql'; -import { SysSavedReport, SysReportSchedule } from '@objectstack/platform-objects/audit'; -import { ReportService, type ReportEngine, type ReportEmail } from './report-service.js'; - -/** - * A tenant-scoped business object. `organization_id` is what makes the driver's - * native tenant scope engage at all (`isTenancyDisabled` / the SQL driver's - * tenant column), and `day` is a READ-TIME formula field: `applyFormulaPlan` - * evaluates it with `execCtx.timezone`, which the same projection also dropped. - */ -const account = { - name: 'account', - label: 'Account', - fields: { - organization_id: { name: 'organization_id', label: 'Org', type: 'text' }, - name: { name: 'name', label: 'Name', type: 'text' }, - day: { - name: 'day', - label: 'Calendar day', - type: 'formula', - expression: { dialect: 'cel', source: 'today()' }, - }, - }, -}; - -/** The caller: active org `org_a`, membership union `org_a` + `org_b`. */ -const GROUP_CTX = { - userId: 'u1', - tenantId: 'org_a', - positions: [], - permissions: [], - posture: 'MEMBER', - accessible_org_ids: ['org_a', 'org_b'], -} as any; - -const ids = (rows: any[]): string[] => rows.map((r) => String(r.id)).sort(); - -/** - * The engine surface this harness drives. `getService('objectql')` is declared - * as the data-plane contract `IDataEngine`; the boot-time knobs below - * (`registerDriver`, `registry`, `syncSchemas`) and the posture provider - * plugin-security wires in production sit outside it, so they are named here - * rather than erased with `any`. - */ -interface TestEngine extends IDataEngine { - registerDriver(driver: unknown, isDefault?: boolean): void; - registry: { registerObject(def: unknown, packageId: string, namespace: string): void }; - syncSchemas(): Promise; - setTenancyPostureProvider(provider: () => string | undefined): void; - destroy(): Promise; -} - -describe('#7204 a group-posture report reads the caller\'s whole membership union', () => { - let objectql: TestEngine | undefined; - let svc: ReportService; - let email: ReportEmail & { _sent: any[] }; - - afterEach(async () => { - vi.useRealTimers(); - try { await objectql?.destroy(); } catch { /* noop */ } - }); - - beforeEach(async () => { - const kernel = new ObjectKernel({ logger: { level: 'error' } }); - await kernel.use(new ObjectQLPlugin()); - await kernel.bootstrap(); - objectql = kernel.getService('objectql') as TestEngine; - - // The engine's own `init()` ran during bootstrap, before this driver - // existed, so the connect the engine would have done is done here. - const driver = new SqlDriver({ - client: 'better-sqlite3', - connection: { filename: ':memory:' }, - useNullAsDefault: true, - }); - await driver.connect(); - engine().registerDriver(driver, true); - for (const def of [account, SysSavedReport, SysReportSchedule]) { - engine().registry.registerObject(def, 'reports-test', 'reports-test'); - } - await engine().syncSchemas(); - - // Two rows in the active org, one in the other org the caller belongs to. - for (const row of [ - { id: 'a1', organization_id: 'org_a', name: 'A1' }, - { id: 'a2', organization_id: 'org_a', name: 'A2' }, - { id: 'b1', organization_id: 'org_b', name: 'B1' }, - ]) { - await engine().insert('account', row, { context: { isSystem: true } }); - } - - const sent: any[] = []; - email = { - _sent: sent, - async send(input) { sent.push(input); return { status: 'sent' as const }; }, - }; - svc = new ReportService({ - engine: engine() as unknown as ReportEngine, - email, - // A scheduled run executes as the report's OWNER (#2849 / #2980) — the - // resolver hands back a real RLS-bearing envelope, membership set and all. - resolveOwnerContext: async (ownerId: string) => - (ownerId === 'u1' ? ({ ...GROUP_CTX } as any) : null), - }); - }); - - /** What plugin-security's wiring reports in a deployment of this posture. */ - const posture = (p: string | undefined) => engine().setTenancyPostureProvider(() => p); - - /** The booted engine — narrowed once so every call site stays typed. */ - const engine = (): TestEngine => objectql as TestEngine; - - const saveAccountReport = async (format = 'csv') => - svc.saveReport( - { name: 'Accounts', object: 'account', query: {}, format } as any, - GROUP_CTX, - ); - - describe('group posture', () => { - beforeEach(() => posture('group')); - - it('a SAVED report returns the same rows as the identical interactive query', async () => { - const interactive = await engine().find('account', {}, { context: GROUP_CTX }); - expect(ids(interactive), 'the interactive baseline is the union').toEqual(['a1', 'a2', 'b1']); - - const report = await svc.run((await saveAccountReport()).id, GROUP_CTX); - - // The card's exact claim: the saved-report path used to return FEWER rows - // (['a1','a2'] — active-org equality) than the interactive query above. - expect(ids(report.rows)).toEqual(ids(interactive)); - expect(report.rowCount).toBe(3); - }); - - it('an AD-HOC report returns the union too', async () => { - const report = await svc.runAdHoc( - { name: 'Ad hoc', object: 'account', query: {} } as any, - GROUP_CTX, - ); - expect(ids(report.rows)).toEqual(['a1', 'a2', 'b1']); - }); - - it('a SCHEDULED run emails the owner the union, not the active org', async () => { - const report = await saveAccountReport(); - await svc.scheduleReport( - { reportId: report.id, recipients: ['ops@example.com'], format: 'csv', intervalMinutes: 60 }, - GROUP_CTX, - ); - - const result = await svc.dispatchDue(new Date(Date.now() + 3 * 60 * 60 * 1000)); - expect(result, 'the sweep must actually fire').toMatchObject({ fired: 1, failed: 0 }); - - const csv: string = email._sent[email._sent.length - 1]?.attachments?.[0]?.content ?? ''; - const dataRows = csv.split('\r\n').slice(1).filter(Boolean); - expect(dataRows).toHaveLength(3); - expect(csv).toContain('B1'); - }); - - it('the report also sees rows the ACTIVE org has none of', async () => { - // Nothing in org_a at all: under equality the report is empty, under the - // union it is the one org_b row. Isolates the widening from "the active - // org happened to hold most of the rows". - const report = await svc.runAdHoc( - { name: 'B only', object: 'account', query: { filter: { name: 'B1' } } } as any, - GROUP_CTX, - ); - expect(ids(report.rows)).toEqual(['b1']); - }); - - it('an EMPTY accessible set still collapses to active-org equality (fail toward isolation)', async () => { - const ctx = { ...GROUP_CTX, accessible_org_ids: [] }; - const interactive = await engine().find('account', {}, { context: ctx }); - const report = await svc.runAdHoc({ name: 'r', object: 'account', query: {} } as any, ctx); - expect(ids(report.rows)).toEqual(['a1', 'a2']); - expect(ids(report.rows)).toEqual(ids(interactive)); - }); - - it('forwards the business timezone, so a read-time formula field resolves the caller\'s calendar day', async () => { - // 20:00Z is the day BEFORE in UTC and the day AFTER in UTC+14, so the two - // timezones disagree deterministically at this instant. `today()` is a - // read-time formula evaluated by `applyFormulaPlan` with `execCtx.timezone` - // — dropped by the same projection, and observable on the row's VALUE. - vi.useFakeTimers({ shouldAdvanceTime: true }); - vi.setSystemTime(new Date('2026-08-10T20:00:00Z')); - const ctx = { ...GROUP_CTX, timezone: 'Pacific/Kiritimati' }; - - const interactive = await engine().find('account', { where: { id: 'a1' } }, { context: ctx }); - const report = await svc.runAdHoc( - { name: 'r', object: 'account', query: { filter: { id: 'a1' } } } as any, - ctx, - ); - - const dayOf = (v: unknown): string => - String(v instanceof Date ? v.toISOString() : v).slice(0, 10); - expect(dayOf(interactive[0]?.day), 'UTC+14 is already on the 11th').toBe('2026-08-11'); - expect(dayOf((report.rows[0] as any)?.day)).toBe(dayOf(interactive[0]?.day)); - }); - }); - - describe('every other posture is unchanged — the widening is group-only', () => { - it('isolated posture: the report stays at active-org equality', async () => { - posture('isolated'); - const interactive = await engine().find('account', {}, { context: GROUP_CTX }); - const report = await svc.runAdHoc({ name: 'r', object: 'account', query: {} } as any, GROUP_CTX); - expect(ids(report.rows)).toEqual(['a1', 'a2']); - expect(ids(report.rows)).toEqual(ids(interactive)); - }); - - it('no posture provider (no enforcement layer): equality, never widened', async () => { - const interactive = await engine().find('account', {}, { context: GROUP_CTX }); - const report = await svc.runAdHoc({ name: 'r', object: 'account', query: {} } as any, GROUP_CTX); - expect(ids(report.rows)).toEqual(['a1', 'a2']); - expect(ids(report.rows)).toEqual(ids(interactive)); - }); - }); -}); diff --git a/packages/plugins/plugin-reports/src/report-service.test.ts b/packages/plugins/plugin-reports/src/report-service.test.ts deleted file mode 100644 index dc0f2238949..00000000000 --- a/packages/plugins/plugin-reports/src/report-service.test.ts +++ /dev/null @@ -1,945 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { ReportService, renderReport, type ReportEmail } from './report-service.js'; - -// ─── Fake engine ────────────────────────────────────────────────── - -interface FakeRow { [k: string]: any } - -function makeFakeEngine() { - const tables: Record = {}; - const ensure = (n: string) => (tables[n] ??= []); - - function matches(row: FakeRow, filter: any): boolean { - if (!filter || typeof filter !== 'object') return true; - for (const [k, v] of Object.entries(filter)) { - if (k.startsWith('$')) throw new Error(`fake driver: unsupported operator ${k}`); - if (row[k] !== v) return false; - } - return true; - } - - return { - _tables: tables, - async find(object: string, options?: any) { - const rows = ensure(object).filter(r => matches(r, options?.filter ?? options?.where)); - if (options?.orderBy?.[0]) { - // Canonical SortNode key only (spec/data/query.zod.ts): the real - // engine strips an unknown `direction:` key and defaults to asc, so - // the mock must too — honoring both keys masks wrong-key sorts. - const { field, order } = options.orderBy[0]; - rows.sort((a, b) => { - const av = a[field]; const bv = b[field]; - if (av === bv) return 0; - const cmp = av > bv ? 1 : -1; - return order === 'desc' ? -cmp : cmp; - }); - } - return rows.slice(0, options?.limit ?? 1000); - }, - async insert(object: string, data: any) { - ensure(object).push({ ...data }); - return { ...data }; - }, - async update(object: string, idOrData: any, _opts?: any) { - const data = typeof idOrData === 'object' ? idOrData : _opts; - const id = typeof idOrData === 'object' ? idOrData.id : idOrData; - const table = ensure(object); - const i = table.findIndex(r => r.id === id); - if (i >= 0) table[i] = { ...table[i], ...data }; - return table[i]; - }, - async delete(object: string, options?: any) { - const table = ensure(object); - const id = options?.where?.id ?? options?.id; - const i = table.findIndex(r => r.id === id); - if (i >= 0) table.splice(i, 1); - return { id }; - }, - }; -} - -function makeFakeEmail() { - const sent: any[] = []; - const email: ReportEmail & { _sent: any[] } = { - _sent: sent, - async send(input) { sent.push(input); return { status: 'sent' }; }, - }; - return email; -} - -const CTX = { userId: 'u1', tenantId: 't1', positions: [], permissions: [] }; - -// ─── Rendering ───────────────────────────────────────────────────── - -describe('renderReport', () => { - it('csv: escapes quotes / commas / newlines per RFC 4180', () => { - const out = renderReport( - [{ a: 'x,y', b: 'has "q"', c: 'line\n2' }], - 'csv', - ['a', 'b', 'c'], - ); - expect(out).toBe('a,b,c\r\n"x,y","has ""q""","line\n2"'); - }); - - it('csv: header-only when no rows', () => { - expect(renderReport([], 'csv', ['a', 'b'])).toBe('a,b'); - }); - - it('html_table: escapes HTML entities', () => { - const out = renderReport([{ name: '