@jini-ai/core 0.4.1 → 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 (118) hide show
  1. package/CHANGELOG.md +6 -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 +13 -0
  18. package/dist/registration-kit.js +12 -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 +60 -3
@@ -0,0 +1,139 @@
1
+ import type { JsonValue } from "../primitives/index.js";
2
+ import { DefinitionInvalidError, SecretNotSupportedError } from "./errors.js";
3
+ import type { SettingsRepoPort } from "./ports.js";
4
+ import { type SettingDefinitionRecord, type SettingOwnerKind, type SettingScopeContext, type SettingValueSchema } from "./types.js";
5
+ /**
6
+ * @file The settings resolver + pure definition-registration validation
7
+ * ("one evaluator").
8
+ *
9
+ * Purpose:
10
+ * `validateDefinitionInput` is pure (no I/O) — the write-side chokepoint in
11
+ * `write-service.ts` calls it before persisting. `getEffective`/`getLayer`/
12
+ * `resolveDefinition` are read-only against `SettingsRepoPort`. Neither
13
+ * mutates anything; every mutation goes through `write-service.ts`.
14
+ * See docs/decisions/DR-003-settings-ledger-invariants.md.
15
+ */
16
+ export interface DefinitionInput {
17
+ namespace: string;
18
+ key: string;
19
+ ownerKind: SettingOwnerKind;
20
+ workspaceId: string | null;
21
+ ownerId?: string | null | undefined;
22
+ schema: SettingValueSchema;
23
+ defaultValue: JsonValue | null;
24
+ scopes: number;
25
+ secret: boolean;
26
+ }
27
+ /**
28
+ * — the pure half of `registerDefinitions`.
29
+ * Never throws; returns a discriminated result so the chokepoint decides how
30
+ * to surface the failure (matches `ValueValidationFailedError`'s pattern of
31
+ * keeping I/O out of validation).
32
+ *
33
+ * @complexity O(1) per definition.
34
+ * @overallScore 100
35
+ * See docs/decisions/DR-003-settings-ledger-invariants.md.
36
+ */
37
+ export declare function validateDefinitionInput(input: DefinitionInput): {
38
+ valid: true;
39
+ } | {
40
+ valid: false;
41
+ error: DefinitionInvalidError | SecretNotSupportedError;
42
+ };
43
+ export declare function validateValueAgainstSchema(schema: SettingValueSchema, value: JsonValue): boolean;
44
+ export declare function registerCoercer({ tag, fn }: {
45
+ tag: string;
46
+ fn: (required: {
47
+ value: JsonValue;
48
+ }) => JsonValue;
49
+ }, _optional?: Record<string, never>): void;
50
+ /**
51
+ * Bumps the namespace's definition-cache epoch (lazy invalidation) — every
52
+ * previously-cached definition-cache entry for this
53
+ * namespace (across every workspace) becomes unreachable on the next read,
54
+ * without needing to enumerate tenants. Call after any write that changes a
55
+ * definition's identity/shape/status in this namespace: register, rename
56
+ * (both the old and new namespace), retype, deprecate, tombstone.
57
+ */
58
+ export declare function invalidateDefinitionNamespaceCache(repo: SettingsRepoPort, namespace: string): void;
59
+ /**
60
+ * Purge support — now a no-op, deliberately kept rather than deleted.
61
+ *
62
+ * This cleared the workspace- and user-scope layer-cache entries a tenant/principal teardown
63
+ * invalidated, because a purge deletes an unbounded number of value rows across an unknown set of
64
+ * namespaces and per-namespace single-key invalidation was not practical for it. With the layer
65
+ * cache gone (see this module's cache header) there is nothing left to clear: the rows are deleted,
66
+ * and the next read goes to the repo and sees them gone.
67
+ *
68
+ * Kept as an explicit no-op instead of removed because of what it guards. `purge-service.ts` calls
69
+ * it as the last step of deleting a tenant's settings, and its correctness question — "can a purged
70
+ * value still be read back?" — is exactly the kind that must be answered again if anyone
71
+ * reintroduces caching. Deleting the call site would remove the place that question is asked. A
72
+ * cache added without restoring a purge hook is a tenant-teardown leak, so this stays as the seam.
73
+ */
74
+ export declare function invalidateWorkspaceSettingsCache(_required: {
75
+ repo: SettingsRepoPort;
76
+ workspaceId: string;
77
+ }, _optional?: {
78
+ principalId?: string | undefined;
79
+ }): void;
80
+ /**
81
+ * Follows an alias marker (depth <=1) to the current row and returns it as
82
+ * stored — status intact, including `tombstone`. `resolveDefinition` (below)
83
+ * is the read-path wrapper that collapses tombstone to typed-absent;
84
+ * `write-service.ts` uses this raw form directly so it can report
85
+ * `DEFINITION_TOMBSTONED` distinctly from "not found".
86
+ * Malformed alias targets, cycles and chains deeper than one fail closed as absent.
87
+ * See docs/decisions/DR-003-settings-ledger-invariants.md.
88
+ */
89
+ export declare function resolveDefinitionRaw(deps: {
90
+ repo: SettingsRepoPort;
91
+ }, input: {
92
+ namespace: string;
93
+ key: string;
94
+ workspaceId: string | null;
95
+ }): Promise<SettingDefinitionRecord | null>;
96
+ /**
97
+ * The read-path resolver: typed-absent (`null`) for a tombstoned or
98
+ * missing key. Cached — keyed by
99
+ * `(workspaceId, namespace, key, namespace-epoch)`, so it is workspace-
100
+ * qualified by construction and invalidated wholesale by
101
+ * `invalidateDefinitionNamespaceCache` on any definition-lifecycle write
102
+ * (register/rename/retype/deprecate/tombstone). `resolveDefinitionRaw`
103
+ * itself stays uncached — the write chokepoint (`write-service.ts`) calls it
104
+ * directly so authorization/validation decisions are always made against
105
+ * live data, never a cached read.
106
+ */
107
+ export declare function resolveDefinition(deps: {
108
+ repo: SettingsRepoPort;
109
+ }, input: {
110
+ namespace: string;
111
+ key: string;
112
+ workspaceId: string | null;
113
+ }): Promise<SettingDefinitionRecord | null>;
114
+ export interface ResolvedSetting {
115
+ value: JsonValue | null;
116
+ sourceLayer: "user" | "workspace" | "global" | "default";
117
+ defVersion: number;
118
+ }
119
+ /**
120
+ * — total for a live key: never throws, never returns
121
+ * undefined. Precedence `user ?? workspace ?? global ?? default`. A
122
+ * `cleared` row is treated as absent at that layer (behavior.spec §1.2).
123
+ *
124
+ * Per-layer reads go straight to the repo (see this module's cache header for
125
+ * why the per-layer value cache that used to wrap these three calls does not
126
+ * exist here).
127
+ *
128
+ * @complexity O(1) — up to 3 layer reads + 1 definition read.
129
+ * @overallScore 100
130
+ * See docs/decisions/DR-003-settings-ledger-invariants.md.
131
+ */
132
+ export declare function getEffective(deps: {
133
+ repo: SettingsRepoPort;
134
+ }, input: {
135
+ namespace: string;
136
+ key: string;
137
+ scopeContext: SettingScopeContext;
138
+ }): Promise<ResolvedSetting | null>;
139
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1,259 @@
1
+ import { DefinitionInvalidError, SecretNotSupportedError } from "./errors.js";
2
+ import { SCOPE_BIT, } from "./types.js";
3
+ const NAMESPACE_FENCE = {
4
+ core: (input) => input.namespace.startsWith("core.") && input.workspaceId === null,
5
+ theme: (input) => input.namespace.startsWith("theme.") && input.workspaceId === null,
6
+ site: (input) => input.namespace.startsWith("site.") && input.workspaceId !== null,
7
+ };
8
+ /**
9
+ * — the pure half of `registerDefinitions`.
10
+ * Never throws; returns a discriminated result so the chokepoint decides how
11
+ * to surface the failure (matches `ValueValidationFailedError`'s pattern of
12
+ * keeping I/O out of validation).
13
+ *
14
+ * @complexity O(1) per definition.
15
+ * @overallScore 100
16
+ * See docs/decisions/DR-003-settings-ledger-invariants.md.
17
+ */
18
+ export function validateDefinitionInput(input) {
19
+ if (input.secret) {
20
+ return {
21
+ valid: false,
22
+ error: new SecretNotSupportedError({
23
+ // See docs/decisions/DR-003-settings-ledger-invariants.md for the invariant behind this refusal; keep external identifiers out of caller-facing messages.
24
+ message: "secret:true definitions are not supported in the core-only subset",
25
+ }),
26
+ };
27
+ }
28
+ if (!NAMESPACE_FENCE[input.ownerKind](input)) {
29
+ return {
30
+ valid: false,
31
+ error: new DefinitionInvalidError(
32
+ // See docs/decisions/DR-003-settings-ledger-invariants.md for the invariant behind this refusal; keep external identifiers out of caller-facing messages.
33
+ `namespace '${input.namespace}' does not match the owner fence for owner_kind '${input.ownerKind}'`),
34
+ };
35
+ }
36
+ if (input.scopes < 1 || input.scopes > 7) {
37
+ return {
38
+ valid: false,
39
+ error: new DefinitionInvalidError(`scopes bitmask ${input.scopes} is out of range 1..7`),
40
+ };
41
+ }
42
+ // a site-owned def (workspace_id NOT NULL) may never declare the global scope bit. See docs/decisions/DR-003-settings-ledger-invariants.md.
43
+ if (input.workspaceId !== null && (input.scopes & SCOPE_BIT.global) !== 0) {
44
+ return {
45
+ valid: false,
46
+ error: new DefinitionInvalidError(
47
+ // See docs/decisions/DR-003-settings-ledger-invariants.md for the invariant behind this refusal; keep external identifiers out of caller-facing messages.
48
+ "a site-owned definition may not declare the global scope bit"),
49
+ };
50
+ }
51
+ // Totality (behavior.spec §3): every non-secret def needs a non-null default so
52
+ // getEffective is always total and factory-reset is provably bootable. See docs/decisions/DR-003-settings-ledger-invariants.md.
53
+ if (input.defaultValue === null) {
54
+ return {
55
+ valid: false,
56
+ error: new DefinitionInvalidError(
57
+ // See docs/decisions/DR-003-settings-ledger-invariants.md for the invariant behind this refusal; keep external identifiers out of caller-facing messages.
58
+ "non-secret definitions require a non-null default_json (totality)"),
59
+ };
60
+ }
61
+ return { valid: true };
62
+ }
63
+ export function validateValueAgainstSchema(schema, value) {
64
+ if (value === null)
65
+ return schema.nullable === true;
66
+ switch (schema.type) {
67
+ case "string":
68
+ return typeof value === "string";
69
+ case "number":
70
+ return typeof value === "number";
71
+ case "boolean":
72
+ return typeof value === "boolean";
73
+ case "enum":
74
+ return typeof value === "string" && schema.values.includes(value);
75
+ case "json":
76
+ // Any JSON value is accepted; internal shape/length validation is the
77
+ // registering feature's own write-path job — see
78
+ // docs/decisions/settings-json-schema-variant.md.
79
+ return true;
80
+ }
81
+ }
82
+ /** Identity-registry of total coercers keyed by `coercionTag`. `"identity"` is always registered. See docs/decisions/DR-003-settings-ledger-invariants.md. */
83
+ const coercers = new Map([["identity", ({ value }) => value]]);
84
+ export function registerCoercer({ tag, fn }, _optional = {}) {
85
+ coercers.set(tag, fn);
86
+ }
87
+ const cacheByRepo = new WeakMap();
88
+ function getCacheStore(repo) {
89
+ let store = cacheByRepo.get(repo);
90
+ if (!store) {
91
+ store = { definitions: new Map(), epoch: new Map() };
92
+ cacheByRepo.set(repo, store);
93
+ }
94
+ return store;
95
+ }
96
+ function namespaceEpoch(store, namespace) {
97
+ return store.epoch.get(namespace) ?? 0;
98
+ }
99
+ function workspaceCachePart(workspaceId) {
100
+ return workspaceId ?? "platform";
101
+ }
102
+ function definitionCacheKey(workspaceId, namespace, key, epoch) {
103
+ return `def:${workspaceCachePart(workspaceId)}:${namespace}:${key}:e${epoch}`;
104
+ }
105
+ /**
106
+ * Bumps the namespace's definition-cache epoch (lazy invalidation) — every
107
+ * previously-cached definition-cache entry for this
108
+ * namespace (across every workspace) becomes unreachable on the next read,
109
+ * without needing to enumerate tenants. Call after any write that changes a
110
+ * definition's identity/shape/status in this namespace: register, rename
111
+ * (both the old and new namespace), retype, deprecate, tombstone.
112
+ */
113
+ export function invalidateDefinitionNamespaceCache(repo, namespace) {
114
+ const store = getCacheStore(repo);
115
+ store.epoch.set(namespace, namespaceEpoch(store, namespace) + 1);
116
+ }
117
+ /**
118
+ * Purge support — now a no-op, deliberately kept rather than deleted.
119
+ *
120
+ * This cleared the workspace- and user-scope layer-cache entries a tenant/principal teardown
121
+ * invalidated, because a purge deletes an unbounded number of value rows across an unknown set of
122
+ * namespaces and per-namespace single-key invalidation was not practical for it. With the layer
123
+ * cache gone (see this module's cache header) there is nothing left to clear: the rows are deleted,
124
+ * and the next read goes to the repo and sees them gone.
125
+ *
126
+ * Kept as an explicit no-op instead of removed because of what it guards. `purge-service.ts` calls
127
+ * it as the last step of deleting a tenant's settings, and its correctness question — "can a purged
128
+ * value still be read back?" — is exactly the kind that must be answered again if anyone
129
+ * reintroduces caching. Deleting the call site would remove the place that question is asked. A
130
+ * cache added without restoring a purge hook is a tenant-teardown leak, so this stays as the seam.
131
+ */
132
+ export function invalidateWorkspaceSettingsCache(_required, _optional = {}) {
133
+ // Intentionally empty — see doc above. Do not delete without re-reading `purge-service.ts`.
134
+ }
135
+ /**
136
+ * Follows an alias marker (depth <=1) to the current row and returns it as
137
+ * stored — status intact, including `tombstone`. `resolveDefinition` (below)
138
+ * is the read-path wrapper that collapses tombstone to typed-absent;
139
+ * `write-service.ts` uses this raw form directly so it can report
140
+ * `DEFINITION_TOMBSTONED` distinctly from "not found".
141
+ * Malformed alias targets, cycles and chains deeper than one fail closed as absent.
142
+ * See docs/decisions/DR-003-settings-ledger-invariants.md.
143
+ */
144
+ export async function resolveDefinitionRaw(deps, input) {
145
+ // Namespace fencing makes `site.*` (non-null workspace_id) and. See docs/decisions/DR-003-settings-ledger-invariants.md.
146
+ // `core.*`/`theme.*` (null workspace_id) disjoint by construction, so a
147
+ // caller resolving inside a workspace context may still be asking for a
148
+ // platform definition. Try the caller's own partition first, then fall
149
+ // back to the platform (null) partition.
150
+ const visited = new Set();
151
+ let cursor = input;
152
+ for (let depth = 0; depth <= 1; depth++) {
153
+ const address = JSON.stringify([cursor.workspaceId, cursor.namespace, cursor.key]);
154
+ if (visited.has(address))
155
+ return null;
156
+ visited.add(address);
157
+ const found = (await deps.repo.findActiveDefinition(cursor)) ??
158
+ (cursor.workspaceId !== null
159
+ ? await deps.repo.findActiveDefinition({ ...cursor, workspaceId: null })
160
+ : null);
161
+ if (!found)
162
+ return null;
163
+ if (found.status !== "alias")
164
+ return found;
165
+ if (depth === 1 || found.aliasOfNamespace == null || found.aliasOfKey == null)
166
+ return null;
167
+ cursor = {
168
+ namespace: found.aliasOfNamespace,
169
+ key: found.aliasOfKey,
170
+ workspaceId: input.workspaceId,
171
+ };
172
+ }
173
+ return null;
174
+ }
175
+ /**
176
+ * The read-path resolver: typed-absent (`null`) for a tombstoned or
177
+ * missing key. Cached — keyed by
178
+ * `(workspaceId, namespace, key, namespace-epoch)`, so it is workspace-
179
+ * qualified by construction and invalidated wholesale by
180
+ * `invalidateDefinitionNamespaceCache` on any definition-lifecycle write
181
+ * (register/rename/retype/deprecate/tombstone). `resolveDefinitionRaw`
182
+ * itself stays uncached — the write chokepoint (`write-service.ts`) calls it
183
+ * directly so authorization/validation decisions are always made against
184
+ * live data, never a cached read.
185
+ */
186
+ export async function resolveDefinition(deps, input) {
187
+ const store = getCacheStore(deps.repo);
188
+ const epoch = namespaceEpoch(store, input.namespace);
189
+ const cacheKey = definitionCacheKey(input.workspaceId, input.namespace, input.key, epoch);
190
+ if (store.definitions.has(cacheKey))
191
+ return store.definitions.get(cacheKey);
192
+ const found = await resolveDefinitionRaw(deps, input);
193
+ const resolved = !found || found.status === "tombstone" ? null : found;
194
+ store.definitions.set(cacheKey, resolved);
195
+ return resolved;
196
+ }
197
+ /**
198
+ * — total for a live key: never throws, never returns
199
+ * undefined. Precedence `user ?? workspace ?? global ?? default`. A
200
+ * `cleared` row is treated as absent at that layer (behavior.spec §1.2).
201
+ *
202
+ * Per-layer reads go straight to the repo (see this module's cache header for
203
+ * why the per-layer value cache that used to wrap these three calls does not
204
+ * exist here).
205
+ *
206
+ * @complexity O(1) — up to 3 layer reads + 1 definition read.
207
+ * @overallScore 100
208
+ * See docs/decisions/DR-003-settings-ledger-invariants.md.
209
+ */
210
+ export async function getEffective(deps, input) {
211
+ const definition = await resolveDefinition(deps, {
212
+ namespace: input.namespace,
213
+ key: input.key,
214
+ workspaceId: input.scopeContext.workspaceId ?? null,
215
+ });
216
+ if (!definition)
217
+ return null;
218
+ const coerce = (value, defVersion) => {
219
+ if (defVersion === definition.version)
220
+ return value;
221
+ const coercer = coercers.get(definition.coercionTag ?? "identity") ?? coercers.get("identity");
222
+ return coercer({ value });
223
+ };
224
+ // Every layer read below goes straight to the repo. See this module's cache header for why there
225
+ // is no per-layer cache wrapping these three calls.
226
+ if (input.scopeContext.workspaceId && input.scopeContext.principalId) {
227
+ const workspaceId = input.scopeContext.workspaceId;
228
+ const principalId = input.scopeContext.principalId;
229
+ const userValue = await deps.repo.getUserValue({ workspaceId, principalId, settingId: definition.settingId });
230
+ if (userValue && userValue.state === "set") {
231
+ return {
232
+ value: coerce(userValue.valueJson, userValue.defVersion),
233
+ sourceLayer: "user",
234
+ defVersion: definition.version,
235
+ };
236
+ }
237
+ }
238
+ if (input.scopeContext.workspaceId) {
239
+ const workspaceId = input.scopeContext.workspaceId;
240
+ const workspaceValue = await deps.repo.getWorkspaceValue({ workspaceId, settingId: definition.settingId });
241
+ if (workspaceValue && workspaceValue.state === "set") {
242
+ return {
243
+ value: coerce(workspaceValue.valueJson, workspaceValue.defVersion),
244
+ sourceLayer: "workspace",
245
+ defVersion: definition.version,
246
+ };
247
+ }
248
+ }
249
+ const globalValue = await deps.repo.getGlobalValue(definition.settingId);
250
+ if (globalValue && globalValue.state === "set") {
251
+ return {
252
+ value: coerce(globalValue.valueJson, globalValue.defVersion),
253
+ sourceLayer: "global",
254
+ defVersion: definition.version,
255
+ };
256
+ }
257
+ return { value: definition.defaultValue, sourceLayer: "default", defVersion: definition.version };
258
+ }
259
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1,54 @@
1
+ import type { IdGenerator } from "../primitives/index.js";
2
+ import type { Clock } from "../primitives/index.js";
3
+ /**
4
+ * @file Settings tools: three reads and three bounded value writes. Bulk reset and definition
5
+ * registration stay unwired. The generic tools reuse write-service authorization/validation and
6
+ * return the previous override. Key policy and the optional host confirmation port enforce the
7
+ * owner's 2026-10-01 decision: privacy/instructions/self-configuration require a human card.
8
+ * Direct reads supply their own permission checks; writes pre-check before reading undo values
9
+ * and the service repeats authorization at mutation time. No host HTTP/runtime imports.
10
+ */
11
+ import type { LegacyToolAuthorizeFn as AuthorizeFn } from "../tool-permissions.js";
12
+ import { type SettingsWriteConfirmation } from "./agent-value-write-tools.js";
13
+ import { type AgentSettingWriteRule } from "./agent-write-denylist.js";
14
+ import { type DerivedRiskByToolId } from "../registration-kit.js";
15
+ import type { ToolExecutionOptions, ToolRegistration } from "../tool-registry.js";
16
+ import type { SettingsPrincipalLookupPort } from "./principal-lookup.js";
17
+ import type { SettingsRepoPort } from "./ports.js";
18
+ import { set as setSettingValue } from "./write-service.js";
19
+ /**
20
+ * The exact slice of a host's route-deps bag Settings' tool handlers read. Declared structurally
21
+ * (rather than importing any host's own route-deps type) so this module carries no back-edge into
22
+ * a host's composition root. A host satisfies this structurally by passing its existing route deps
23
+ * object after binding kernel clock/IDs and an active, workspace-scoped principal lookup.
24
+ */
25
+ export interface SettingsToolDeps {
26
+ authorize: AuthorizeFn;
27
+ workspaceId: string;
28
+ clock: Clock;
29
+ idGen: IdGenerator;
30
+ settingsReady: Promise<void>;
31
+ settingsUiTabsReady: Promise<void>;
32
+ settingsRepo: SettingsRepoPort;
33
+ principalRepo: SettingsPrincipalLookupPort;
34
+ /** Host extensions: explicit deny or human-confirmation rules, never agent inputs. */
35
+ extraDeniedSettings?: readonly AgentSettingWriteRule[];
36
+ extraConfirmationSettings?: readonly AgentSettingWriteRule[];
37
+ /** Host policy replaces the package defaults; extensions still add explicit protected keys. */
38
+ confirmationSettings?: readonly AgentSettingWriteRule[];
39
+ /** Must obtain an authenticated human card decision; absent means protected keys fail closed.
40
+ * `options` is the invoking transport's execution options (its `emitSurface`), forwarded from the
41
+ * tool handler's second argument: the card can only reach a human through that seam. */
42
+ confirmWrite?: (request: SettingsWriteConfirmation, options?: ToolExecutionOptions) => Promise<boolean>;
43
+ /** Host's validation wrapper; defaults to the package's existing set chokepoint. */
44
+ setValue?: typeof setSettingValue;
45
+ }
46
+ /**
47
+ * This wiring layer's OWN risk classification, authored from what each handler below actually
48
+ * calls. See `DerivedRiskByToolId` in the kit for why it is independent of the catalog's own
49
+ * `sideEffects` declaration — and note that the two excluded bulk/schema writes appear NOWHERE here, which is
50
+ * itself the strongest of the guards: an unclassified id cannot be wired at all.
51
+ */
52
+ export declare const settingsDerivedRisk: DerivedRiskByToolId;
53
+ export declare function buildSettingsRegistrations(routeDeps: SettingsToolDeps): ToolRegistration[];
54
+ //# sourceMappingURL=tool-registrations.d.ts.map