@objectstack/core 17.0.0-rc.5 → 17.0.0-rc.6
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 +470 -0
- package/dist/index.cjs +217 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +407 -42
- package/dist/index.d.ts +407 -42
- package/dist/index.js +211 -52
- package/dist/index.js.map +1 -1
- package/dist/logger.cjs +9 -1
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.js +9 -1
- package/dist/logger.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -6,7 +6,7 @@ import { ObjectLogger } from './logger.cjs';
|
|
|
6
6
|
export { createLogger } from './logger.cjs';
|
|
7
7
|
import * as QA from '@objectstack/spec/qa';
|
|
8
8
|
import { KeyObject } from 'node:crypto';
|
|
9
|
-
import { PluginCapability, PluginPermissions as PluginPermissions$1, PluginPermissionSet, ResourceType, PermissionAction, PluginPermission, SandboxConfig, KernelSecurityScanResult, KernelSecurityVulnerability,
|
|
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
10
|
import { AuthzPosture } from '@objectstack/spec/security';
|
|
11
11
|
export { nextUtcCalendarDay, utcInstantMs } from '@objectstack/spec/data';
|
|
12
12
|
|
|
@@ -353,13 +353,18 @@ declare class ObjectKernel {
|
|
|
353
353
|
* one bad handler must not amplify into leaked resources and unflushed
|
|
354
354
|
* writes. Same reasoning, same wording, same `Hook handler failed:
|
|
355
355
|
* kernel:shutdown` log line as `LiteKernel`'s dispatch site, which reaches
|
|
356
|
-
* the
|
|
356
|
+
* the isolating dispatcher through `ObjectKernelBase.triggerHook` (#5257).
|
|
357
357
|
*
|
|
358
|
-
*
|
|
358
|
+
* Until #5282 "same wording" was literally that — the loop was typed out a
|
|
359
|
+
* second time here, because `ObjectKernel` does not extend
|
|
359
360
|
* `ObjectKernelBase` (only `LiteKernel` does) and owns its own `hooks` map,
|
|
360
|
-
* so the
|
|
361
|
-
*
|
|
362
|
-
*
|
|
361
|
+
* so the base's `protected triggerHook` is out of reach. The loop now lives
|
|
362
|
+
* in {@link dispatchHookIsolating}, which BOTH sides call: the storage is
|
|
363
|
+
* still two maps (deliberately — unifying it was out of #5282's scope), but
|
|
364
|
+
* "isolating" is one implementation, so it can no longer drift on one
|
|
365
|
+
* kernel while the other keeps the old shape. That drift is exactly the bug
|
|
366
|
+
* #5170 / #5257 / #5274 each closed one hook at a time, and the paired-pin
|
|
367
|
+
* gate (`scripts/check-kernel-hook-pairs.mjs`) covers the residue.
|
|
363
368
|
*/
|
|
364
369
|
private triggerShutdownHookIsolating;
|
|
365
370
|
private performShutdown;
|
|
@@ -622,6 +627,11 @@ declare abstract class ObjectKernelBase {
|
|
|
622
627
|
* (`kernel:ready`, `kernel:bootstrapped`, `kernel:listening`) use
|
|
623
628
|
* {@link triggerHookOrThrow} (#5170, #5257).
|
|
624
629
|
*
|
|
630
|
+
* The loop itself lives in {@link dispatchHookIsolating} — one
|
|
631
|
+
* implementation shared with `ObjectKernel`'s own `kernel:shutdown`
|
|
632
|
+
* dispatch, which cannot inherit this method (`ObjectKernel` does not
|
|
633
|
+
* extend this class) and used to hand-mirror it (#5282).
|
|
634
|
+
*
|
|
625
635
|
* @param name - Hook name
|
|
626
636
|
* @param args - Arguments to pass to handlers
|
|
627
637
|
*/
|
|
@@ -661,6 +671,10 @@ declare abstract class ObjectKernelBase {
|
|
|
661
671
|
* default — and it is the reason this dispatcher is chosen per hook rather
|
|
662
672
|
* than swapped in wholesale.
|
|
663
673
|
*
|
|
674
|
+
* The loop itself lives in {@link dispatchHookPropagating} — the same
|
|
675
|
+
* function `PluginContext.trigger` runs on both kernels, so "propagating"
|
|
676
|
+
* means one thing repo-wide (#5282).
|
|
677
|
+
*
|
|
664
678
|
* @param name - Hook name
|
|
665
679
|
* @param args - Arguments to pass to handlers
|
|
666
680
|
*/
|
|
@@ -1831,6 +1845,275 @@ declare function resolveLocalizationContext(input: ResolveLocalizationInput): Pr
|
|
|
1831
1845
|
currency?: string;
|
|
1832
1846
|
}>;
|
|
1833
1847
|
|
|
1848
|
+
/**
|
|
1849
|
+
* ADR-0069 — authentication-policy session gate.
|
|
1850
|
+
*
|
|
1851
|
+
* Some auth policies (password expiry, enforced MFA) must block an
|
|
1852
|
+
* authenticated user from PROTECTED RESOURCES until they remediate, while
|
|
1853
|
+
* still letting them reach the auth endpoints (change-password, two-factor
|
|
1854
|
+
* enrollment, sign-out) and a few UI-bootstrap reads.
|
|
1855
|
+
*
|
|
1856
|
+
* The posture is computed ONCE, in the auth `customSession` enrichment, and
|
|
1857
|
+
* attached to the session user as `user.authGate = { code, message }`. The
|
|
1858
|
+
* transport seams (REST middleware, dispatcher) then call
|
|
1859
|
+
* {@link evaluateAuthGate} to decide whether THIS request is blocked. Keeping
|
|
1860
|
+
* the allow-list + decision in one pure function means the seams can never
|
|
1861
|
+
* drift on what is blocked.
|
|
1862
|
+
*/
|
|
1863
|
+
|
|
1864
|
+
/**
|
|
1865
|
+
* The gate posture, DERIVED from its declaration on `ExecutionContextSchema`
|
|
1866
|
+
* (`packages/spec/src/kernel/execution-context.zod.ts`) rather than restated
|
|
1867
|
+
* here (#7280).
|
|
1868
|
+
*
|
|
1869
|
+
* It was a hand-written interface while the envelope field was undeclared, so
|
|
1870
|
+
* the two could have drifted with nothing to catch it — the exact class of
|
|
1871
|
+
* defect the closed entry field set (#6216) exists to make unrepresentable.
|
|
1872
|
+
* One declaration, one type.
|
|
1873
|
+
*/
|
|
1874
|
+
type AuthGate = NonNullable<ExecutionContext['authGate']>;
|
|
1875
|
+
/**
|
|
1876
|
+
* Normalize the `authGate` a better-auth session user carries into the shape
|
|
1877
|
+
* `ExecutionContextSchema` declares — or `null` when there is no gate.
|
|
1878
|
+
*
|
|
1879
|
+
* The session user crosses an external boundary as `any`, so this is where the
|
|
1880
|
+
* declared contract is actually met: a gate naming no string `code` is not a
|
|
1881
|
+
* gate, and a missing/blank `message` is filled with the default rather than
|
|
1882
|
+
* riding onto the envelope (and into a `403` body) as `undefined`. Both
|
|
1883
|
+
* consumers normalize HERE, at the one producer, instead of tolerating a loose
|
|
1884
|
+
* shape downstream: {@link evaluateAuthGate} for the seams that decide per
|
|
1885
|
+
* path, and REST's `computeExecCtx` for the seam that lifts the posture onto
|
|
1886
|
+
* the execution context.
|
|
1887
|
+
*/
|
|
1888
|
+
declare function normalizeAuthGate(sessionUser: any): AuthGate | null;
|
|
1889
|
+
/** True when `path` is exempt from the auth gate (auth + remediation + health). */
|
|
1890
|
+
declare function isAuthGateAllowlisted(rawPath: string | undefined | null): boolean;
|
|
1891
|
+
/**
|
|
1892
|
+
* Returns the active gate when `sessionUser` carries an `authGate` AND `path`
|
|
1893
|
+
* is not allow-listed; otherwise null. Anonymous users (no `authGate`) and
|
|
1894
|
+
* allow-listed paths always pass.
|
|
1895
|
+
*/
|
|
1896
|
+
declare function evaluateAuthGate(sessionUser: any, path: string): AuthGate | null;
|
|
1897
|
+
|
|
1898
|
+
/**
|
|
1899
|
+
* assembleExecutionContext — the SINGLE assembly of an inbound request's
|
|
1900
|
+
* {@link ExecutionContext}, shared by every transport entry point.
|
|
1901
|
+
*
|
|
1902
|
+
* `resolveAuthzContext` (next door) already made AUTHORIZATION resolution
|
|
1903
|
+
* single-sourced. The step AFTER it — turning the resolved
|
|
1904
|
+
* {@link ResolvedAuthzContext} into the `ExecutionContext` envelope that
|
|
1905
|
+
* reaches enforcement — stayed hand-written per transport, and that duplication
|
|
1906
|
+
* produced a measured defect family:
|
|
1907
|
+
*
|
|
1908
|
+
* - **#6071 — field drift.** The REST copy never set `principalKind`, so every
|
|
1909
|
+
* enforcement judgment reading it (explain's guest⇒EXTERNAL floor, the
|
|
1910
|
+
* security plugin's agent baseline, the perf-disclosure gate) was silently
|
|
1911
|
+
* never-true on that face.
|
|
1912
|
+
* - **#6206 / #6551 — dropped fields.** The share-link copies omitted
|
|
1913
|
+
* `accessible_org_ids`, and the `group` posture's Layer 0 wall reads it
|
|
1914
|
+
* directly: real 403s for callers who should have been let through. Both
|
|
1915
|
+
* surfaces were since converted to pass the WHOLE envelope through.
|
|
1916
|
+
*
|
|
1917
|
+
* Both defects are the same shape: a field exists on `ExecutionContext`, one
|
|
1918
|
+
* copy carries it, another silently does not. This module makes that shape
|
|
1919
|
+
* unrepresentable by CLOSING the field set with a type
|
|
1920
|
+
* ({@link ExecutionContextEntryFields}) — every field a transport entry point
|
|
1921
|
+
* decides must be decided HERE, explicitly, and a new `ExecutionContext` field
|
|
1922
|
+
* fails to compile until it is either assembled or listed as
|
|
1923
|
+
* non-entry-resolved.
|
|
1924
|
+
*
|
|
1925
|
+
* ## Two named entries — the anonymous face is genuinely divergent (#6216)
|
|
1926
|
+
*
|
|
1927
|
+
* The maintainer ruling of 2026-08-08 on #6216 (Option A) settled the one
|
|
1928
|
+
* question that blocked convergence: what an anonymous request yields.
|
|
1929
|
+
*
|
|
1930
|
+
* - {@link assembleExecutionContext} — the DEFAULT, fail-closed entry. No
|
|
1931
|
+
* resolved principal → `undefined`, and the surface answers 401. This is the
|
|
1932
|
+
* REST face's contract, unchanged.
|
|
1933
|
+
* - {@link assembleExecutionContextOrGuest} — the EXPLICIT guest entry. No
|
|
1934
|
+
* resolved principal → a first-class guest envelope
|
|
1935
|
+
* (`principalKind: 'guest'`, `positions: ['guest']`), which the runtime /
|
|
1936
|
+
* MCP dispatcher has always produced and whose consumers are live
|
|
1937
|
+
* (`plugin-security/explain-engine.ts`: guest ⇒ `EXTERNAL` posture). A
|
|
1938
|
+
* surface adopts this entry ONLY when its product semantics serve anonymous
|
|
1939
|
+
* principals.
|
|
1940
|
+
*
|
|
1941
|
+
* Neither surface's runtime behaviour changes. What changes is that the
|
|
1942
|
+
* divergence is now NAMED API rather than drift — and the same is true of the
|
|
1943
|
+
* per-face values below (`accessToken`, `oauth`, `localization`): they are
|
|
1944
|
+
* REQUIRED inputs, so a face cannot silently omit one, and what a face chooses
|
|
1945
|
+
* to withhold it withholds on the record.
|
|
1946
|
+
*
|
|
1947
|
+
* Options B (guest everywhere — turns REST's anonymous 401s into authz-shaped
|
|
1948
|
+
* denials and makes anonymous-serving the DEFAULT posture of a new surface) and
|
|
1949
|
+
* C (no-ctx everywhere — deletes the guest principal, the `guest` position and
|
|
1950
|
+
* explain's `EXTERNAL` floor) were both considered and rejected: each breaks a
|
|
1951
|
+
* live consumer side.
|
|
1952
|
+
*/
|
|
1953
|
+
|
|
1954
|
+
/**
|
|
1955
|
+
* `ExecutionContext` fields a transport ENTRY POINT does not resolve — they are
|
|
1956
|
+
* per-operation flags, engine internals, or attribution supplied further down
|
|
1957
|
+
* the stack. Listing one here is a deliberate, reviewable statement that no
|
|
1958
|
+
* request-identity resolution produces it; everything NOT listed is part of the
|
|
1959
|
+
* closed entry set below and must be assembled.
|
|
1960
|
+
*
|
|
1961
|
+
* - `actor` / `attributedUserId` — audit attribution, set by the host or the
|
|
1962
|
+
* hook layer (ADR-0014 D2, #4586), never by identity resolution.
|
|
1963
|
+
* - `rlsMembership` — engine-side RLS scoping cache.
|
|
1964
|
+
* - `transaction` / `traceId` — per-operation handles.
|
|
1965
|
+
* - `flowRunId`, `skipTriggers`, `skipAutomations`, `seedReplay`,
|
|
1966
|
+
* `skipStateMachine`, `preserveAudit` — per-write behaviour flags,
|
|
1967
|
+
* server-constructed at the call site.
|
|
1968
|
+
*/
|
|
1969
|
+
type NonEntryExecutionContextField = 'actor' | 'attributedUserId' | 'rlsMembership' | 'transaction' | 'traceId' | 'flowRunId' | 'skipTriggers' | 'skipAutomations' | 'seedReplay' | 'skipStateMachine' | 'preserveAudit';
|
|
1970
|
+
/**
|
|
1971
|
+
* The CLOSED field set every transport entry point must decide. Derived from
|
|
1972
|
+
* `ExecutionContext` itself, so adding a field to `ExecutionContextSchema`
|
|
1973
|
+
* widens this union automatically — and the assembly below stops compiling
|
|
1974
|
+
* until the new field is either assembled or declared non-entry-resolved.
|
|
1975
|
+
*/
|
|
1976
|
+
type EntryExecutionContextField = Exclude<keyof ExecutionContext, NonEntryExecutionContextField>;
|
|
1977
|
+
/**
|
|
1978
|
+
* One value per closed field. Every key is REQUIRED (`-?`) while the VALUE may
|
|
1979
|
+
* be `undefined` — the decision may not be omitted, only made explicitly. This
|
|
1980
|
+
* is the type that makes the #6071 drift class unrepresentable.
|
|
1981
|
+
*/
|
|
1982
|
+
type ExecutionContextEntryFields = {
|
|
1983
|
+
[K in EntryExecutionContextField]-?: ExecutionContext[K];
|
|
1984
|
+
};
|
|
1985
|
+
/**
|
|
1986
|
+
* Emission order of the assembled envelope, and a second, independent
|
|
1987
|
+
* exhaustiveness bite: `satisfies` rejects a stale name, and
|
|
1988
|
+
* `_ENTRY_FIELDS_EXHAUSTIVE` below rejects a missing one.
|
|
1989
|
+
*/
|
|
1990
|
+
declare const ENTRY_EXECUTION_CONTEXT_FIELDS: readonly ["positions", "permissions", "systemPermissions", "isSystem", "principalKind", "onBehalfOf", "audience", "userId", "tenantId", "email", "accessToken", "tabPermissions", "posture", "authGate", "org_user_ids", "accessible_org_ids", "oauthScopes", "timezone", "locale", "currency"];
|
|
1991
|
+
/**
|
|
1992
|
+
* OAuth 2.1 access-token provenance. Reaches the assembler from the `/mcp`
|
|
1993
|
+
* dispatch door ALONE (`acceptOAuthAccessToken`) — OAuth bearers carry coarse
|
|
1994
|
+
* tool-family scopes enforced at MCP tool dispatch, so honouring them on
|
|
1995
|
+
* another surface would bypass that scope model entirely.
|
|
1996
|
+
*/
|
|
1997
|
+
interface OAuthTokenProvenance {
|
|
1998
|
+
/** The human `sub` the token was issued for. */
|
|
1999
|
+
userId: string;
|
|
2000
|
+
/** Granted scopes, surfaced on the envelope so MCP can narrow tool families. */
|
|
2001
|
+
scopes: string[];
|
|
2002
|
+
/**
|
|
2003
|
+
* The authorized client (`azp`). Present ⇒ this is an AI AGENT acting on
|
|
2004
|
+
* behalf of the human `userId`; absent ⇒ the token names no client and the
|
|
2005
|
+
* principal stays human (the scopes are still surfaced).
|
|
2006
|
+
*/
|
|
2007
|
+
clientId?: string;
|
|
2008
|
+
/**
|
|
2009
|
+
* The agent's OWN permission CEILING, derived from {@link scopes} by the door
|
|
2010
|
+
* that speaks the OAuth scope vocabulary
|
|
2011
|
+
* (`scopesToAgentPermissionSets`, `@objectstack/spec/ai`).
|
|
2012
|
+
*
|
|
2013
|
+
* Interpreted THERE and not here on purpose: the scope vocabulary is
|
|
2014
|
+
* MCP-domain knowledge and `@objectstack/core` is the microkernel — it should
|
|
2015
|
+
* not acquire a dependency on the AI subdomain (concretely, every package
|
|
2016
|
+
* whose test config aliases `@objectstack/core` to its source would then have
|
|
2017
|
+
* to resolve `@objectstack/spec/ai` too, down to `driver-memory`). What the
|
|
2018
|
+
* ceiling REPLACES on the envelope is decided below, once, for every face —
|
|
2019
|
+
* and that is the part that drifted.
|
|
2020
|
+
*/
|
|
2021
|
+
scopePermissions: string[];
|
|
2022
|
+
/**
|
|
2023
|
+
* Whether the token carries the user's consent to let this agent invoke
|
|
2024
|
+
* actions on their behalf — the `actions:execute` scope
|
|
2025
|
+
* (`MCP_OAUTH_SCOPE_ACTIONS`), evaluated at the same door for the same
|
|
2026
|
+
* reason.
|
|
2027
|
+
*/
|
|
2028
|
+
delegatesActions: boolean;
|
|
2029
|
+
}
|
|
2030
|
+
/** Reference localization for an authenticated principal (@see resolveLocalizationContext). */
|
|
2031
|
+
interface EntryLocalization {
|
|
2032
|
+
timezone?: string;
|
|
2033
|
+
locale?: string;
|
|
2034
|
+
currency?: string;
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Everything the shared assembly needs. Every key is REQUIRED so a face cannot
|
|
2038
|
+
* silently omit one — a face that has no value for an input passes `undefined`
|
|
2039
|
+
* on the record, which is what turns the remaining divergences into named API.
|
|
2040
|
+
*/
|
|
2041
|
+
interface ExecutionContextAssemblyInput {
|
|
2042
|
+
/** The shared authorization envelope (@see resolveAuthzContext). */
|
|
2043
|
+
authz: ResolvedAuthzContext;
|
|
2044
|
+
/**
|
|
2045
|
+
* OAuth access-token provenance, or `undefined` on a face that does not
|
|
2046
|
+
* accept one. Only the `/mcp` dispatch door passes a value; REST passes
|
|
2047
|
+
* `undefined` — which is why `principalKind: 'agent'`, `onBehalfOf` and
|
|
2048
|
+
* `oauthScopes` are not representable there.
|
|
2049
|
+
*/
|
|
2050
|
+
oauth: OAuthTokenProvenance | undefined;
|
|
2051
|
+
/**
|
|
2052
|
+
* Resolved reference localization, or `undefined` when the face resolved
|
|
2053
|
+
* none (anonymous requests have no scope to resolve against).
|
|
2054
|
+
*/
|
|
2055
|
+
localization: EntryLocalization | undefined;
|
|
2056
|
+
/**
|
|
2057
|
+
* The request's OWN locale preference (`Accept-Language`, `?locale`, …),
|
|
2058
|
+
* which wins over the workspace default; `undefined` when the caller
|
|
2059
|
+
* expresses none. Each face extracts it its own way — the PRECEDENCE lives
|
|
2060
|
+
* here so the two cannot disagree about it (#3957).
|
|
2061
|
+
*/
|
|
2062
|
+
requestLocale: string | undefined;
|
|
2063
|
+
/**
|
|
2064
|
+
* The session bearer to carry on the envelope, surfaced to hooks as
|
|
2065
|
+
* `session.accessToken` (`objectql/engine.ts` `buildSession`,
|
|
2066
|
+
* `spec/data/hook.zod.ts`).
|
|
2067
|
+
*
|
|
2068
|
+
* A NAMED per-face divergence, preserved deliberately (#6216): the runtime /
|
|
2069
|
+
* MCP dispatcher passes `authz.accessToken`; the REST face has never carried
|
|
2070
|
+
* it and passes `undefined`, because widening a published hook surface to
|
|
2071
|
+
* expose the session token on a second transport is a product decision, not a
|
|
2072
|
+
* refactor. Being a required input, the choice is on the record at each face
|
|
2073
|
+
* instead of being an omission nobody can see.
|
|
2074
|
+
*/
|
|
2075
|
+
accessToken: string | undefined;
|
|
2076
|
+
/**
|
|
2077
|
+
* [ADR-0069] The AUTHENTICATION-policy gate posture resolved for this
|
|
2078
|
+
* request's session (expired password / enforced MFA), or `undefined` when
|
|
2079
|
+
* the face resolves none — normalize a session user through
|
|
2080
|
+
* `normalizeAuthGate` rather than copying its `authGate` verbatim.
|
|
2081
|
+
*
|
|
2082
|
+
* A NAMED per-face divergence, on the same footing as {@link accessToken}
|
|
2083
|
+
* (#7280):
|
|
2084
|
+
*
|
|
2085
|
+
* - the **REST** face lifts it onto the envelope, because that is where its
|
|
2086
|
+
* consumer reads it (`RestServer.enforceAuth` → `403 { code, message }`);
|
|
2087
|
+
* - the **runtime / MCP dispatcher** passes `undefined`, because it enforces
|
|
2088
|
+
* the same ADR-0069 gate at its OWN seam (`HttpDispatcher.enforceAuthGate`
|
|
2089
|
+
* re-reads the session and calls `evaluateAuthGate` there) and never reads
|
|
2090
|
+
* `context.authGate` — carrying it would be a second, unread copy.
|
|
2091
|
+
*
|
|
2092
|
+
* Until #7280 declared it, this posture reached the envelope through an
|
|
2093
|
+
* `as any` spread AFTER assembly, which put it outside this closed set
|
|
2094
|
+
* entirely — the blind spot the set exists to remove.
|
|
2095
|
+
*/
|
|
2096
|
+
authGate: AuthGate | undefined;
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* The DEFAULT, fail-closed entry (#6216 Option A). An unauthenticated request
|
|
2100
|
+
* yields NO context — the surface answers 401. Every surface uses this one
|
|
2101
|
+
* unless serving anonymous principals is part of its product semantics.
|
|
2102
|
+
*/
|
|
2103
|
+
declare function assembleExecutionContext(input: ExecutionContextAssemblyInput): ExecutionContext | undefined;
|
|
2104
|
+
/**
|
|
2105
|
+
* The EXPLICIT guest entry (#6216 Option A). An unauthenticated request becomes
|
|
2106
|
+
* a first-class guest principal — `principalKind: 'guest'`, `positions:
|
|
2107
|
+
* ['guest']` — which enforcement consumers read today
|
|
2108
|
+
* (`plugin-security/explain-engine.ts`: guest ⇒ `EXTERNAL` posture).
|
|
2109
|
+
*
|
|
2110
|
+
* Adopt this ONLY on a surface that genuinely serves anonymous principals: the
|
|
2111
|
+
* built-in `guest` position is the declared vocabulary for "what anonymous may
|
|
2112
|
+
* do", and handing a guest envelope to a surface that previously answered 401
|
|
2113
|
+
* converts an authentication failure into an authorization evaluation.
|
|
2114
|
+
*/
|
|
2115
|
+
declare function assembleExecutionContextOrGuest(input: ExecutionContextAssemblyInput): ExecutionContext;
|
|
2116
|
+
|
|
1834
2117
|
/**
|
|
1835
2118
|
* ── The monotonic posture ladder (ADR-0095 D2/D3) ───────────────────────────
|
|
1836
2119
|
*
|
|
@@ -1936,36 +2219,6 @@ interface LadderPrincipal {
|
|
|
1936
2219
|
*/
|
|
1937
2220
|
declare function postureVisibleRows(posture: AuthzPosture, rows: readonly LadderRow[], principal: LadderPrincipal): LadderRow[];
|
|
1938
2221
|
|
|
1939
|
-
/**
|
|
1940
|
-
* ADR-0069 — authentication-policy session gate.
|
|
1941
|
-
*
|
|
1942
|
-
* Some auth policies (password expiry, enforced MFA) must block an
|
|
1943
|
-
* authenticated user from PROTECTED RESOURCES until they remediate, while
|
|
1944
|
-
* still letting them reach the auth endpoints (change-password, two-factor
|
|
1945
|
-
* enrollment, sign-out) and a few UI-bootstrap reads.
|
|
1946
|
-
*
|
|
1947
|
-
* The posture is computed ONCE, in the auth `customSession` enrichment, and
|
|
1948
|
-
* attached to the session user as `user.authGate = { code, message }`. The
|
|
1949
|
-
* transport seams (REST middleware, dispatcher) then call
|
|
1950
|
-
* {@link evaluateAuthGate} to decide whether THIS request is blocked. Keeping
|
|
1951
|
-
* the allow-list + decision in one pure function means the seams can never
|
|
1952
|
-
* drift on what is blocked.
|
|
1953
|
-
*/
|
|
1954
|
-
interface AuthGate {
|
|
1955
|
-
/** Stable machine code, e.g. `PASSWORD_EXPIRED` / `MFA_REQUIRED`. */
|
|
1956
|
-
code: string;
|
|
1957
|
-
/** Human-facing message. */
|
|
1958
|
-
message: string;
|
|
1959
|
-
}
|
|
1960
|
-
/** True when `path` is exempt from the auth gate (auth + remediation + health). */
|
|
1961
|
-
declare function isAuthGateAllowlisted(rawPath: string | undefined | null): boolean;
|
|
1962
|
-
/**
|
|
1963
|
-
* Returns the active gate when `sessionUser` carries an `authGate` AND `path`
|
|
1964
|
-
* is not allow-listed; otherwise null. Anonymous users (no `authGate`) and
|
|
1965
|
-
* allow-listed paths always pass.
|
|
1966
|
-
*/
|
|
1967
|
-
declare function evaluateAuthGate(sessionUser: any, path: string): AuthGate | null;
|
|
1968
|
-
|
|
1969
2222
|
/** HTTP status every seam returns for an anonymous-denied request. */
|
|
1970
2223
|
declare const ANONYMOUS_DENY_STATUS: 401;
|
|
1971
2224
|
/** Stable machine code (mirrors the REST `enforceAuth` seam). ADR-0112: SCREAMING, a `StandardErrorCode` member. */
|
|
@@ -2059,6 +2312,117 @@ declare function isGrantActive(row: GrantValidityWindow | null | undefined, nowM
|
|
|
2059
2312
|
*/
|
|
2060
2313
|
declare function isGrantExpired(row: GrantValidityWindow | null | undefined, nowMs: number): boolean;
|
|
2061
2314
|
|
|
2315
|
+
/**
|
|
2316
|
+
* [#7284] The `__` operation-private-key convention — one owner, on the
|
|
2317
|
+
* CONSUMER side.
|
|
2318
|
+
*
|
|
2319
|
+
* `assemble-execution-context.ts` next door is the single place an
|
|
2320
|
+
* `ExecutionContext` is BUILT at a transport entry point (#6216). This file is
|
|
2321
|
+
* its counterpart at the other end: the single place one is stripped back down
|
|
2322
|
+
* before being forwarded to a question it was not resolved for.
|
|
2323
|
+
*
|
|
2324
|
+
* ## What a `__` key is
|
|
2325
|
+
*
|
|
2326
|
+
* plugin-security's middleware STAMPS keys onto the operation context, resolved
|
|
2327
|
+
* for the object of the operation IN FLIGHT. They are middleware-private
|
|
2328
|
+
* vocabulary, not fields of `ExecutionContext`, and every one of them is read as
|
|
2329
|
+
* a WIDENING input by whoever consumes it:
|
|
2330
|
+
*
|
|
2331
|
+
* - the ADR-0057 D1 access DEPTH the sharing owner-match expands to —
|
|
2332
|
+
* `__readScope` / `__writeScope`, plus the ADR-0090 D10 delegator halves
|
|
2333
|
+
* `__delegatorReadScope` / `__delegatorWriteScope`, stamped in place by
|
|
2334
|
+
* `security-plugin.ts` (`sc.__readScope = …`);
|
|
2335
|
+
* - the engine's internal privilege markers on the same channel —
|
|
2336
|
+
* `__expandRead` waives the object-level CRUD check for a lookup expansion,
|
|
2337
|
+
* `__referentialFieldClear` the referential-clear write.
|
|
2338
|
+
*
|
|
2339
|
+
* plugin-security is the PRODUCER of that vocabulary and would be the most
|
|
2340
|
+
* honest owner of the rule for consuming it, but none of the three consumers
|
|
2341
|
+
* depends on it and a string-prefix filter does not justify three new dependency
|
|
2342
|
+
* edges onto a plugin (the trade the filing card priced, #7284). `@objectstack/
|
|
2343
|
+
* spec` is fenced off by Prime Directive #2. `@objectstack/core` is the only
|
|
2344
|
+
* candidate every consumer already depends on, so the rule lives here and the
|
|
2345
|
+
* producer stays free of reverse edges.
|
|
2346
|
+
*
|
|
2347
|
+
* ## Why a consumer must drop them
|
|
2348
|
+
*
|
|
2349
|
+
* A caller's envelope carries a depth resolved for the object the middleware
|
|
2350
|
+
* last saw. A consumer that forwards that envelope to ask about a DIFFERENT
|
|
2351
|
+
* object applies one object's widening to another object's question — the exact
|
|
2352
|
+
* stale-scope leak `resolveWriteScopeForSharing` was extracted to prevent ("a
|
|
2353
|
+
* stale value can never leak in through a spread", `security-plugin.ts`).
|
|
2354
|
+
*
|
|
2355
|
+
* The leak is not hypothetical and does not require the consumer to be careless:
|
|
2356
|
+
* plugin-security only OVERWRITES `__readScope` when it actually resolves
|
|
2357
|
+
* permission sets for the new object (`if (permissionSets.length > 0)`), so a
|
|
2358
|
+
* stale depth SURVIVES into a question it was never resolved for whenever that
|
|
2359
|
+
* branch does not fire. A REST request that touched another object before
|
|
2360
|
+
* reaching, say, `/reports/:id/run` hands over an envelope the middleware has
|
|
2361
|
+
* already written into.
|
|
2362
|
+
*
|
|
2363
|
+
* Dropping is safe in the one direction that matters: the middleware re-stamps
|
|
2364
|
+
* the depth for THIS object when it resolves any set, so the only thing dropping
|
|
2365
|
+
* can do is leave the sharing owner-match at its narrowest (`own`) — the safe
|
|
2366
|
+
* direction.
|
|
2367
|
+
*
|
|
2368
|
+
* ## Why by PREFIX and never by a name list
|
|
2369
|
+
*
|
|
2370
|
+
* The `__` convention is what marks a key as belonging to the operation in
|
|
2371
|
+
* flight. A hand-maintained list of the six names above would go stale the day
|
|
2372
|
+
* the middleware stamps a seventh, and it would go stale SILENTLY — a forwarded
|
|
2373
|
+
* key nobody remembered to add reads exactly like a key that was meant to be
|
|
2374
|
+
* forwarded. The prefix is the contract; the names are its current membership.
|
|
2375
|
+
*
|
|
2376
|
+
* ⛔ The corollary, for whoever changes the middleware: a key that is
|
|
2377
|
+
* operation-private MUST carry the `__` prefix. Stamping one without it makes it
|
|
2378
|
+
* invisible to every consumer at once, and there is no compiler error.
|
|
2379
|
+
*
|
|
2380
|
+
* ## Known consumers
|
|
2381
|
+
*
|
|
2382
|
+
* `plugin-audit` (`comment-access-hooks.ts`, #7141), `service-storage`
|
|
2383
|
+
* (`attachment-access-hooks.ts`, #7145) and `plugin-reports`
|
|
2384
|
+
* (`report-service.ts`, #7204) — each forwarding a caller envelope to a gate or
|
|
2385
|
+
* a read that asks about a parent/target object rather than about the object the
|
|
2386
|
+
* middleware resolved for. Each of the three grew its own byte-equivalent copy
|
|
2387
|
+
* of this file by hand before #7284 gave the rule a home; `operation-private-
|
|
2388
|
+
* keys.pin.test.ts` is what now catches a fourth.
|
|
2389
|
+
*/
|
|
2390
|
+
|
|
2391
|
+
/**
|
|
2392
|
+
* The prefix marking a key as private to the operation plugin-security has in
|
|
2393
|
+
* flight. See this module's header for why the convention is a prefix and not a
|
|
2394
|
+
* list of names.
|
|
2395
|
+
*/
|
|
2396
|
+
declare const OPERATION_PRIVATE_KEY_PREFIX = "__";
|
|
2397
|
+
/**
|
|
2398
|
+
* The caller's execution envelope, minus the operation-private keys.
|
|
2399
|
+
*
|
|
2400
|
+
* A FRESH object every time, and that is load-bearing in BOTH directions:
|
|
2401
|
+
*
|
|
2402
|
+
* - outbound — a callee that stamps its own `__writeScope` onto what it
|
|
2403
|
+
* receives (which is exactly what plugin-security does before it calls the
|
|
2404
|
+
* sharing service) can never write back into the operation context the caller
|
|
2405
|
+
* was handed;
|
|
2406
|
+
* - inbound — the engine's middleware stamps a depth for the object it is about
|
|
2407
|
+
* to read onto whatever it is handed, so forwarding a caller's envelope BY
|
|
2408
|
+
* REFERENCE would write that depth back into the request context the route
|
|
2409
|
+
* goes on using.
|
|
2410
|
+
*
|
|
2411
|
+
* ⛔ Never `return exec;` on the "nothing to strip" path. The copy is the point,
|
|
2412
|
+
* not an optimisation to skip when the envelope happens to be clean — the two
|
|
2413
|
+
* hazards above are about the callee's future writes, not about the current
|
|
2414
|
+
* contents.
|
|
2415
|
+
*
|
|
2416
|
+
* Note what this deliberately does NOT do: it strips, it never SYNTHESISES a
|
|
2417
|
+
* depth for the new object. Absent depth leaves the sharing owner-match at its
|
|
2418
|
+
* narrowest (`own`), which is the safe direction and byte-for-byte what the
|
|
2419
|
+
* five-field projections these call sites replaced produced.
|
|
2420
|
+
*
|
|
2421
|
+
* @param exec the caller's envelope, as a bare record
|
|
2422
|
+
* @returns a new envelope carrying only the non-operation-private keys
|
|
2423
|
+
*/
|
|
2424
|
+
declare function withoutOperationPrivateKeys(exec: Record<string, unknown>): ExecutionContext;
|
|
2425
|
+
|
|
2062
2426
|
/**
|
|
2063
2427
|
* Environment utilities for universal (Node/Browser) compatibility.
|
|
2064
2428
|
*/
|
|
@@ -2564,8 +2928,9 @@ declare function resolveFilterTokens<T>(filter: T, ctx?: FilterTokenResolutionCo
|
|
|
2564
2928
|
* `current_user.organization_id`).
|
|
2565
2929
|
*
|
|
2566
2930
|
* Typed structurally, not as `ExecutionContext`, so both the parsed context
|
|
2567
|
-
* (defaults applied) and the pre-parse
|
|
2568
|
-
* mid-pipeline satisfy it. The three fields
|
|
2931
|
+
* (`ExecutionContextParsed`, defaults applied) and the pre-parse
|
|
2932
|
+
* `ExecutionContext` a caller holds mid-pipeline satisfy it. The three fields
|
|
2933
|
+
* read here are optional in both.
|
|
2569
2934
|
*/
|
|
2570
2935
|
declare function filterTokenContextFrom(execCtx: ExecutionContextLike | undefined, now?: Date): FilterTokenResolutionContext;
|
|
2571
2936
|
|
|
@@ -2787,7 +3152,7 @@ declare class PluginHealthMonitor {
|
|
|
2787
3152
|
/**
|
|
2788
3153
|
* Register a plugin for health monitoring
|
|
2789
3154
|
*/
|
|
2790
|
-
registerPlugin(pluginName: string, config:
|
|
3155
|
+
registerPlugin(pluginName: string, config: PluginHealthCheckParsed): void;
|
|
2791
3156
|
/**
|
|
2792
3157
|
* Start monitoring a plugin
|
|
2793
3158
|
*/
|
|
@@ -2862,7 +3227,7 @@ declare class PluginStateManager {
|
|
|
2862
3227
|
/**
|
|
2863
3228
|
* Save plugin state before reload
|
|
2864
3229
|
*/
|
|
2865
|
-
saveState(pluginId: string, version: string, state: Record<string, any>, config:
|
|
3230
|
+
saveState(pluginId: string, version: string, state: Record<string, any>, config: HotReloadConfigParsed): Promise<string>;
|
|
2866
3231
|
/**
|
|
2867
3232
|
* Restore plugin state after reload
|
|
2868
3233
|
*/
|
|
@@ -2895,7 +3260,7 @@ declare class HotReloadManager {
|
|
|
2895
3260
|
/**
|
|
2896
3261
|
* Register a plugin for hot reload
|
|
2897
3262
|
*/
|
|
2898
|
-
registerPlugin(pluginName: string, config:
|
|
3263
|
+
registerPlugin(pluginName: string, config: HotReloadConfigParsed): void;
|
|
2899
3264
|
/**
|
|
2900
3265
|
* Start watching for changes (requires file system integration)
|
|
2901
3266
|
*/
|
|
@@ -3085,4 +3450,4 @@ declare class NamespaceResolver {
|
|
|
3085
3450
|
private suggestAlternative;
|
|
3086
3451
|
}
|
|
3087
3452
|
|
|
3088
|
-
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, type EngineWithTransaction, 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, 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, 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, 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, zonedDateStartToUtcMs };
|
|
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 };
|