@objectstack/core 17.0.0-rc.6 → 17.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3361 -0
- package/dist/index.cjs +521 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +584 -15
- package/dist/index.d.ts +584 -15
- package/dist/index.js +492 -45
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Logger, LifecycleEventName, IServiceRegistry, IObjectQLEngine } from '@objectstack/spec/contracts';
|
|
2
|
-
export { EngineSchemaRegistryView, EngineTransactionInfo, EngineTransactionOptions, IDataDriver, IDataEngine, IHttpRequest, IHttpResponse, IHttpServer, IObjectQLEngine, Logger, Middleware, RouteHandler } from '@objectstack/spec/contracts';
|
|
1
|
+
import { Logger, LifecycleEventName, IServiceRegistry, AudienceBindingSuggestionFilter, IObjectQLEngine } from '@objectstack/spec/contracts';
|
|
2
|
+
export { EngineSchemaRegistryView, EngineTransactionInfo, EngineTransactionOptions, HttpResponseObservation, HttpResponseObserver, IDataDriver, IDataEngine, IHttpRequest, IHttpResponse, IHttpServer, IObjectQLEngine, Logger, Middleware, RouteHandler, UNMATCHED_ROUTE_PATTERN } from '@objectstack/spec/contracts';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { LoggerConfig, MigrationOnCrashPolicy, MigrationJournalEvent } from '@objectstack/spec/system';
|
|
5
5
|
import { ObjectLogger } from './logger.cjs';
|
|
@@ -7,7 +7,7 @@ export { createLogger } from './logger.cjs';
|
|
|
7
7
|
import * as QA from '@objectstack/spec/qa';
|
|
8
8
|
import { KeyObject } from 'node:crypto';
|
|
9
9
|
import { PluginCapability, PluginPermissions as PluginPermissions$1, PluginPermissionSet, ResourceType, PermissionAction, PluginPermission, SandboxConfig, KernelSecurityScanResult, KernelSecurityVulnerability, ExecutionContext, PluginHealthCheckParsed, PluginHealthStatus as PluginHealthStatus$1, PluginHealthReport, HotReloadConfigParsed, VersionConstraint, DependencyConflict, SemanticVersion, CompatibilityLevel } from '@objectstack/spec/kernel';
|
|
10
|
-
import { AuthzPosture } from '@objectstack/spec/security';
|
|
10
|
+
import { TenancyPosture, AuthzPosture } from '@objectstack/spec/security';
|
|
11
11
|
export { nextUtcCalendarDay, utcInstantMs } from '@objectstack/spec/data';
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -222,6 +222,14 @@ declare class ObjectKernel {
|
|
|
222
222
|
constructor(config?: ObjectKernelConfig);
|
|
223
223
|
/**
|
|
224
224
|
* Register a plugin with enhanced validation
|
|
225
|
+
*
|
|
226
|
+
* Duplicate names OVERWRITE, with one `warn` naming both versions — the
|
|
227
|
+
* declared contract in `plugin-registration.ts`, applied identically by
|
|
228
|
+
* `LiteKernel.use()` (#9864, maintainer ruling 2026-08-19). The overwrite
|
|
229
|
+
* itself is unchanged: it is what lets an app config's `plugins` entry
|
|
230
|
+
* supersede a plugin the CLI auto-registered earlier in the same boot
|
|
231
|
+
* (#9863). What changes is that it is no longer silent, and no longer
|
|
232
|
+
* disagrees with the other kernel.
|
|
225
233
|
*/
|
|
226
234
|
use(plugin: Plugin): Promise<this>;
|
|
227
235
|
/**
|
|
@@ -811,6 +819,17 @@ declare class LiteKernel extends ObjectKernelBase {
|
|
|
811
819
|
/**
|
|
812
820
|
* Register a plugin
|
|
813
821
|
* @param plugin - Plugin instance
|
|
822
|
+
*
|
|
823
|
+
* Duplicate names OVERWRITE, with one `warn` naming both versions — the
|
|
824
|
+
* declared contract in `plugin-registration.ts`, applied identically by
|
|
825
|
+
* `ObjectKernel.use()` (#9864, maintainer ruling 2026-08-19).
|
|
826
|
+
*
|
|
827
|
+
* This method used to `throw` `[Kernel] Plugin '<name>' already
|
|
828
|
+
* registered` here while `ObjectKernel` overwrote silently, so one input
|
|
829
|
+
* had two meanings depending on which kernel was running — and the kernel
|
|
830
|
+
* that runs in production was the silent one. The ruling converged them on
|
|
831
|
+
* the behaviour that already works (an app config superseding a plugin the
|
|
832
|
+
* CLI auto-registered, #9863) and made it audible rather than removing it.
|
|
814
833
|
*/
|
|
815
834
|
use(plugin: Plugin): this;
|
|
816
835
|
/**
|
|
@@ -882,7 +901,67 @@ declare class TestRunner {
|
|
|
882
901
|
declare class HttpTestAdapter implements TestExecutionAdapter {
|
|
883
902
|
private baseUrl;
|
|
884
903
|
private authToken?;
|
|
904
|
+
/**
|
|
905
|
+
* The single discovery probe of a run, memoised as the in-flight promise so
|
|
906
|
+
* concurrent record actions share one request rather than racing N.
|
|
907
|
+
*
|
|
908
|
+
* `os test` builds ONE adapter for the whole run (`packages/cli/src/commands/
|
|
909
|
+
* test.ts`) and hands it to every suite, so instance scope IS run scope.
|
|
910
|
+
*/
|
|
911
|
+
private mountPromise?;
|
|
885
912
|
constructor(baseUrl: string, authToken?: string | undefined);
|
|
913
|
+
/** The resolved data mount; probes at most once per adapter. */
|
|
914
|
+
private dataMount;
|
|
915
|
+
/**
|
|
916
|
+
* Ask the server where it serves the Data Protocol, and fall back to the
|
|
917
|
+
* convention — loudly — when it cannot say.
|
|
918
|
+
*
|
|
919
|
+
* ## [#7983] What the probe recovers, measured rather than assumed
|
|
920
|
+
*
|
|
921
|
+
* `@objectstack/client` answers the same question through discovery
|
|
922
|
+
* (`getRoute`, `packages/client/src/index.ts`), and this follows it: prefer
|
|
923
|
+
* the server's own `routes.data`, fall back to the convention. Measured on a
|
|
924
|
+
* booted stack (REST generator + dispatcher bridge, three configs):
|
|
925
|
+
*
|
|
926
|
+
* | deployment | `{apiBase}/discovery` | serves |
|
|
927
|
+
* |--------------------------------|-----------------------|---------------|
|
|
928
|
+
* | stock | 200 `/api/v1/data` | `/api/v1/data`|
|
|
929
|
+
* | `crud.dataPrefix: '/objects'` | 200 `/api/v1/objects` | `/api/v1/objects` |
|
|
930
|
+
* | `api.apiPath: '/api/2026-01'` | **404** | `/api/2026-01/data` |
|
|
931
|
+
*
|
|
932
|
+
* So the probe closes the `dataPrefix` row exactly: `RestServer`'s discovery
|
|
933
|
+
* handler substitutes the configured prefix into `routes.data`, and reading
|
|
934
|
+
* it is strictly better than recomputing it here. The `apiPath` row it cannot
|
|
935
|
+
* close, and the reason is structural rather than an oversight — `apiPath`
|
|
936
|
+
* moves the base that discovery itself is mounted under, so the document that
|
|
937
|
+
* would name the new mount is behind the very prefix we are missing.
|
|
938
|
+
*
|
|
939
|
+
* ⛔ And the one discovery document at a FIXED path does not rescue it:
|
|
940
|
+
* `/.well-known/objectstack` is mounted at the site root by the dispatcher
|
|
941
|
+
* bridge, but its `routes.data` is the DISPATCHER's own `${prefix}/data` —
|
|
942
|
+
* measured as `/api/v1/data` under all three configs above, including the two
|
|
943
|
+
* where the server serves elsewhere. Falling back to it would turn "we could
|
|
944
|
+
* not resolve the mount" into "discovery told us `/api/v1/data`": the same
|
|
945
|
+
* 404, now with a false provenance attached. Not probed, deliberately.
|
|
946
|
+
*
|
|
947
|
+
* Hence: one probe, then a diagnostic that NAMES the mount, the evidence and
|
|
948
|
+
* the remedy. `api_call` takes the path it is given and is unaffected either
|
|
949
|
+
* way — it stays the escape hatch for a host this cannot reach.
|
|
950
|
+
*/
|
|
951
|
+
private resolveDataMount;
|
|
952
|
+
/** `{baseUrl}{dataMount}/{object}` — the collection URL. */
|
|
953
|
+
private collectionUrl;
|
|
954
|
+
/** `{collection}/{id}` — the single-record URL. */
|
|
955
|
+
private recordUrl;
|
|
956
|
+
/**
|
|
957
|
+
* The provenance clause appended to a failed record action's error.
|
|
958
|
+
*
|
|
959
|
+
* The card this closes is about a 404 that reads like the author's own URL
|
|
960
|
+
* mistake; the mount is the one fact that distinguishes the two, so it rides
|
|
961
|
+
* on the failure itself rather than only on a warning printed earlier in the
|
|
962
|
+
* transcript.
|
|
963
|
+
*/
|
|
964
|
+
private mountNote;
|
|
886
965
|
execute(action: QA.TestAction, _context: Record<string, unknown>): Promise<unknown>;
|
|
887
966
|
private createRecord;
|
|
888
967
|
private updateRecord;
|
|
@@ -1708,9 +1787,74 @@ declare function isExpired(value: unknown, nowMs: number): boolean;
|
|
|
1708
1787
|
/** The principal resolved from a valid `sys_api_key`. */
|
|
1709
1788
|
interface ApiKeyPrincipal {
|
|
1710
1789
|
userId: string;
|
|
1790
|
+
/**
|
|
1791
|
+
* The organization this key authenticates INTO — read from the row's
|
|
1792
|
+
* `active_organization_id` and adopted by `resolveAuthzContext` as the
|
|
1793
|
+
* request's active organization (`ExecutionContext.tenantId`), which is what
|
|
1794
|
+
* lets the ADR-0105 Layer 0 wall match. `undefined` for a key minted before
|
|
1795
|
+
* #8287, or one minted under the `single` posture where there is no
|
|
1796
|
+
* organization to inherit.
|
|
1797
|
+
*/
|
|
1711
1798
|
tenantId?: string;
|
|
1712
1799
|
scopes: string[];
|
|
1713
1800
|
}
|
|
1801
|
+
/**
|
|
1802
|
+
* [#8287] Why a key was refused. Distinct from "no key present" and from "this
|
|
1803
|
+
* key is unknown/revoked/expired": a refusal means the credential is real and
|
|
1804
|
+
* intact but cannot be admitted under this deployment's tenancy posture.
|
|
1805
|
+
*/
|
|
1806
|
+
type ApiKeyRefusalReason = 'organization_required' | 'organization_membership_ended';
|
|
1807
|
+
/**
|
|
1808
|
+
* The verdict on an inbound API key. Three outcomes, deliberately distinct:
|
|
1809
|
+
*
|
|
1810
|
+
* - `none` — no key header, or a key that is unknown / revoked / expired /
|
|
1811
|
+
* owner-less. Indistinguishable by design (never tell a prober which), and
|
|
1812
|
+
* the caller MAY fall through to the session path exactly as before.
|
|
1813
|
+
* - `admitted` — a usable principal.
|
|
1814
|
+
* - `refused` — a real, intact key the posture cannot admit. The caller must
|
|
1815
|
+
* NOT fall through to the session path: falling through would be more
|
|
1816
|
+
* permissive than today's behaviour (an API key already outranks a session),
|
|
1817
|
+
* and the whole point of the refusal is that it is LOUD at call time.
|
|
1818
|
+
*/
|
|
1819
|
+
type ApiKeyAdmission = {
|
|
1820
|
+
outcome: 'none';
|
|
1821
|
+
} | {
|
|
1822
|
+
outcome: 'admitted';
|
|
1823
|
+
principal: ApiKeyPrincipal;
|
|
1824
|
+
} | {
|
|
1825
|
+
outcome: 'refused';
|
|
1826
|
+
reason: ApiKeyRefusalReason;
|
|
1827
|
+
message: string;
|
|
1828
|
+
};
|
|
1829
|
+
/**
|
|
1830
|
+
* The shape of the kernel's `tenancy` service this module reads a posture from.
|
|
1831
|
+
* Structural on purpose: `@objectstack/core` must not depend on the plugin that
|
|
1832
|
+
* provides it, and an embedding without that plugin simply supplies nothing.
|
|
1833
|
+
*/
|
|
1834
|
+
interface TenancyPostureSource {
|
|
1835
|
+
posture?: string;
|
|
1836
|
+
isolationActive?: boolean;
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* [#8287] Resolve the EFFECTIVE tenancy posture from the kernel's `tenancy`
|
|
1840
|
+
* service — the same reconciliation `plugin-security` performs before handing a
|
|
1841
|
+
* posture to `computeTenantLayer0Filter`, so the wall and the API-key admission
|
|
1842
|
+
* can never disagree about which posture is in force.
|
|
1843
|
+
*
|
|
1844
|
+
* ⚠️ Deliberately NOT `resolveTenancyPosture()` from `@objectstack/types`, which
|
|
1845
|
+
* reads `OS_TENANCY_POSTURE` directly. That answers what the operator ASKED
|
|
1846
|
+
* for, not what is ENFORCED: under ADR-0093 D4/D5 a deployment that requests
|
|
1847
|
+
* `isolated` without the enterprise `@objectstack/organizations` runtime
|
|
1848
|
+
* resolves to `single` and runs with NO organization wall. Reading the env
|
|
1849
|
+
* there would refuse org-less API keys on a deployment whose wall is not even
|
|
1850
|
+
* active — breaking working automation to enforce a boundary that does not
|
|
1851
|
+
* exist. The `tenancy` service is the one place that already knows the
|
|
1852
|
+
* difference.
|
|
1853
|
+
*
|
|
1854
|
+
* Returns `undefined` when no service is available, which callers must treat as
|
|
1855
|
+
* "no posture-conditional refusal" — see {@link resolveApiKeyAdmission}.
|
|
1856
|
+
*/
|
|
1857
|
+
declare function effectiveTenancyPosture(tenancy: TenancyPostureSource | undefined | null): TenancyPosture | undefined;
|
|
1714
1858
|
/**
|
|
1715
1859
|
* Verify an inbound API key against `sys_api_key` and resolve its principal.
|
|
1716
1860
|
* This is the ONE verify path shared by the dispatcher/MCP and REST surfaces.
|
|
@@ -1722,7 +1866,21 @@ interface ApiKeyPrincipal {
|
|
|
1722
1866
|
* @param headers Request headers (Web `Headers` or a plain object).
|
|
1723
1867
|
* @param nowMs Clock for expiry checks (injectable for tests).
|
|
1724
1868
|
*/
|
|
1725
|
-
declare function resolveApiKeyPrincipal(ql: any, headers: any, nowMs?: number): Promise<ApiKeyPrincipal | undefined>;
|
|
1869
|
+
declare function resolveApiKeyPrincipal(ql: any, headers: any, nowMs?: number, tenancyPosture?: TenancyPosture): Promise<ApiKeyPrincipal | undefined>;
|
|
1870
|
+
/**
|
|
1871
|
+
* [#8287] The full verdict behind {@link resolveApiKeyPrincipal} — same lookup,
|
|
1872
|
+
* but it distinguishes a POSTURE REFUSAL from "no principal".
|
|
1873
|
+
*
|
|
1874
|
+
* `resolveApiKeyPrincipal` collapses `refused` into `undefined` so every
|
|
1875
|
+
* existing caller keeps working and keeps failing closed; a caller that can
|
|
1876
|
+
* report WHY (the shared `resolveAuthzContext`) uses this instead.
|
|
1877
|
+
*
|
|
1878
|
+
* The only refusal decided here is the org-less one, because it needs nothing
|
|
1879
|
+
* but the row and the posture. The ex-member refusal needs the caller's
|
|
1880
|
+
* membership set and is decided in `resolveAuthzContext`, where that set is
|
|
1881
|
+
* already resolved.
|
|
1882
|
+
*/
|
|
1883
|
+
declare function resolveApiKeyAdmission(ql: any, headers: any, nowMs?: number, tenancyPosture?: TenancyPosture): Promise<ApiKeyAdmission>;
|
|
1726
1884
|
|
|
1727
1885
|
/** The transport-agnostic authorization envelope produced from a request. */
|
|
1728
1886
|
interface ResolvedAuthzContext {
|
|
@@ -1754,6 +1912,24 @@ interface ResolvedAuthzContext {
|
|
|
1754
1912
|
* anonymous requests carry no rung.
|
|
1755
1913
|
*/
|
|
1756
1914
|
posture?: AuthzPosture;
|
|
1915
|
+
/**
|
|
1916
|
+
* [#8287] Set when an inbound API key was REFUSED — a real, intact
|
|
1917
|
+
* credential this deployment's tenancy posture cannot admit. The context is
|
|
1918
|
+
* otherwise EMPTY (no `userId`), so every transport already fails it closed
|
|
1919
|
+
* to 401 with no change; this field only lets a transport that wants to say
|
|
1920
|
+
* WHY do so, instead of answering the operator with a bare "unauthenticated"
|
|
1921
|
+
* for a key they can see is neither revoked nor expired.
|
|
1922
|
+
*
|
|
1923
|
+
* ⚠️ `reason` is NOT an `error.code`. The wire vocabulary is closed
|
|
1924
|
+
* (ADR-0112: `StandardErrorCode ∪ ERROR_CODE_LEDGER`, both in `packages/spec`)
|
|
1925
|
+
* and a refused credential's standard member is `UNAUTHENTICATED`. This is a
|
|
1926
|
+
* diagnostic discriminator for the message, deliberately lowercase so it can
|
|
1927
|
+
* never be mistaken for one.
|
|
1928
|
+
*/
|
|
1929
|
+
authRefusal?: {
|
|
1930
|
+
reason: ApiKeyRefusalReason;
|
|
1931
|
+
message: string;
|
|
1932
|
+
};
|
|
1757
1933
|
}
|
|
1758
1934
|
interface ResolveAuthzInput {
|
|
1759
1935
|
/** Data engine (ObjectQL) exposing `find(object, { where, limit, context })`. */
|
|
@@ -1768,6 +1944,18 @@ interface ResolveAuthzInput {
|
|
|
1768
1944
|
getSession?: (headers: any) => Promise<any> | any;
|
|
1769
1945
|
/** Clock injection for API-key expiry (tests). */
|
|
1770
1946
|
nowMs?: number;
|
|
1947
|
+
/**
|
|
1948
|
+
* [#8287] The deployment's EFFECTIVE tenancy posture, as resolved from the
|
|
1949
|
+
* kernel's `tenancy` service (`effectiveTenancyPosture`) — never from
|
|
1950
|
+
* `OS_TENANCY_POSTURE`, which reports what was requested rather than what is
|
|
1951
|
+
* enforced (ADR-0093 D4/D5).
|
|
1952
|
+
*
|
|
1953
|
+
* Supplied by the transport because this resolver is deliberately
|
|
1954
|
+
* kernel-agnostic. OMITTING it disables the two posture-conditional API-key
|
|
1955
|
+
* refusals and leaves behaviour exactly as it was — so an unwired caller is
|
|
1956
|
+
* never made WORSE, only less strict.
|
|
1957
|
+
*/
|
|
1958
|
+
tenancyPosture?: TenancyPosture;
|
|
1771
1959
|
}
|
|
1772
1960
|
/**
|
|
1773
1961
|
* Resolve the authorization context for an inbound request. Always resolves —
|
|
@@ -2226,12 +2414,16 @@ declare const ANONYMOUS_DENY_CODE: "UNAUTHENTICATED";
|
|
|
2226
2414
|
/** Human-facing message. */
|
|
2227
2415
|
declare const ANONYMOUS_DENY_MESSAGE = "Authentication is required to access this endpoint.";
|
|
2228
2416
|
/**
|
|
2229
|
-
* The **REST seam's** 401 body — flat `{ error, message }`. NOT the
|
|
2230
|
-
* only one; see the two-envelope table below before you reuse this
|
|
2417
|
+
* The **REST seam's** 401 body — flat `{ error, code, message }`. NOT the
|
|
2418
|
+
* platform's only one; see the two-envelope table below before you reuse this
|
|
2419
|
+
* shape.
|
|
2231
2420
|
*
|
|
2232
|
-
*
|
|
2233
|
-
* (`rest-server.ts` —
|
|
2234
|
-
* which owns the
|
|
2421
|
+
* Two consumers write it verbatim, both flat-family seams: `@objectstack/rest`'s
|
|
2422
|
+
* `enforceAuth` (`rest-server.ts` —
|
|
2423
|
+
* `res.status(ANONYMOUS_DENY_STATUS).json(ANONYMOUS_DENY_BODY)`), which owns the
|
|
2424
|
+
* `/data/*` and `/meta` surfaces, and `@objectstack/runtime`'s
|
|
2425
|
+
* `mountRouteOnServer` (`dispatcher-plugin.ts` — the endpoint-route 401 arm,
|
|
2426
|
+
* #9823), which answers declared routes mounted on the HTTP server.
|
|
2235
2427
|
*
|
|
2236
2428
|
* ## Two live envelopes, one denial (#5632)
|
|
2237
2429
|
*
|
|
@@ -2240,8 +2432,11 @@ declare const ANONYMOUS_DENY_MESSAGE = "Authentication is required to access thi
|
|
|
2240
2432
|
* {@link ANONYMOUS_DENY_MESSAGE}). What differs is the **wrapper**:
|
|
2241
2433
|
*
|
|
2242
2434
|
* - **REST seam** — `@objectstack/rest` `enforceAuth`, this constant, verbatim:
|
|
2243
|
-
* `{ error: 'UNAUTHENTICATED', message: '…' }`.
|
|
2244
|
-
*
|
|
2435
|
+
* `{ error: 'UNAUTHENTICATED', code: 'UNAUTHENTICATED', message: '…' }`.
|
|
2436
|
+
* The machine code lives in the top-level `code` key — the same documented
|
|
2437
|
+
* key every other REST error family answers (#9487, maintainer-ruled
|
|
2438
|
+
* ADDITIVE: `error` keeps carrying the code value it always has, so no
|
|
2439
|
+
* existing reader breaks). There is no `success` key and no nesting.
|
|
2245
2440
|
* - **Dispatcher seams** — the five runtime domains `domains/ai.ts`,
|
|
2246
2441
|
* `domains/meta.ts`, `domains/security.ts`, `domains/actions.ts` and
|
|
2247
2442
|
* `domains/automation.ts` do NOT use this constant. Each calls
|
|
@@ -2253,7 +2448,10 @@ declare const ANONYMOUS_DENY_MESSAGE = "Authentication is required to access thi
|
|
|
2253
2448
|
* (#4007) records the flat and wrapped envelopes as the two live ones, and
|
|
2254
2449
|
* assigns retiring one of them to the envelope-convergence line (#3843 family).
|
|
2255
2450
|
* Converging them is a breaking wire change; it is not this module's to make,
|
|
2256
|
-
* and this constant must not be read as if it had already happened.
|
|
2451
|
+
* and this constant must not be read as if it had already happened. The #9487
|
|
2452
|
+
* `code` key does NOT settle that question either way (ADR-0112 D5 stays
|
|
2453
|
+
* open): it aligns the flat family to the `{ error, code }` shape the other
|
|
2454
|
+
* flat REST error families already answer, without moving or removing a key.
|
|
2257
2455
|
*
|
|
2258
2456
|
* ## Reading this from a consumer (human or AI author)
|
|
2259
2457
|
*
|
|
@@ -2270,6 +2468,7 @@ declare const ANONYMOUS_DENY_MESSAGE = "Authentication is required to access thi
|
|
|
2270
2468
|
*/
|
|
2271
2469
|
declare const ANONYMOUS_DENY_BODY: {
|
|
2272
2470
|
readonly error: "UNAUTHENTICATED";
|
|
2471
|
+
readonly code: "UNAUTHENTICATED";
|
|
2273
2472
|
readonly message: "Authentication is required to access this endpoint.";
|
|
2274
2473
|
};
|
|
2275
2474
|
interface AnonymousDenyInput {
|
|
@@ -2312,6 +2511,156 @@ declare function isGrantActive(row: GrantValidityWindow | null | undefined, nowM
|
|
|
2312
2511
|
*/
|
|
2313
2512
|
declare function isGrantExpired(row: GrantValidityWindow | null | undefined, nowMs: number): boolean;
|
|
2314
2513
|
|
|
2514
|
+
/** A catalogue row that may carry the `active` flag (`sys_permission_set`, `sys_position`). */
|
|
2515
|
+
interface ActivatableRow {
|
|
2516
|
+
active?: unknown;
|
|
2517
|
+
}
|
|
2518
|
+
/**
|
|
2519
|
+
* True unless the row carries an `active` column that is explicitly OFF.
|
|
2520
|
+
*
|
|
2521
|
+
* The ONE predicate every reader of `sys_permission_set.active` /
|
|
2522
|
+
* `sys_position.active` uses, so the resolver that enforces the flag and the
|
|
2523
|
+
* break-glass guard that simulates a write to it can never disagree about what
|
|
2524
|
+
* "deactivated" means.
|
|
2525
|
+
*/
|
|
2526
|
+
declare function isRowActive(row: ActivatableRow | null | undefined): boolean;
|
|
2527
|
+
|
|
2528
|
+
/**
|
|
2529
|
+
* ADMIN_STANDING_SURFACE — what `resolveAuthzContext` READS when it decides
|
|
2530
|
+
* who is an administrator, declared beside the resolver that reads it.
|
|
2531
|
+
*
|
|
2532
|
+
* ## Why this file exists (#8734)
|
|
2533
|
+
*
|
|
2534
|
+
* `plugin-auth`'s break-glass guard (`last-admin-guard.ts`, ADR-0024 D5.2)
|
|
2535
|
+
* decides whether a pending write can empty the administrator population by
|
|
2536
|
+
* testing the payload against three standing-key lists — `MEMBER_STANDING_KEYS`,
|
|
2537
|
+
* `GRANT_STANDING_KEYS`, `PERMISSION_SET_STANDING_KEYS`. Those lists are not an
|
|
2538
|
+
* independent design artifact: they are a CACHE of the columns this resolver
|
|
2539
|
+
* consumes. A payload touching none of them is skipped without any reads, so a
|
|
2540
|
+
* column this resolver starts reading and the guard's list omits is a write
|
|
2541
|
+
* class the guard silently stops judging — the one write class that can lock an
|
|
2542
|
+
* installation out of its own administration, with no in-product recovery.
|
|
2543
|
+
*
|
|
2544
|
+
* Nothing bound the two together. The correspondence was carried by a comment,
|
|
2545
|
+
* and it had already gone false once: #6084 wrote, beside the list, that
|
|
2546
|
+
* everything a permission-set write touches other than `name` — naming `active`
|
|
2547
|
+
* explicitly — is invisible to "who is an administrator". That was true when
|
|
2548
|
+
* written. #8613 made `active` a resolution-time predicate (a DEACTIVATED
|
|
2549
|
+
* `admin_full_access` set confers nothing, §6b below), and the sentence became
|
|
2550
|
+
* false. It was caught by one agent reading the comment closely enough to
|
|
2551
|
+
* notice it contradicted the code being written. Nothing mechanical would have
|
|
2552
|
+
* caught it: the guard's own tests stay green, because the guard is simply never
|
|
2553
|
+
* consulted for that write.
|
|
2554
|
+
*
|
|
2555
|
+
* ## What this file is, and what it is NOT
|
|
2556
|
+
*
|
|
2557
|
+
* It is a MEASUREMENT, not a wish. Its column lists are asserted equal to what
|
|
2558
|
+
* the resolver actually reads at runtime, by
|
|
2559
|
+
* `admin-standing-surface.test.ts`, which drives the real
|
|
2560
|
+
* `resolveAuthzContext` over a recording engine and collects every property
|
|
2561
|
+
* access and every `where` key per table. That is deliberate: a hand-written
|
|
2562
|
+
* list of "columns the derivation reads" is the same artifact as the comment
|
|
2563
|
+
* that went stale, one indirection along. Observation is also the only reading
|
|
2564
|
+
* that survives the derivation moving INTO a helper — `active` is read by
|
|
2565
|
+
* `isRowActive(ps)` and the window bounds by `isGrantActive(row, now)`, neither
|
|
2566
|
+
* of which names a column at the resolver's own call site.
|
|
2567
|
+
*
|
|
2568
|
+
* It is NOT a projection the resolver consumes. `ql.find` here returns whole
|
|
2569
|
+
* rows and the reads are ordinary property accesses on untyped rows, so nothing
|
|
2570
|
+
* in this file can FORCE the resolver to read only what it declares. The force
|
|
2571
|
+
* comes from the observation test: add a read, and this declaration is red
|
|
2572
|
+
* until it is updated; update this declaration, and `plugin-auth`'s
|
|
2573
|
+
* correspondence test is red until every new column is either in a standing-key
|
|
2574
|
+
* list or explicitly excluded with a reason.
|
|
2575
|
+
*
|
|
2576
|
+
* ## Reading the entries
|
|
2577
|
+
*
|
|
2578
|
+
* Every table this resolution path reads is listed — including the ones that
|
|
2579
|
+
* CANNOT confer administrator standing, each with the reason it cannot. That is
|
|
2580
|
+
* the table-level half of the same guarantee: a resolver that starts deriving
|
|
2581
|
+
* administrator standing from a new table would otherwise be invisible to a
|
|
2582
|
+
* column-set comparison, because the new table appears in neither side's list.
|
|
2583
|
+
*/
|
|
2584
|
+
/** How a table this resolver reads relates to "who is an administrator". */
|
|
2585
|
+
interface AdminStandingTable {
|
|
2586
|
+
/**
|
|
2587
|
+
* `derives` — a write to this table can change the administrator population,
|
|
2588
|
+
* so `last-admin-guard.ts` must carry a standing-key list for it.
|
|
2589
|
+
* `reads-only` — this resolver reads the table for something else entirely.
|
|
2590
|
+
*/
|
|
2591
|
+
readonly role: 'derives' | 'reads-only';
|
|
2592
|
+
/** Why the row above is the right classification. Prose, but pinned to a measured table. */
|
|
2593
|
+
readonly reason: string;
|
|
2594
|
+
/**
|
|
2595
|
+
* Every column this resolver reads on the table — property accesses and
|
|
2596
|
+
* `where` keys alike, in every spelling it actually touches. Declared for
|
|
2597
|
+
* `derives` tables only; asserted equal to the observed set.
|
|
2598
|
+
*/
|
|
2599
|
+
readonly columns?: readonly string[];
|
|
2600
|
+
}
|
|
2601
|
+
/**
|
|
2602
|
+
* The measured read surface of the administrator derivation.
|
|
2603
|
+
*
|
|
2604
|
+
* Scope, stated so the gate cannot be read as claiming more than it measures:
|
|
2605
|
+
* this is the SESSION/user-id resolution path — `resolveAuthzContext` with a
|
|
2606
|
+
* principal, and therefore all of `resolveUserAuthzGrants`. The API-key
|
|
2607
|
+
* ADMISSION path (`resolveApiKeyAdmission`) is outside it on purpose: it
|
|
2608
|
+
* authenticates a principal and seeds `permissions` with the key's scopes, and
|
|
2609
|
+
* confers no administrator standing of its own — `hasPlatformAdminGrant` (§6b)
|
|
2610
|
+
* is set only from a `sys_permission_set` row reached through an UNSCOPED
|
|
2611
|
+
* `sys_user_permission_set` grant, never from a scope string.
|
|
2612
|
+
*/
|
|
2613
|
+
declare const ADMIN_STANDING_SURFACE: Readonly<Record<string, AdminStandingTable>>;
|
|
2614
|
+
/** The tables a write to which can change who is an administrator. */
|
|
2615
|
+
declare function adminStandingTables(): string[];
|
|
2616
|
+
/**
|
|
2617
|
+
* The columns this resolver reads on `table`, or `undefined` when the table is
|
|
2618
|
+
* not part of the administrator derivation.
|
|
2619
|
+
*/
|
|
2620
|
+
declare function adminStandingColumns(table: string): readonly string[] | undefined;
|
|
2621
|
+
|
|
2622
|
+
/**
|
|
2623
|
+
* [#7678] The `?status=` vocabulary of the audience-binding suggestion list
|
|
2624
|
+
* (ADR-0090 D5/D9) — ONE owner for a rule that had exactly one implementation
|
|
2625
|
+
* and two seams needing it.
|
|
2626
|
+
*
|
|
2627
|
+
* The predicate was written for the runtime dispatcher's `/security` domain and
|
|
2628
|
+
* lived there, private. The **live** REST route
|
|
2629
|
+
* (`rest-server.ts` → `registerSecurityEndpoints`) is a second seam onto the
|
|
2630
|
+
* same service call and never had it, so `?status=garbage` reached the service,
|
|
2631
|
+
* matched no row, and answered **200 with an empty list** — which reads as
|
|
2632
|
+
* "there are no suggestions", a plausible and actionable-looking answer, rather
|
|
2633
|
+
* than "your filter was not a status". That silent arm is the defect; the two
|
|
2634
|
+
* seams disagreeing about one contract is the cause.
|
|
2635
|
+
*
|
|
2636
|
+
* So this module is the convergence, not a copy: `domains/security.ts` and
|
|
2637
|
+
* `rest-server.ts` both import from here, and the vocabulary — including the
|
|
2638
|
+
* refusal wording — exists once.
|
|
2639
|
+
*
|
|
2640
|
+
* The record is keyed BY the contract type on purpose (carried over from the
|
|
2641
|
+
* original): adding a status to `AudienceBindingSuggestionFilter` leaves a key
|
|
2642
|
+
* missing here and renaming one leaves a key excess, and either way this fails
|
|
2643
|
+
* to compile. A plain `['pending', …]` array would silently drift.
|
|
2644
|
+
*/
|
|
2645
|
+
|
|
2646
|
+
/** The `status` arm of {@link AudienceBindingSuggestionFilter}, named. */
|
|
2647
|
+
type AudienceBindingSuggestionStatus = NonNullable<AudienceBindingSuggestionFilter['status']>;
|
|
2648
|
+
/** The accepted `?status=` values, keyed by the contract type (see module note). */
|
|
2649
|
+
declare const AUDIENCE_BINDING_SUGGESTION_STATUSES: Record<AudienceBindingSuggestionStatus, true>;
|
|
2650
|
+
/**
|
|
2651
|
+
* The same vocabulary as a list — for refusal messages, and for tests that must
|
|
2652
|
+
* enumerate every valid value FROM the type rather than hand-picking one.
|
|
2653
|
+
*/
|
|
2654
|
+
declare const AUDIENCE_BINDING_SUGGESTION_STATUS_VALUES: readonly AudienceBindingSuggestionStatus[];
|
|
2655
|
+
/**
|
|
2656
|
+
* Is `value` one of the three statuses the contract declares? Case-sensitive on
|
|
2657
|
+
* purpose — the contract's values are lowercase, so `PENDING` is not a status
|
|
2658
|
+
* and gets the same refusal as `garbage`.
|
|
2659
|
+
*/
|
|
2660
|
+
declare const isAudienceBindingSuggestionStatus: (value: string) => value is AudienceBindingSuggestionStatus;
|
|
2661
|
+
/** The refusal wording, shared so both seams answer an unknown status identically. */
|
|
2662
|
+
declare const unknownAudienceBindingSuggestionStatusMessage: (value: string) => string;
|
|
2663
|
+
|
|
2315
2664
|
/**
|
|
2316
2665
|
* [#7284] The `__` operation-private-key convention — one owner, on the
|
|
2317
2666
|
* CONSUMER side.
|
|
@@ -2333,8 +2682,10 @@ declare function isGrantExpired(row: GrantValidityWindow | null | undefined, now
|
|
|
2333
2682
|
* `__delegatorReadScope` / `__delegatorWriteScope`, stamped in place by
|
|
2334
2683
|
* `security-plugin.ts` (`sc.__readScope = …`);
|
|
2335
2684
|
* - the engine's internal privilege markers on the same channel —
|
|
2336
|
-
* `__expandRead`
|
|
2337
|
-
*
|
|
2685
|
+
* `__expandRead` marks a read as a lookup EXPANSION sub-read (it no longer
|
|
2686
|
+
* relaxes any gate — #7626 removed that waiver — but it still travels with
|
|
2687
|
+
* one operation and must not be inherited by another), `__referentialFieldClear`
|
|
2688
|
+
* authorizes the referential-clear write.
|
|
2338
2689
|
*
|
|
2339
2690
|
* plugin-security is the PRODUCER of that vocabulary and would be the most
|
|
2340
2691
|
* honest owner of the rule for consuming it, but none of the three consumers
|
|
@@ -2464,6 +2815,20 @@ interface CalendarParts {
|
|
|
2464
2815
|
month: number;
|
|
2465
2816
|
day: number;
|
|
2466
2817
|
}
|
|
2818
|
+
/**
|
|
2819
|
+
* A wall clock as a human writes it — calendar day plus an optional
|
|
2820
|
+
* time-of-day, with **no zone attached**. `2026-08-01 06:00:00` is this shape:
|
|
2821
|
+
* it names a reading on a clock, and only a reference timezone turns it into an
|
|
2822
|
+
* instant. Omitted time components default to 0, so {@link CalendarParts} alone
|
|
2823
|
+
* is midnight.
|
|
2824
|
+
*/
|
|
2825
|
+
interface WallClockParts extends CalendarParts {
|
|
2826
|
+
/** 0-23. */
|
|
2827
|
+
hour?: number;
|
|
2828
|
+
minute?: number;
|
|
2829
|
+
second?: number;
|
|
2830
|
+
millisecond?: number;
|
|
2831
|
+
}
|
|
2467
2832
|
/**
|
|
2468
2833
|
* The year/month/day an instant falls on in `tz`. Throws if `tz` is not a
|
|
2469
2834
|
* valid IANA zone (callers treat that as a fall-through to UTC).
|
|
@@ -2488,8 +2853,52 @@ declare function calendarPartsInTzOrUtc(d: Date, tz?: string): CalendarParts;
|
|
|
2488
2853
|
*
|
|
2489
2854
|
* Used by date-bucket drill ranges (#1752): a `datetime` field buckets on the
|
|
2490
2855
|
* reference-tz calendar, so its bucket boundary is that tz's midnight instant.
|
|
2856
|
+
*
|
|
2857
|
+
* Date-only by contract: a `YYYY-MM-DD HH:mm:ss` argument is still `NaN` here.
|
|
2858
|
+
* Callers holding a wall clock with a time-of-day want
|
|
2859
|
+
* {@link zonedWallClockToUtcMs}, which this delegates its zone arithmetic to.
|
|
2491
2860
|
*/
|
|
2492
2861
|
declare function zonedDateStartToUtcMs(ymd: string, tz?: string): number;
|
|
2862
|
+
/**
|
|
2863
|
+
* The UTC instant (epoch ms) at which a **wall clock** reading happens in
|
|
2864
|
+
* reference timezone `tz` — the general inverse of {@link calendarPartsInTz},
|
|
2865
|
+
* of which {@link zonedDateStartToUtcMs} is the midnight special case.
|
|
2866
|
+
*
|
|
2867
|
+
* `2026-08-01 06:00:00` in `Asia/Shanghai` is `2026-07-31T22:00:00Z`: a
|
|
2868
|
+
* different day, month and quarter. That gap is why this direction exists as a
|
|
2869
|
+
* shared primitive at all — bulk import (#8485) reads offset-free spreadsheet
|
|
2870
|
+
* cells, which are wall clocks and nothing more, and `new Date(cell)` resolves
|
|
2871
|
+
* them against the **process** `TZ`, i.e. a host setting rather than the
|
|
2872
|
+
* tenant's configured zone.
|
|
2873
|
+
*
|
|
2874
|
+
* DST-safe: the zone offset is read from the platform tz database via
|
|
2875
|
+
* `Intl.DateTimeFormat` (never hand-computed), and a two-pass resolution settles
|
|
2876
|
+
* the case where the offset differs side-to-side of the target instant. Two
|
|
2877
|
+
* wall clocks are not a bijection with instants, and this function resolves
|
|
2878
|
+
* both degenerate cases to the **earlier candidate instant** — in both, the
|
|
2879
|
+
* final pass reads the offset on the DST side of the transition (measured, not
|
|
2880
|
+
* merely intended — `datetime.test.ts` pins both):
|
|
2881
|
+
* - a clock reading the zone **skips** (spring forward: `02:30` on a US
|
|
2882
|
+
* spring-forward day) settles on the *post*-transition offset (EDT, −04),
|
|
2883
|
+
* which places the instant just **before** the gap: it reads `01:30` EST
|
|
2884
|
+
* locally, not `03:30` EDT. Note this is the opposite of Temporal's
|
|
2885
|
+
* `'compatible'` disambiguation, which pushes a gap reading forward;
|
|
2886
|
+
* - a clock reading that happens **twice** (fall back: `01:30` on a US
|
|
2887
|
+
* fall-back day) resolves to its first occurrence, the one still on the
|
|
2888
|
+
* pre-transition DST offset (EDT, −04).
|
|
2889
|
+
*
|
|
2890
|
+
* A spreadsheet cell naming a wall clock that its zone never had is ambiguous
|
|
2891
|
+
* by construction; what matters for an import is that the answer is
|
|
2892
|
+
* deterministic and host-independent, which both branches above are.
|
|
2893
|
+
*
|
|
2894
|
+
* FALLBACK — an unset, `'UTC'`, or invalid `tz` reads the wall clock **as UTC**,
|
|
2895
|
+
* never as the process-local clock. Every caller of this family already degrades
|
|
2896
|
+
* that way ({@link zonedDateStartToUtcMs}, and the export renderer's cell path),
|
|
2897
|
+
* and a host `TZ` fallback would reintroduce exactly the deployment-dependent
|
|
2898
|
+
* instant this primitive exists to remove. A parts object that produces an
|
|
2899
|
+
* invalid date (`NaN` components) returns `NaN`, as `Date.UTC` does.
|
|
2900
|
+
*/
|
|
2901
|
+
declare function zonedWallClockToUtcMs(parts: WallClockParts, tz?: string): number;
|
|
2493
2902
|
|
|
2494
2903
|
/**
|
|
2495
2904
|
* Granularity of a canonical date-bucket key. Mirrors `@objectstack/spec`'s
|
|
@@ -2641,6 +3050,30 @@ declare function withTransientRetry<T>(fn: (attempt: number) => Promise<T>, opts
|
|
|
2641
3050
|
*/
|
|
2642
3051
|
declare function bulkWrite<TRow, TRecord = any>(rows: TRow[], opts: BulkWriteOptions<TRow, TRecord>): Promise<BulkWriteRowResult<TRecord>[]>;
|
|
2643
3052
|
|
|
3053
|
+
/**
|
|
3054
|
+
* Collect the names of fields declared `internal: true` on `schema`.
|
|
3055
|
+
*
|
|
3056
|
+
* Same verdicts as objectql's `collectInternalReadFields` (see the module
|
|
3057
|
+
* header for why it is restated rather than imported): strict `=== true`,
|
|
3058
|
+
* empty result for a missing/field-less schema.
|
|
3059
|
+
*/
|
|
3060
|
+
declare function collectInternalWriteResponseFields(schema: unknown): string[];
|
|
3061
|
+
/**
|
|
3062
|
+
* Drop every `internal: true` field from a write response's record(s), in
|
|
3063
|
+
* place. THE single helper every external write mouth goes through — see the
|
|
3064
|
+
* module header; the three tripwires enforce the "every".
|
|
3065
|
+
*
|
|
3066
|
+
* @param schema The registered object schema (`engine.registry.getObject(...)`
|
|
3067
|
+
* / the protocol's own registry view / `metadataService
|
|
3068
|
+
* .getObject(...)`). An unknown object (no schema) strips
|
|
3069
|
+
* nothing — the write itself would have been refused upstream
|
|
3070
|
+
* by the object-existence gate.
|
|
3071
|
+
* @param records A single record, an array of records, or anything a write
|
|
3072
|
+
* mouth hands back where a record could sit (`null`, a count, a
|
|
3073
|
+
* boolean): non-objects are skipped, arrays are walked.
|
|
3074
|
+
*/
|
|
3075
|
+
declare function omitInternalFieldsFromWriteResponse(schema: unknown, records: unknown): void;
|
|
3076
|
+
|
|
2644
3077
|
/**
|
|
2645
3078
|
* Can this runtime actually roll back? — the ADR-0119 D4 gate, shared.
|
|
2646
3079
|
*
|
|
@@ -2934,6 +3367,87 @@ declare function resolveFilterTokens<T>(filter: T, ctx?: FilterTokenResolutionCo
|
|
|
2934
3367
|
*/
|
|
2935
3368
|
declare function filterTokenContextFrom(execCtx: ExecutionContextLike | undefined, now?: Date): FilterTokenResolutionContext;
|
|
2936
3369
|
|
|
3370
|
+
/** Which temporal storage rule a declared field takes. */
|
|
3371
|
+
type TemporalComparandKind = 'datetime' | 'date' | 'time';
|
|
3372
|
+
/**
|
|
3373
|
+
* The kind a declared field's `type` takes, or `null` for every non-temporal
|
|
3374
|
+
* field.
|
|
3375
|
+
*
|
|
3376
|
+
* The same three-way split `driver-memory`'s `indexTemporalFields` and
|
|
3377
|
+
* `SqlDriver.temporalFieldKind` make, so the door and the drivers cannot
|
|
3378
|
+
* disagree about which fields are temporal at all.
|
|
3379
|
+
*/
|
|
3380
|
+
declare function temporalComparandKind(fieldType: unknown): TemporalComparandKind | null;
|
|
3381
|
+
/**
|
|
3382
|
+
* Is `value` a comparand that a `kind` column's storage rule cannot read?
|
|
3383
|
+
*
|
|
3384
|
+
* `true` ONLY for a non-empty, non-placeholder STRING that the kind's rule
|
|
3385
|
+
* would hand back unchanged. Everything else — a number, a `Date`, `null`, a
|
|
3386
|
+
* `{ $field }` reference, filter structure, the empty string, a `{token}` —
|
|
3387
|
+
* answers `false`, each for a reason recorded in the module note or below.
|
|
3388
|
+
*
|
|
3389
|
+
* A `{placeholder}` is stepped around rather than judged because it is another
|
|
3390
|
+
* layer's vocabulary and that layer already refuses the unknown ones loudly
|
|
3391
|
+
* (`FILTER_TOKEN_UNKNOWN` / 400, with the resolvable tokens listed). Both doors
|
|
3392
|
+
* that call this run BEFORE token resolution, so judging a placeholder here
|
|
3393
|
+
* would refuse `{30_days_ago}` — the platform's own correct spelling, and the
|
|
3394
|
+
* positive control this fix is pinned against.
|
|
3395
|
+
*/
|
|
3396
|
+
declare function isUninterpretableTemporalComparand(kind: TemporalComparandKind, value: unknown): boolean;
|
|
3397
|
+
|
|
3398
|
+
/**
|
|
3399
|
+
* [#4435] The 404 a single-record operation answers when the id names no row.
|
|
3400
|
+
*
|
|
3401
|
+
* Extracted so the READ and the two WRITE paths cannot disagree about it. They
|
|
3402
|
+
* did: `getData` answered `404 RECORD_NOT_FOUND` while `updateData` returned
|
|
3403
|
+
* `200 { record: null }` and `deleteData` returned `200 { success: true }` for
|
|
3404
|
+
* any string in the path — so a typo'd id, an already-deleted row and a real
|
|
3405
|
+
* deletion were indistinguishable, and a client PATCHing a concurrently deleted
|
|
3406
|
+
* record was told its write had landed.
|
|
3407
|
+
*
|
|
3408
|
+
* That is the same silent-no-op shape the v17 train removed everywhere else
|
|
3409
|
+
* this window (#4240/#4303/#4315 refuse missing fields, #4169 refuses unknown
|
|
3410
|
+
* params, #4190 stopped dropping filters) — a write that touched zero rows
|
|
3411
|
+
* reporting 200 is that shape one level up, on the verb where it costs the
|
|
3412
|
+
* most.
|
|
3413
|
+
*
|
|
3414
|
+
* [#5138] EXPORTED, for the same "cannot disagree about it" reason one layer
|
|
3415
|
+
* out. `@objectstack/runtime`'s `callData` is protocol-first with an ObjectQL
|
|
3416
|
+
* FALLBACK, and the fallback had reinvented this fact three incompatible ways
|
|
3417
|
+
* (`get` → `null`, `update` → a bare `Error` with no status ⇒ 500, `delete` →
|
|
3418
|
+
* no check at all ⇒ `200 { deleted: true }` for a row that never existed). It
|
|
3419
|
+
* now calls THIS function, so the two paths behind one `callData` answer a
|
|
3420
|
+
* missing id identically — which is the only reason a caller may stop caring
|
|
3421
|
+
* which of them served it. Re-spelling the envelope there would have been a
|
|
3422
|
+
* second not-found envelope; `RECORD_NOT_FOUND` (#5088) is the one this repo
|
|
3423
|
+
* has.
|
|
3424
|
+
*
|
|
3425
|
+
* ── [#7867] Why it lives in `@objectstack/core` and not where it was written ──
|
|
3426
|
+
*
|
|
3427
|
+
* Because the THIRD path that needed it could not reach the second one. An
|
|
3428
|
+
* action body's `ctx.api.object(name).update({ id, … })` traverses neither
|
|
3429
|
+
* `protocol.updateData` nor `callData`: it reaches `ObjectQL.update()`'s by-id
|
|
3430
|
+
* branch directly, which had no existence gate at all, so a ghost id was a
|
|
3431
|
+
* silent no-op that then died on whatever the pipeline complained about first
|
|
3432
|
+
* (a `HookConditionError` 400 on a hooked object, a required-field
|
|
3433
|
+
* `VALIDATION_FAILED` 400 on an unhooked one — the 400 class varied with the
|
|
3434
|
+
* object's declarations; the missing 404 was the constant).
|
|
3435
|
+
*
|
|
3436
|
+
* The gate for that path belongs in the engine, and `packages/objectql` cannot
|
|
3437
|
+
* import `@objectstack/metadata-protocol` where this function was written:
|
|
3438
|
+
* ADR-0076 D2's boundary ratchet (`core-boundary.ratchet.test.ts`) forbids the
|
|
3439
|
+
* whole `@objectstack/objectql/core` closure — `engine.ts` included — from
|
|
3440
|
+
* pulling that package in. So the choice was a FOURTH spelling of the envelope
|
|
3441
|
+
* or one home both layers already depend on. #5138's own sentence rules the
|
|
3442
|
+
* first out, so this is the second: the factory moved down to the lowest
|
|
3443
|
+
* package the three producers share, and `@objectstack/metadata-protocol`
|
|
3444
|
+
* re-exports it unchanged for every existing importer.
|
|
3445
|
+
*
|
|
3446
|
+
* This is the same move `engineCanRollBack` made for the same reason — a fact
|
|
3447
|
+
* two layers must agree on lives in the layer beneath both, not in a copy each.
|
|
3448
|
+
*/
|
|
3449
|
+
declare function recordNotFoundError(object: string, id: string | number): Error;
|
|
3450
|
+
|
|
2937
3451
|
/**
|
|
2938
3452
|
* In-memory Map-backed cache fallback.
|
|
2939
3453
|
*
|
|
@@ -3069,7 +3583,24 @@ declare function createMemoryI18n(): {
|
|
|
3069
3583
|
* not a merge — so deleted items/keys stop resolving on the next sync.
|
|
3070
3584
|
*/
|
|
3071
3585
|
replaceAuthoredTranslations(byLocale: Record<string, Record<string, unknown>>): void;
|
|
3586
|
+
/**
|
|
3587
|
+
* Report the locales this stack offers.
|
|
3588
|
+
*
|
|
3589
|
+
* [#7679] When the app declared `i18n.supportedLocales`, that declaration
|
|
3590
|
+
* IS the answer — in declared order, and including a declared locale no
|
|
3591
|
+
* bundle was ever loaded for (declared-but-unserved). Reporting the
|
|
3592
|
+
* declaration rather than an intersection is what gives a client the
|
|
3593
|
+
* signal that the locale it is being offered has nothing behind it yet;
|
|
3594
|
+
* quietly dropping it would leave the gap invisible on both sides. It is
|
|
3595
|
+
* also the only answer that does not depend on how much had loaded by the
|
|
3596
|
+
* time this was called.
|
|
3597
|
+
*
|
|
3598
|
+
* With nothing declared, the loaded set — the behaviour every app that
|
|
3599
|
+
* never opted in already has.
|
|
3600
|
+
*/
|
|
3072
3601
|
getLocales(): string[];
|
|
3602
|
+
/** @see II18nService.setSupportedLocales — [#7679] */
|
|
3603
|
+
setSupportedLocales(locales: readonly string[] | undefined): void;
|
|
3073
3604
|
getDefaultLocale(): string;
|
|
3074
3605
|
setDefaultLocale(locale: string): void;
|
|
3075
3606
|
};
|
|
@@ -3080,6 +3611,13 @@ declare function createMemoryI18n(): {
|
|
|
3080
3611
|
* Implements the IMetadataService contract with a simple Map-of-Maps store.
|
|
3081
3612
|
* Used by ObjectKernel as an automatic fallback when no real metadata plugin
|
|
3082
3613
|
* (e.g. MetadataPlugin with file-system persistence) is registered.
|
|
3614
|
+
*
|
|
3615
|
+
* [#7378] Carries the ruled register/read argument contract
|
|
3616
|
+
* (`../metadata-service-contract.ts` — the ruling is quoted there):
|
|
3617
|
+
* `register` refuses a `data.name` that disagrees with the `name` argument and
|
|
3618
|
+
* refuses a non-document `data` (rows 1/3), and every type store is keyed on
|
|
3619
|
+
* the CANONICAL type (row 2), so `register('objects', n, d)` and
|
|
3620
|
+
* `get('object', n)` address one store rather than two.
|
|
3083
3621
|
*/
|
|
3084
3622
|
declare function createMemoryMetadata(): {
|
|
3085
3623
|
__serviceInfo: {
|
|
@@ -3133,6 +3671,37 @@ declare function wireAuthoredTranslationSync(ctx: MinimalCtx): void;
|
|
|
3133
3671
|
*/
|
|
3134
3672
|
declare const CORE_FALLBACK_FACTORIES: Record<string, () => Record<string, any>>;
|
|
3135
3673
|
|
|
3674
|
+
/**
|
|
3675
|
+
* The canonical spelling an `IMetadataService` type store is keyed on
|
|
3676
|
+
* (#7378 row 2). Folds a plural manifest spelling to the singular metadata
|
|
3677
|
+
* type name (`'objects'` → `'object'`, `'views'` → `'view'`, …) through the
|
|
3678
|
+
* platform's one plural↔singular map (`PLURAL_TO_SINGULAR`,
|
|
3679
|
+
* `@objectstack/spec/shared`); a name with no plural mapping — which includes
|
|
3680
|
+
* every canonical singular type — passes through unchanged.
|
|
3681
|
+
*/
|
|
3682
|
+
declare function canonicalMetadataServiceType(type: string): string;
|
|
3683
|
+
/**
|
|
3684
|
+
* Enforce rows 1 and 3 of the #7378 ruling on a
|
|
3685
|
+
* `register(type, name, data)` payload — call it before the first store write,
|
|
3686
|
+
* so a refusal writes nothing anywhere.
|
|
3687
|
+
*
|
|
3688
|
+
* Refuses, with a locating `VALIDATION_ERROR` (status 400):
|
|
3689
|
+
*
|
|
3690
|
+
* - **a non-document `data`** (row 3): anything that is not a plain object —
|
|
3691
|
+
* primitives, `null`, arrays. The contract declares `data: unknown`, so
|
|
3692
|
+
* this is a runtime refusal, not a type error;
|
|
3693
|
+
* - **a `data.name` that disagrees with the `name` argument** (row 1), in
|
|
3694
|
+
* either direction. A document with NO `name` of its own is fine — the
|
|
3695
|
+
* argument is the key, and there is no disagreement to refuse.
|
|
3696
|
+
*
|
|
3697
|
+
* Deliberately NOT called by `registerInMemory`: that optional member is a
|
|
3698
|
+
* boot-time seeding primitive outside the ruled surface (the ruling names
|
|
3699
|
+
* `register`), and its callers hand it artefacts whose shape source control
|
|
3700
|
+
* owns. It shares the row-2 canonical fold — a store key is a store fact, not
|
|
3701
|
+
* a per-member choice — just not the refusals.
|
|
3702
|
+
*/
|
|
3703
|
+
declare function assertMetadataRegisterContract(type: string, name: string, data: unknown): asserts data is Record<string, unknown>;
|
|
3704
|
+
|
|
3136
3705
|
/**
|
|
3137
3706
|
* Plugin Health Monitor
|
|
3138
3707
|
*
|
|
@@ -3450,4 +4019,4 @@ declare class NamespaceResolver {
|
|
|
3450
4019
|
private suggestAlternative;
|
|
3451
4020
|
}
|
|
3452
4021
|
|
|
3453
|
-
export { ANONYMOUS_DENY_BODY, ANONYMOUS_DENY_CODE, ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, API_KEY_PREFIX, type AnonymousDenyInput, type ApiKeyPrincipal, type AuthGate, type BucketGranularity, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, ENTRY_EXECUTION_CONTEXT_FIELDS, type EngineWithTransaction, type EntryExecutionContextField, type EntryLocalization, type ExecutionContextAssemblyInput, type ExecutionContextEntryFields, type ExecutionContextLike, type FilterTokenResolutionContext, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, type InterruptedRun, type KernelState, type KeyInput, type LadderPrincipal, type LadderRow, LiteKernel, type MigrationChunk, type MigrationChunkContext, MigrationJournalRefusal, type MigrationPlan, type MigrationPlanProvider, MigrationPlanRegistry, type MigrationPlanStep, type MigrationRunResult, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, type OAuthTokenProvenance, OPERATION_PRIVATE_KEY_PREFIX, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type OrderablePlugin, POSTURE_INJECTION_RULE, POSTURE_LADDER, POSTURE_RANK, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PostureEvidence, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolveUserAuthzGrantsOptions, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, type RunMigrationJournalOptions, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, UnknownFilterTokenError, UnresolvedFilterTokenError, type UserAuthzGrants, type VersionCompatibility, assembleExecutionContext, assembleExecutionContextOrGuest, assertInitServiceRequirements, bucketKeyToCalendarRange, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, derivePosture, describeInitOrderFault, engineCanRollBack, evaluateAuthGate, extractApiKey, filterTokenContextFrom, findInterruptedRuns, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, hashMigrationPlan, isAuthGateAllowlisted, isExpired, isGrantActive, isGrantExpired, isNode, normalizeAuthGate, parseScopes, parseSignature, planChunks, postureVisibleRows, readAuthoredTranslationLayer, readRunJournal, resolveApiKeyPrincipal, resolveAuthzContext, resolveFilterToken, resolveFilterTokens, resolveLocale, resolveLocalizationContext, resolvePluginOrder, resolveUserAuthzGrants, resumeMigrationJournal, runMigrationJournal, safeExit, shouldDenyAnonymous, signPayload, validateInitServiceContract, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry, withoutOperationPrivateKeys, zonedDateStartToUtcMs };
|
|
4022
|
+
export { ADMIN_STANDING_SURFACE, ANONYMOUS_DENY_BODY, ANONYMOUS_DENY_CODE, ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, API_KEY_PREFIX, AUDIENCE_BINDING_SUGGESTION_STATUSES, AUDIENCE_BINDING_SUGGESTION_STATUS_VALUES, type ActivatableRow, type AdminStandingTable, type AnonymousDenyInput, type ApiKeyAdmission, type ApiKeyPrincipal, type ApiKeyRefusalReason, type AudienceBindingSuggestionStatus, type AuthGate, type BucketGranularity, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, ENTRY_EXECUTION_CONTEXT_FIELDS, type EngineWithTransaction, type EntryExecutionContextField, type EntryLocalization, type ExecutionContextAssemblyInput, type ExecutionContextEntryFields, type ExecutionContextLike, type FilterTokenResolutionContext, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, type InterruptedRun, type KernelState, type KeyInput, type LadderPrincipal, type LadderRow, LiteKernel, type MigrationChunk, type MigrationChunkContext, MigrationJournalRefusal, type MigrationPlan, type MigrationPlanProvider, MigrationPlanRegistry, type MigrationPlanStep, type MigrationRunResult, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, type OAuthTokenProvenance, OPERATION_PRIVATE_KEY_PREFIX, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type OrderablePlugin, POSTURE_INJECTION_RULE, POSTURE_LADDER, POSTURE_RANK, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PostureEvidence, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolveUserAuthzGrantsOptions, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, type RunMigrationJournalOptions, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type TemporalComparandKind, type TenancyPostureSource, UnknownFilterTokenError, UnresolvedFilterTokenError, type UserAuthzGrants, type VersionCompatibility, type WallClockParts, adminStandingColumns, adminStandingTables, assembleExecutionContext, assembleExecutionContextOrGuest, assertInitServiceRequirements, assertMetadataRegisterContract, bucketKeyToCalendarRange, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, canonicalMetadataServiceType, collectInternalWriteResponseFields, counterSignPayload, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, derivePosture, describeInitOrderFault, effectiveTenancyPosture, engineCanRollBack, evaluateAuthGate, extractApiKey, filterTokenContextFrom, findInterruptedRuns, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, hashMigrationPlan, isAudienceBindingSuggestionStatus, isAuthGateAllowlisted, isExpired, isGrantActive, isGrantExpired, isNode, isRowActive, isUninterpretableTemporalComparand, normalizeAuthGate, omitInternalFieldsFromWriteResponse, parseScopes, parseSignature, planChunks, postureVisibleRows, readAuthoredTranslationLayer, readRunJournal, recordNotFoundError, resolveApiKeyAdmission, resolveApiKeyPrincipal, resolveAuthzContext, resolveFilterToken, resolveFilterTokens, resolveLocale, resolveLocalizationContext, resolvePluginOrder, resolveUserAuthzGrants, resumeMigrationJournal, runMigrationJournal, safeExit, shouldDenyAnonymous, signPayload, temporalComparandKind, unknownAudienceBindingSuggestionStatusMessage, validateInitServiceContract, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry, withoutOperationPrivateKeys, zonedDateStartToUtcMs, zonedWallClockToUtcMs };
|