@jini-ai/core 0.4.0 → 0.4.2

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.
Files changed (120) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +18 -1
  3. package/dist/agent-tools.d.ts +1 -0
  4. package/dist/approval.d.ts +134 -0
  5. package/dist/approval.js +141 -0
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.js +5 -0
  8. package/dist/model-facing-tool-errors.d.ts +152 -0
  9. package/dist/model-facing-tool-errors.js +124 -0
  10. package/dist/native-regexp.d.ts +10 -0
  11. package/dist/native-regexp.js +2 -0
  12. package/dist/primitives/index.d.ts +2 -0
  13. package/dist/primitives/index.js +1 -0
  14. package/dist/primitives/sse.d.ts +60 -0
  15. package/dist/primitives/sse.js +122 -0
  16. package/dist/redact.js +4 -3
  17. package/dist/registration-kit.d.ts +47 -0
  18. package/dist/registration-kit.js +55 -0
  19. package/dist/settings/agent-tools.d.ts +10 -0
  20. package/dist/settings/agent-tools.js +204 -0
  21. package/dist/settings/agent-value-write-tools.d.ts +22 -0
  22. package/dist/settings/agent-value-write-tools.js +157 -0
  23. package/dist/settings/agent-writable-preferences.d.ts +67 -0
  24. package/dist/settings/agent-writable-preferences.js +132 -0
  25. package/dist/settings/agent-write-denylist.d.ts +19 -0
  26. package/dist/settings/agent-write-denylist.js +15 -0
  27. package/dist/settings/change-feed.d.ts +83 -0
  28. package/dist/settings/change-feed.js +61 -0
  29. package/dist/settings/definitions-dispatch.d.ts +56 -0
  30. package/dist/settings/definitions-dispatch.js +86 -0
  31. package/dist/settings/dictionaries/index.d.ts +48 -0
  32. package/dist/settings/dictionaries/index.js +69 -0
  33. package/dist/settings/dictionaries/settings-dialog.ar.d.ts +3 -0
  34. package/dist/settings/dictionaries/settings-dialog.ar.js +27 -0
  35. package/dist/settings/dictionaries/settings-dialog.bn.d.ts +3 -0
  36. package/dist/settings/dictionaries/settings-dialog.bn.js +27 -0
  37. package/dist/settings/dictionaries/settings-dialog.de.d.ts +3 -0
  38. package/dist/settings/dictionaries/settings-dialog.de.js +27 -0
  39. package/dist/settings/dictionaries/settings-dialog.es.d.ts +3 -0
  40. package/dist/settings/dictionaries/settings-dialog.es.js +27 -0
  41. package/dist/settings/dictionaries/settings-dialog.fa.d.ts +3 -0
  42. package/dist/settings/dictionaries/settings-dialog.fa.js +27 -0
  43. package/dist/settings/dictionaries/settings-dialog.fr.d.ts +3 -0
  44. package/dist/settings/dictionaries/settings-dialog.fr.js +27 -0
  45. package/dist/settings/dictionaries/settings-dialog.hi.d.ts +3 -0
  46. package/dist/settings/dictionaries/settings-dialog.hi.js +27 -0
  47. package/dist/settings/dictionaries/settings-dialog.hu.d.ts +3 -0
  48. package/dist/settings/dictionaries/settings-dialog.hu.js +27 -0
  49. package/dist/settings/dictionaries/settings-dialog.id.d.ts +3 -0
  50. package/dist/settings/dictionaries/settings-dialog.id.js +27 -0
  51. package/dist/settings/dictionaries/settings-dialog.it.d.ts +3 -0
  52. package/dist/settings/dictionaries/settings-dialog.it.js +27 -0
  53. package/dist/settings/dictionaries/settings-dialog.ja.d.ts +3 -0
  54. package/dist/settings/dictionaries/settings-dialog.ja.js +27 -0
  55. package/dist/settings/dictionaries/settings-dialog.ko.d.ts +3 -0
  56. package/dist/settings/dictionaries/settings-dialog.ko.js +27 -0
  57. package/dist/settings/dictionaries/settings-dialog.pl.d.ts +3 -0
  58. package/dist/settings/dictionaries/settings-dialog.pl.js +27 -0
  59. package/dist/settings/dictionaries/settings-dialog.pt-BR.d.ts +3 -0
  60. package/dist/settings/dictionaries/settings-dialog.pt-BR.js +27 -0
  61. package/dist/settings/dictionaries/settings-dialog.ru.d.ts +3 -0
  62. package/dist/settings/dictionaries/settings-dialog.ru.js +27 -0
  63. package/dist/settings/dictionaries/settings-dialog.th.d.ts +3 -0
  64. package/dist/settings/dictionaries/settings-dialog.th.js +27 -0
  65. package/dist/settings/dictionaries/settings-dialog.tr.d.ts +3 -0
  66. package/dist/settings/dictionaries/settings-dialog.tr.js +27 -0
  67. package/dist/settings/dictionaries/settings-dialog.uk.d.ts +3 -0
  68. package/dist/settings/dictionaries/settings-dialog.uk.js +27 -0
  69. package/dist/settings/dictionaries/settings-dialog.ur.d.ts +3 -0
  70. package/dist/settings/dictionaries/settings-dialog.ur.js +27 -0
  71. package/dist/settings/dictionaries/settings-dialog.zh-CN.d.ts +3 -0
  72. package/dist/settings/dictionaries/settings-dialog.zh-CN.js +27 -0
  73. package/dist/settings/dictionaries/settings-dialog.zh-TW.d.ts +3 -0
  74. package/dist/settings/dictionaries/settings-dialog.zh-TW.js +27 -0
  75. package/dist/settings/ensure-definitions.d.ts +101 -0
  76. package/dist/settings/ensure-definitions.js +101 -0
  77. package/dist/settings/errors.d.ts +42 -0
  78. package/dist/settings/errors.js +46 -0
  79. package/dist/settings/express/cms-adapter.d.ts +20 -0
  80. package/dist/settings/express/cms-adapter.js +124 -0
  81. package/dist/settings/express/contracts.d.ts +135 -0
  82. package/dist/settings/express/contracts.js +2 -0
  83. package/dist/settings/express/http.d.ts +10 -0
  84. package/dist/settings/express/http.js +211 -0
  85. package/dist/settings/express/index.d.ts +5 -0
  86. package/dist/settings/express/index.js +4 -0
  87. package/dist/settings/express/shared.d.ts +68 -0
  88. package/dist/settings/express/shared.js +54 -0
  89. package/dist/settings/express/stream.d.ts +24 -0
  90. package/dist/settings/express/stream.js +147 -0
  91. package/dist/settings/index.d.ts +40 -0
  92. package/dist/settings/index.js +37 -0
  93. package/dist/settings/ports.d.ts +118 -0
  94. package/dist/settings/ports.js +2 -0
  95. package/dist/settings/principal-lookup.d.ts +9 -0
  96. package/dist/settings/principal-lookup.js +2 -0
  97. package/dist/settings/purge-service.d.ts +54 -0
  98. package/dist/settings/purge-service.js +106 -0
  99. package/dist/settings/repo.memory.d.ts +119 -0
  100. package/dist/settings/repo.memory.js +199 -0
  101. package/dist/settings/settings.d.ts +139 -0
  102. package/dist/settings/settings.js +259 -0
  103. package/dist/settings/tool-registrations.d.ts +54 -0
  104. package/dist/settings/tool-registrations.js +250 -0
  105. package/dist/settings/types.d.ts +125 -0
  106. package/dist/settings/types.js +3 -0
  107. package/dist/settings/ui-tab-definitions.d.ts +41 -0
  108. package/dist/settings/ui-tab-definitions.js +148 -0
  109. package/dist/settings/write-service.d.ts +320 -0
  110. package/dist/settings/write-service.js +683 -0
  111. package/dist/surface-exchanges.d.ts +209 -0
  112. package/dist/surface-exchanges.js +55 -0
  113. package/dist/tool-metadata.d.ts +71 -0
  114. package/dist/tool-metadata.js +56 -0
  115. package/dist/tool-permissions.d.ts +76 -0
  116. package/dist/tool-permissions.js +57 -0
  117. package/dist/tool-registry.d.ts +3 -0
  118. package/package.json +62 -4
  119. package/dist/internal.d.ts +0 -41
  120. package/dist/internal.js +0 -39
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=contracts.js.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