@jini-ai/core 0.4.1 → 0.4.3
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 +8 -0
- package/README.md +19 -1
- package/dist/agent-tools.d.ts +1 -0
- package/dist/approval.d.ts +134 -0
- package/dist/approval.js +141 -0
- package/dist/fetch/cache.d.ts +129 -0
- package/dist/fetch/cache.js +243 -0
- package/dist/fetch/index.d.ts +5 -0
- package/dist/fetch/index.js +3 -0
- package/dist/fetch/types.d.ts +93 -0
- package/dist/fetch/types.js +2 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/model-facing-tool-errors.d.ts +152 -0
- package/dist/model-facing-tool-errors.js +124 -0
- package/dist/native-regexp.d.ts +10 -0
- package/dist/native-regexp.js +2 -0
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/sse.d.ts +60 -0
- package/dist/primitives/sse.js +122 -0
- package/dist/redact.js +4 -3
- package/dist/registration-kit.d.ts +13 -0
- package/dist/registration-kit.js +12 -0
- package/dist/settings/agent-tools.d.ts +10 -0
- package/dist/settings/agent-tools.js +204 -0
- package/dist/settings/agent-value-write-tools.d.ts +22 -0
- package/dist/settings/agent-value-write-tools.js +157 -0
- package/dist/settings/agent-writable-preferences.d.ts +67 -0
- package/dist/settings/agent-writable-preferences.js +132 -0
- package/dist/settings/agent-write-denylist.d.ts +19 -0
- package/dist/settings/agent-write-denylist.js +15 -0
- package/dist/settings/change-feed.d.ts +83 -0
- package/dist/settings/change-feed.js +61 -0
- package/dist/settings/definitions-dispatch.d.ts +56 -0
- package/dist/settings/definitions-dispatch.js +86 -0
- package/dist/settings/dictionaries/index.d.ts +48 -0
- package/dist/settings/dictionaries/index.js +69 -0
- package/dist/settings/dictionaries/settings-dialog.ar.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ar.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.bn.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.bn.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.de.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.de.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.es.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.es.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.fa.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.fa.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.fr.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.fr.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.hi.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.hi.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.hu.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.hu.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.id.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.id.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.it.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.it.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ja.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ja.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ko.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ko.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.pl.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.pl.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ru.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ru.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.th.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.th.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.tr.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.tr.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.uk.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.uk.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ur.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ur.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js +27 -0
- package/dist/settings/ensure-definitions.d.ts +101 -0
- package/dist/settings/ensure-definitions.js +101 -0
- package/dist/settings/errors.d.ts +42 -0
- package/dist/settings/errors.js +46 -0
- package/dist/settings/express/cms-adapter.d.ts +20 -0
- package/dist/settings/express/cms-adapter.js +124 -0
- package/dist/settings/express/contracts.d.ts +135 -0
- package/dist/settings/express/contracts.js +2 -0
- package/dist/settings/express/http.d.ts +10 -0
- package/dist/settings/express/http.js +211 -0
- package/dist/settings/express/index.d.ts +5 -0
- package/dist/settings/express/index.js +4 -0
- package/dist/settings/express/shared.d.ts +68 -0
- package/dist/settings/express/shared.js +54 -0
- package/dist/settings/express/stream.d.ts +24 -0
- package/dist/settings/express/stream.js +147 -0
- package/dist/settings/index.d.ts +40 -0
- package/dist/settings/index.js +37 -0
- package/dist/settings/ports.d.ts +118 -0
- package/dist/settings/ports.js +2 -0
- package/dist/settings/principal-lookup.d.ts +9 -0
- package/dist/settings/principal-lookup.js +2 -0
- package/dist/settings/purge-service.d.ts +54 -0
- package/dist/settings/purge-service.js +106 -0
- package/dist/settings/repo.memory.d.ts +119 -0
- package/dist/settings/repo.memory.js +199 -0
- package/dist/settings/settings.d.ts +139 -0
- package/dist/settings/settings.js +259 -0
- package/dist/settings/tool-registrations.d.ts +54 -0
- package/dist/settings/tool-registrations.js +250 -0
- package/dist/settings/types.d.ts +125 -0
- package/dist/settings/types.js +3 -0
- package/dist/settings/ui-tab-definitions.d.ts +41 -0
- package/dist/settings/ui-tab-definitions.js +148 -0
- package/dist/settings/write-service.d.ts +320 -0
- package/dist/settings/write-service.js +683 -0
- package/dist/surface-exchanges.d.ts +209 -0
- package/dist/surface-exchanges.js +55 -0
- package/dist/text/escape.d.ts +20 -0
- package/dist/text/escape.js +23 -0
- package/dist/text/index.d.ts +1 -0
- package/dist/text/index.js +1 -0
- package/dist/tool-metadata.d.ts +71 -0
- package/dist/tool-metadata.js +56 -0
- package/dist/tool-permissions.d.ts +76 -0
- package/dist/tool-permissions.js +57 -0
- package/dist/tool-registry.d.ts +19 -1
- package/dist/tool-registry.js +44 -0
- package/package.json +79 -4
- package/plugin-directory.d.ts +32 -0
- package/plugin-directory.js +140 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Clock, IdGenerator, JsonValue, UUID } from "../primitives/index.js";
|
|
2
|
+
import type { SettingsPrincipalLookupPort } from "./principal-lookup.js";
|
|
3
|
+
import type { SettingsRepoPort } from "./ports.js";
|
|
4
|
+
import { type SettingValueSchema } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* @file The one boot-time "register these definitions if they aren't already,
|
|
7
|
+
* and keep their defaults honest if they are" loop, shared by every registrar
|
|
8
|
+
* that backs a settings-dialog tab.
|
|
9
|
+
*
|
|
10
|
+
* This exists because the loop is genuinely identical everywhere it appears —
|
|
11
|
+
* the first host to use this library had one hand-written copy per tab before
|
|
12
|
+
* this helper existed, and each additional tab would have added another. A
|
|
13
|
+
* host's per-tab modules keep what actually differs (the namespace, the key
|
|
14
|
+
* list, each key's schema and default) and hand it to
|
|
15
|
+
* `ensureSettingDefinitions` as data.
|
|
16
|
+
*
|
|
17
|
+
* Site-owned groups also share registration, but keep their bespoke get/set
|
|
18
|
+
* services, validation and domain permissions. Unlike code-owned core defaults,
|
|
19
|
+
* an existing site definition is never reconciled: its stored default belongs
|
|
20
|
+
* to the site. The explicit site target preserves that ownership difference.
|
|
21
|
+
*/
|
|
22
|
+
/** One key's registration data — everything `registerDefinitions` needs that
|
|
23
|
+
* varies per key. */
|
|
24
|
+
export interface SettingDefinitionSpec<Key extends string = string> {
|
|
25
|
+
key: Key;
|
|
26
|
+
schema: SettingValueSchema;
|
|
27
|
+
/**
|
|
28
|
+
* Every non-secret definition needs a NON-NULL default.
|
|
29
|
+
* `validateDefinitionInput` (`settings.ts`) rejects a null `default_json` for
|
|
30
|
+
* every non-secret definition, full stop — there is no nullable-schema
|
|
31
|
+
* carve-out in the actual check, regardless of what a nullable schema might
|
|
32
|
+
* suggest. A field that is nullable in spirit needs a sentinel instead (a
|
|
33
|
+
* distinguished non-null value the host's own code treats as "unset"). See
|
|
34
|
+
* `docs/decisions/settings-json-schema-variant.md` for why this diverges
|
|
35
|
+
* from this ledger's own originating design notes.
|
|
36
|
+
*
|
|
37
|
+
* Typed `Exclude<…, null>` rather than a bare primitive union: `DefinitionInput`
|
|
38
|
+
* already accepts any `JsonValue`, and a `{ type: "json" }` definition needs an
|
|
39
|
+
* array/object default. Excluding `null` at the type level keeps the non-null
|
|
40
|
+
* rule above a compile error rather than a boot-time throw.
|
|
41
|
+
*/
|
|
42
|
+
defaultValue: Exclude<JsonValue, null>;
|
|
43
|
+
/**
|
|
44
|
+
* Bitmask over `SCOPE_BIT`. Defaults to `workspace`, which is what most
|
|
45
|
+
* settings-dialog tabs want: one operator's choice shouldn't silently
|
|
46
|
+
* become every workspace's. Pass `SCOPE_BIT.user` (or a mask including it)
|
|
47
|
+
* for a genuinely per-operator preference.
|
|
48
|
+
*/
|
|
49
|
+
scopes?: number;
|
|
50
|
+
}
|
|
51
|
+
export interface EnsureSettingDefinitionsDeps {
|
|
52
|
+
settingsRepo: SettingsRepoPort;
|
|
53
|
+
clock: Clock;
|
|
54
|
+
ids: IdGenerator;
|
|
55
|
+
principals: SettingsPrincipalLookupPort;
|
|
56
|
+
}
|
|
57
|
+
interface SettingDefinitionsInput {
|
|
58
|
+
namespace: string;
|
|
59
|
+
definitions: readonly SettingDefinitionSpec[];
|
|
60
|
+
/** The trusted boot-time actor these writes are attributed to (mirrors a
|
|
61
|
+
* host's other boot-time registrars' identical convention). */
|
|
62
|
+
systemPrincipalId: UUID;
|
|
63
|
+
}
|
|
64
|
+
/** Core definitions are platform-wide; site definitions require a workspace.
|
|
65
|
+
* Omitting the target retains the existing core-default reconciliation policy. */
|
|
66
|
+
export type EnsureSettingDefinitionsInput = SettingDefinitionsInput & ({
|
|
67
|
+
ownerKind?: "core";
|
|
68
|
+
workspaceId?: never;
|
|
69
|
+
} | {
|
|
70
|
+
ownerKind: "site";
|
|
71
|
+
workspaceId: UUID;
|
|
72
|
+
});
|
|
73
|
+
/**
|
|
74
|
+
* Idempotently registers every definition in `input.definitions` under
|
|
75
|
+
* `input.namespace`, and reconciles the stored default of any that already
|
|
76
|
+
* exist to the default source declares. Safe to call on every boot.
|
|
77
|
+
*
|
|
78
|
+
* The reconcile half matters as much as the register half: registration is
|
|
79
|
+
* once-only, so without it a `defaultValue` edited in source never reaches an
|
|
80
|
+
* install that has booted before. Code owns `core` defaults; the stored row
|
|
81
|
+
* does not get to outvote it.
|
|
82
|
+
*
|
|
83
|
+
* By default definitions are `ownerKind: "core"` — a settings-dialog
|
|
84
|
+
* tab configures a platform-level capability, not per-site content. Per the
|
|
85
|
+
* namespace-fence CHECK, `owner_kind='core'` requires `workspaceId: null` at
|
|
86
|
+
* the DEFINITION level regardless of which workspace is booting, while the
|
|
87
|
+
* `scopes` bitmask still lets each workspace (or user) hold its own VALUE row.
|
|
88
|
+
*
|
|
89
|
+
* Site targets instead use their workspace for lookup, registration and the
|
|
90
|
+
* authorization context, and skip every existing row without changing defaults.
|
|
91
|
+
* Writes are sequential so boot never overlaps transactions on one connection.
|
|
92
|
+
* Errors from the ledger propagate to the caller's readiness promise.
|
|
93
|
+
*
|
|
94
|
+
* @example ensureSettingDefinitions(deps, { namespace: "site.seo", definitions,
|
|
95
|
+
* systemPrincipalId, ownerKind: "site", workspaceId });
|
|
96
|
+
* @complexity O(n) time, O(1) auxiliary space for n definitions — each is one
|
|
97
|
+
* lookup plus at most one registration or default reconciliation.
|
|
98
|
+
*/
|
|
99
|
+
export declare function ensureSettingDefinitions(deps: EnsureSettingDefinitionsDeps, input: EnsureSettingDefinitionsInput): Promise<void>;
|
|
100
|
+
export {};
|
|
101
|
+
//# sourceMappingURL=ensure-definitions.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { resolveDefinitionRaw } from "./settings.js";
|
|
2
|
+
import { SCOPE_BIT } from "./types.js";
|
|
3
|
+
import { reconcileDefinitionDefault, registerDefinitions } from "./write-service.js";
|
|
4
|
+
/** Boot-time infra work is trusted by construction — mirrors every other
|
|
5
|
+
* `ensure*SettingDefinitions`'s identical shim. */
|
|
6
|
+
const alwaysAllowBoot = async () => ({ allowed: true, reason: "system_boot" });
|
|
7
|
+
/**
|
|
8
|
+
* Idempotently registers every definition in `input.definitions` under
|
|
9
|
+
* `input.namespace`, and reconciles the stored default of any that already
|
|
10
|
+
* exist to the default source declares. Safe to call on every boot.
|
|
11
|
+
*
|
|
12
|
+
* The reconcile half matters as much as the register half: registration is
|
|
13
|
+
* once-only, so without it a `defaultValue` edited in source never reaches an
|
|
14
|
+
* install that has booted before. Code owns `core` defaults; the stored row
|
|
15
|
+
* does not get to outvote it.
|
|
16
|
+
*
|
|
17
|
+
* By default definitions are `ownerKind: "core"` — a settings-dialog
|
|
18
|
+
* tab configures a platform-level capability, not per-site content. Per the
|
|
19
|
+
* namespace-fence CHECK, `owner_kind='core'` requires `workspaceId: null` at
|
|
20
|
+
* the DEFINITION level regardless of which workspace is booting, while the
|
|
21
|
+
* `scopes` bitmask still lets each workspace (or user) hold its own VALUE row.
|
|
22
|
+
*
|
|
23
|
+
* Site targets instead use their workspace for lookup, registration and the
|
|
24
|
+
* authorization context, and skip every existing row without changing defaults.
|
|
25
|
+
* Writes are sequential so boot never overlaps transactions on one connection.
|
|
26
|
+
* Errors from the ledger propagate to the caller's readiness promise.
|
|
27
|
+
*
|
|
28
|
+
* @example ensureSettingDefinitions(deps, { namespace: "site.seo", definitions,
|
|
29
|
+
* systemPrincipalId, ownerKind: "site", workspaceId });
|
|
30
|
+
* @complexity O(n) time, O(1) auxiliary space for n definitions — each is one
|
|
31
|
+
* lookup plus at most one registration or default reconciliation.
|
|
32
|
+
*/
|
|
33
|
+
export async function ensureSettingDefinitions(deps, input) {
|
|
34
|
+
const ownerKind = input.ownerKind ?? "core";
|
|
35
|
+
const workspaceId = input.ownerKind === "site" ? input.workspaceId : null;
|
|
36
|
+
const authWorkspaceId = workspaceId ?? input.systemPrincipalId;
|
|
37
|
+
const writeDeps = {
|
|
38
|
+
repo: deps.settingsRepo,
|
|
39
|
+
clock: deps.clock,
|
|
40
|
+
ids: deps.ids,
|
|
41
|
+
authorize: alwaysAllowBoot,
|
|
42
|
+
principals: deps.principals,
|
|
43
|
+
};
|
|
44
|
+
for (const def of input.definitions) {
|
|
45
|
+
const existing = await resolveDefinitionRaw({ repo: deps.settingsRepo }, { namespace: input.namespace, key: def.key, workspaceId });
|
|
46
|
+
if (existing) {
|
|
47
|
+
// Core registration is once-only, but the DEFAULT is not. Without this, a
|
|
48
|
+
// `defaultValue` changed in source is unreachable in any install that
|
|
49
|
+
// has already booted — the stored `default_json` from first boot keeps
|
|
50
|
+
// winning, for every settings-dialog tab. See `reconcileDefinitionDefault`
|
|
51
|
+
// for the bug this closes.
|
|
52
|
+
//
|
|
53
|
+
// Three slots are deliberately left alone rather than reconciled:
|
|
54
|
+
// a non-`active` row (deprecated/tombstoned) is not ours to rewrite; a
|
|
55
|
+
// non-`core` row is operator-owned; and a row whose identity differs
|
|
56
|
+
// from the slot we asked for means `resolveDefinitionRaw` followed an
|
|
57
|
+
// alias, so rewriting it would silently change a DIFFERENT definition's
|
|
58
|
+
// default. `reconcileDefinitionDefault` would throw on the first two —
|
|
59
|
+
// this skips them quietly because boot must not fail on them.
|
|
60
|
+
const isSameSlot = existing.namespace === input.namespace && existing.key === def.key;
|
|
61
|
+
if (ownerKind === "core" && existing.status === "active" && existing.ownerKind === "core" && isSameSlot) {
|
|
62
|
+
await reconcileDefinitionDefault({
|
|
63
|
+
deps: writeDeps,
|
|
64
|
+
input: {
|
|
65
|
+
namespace: input.namespace,
|
|
66
|
+
key: def.key,
|
|
67
|
+
workspaceId: null,
|
|
68
|
+
defaultValue: def.defaultValue,
|
|
69
|
+
callerPrincipalId: input.systemPrincipalId,
|
|
70
|
+
authWorkspaceId,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
await registerDefinitions({
|
|
77
|
+
deps: writeDeps,
|
|
78
|
+
input: {
|
|
79
|
+
callerPrincipalId: input.systemPrincipalId,
|
|
80
|
+
// Platform (core) definitions aren't scoped to any one workspace, but
|
|
81
|
+
// `registerDefinitions`'s authorization check still needs a workspace
|
|
82
|
+
// context to authorize against — the seeded workspace mirrors every
|
|
83
|
+
// other boot-time registrar's identical `authWorkspaceId` shape.
|
|
84
|
+
authWorkspaceId,
|
|
85
|
+
definitions: [
|
|
86
|
+
{
|
|
87
|
+
namespace: input.namespace,
|
|
88
|
+
key: def.key,
|
|
89
|
+
ownerKind,
|
|
90
|
+
workspaceId,
|
|
91
|
+
schema: def.schema,
|
|
92
|
+
defaultValue: def.defaultValue,
|
|
93
|
+
scopes: def.scopes ?? SCOPE_BIT.workspace,
|
|
94
|
+
secret: false,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=ensure-definitions.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Typed domain errors for `settings`.
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* One class per error this module originates, mirroring the
|
|
6
|
+
* `PresentationSettingsNotFoundError`/`PresentationSettingsValidationError`
|
|
7
|
+
* convention a host's own presentation-settings module may use. Route
|
|
8
|
+
* handlers map these 1:1 to HTTP codes.
|
|
9
|
+
*/
|
|
10
|
+
export declare class DefinitionInvalidError extends Error {
|
|
11
|
+
}
|
|
12
|
+
export declare class ScopeNotAllowedError extends Error {
|
|
13
|
+
}
|
|
14
|
+
export declare class SecretNotSupportedError extends Error {
|
|
15
|
+
constructor({ message }: {
|
|
16
|
+
message: string;
|
|
17
|
+
}, optional?: ErrorOptions);
|
|
18
|
+
}
|
|
19
|
+
export declare class ValueValidationFailedError extends Error {
|
|
20
|
+
}
|
|
21
|
+
export declare class RenameRetypeConflictError extends Error {
|
|
22
|
+
}
|
|
23
|
+
export declare class AliasDepthExceededError extends Error {
|
|
24
|
+
}
|
|
25
|
+
export declare class DefinitionTombstonedError extends Error {
|
|
26
|
+
}
|
|
27
|
+
export declare class DefinitionNotFoundError extends Error {
|
|
28
|
+
}
|
|
29
|
+
export declare class PurgeRequiredError extends Error {
|
|
30
|
+
constructor({ message }: {
|
|
31
|
+
message: string;
|
|
32
|
+
}, optional?: ErrorOptions);
|
|
33
|
+
}
|
|
34
|
+
export declare class ForbiddenError extends Error {
|
|
35
|
+
}
|
|
36
|
+
/** : the target `principalId` does not resolve to an active principal in the request's workspace. See docs/decisions/DR-003-settings-ledger-invariants.md. */
|
|
37
|
+
export declare class PrincipalNotFoundError extends Error {
|
|
38
|
+
readonly principalId: string;
|
|
39
|
+
readonly workspaceId: string;
|
|
40
|
+
constructor(message: string, principalId: string, workspaceId: string);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Typed domain errors for `settings`.
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* One class per error this module originates, mirroring the
|
|
6
|
+
* `PresentationSettingsNotFoundError`/`PresentationSettingsValidationError`
|
|
7
|
+
* convention a host's own presentation-settings module may use. Route
|
|
8
|
+
* handlers map these 1:1 to HTTP codes.
|
|
9
|
+
*/
|
|
10
|
+
export class DefinitionInvalidError extends Error {
|
|
11
|
+
}
|
|
12
|
+
export class ScopeNotAllowedError extends Error {
|
|
13
|
+
}
|
|
14
|
+
export class SecretNotSupportedError extends Error {
|
|
15
|
+
constructor({ message }, optional = {}) {
|
|
16
|
+
super(message, optional);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class ValueValidationFailedError extends Error {
|
|
20
|
+
}
|
|
21
|
+
export class RenameRetypeConflictError extends Error {
|
|
22
|
+
}
|
|
23
|
+
export class AliasDepthExceededError extends Error {
|
|
24
|
+
}
|
|
25
|
+
export class DefinitionTombstonedError extends Error {
|
|
26
|
+
}
|
|
27
|
+
export class DefinitionNotFoundError extends Error {
|
|
28
|
+
}
|
|
29
|
+
export class PurgeRequiredError extends Error {
|
|
30
|
+
constructor({ message }, optional = {}) {
|
|
31
|
+
super(message, optional);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class ForbiddenError extends Error {
|
|
35
|
+
}
|
|
36
|
+
/** : the target `principalId` does not resolve to an active principal in the request's workspace. See docs/decisions/DR-003-settings-ledger-invariants.md. */
|
|
37
|
+
export class PrincipalNotFoundError extends Error {
|
|
38
|
+
principalId;
|
|
39
|
+
workspaceId;
|
|
40
|
+
constructor(message, principalId, workspaceId) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.principalId = principalId;
|
|
43
|
+
this.workspaceId = workspaceId;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type SettingsWriteServiceDeps, type SettingsRepoPort } from "../index.js";
|
|
2
|
+
import type { SettingsService, SettingsPermissions, SettingsChangeFeed } from "./contracts.js";
|
|
3
|
+
/** Keep the CMS write chokepoint while translating its permission vocabulary to host policy. */
|
|
4
|
+
/** Bind the CMS ledger and write chokepoint without copying their domain logic.
|
|
5
|
+
* Effective reads scan definitions and resolve each namespace key; mutations inherit CMS transactions/errors.
|
|
6
|
+
* @complexity O(1) setup. Per operation: O(definitions + keys) reads, or O(batch size) writes.
|
|
7
|
+
* @example createCmsSettingsService({ deps: cmsPorts, permissions });
|
|
8
|
+
*/
|
|
9
|
+
export declare function createCmsSettingsService(required: {
|
|
10
|
+
deps: SettingsWriteServiceDeps;
|
|
11
|
+
permissions: SettingsPermissions;
|
|
12
|
+
}): SettingsService;
|
|
13
|
+
/** Adapt the ledger through CMS tenant/principal disclosure rules, returning names and cursors only.
|
|
14
|
+
* @complexity O(1) setup; collect is O(page size), with namespace lookup per visible revision.
|
|
15
|
+
* @example createCmsSettingsChangeFeed({ repo });
|
|
16
|
+
*/
|
|
17
|
+
export declare function createCmsSettingsChangeFeed(required: {
|
|
18
|
+
repo: SettingsRepoPort;
|
|
19
|
+
}): SettingsChangeFeed;
|
|
20
|
+
//# sourceMappingURL=cms-adapter.d.ts.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { getEffective, resolveDefinition, set, clear, resetNamespace, registerDefinitions, NON_REGISTER_DEFINITION_OPS, parseNonRegisterDefinitionOp, collectChangedNamespaces, } from "../index.js";
|
|
2
|
+
/** Keep the CMS write chokepoint while translating its permission vocabulary to host policy. */
|
|
3
|
+
/** Bind the CMS ledger and write chokepoint without copying their domain logic.
|
|
4
|
+
* Effective reads scan definitions and resolve each namespace key; mutations inherit CMS transactions/errors.
|
|
5
|
+
* @complexity O(1) setup. Per operation: O(definitions + keys) reads, or O(batch size) writes.
|
|
6
|
+
* @example createCmsSettingsService({ deps: cmsPorts, permissions });
|
|
7
|
+
*/
|
|
8
|
+
export function createCmsSettingsService(required) {
|
|
9
|
+
const { deps: original, permissions } = required;
|
|
10
|
+
const permissionMap = new Map([
|
|
11
|
+
["settings.definitions.manage", permissions.manageDefinitions],
|
|
12
|
+
["settings.global.write", permissions.writeGlobal],
|
|
13
|
+
["settings.workspace.write", permissions.writeWorkspace],
|
|
14
|
+
["settings.user.self.write", permissions.writeUserSelf],
|
|
15
|
+
["settings.user.write", permissions.writeUserOther],
|
|
16
|
+
["settings.reset.global", permissions.reset.global],
|
|
17
|
+
["settings.reset.workspace", permissions.reset.workspace],
|
|
18
|
+
["settings.reset.user", permissions.reset.user],
|
|
19
|
+
]);
|
|
20
|
+
const deps = { ...original,
|
|
21
|
+
authorize: (input) => {
|
|
22
|
+
const permission = permissionMap.get(input.permission);
|
|
23
|
+
if (permission === undefined)
|
|
24
|
+
return Promise.resolve({ allowed: false, reason: "unknown_settings_permission" });
|
|
25
|
+
return original.authorize({ ...input, permission });
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const repo = deps.repo;
|
|
29
|
+
const listDefinitions = async ({ workspaceId }) => {
|
|
30
|
+
const [platform, workspace] = await Promise.all([
|
|
31
|
+
repo.listActiveDefinitions({ workspaceId: null }), repo.listActiveDefinitions({ workspaceId }),
|
|
32
|
+
]);
|
|
33
|
+
return [...platform, ...workspace];
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
listDefinitions,
|
|
37
|
+
effective: async ({ namespace, workspaceId, principalId }) => {
|
|
38
|
+
const defs = await listDefinitions({ workspaceId });
|
|
39
|
+
const keys = new Set(defs.filter((d) => d.namespace === namespace).map((d) => d.key));
|
|
40
|
+
const data = [];
|
|
41
|
+
for (const key of keys) {
|
|
42
|
+
const result = await getEffective({ repo }, { namespace, key, scopeContext: { workspaceId, principalId } });
|
|
43
|
+
if (result)
|
|
44
|
+
data.push({ key, value: result.value, sourceLayer: result.sourceLayer, defVersion: result.defVersion });
|
|
45
|
+
}
|
|
46
|
+
return data;
|
|
47
|
+
},
|
|
48
|
+
raw: async ({ namespace, key, workspaceId, principalId }) => {
|
|
49
|
+
const definition = await resolveDefinition({ repo }, { namespace, key, workspaceId });
|
|
50
|
+
if (!definition)
|
|
51
|
+
return null;
|
|
52
|
+
const settingId = definition.settingId;
|
|
53
|
+
const records = await Promise.all([
|
|
54
|
+
repo.getGlobalValue(settingId), repo.getWorkspaceValue({ workspaceId, settingId }),
|
|
55
|
+
principalId ? repo.getUserValue({ workspaceId, principalId, settingId }) : Promise.resolve(null),
|
|
56
|
+
]);
|
|
57
|
+
const values = records.map((r) => r?.state === "set" ? r.valueJson : null);
|
|
58
|
+
return { key: `${namespace}.${key}`, global: values[0], workspace: values[1], user: values[2], default: definition.defaultValue };
|
|
59
|
+
},
|
|
60
|
+
set: (input) => set({ deps, input }),
|
|
61
|
+
clear: (input) => clear({ deps, input }),
|
|
62
|
+
reset: async (input) => {
|
|
63
|
+
const definitions = await repo.listActiveDefinitions({ workspaceId: input.scope === "global" ? null : (input.workspaceId ?? input.authWorkspaceId ?? null) });
|
|
64
|
+
const keys = definitions.filter((d) => d.namespace === input.namespace).map((d) => d.key);
|
|
65
|
+
return resetNamespace({ deps, input }, keys);
|
|
66
|
+
},
|
|
67
|
+
definitions: ({ items, callerPrincipalId, authWorkspaceId }) => applyDefinitions({ deps, items, callerPrincipalId, authWorkspaceId }),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Preserve register batching and ordered non-register writes over existing CMS operation contracts.
|
|
71
|
+
* Unknown operations return their name. Earlier non-register writes are not rolled back by later failures.
|
|
72
|
+
* @complexity O(item count) time/space plus CMS effects; this batch is not atomic.
|
|
73
|
+
*/
|
|
74
|
+
async function applyDefinitions(required) {
|
|
75
|
+
const { deps, items, callerPrincipalId, authWorkspaceId } = required;
|
|
76
|
+
const toRegister = [];
|
|
77
|
+
const applied = [];
|
|
78
|
+
for (const raw of items) {
|
|
79
|
+
const item = raw;
|
|
80
|
+
const op = String(item.op ?? "register");
|
|
81
|
+
const namespace = String(item.namespace ?? "");
|
|
82
|
+
const key = String(item.key ?? "");
|
|
83
|
+
const ownerKind = item.ownerKind;
|
|
84
|
+
const workspaceId = ownerKind === "site" ? authWorkspaceId : null;
|
|
85
|
+
const defaultValue = (item.defaultJson ?? null);
|
|
86
|
+
if (op === "register") {
|
|
87
|
+
toRegister.push({ namespace, key, ownerKind, workspaceId, schema: item.schemaJson,
|
|
88
|
+
defaultValue, scopes: Number(item.scopes), secret: Boolean(item.secret ?? false) });
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const parsedOp = parseNonRegisterDefinitionOp({ op });
|
|
92
|
+
if (!parsedOp)
|
|
93
|
+
return { unknownOp: op };
|
|
94
|
+
const opItem = { namespace, key, ownerKind, workspaceId, defaultJson: defaultValue,
|
|
95
|
+
...(item.newNamespace === undefined ? {} : { newNamespace: item.newNamespace }),
|
|
96
|
+
...(item.newKey === undefined ? {} : { newKey: item.newKey }),
|
|
97
|
+
...(item.schemaJson === undefined ? {} : { schemaJson: item.schemaJson }),
|
|
98
|
+
...(item.coercionJson === undefined ? {} : { coercionJson: item.coercionJson }),
|
|
99
|
+
};
|
|
100
|
+
await NON_REGISTER_DEFINITION_OPS[parsedOp]({ ctx: { deps, callerPrincipalId, authWorkspaceId }, item: opItem });
|
|
101
|
+
}
|
|
102
|
+
applied.push({ key: `${namespace}.${key}`, op, status: "applied" });
|
|
103
|
+
}
|
|
104
|
+
if (toRegister.length)
|
|
105
|
+
await registerDefinitions({ deps, input: { definitions: toRegister, callerPrincipalId, authWorkspaceId } });
|
|
106
|
+
return { applied };
|
|
107
|
+
}
|
|
108
|
+
/** Adapt the ledger through CMS tenant/principal disclosure rules, returning names and cursors only.
|
|
109
|
+
* @complexity O(1) setup; collect is O(page size), with namespace lookup per visible revision.
|
|
110
|
+
* @example createCmsSettingsChangeFeed({ repo });
|
|
111
|
+
*/
|
|
112
|
+
export function createCmsSettingsChangeFeed(required) {
|
|
113
|
+
const { repo } = required;
|
|
114
|
+
return {
|
|
115
|
+
head: () => repo.maxRevisionSeq(),
|
|
116
|
+
collect: async ({ sinceSeq, limit, viewer }) => {
|
|
117
|
+
const revisions = await repo.listRevisionsSince({ sinceSeq, limit, workspaceId: viewer.workspaceId });
|
|
118
|
+
// Resolve afresh each batch: a rename must never leave a stale namespace cached forever.
|
|
119
|
+
const batch = await collectChangedNamespaces({ revisions, viewer, resolveNamespace: async ({ settingId }) => (await repo.findDefinitionBySettingId({ settingId }))?.namespace ?? null });
|
|
120
|
+
return { ...batch, examinedCount: revisions.length };
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=cms-adapter.js.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { Request, Response, Express } from "express";
|
|
2
|
+
import type { SettingScope, SettingDefinitionRecord, SetValueRequired, ClearValueRequired, ResetNamespaceRequired, ChangeFeedViewer, ChangeFeedBatch } from "../index.js";
|
|
3
|
+
export type Authorize = (required: {
|
|
4
|
+
principalId: string;
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
permission: string;
|
|
7
|
+
entityType: string;
|
|
8
|
+
}) => Promise<{
|
|
9
|
+
allowed: boolean;
|
|
10
|
+
reason: string;
|
|
11
|
+
}>;
|
|
12
|
+
export type PrincipalResolver = (required: {
|
|
13
|
+
request: Request;
|
|
14
|
+
response: Response;
|
|
15
|
+
}) => Promise<{
|
|
16
|
+
id: string;
|
|
17
|
+
} | null> | {
|
|
18
|
+
id: string;
|
|
19
|
+
} | null;
|
|
20
|
+
export interface Scheduler {
|
|
21
|
+
/** Return an idempotent cancellation callback. */
|
|
22
|
+
every(required: {
|
|
23
|
+
intervalMs: number;
|
|
24
|
+
callback: () => void;
|
|
25
|
+
}): () => void;
|
|
26
|
+
}
|
|
27
|
+
export interface SettingsPermissions {
|
|
28
|
+
read: string;
|
|
29
|
+
readRaw: string;
|
|
30
|
+
readDefinitions: string;
|
|
31
|
+
readOtherUser: string;
|
|
32
|
+
manageDefinitions: string;
|
|
33
|
+
writeGlobal: string;
|
|
34
|
+
writeWorkspace: string;
|
|
35
|
+
writeUserSelf: string;
|
|
36
|
+
writeUserOther: string;
|
|
37
|
+
reset: Record<SettingScope, string>;
|
|
38
|
+
}
|
|
39
|
+
export interface SettingsRoutes {
|
|
40
|
+
definitions: string;
|
|
41
|
+
effective: string;
|
|
42
|
+
raw: string;
|
|
43
|
+
value: string;
|
|
44
|
+
reset: string;
|
|
45
|
+
events: string;
|
|
46
|
+
workspaceParam: string;
|
|
47
|
+
}
|
|
48
|
+
export type EffectiveSettingRow = {
|
|
49
|
+
key: string;
|
|
50
|
+
value: unknown;
|
|
51
|
+
sourceLayer: string;
|
|
52
|
+
defVersion: number;
|
|
53
|
+
};
|
|
54
|
+
export type RawSetting = {
|
|
55
|
+
key: string;
|
|
56
|
+
global: unknown;
|
|
57
|
+
workspace: unknown;
|
|
58
|
+
user: unknown;
|
|
59
|
+
default: unknown;
|
|
60
|
+
};
|
|
61
|
+
export type DefinitionResult = {
|
|
62
|
+
applied: Array<{
|
|
63
|
+
key: string;
|
|
64
|
+
op: string;
|
|
65
|
+
status: string;
|
|
66
|
+
}>;
|
|
67
|
+
} | {
|
|
68
|
+
unknownOp: string;
|
|
69
|
+
};
|
|
70
|
+
export interface SettingsService {
|
|
71
|
+
listDefinitions(required: {
|
|
72
|
+
workspaceId: string;
|
|
73
|
+
}): Promise<SettingDefinitionRecord[]>;
|
|
74
|
+
effective(required: {
|
|
75
|
+
namespace: string;
|
|
76
|
+
workspaceId: string;
|
|
77
|
+
principalId: string | undefined;
|
|
78
|
+
}): Promise<EffectiveSettingRow[]>;
|
|
79
|
+
raw(required: {
|
|
80
|
+
namespace: string;
|
|
81
|
+
key: string;
|
|
82
|
+
workspaceId: string;
|
|
83
|
+
principalId: string | undefined;
|
|
84
|
+
}): Promise<RawSetting | null>;
|
|
85
|
+
set(required: SetValueRequired["input"]): Promise<{
|
|
86
|
+
value: unknown;
|
|
87
|
+
revisionSeq: number;
|
|
88
|
+
}>;
|
|
89
|
+
clear(required: ClearValueRequired["input"]): Promise<{
|
|
90
|
+
revisionSeq: number;
|
|
91
|
+
}>;
|
|
92
|
+
reset(required: ResetNamespaceRequired["input"]): Promise<{
|
|
93
|
+
clearedCount: number;
|
|
94
|
+
revisionSeqs: number[];
|
|
95
|
+
}>;
|
|
96
|
+
definitions(required: {
|
|
97
|
+
items: readonly unknown[];
|
|
98
|
+
callerPrincipalId: string;
|
|
99
|
+
authWorkspaceId: string;
|
|
100
|
+
}): Promise<DefinitionResult>;
|
|
101
|
+
}
|
|
102
|
+
export interface SettingsChangeFeed {
|
|
103
|
+
head(required: Record<string, never>): Promise<number>;
|
|
104
|
+
/** Cursor advances across invisible rows. The emitted id MUST use batch.cursor, never head. */
|
|
105
|
+
collect(required: {
|
|
106
|
+
sinceSeq: number;
|
|
107
|
+
limit: number;
|
|
108
|
+
viewer: ChangeFeedViewer;
|
|
109
|
+
}): Promise<ChangeFeedBatch & {
|
|
110
|
+
examinedCount: number;
|
|
111
|
+
}>;
|
|
112
|
+
}
|
|
113
|
+
export interface SettingsHttpRequired {
|
|
114
|
+
app: Pick<Express, "get" | "post" | "put" | "delete">;
|
|
115
|
+
workspaceId: string;
|
|
116
|
+
ready: Promise<void>;
|
|
117
|
+
service: SettingsService;
|
|
118
|
+
changeFeed: SettingsChangeFeed;
|
|
119
|
+
principalResolver: PrincipalResolver;
|
|
120
|
+
authorize: Authorize;
|
|
121
|
+
scheduler: Scheduler;
|
|
122
|
+
routes: SettingsRoutes;
|
|
123
|
+
permissions: SettingsPermissions;
|
|
124
|
+
}
|
|
125
|
+
export interface SettingsHttpOptions {
|
|
126
|
+
pollIntervalMs?: number;
|
|
127
|
+
keepaliveIntervalMs?: number;
|
|
128
|
+
reauthorizeIntervalMs?: number;
|
|
129
|
+
revisionPageSize?: number;
|
|
130
|
+
onError?: (required: {
|
|
131
|
+
operation: string;
|
|
132
|
+
error: unknown;
|
|
133
|
+
}) => void;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SettingsHttpRequired, SettingsHttpOptions } from "./contracts.js";
|
|
2
|
+
/** Mount all eight endpoints. The returned disposer closes active feeds and cancels their schedules. */
|
|
3
|
+
/** Injected policy and services preserve the settings HTTP response envelopes.
|
|
4
|
+
* O(1) mounting; read/mutation work is delegated. Feed resources are bounded per connection.
|
|
5
|
+
* @example registerSettingsRoutes({ app, workspaceId, ready, service, changeFeed, principalResolver, authorize, scheduler, routes, permissions });
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerSettingsRoutes(required: SettingsHttpRequired, optional?: SettingsHttpOptions): {
|
|
8
|
+
dispose(): void;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=http.d.ts.map
|