@jini-ai/cms 0.3.11 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +47 -19
- package/dist/content-types/agent-tools.d.ts +3 -56
- package/dist/content-types/agent-tools.js +22 -6
- package/dist/content-types/cleanup.d.ts +18 -13
- package/dist/content-types/cleanup.js +16 -11
- package/dist/content-types/errors.d.ts +57 -24
- package/dist/content-types/errors.js +56 -39
- package/dist/content-types/field-defs.d.ts +8 -4
- package/dist/content-types/field-defs.js +19 -14
- package/dist/content-types/index-provisioning.d.ts +61 -47
- package/dist/content-types/index-provisioning.js +90 -66
- package/dist/content-types/index.d.ts +4 -3
- package/dist/content-types/lifecycle-dispatch.d.ts +11 -5
- package/dist/content-types/lifecycle-dispatch.js +5 -4
- package/dist/content-types/lifecycle.d.ts +23 -18
- package/dist/content-types/lifecycle.js +71 -64
- package/dist/content-types/repo.memory.d.ts +5 -4
- package/dist/content-types/repo.memory.js +3 -2
- package/dist/content-types/tool-registrations.d.ts +6 -8
- package/dist/content-types/tool-registrations.js +24 -28
- package/dist/content-types/types.d.ts +17 -13
- package/dist/content-types/types.js +11 -5
- package/dist/content-types/write-service.d.ts +20 -22
- package/dist/content-types/write-service.js +78 -75
- package/dist/core/authorization.d.ts +15 -0
- package/dist/core/authorization.js +2 -0
- package/dist/core/commands/change-set.d.ts +15 -7
- package/dist/core/commands/command.d.ts +45 -26
- package/dist/core/commands/command.js +73 -63
- package/dist/core/entity-liveness.d.ts +11 -7
- package/dist/core/entity-liveness.js +5 -4
- package/dist/core/index.d.ts +12 -5
- package/dist/core/index.js +10 -4
- package/dist/core/ports.d.ts +25 -24
- package/dist/core/ports.js +1 -0
- package/dist/core/tools/args.d.ts +49 -0
- package/dist/core/tools/args.js +46 -0
- package/dist/core/tools/index.d.ts +3 -0
- package/dist/core/tools/index.js +3 -0
- package/dist/entries/agent-tools.d.ts +1 -73
- package/dist/entries/agent-tools.js +19 -13
- package/dist/entries/errors.d.ts +28 -14
- package/dist/entries/errors.js +17 -11
- package/dist/entries/field-validation.d.ts +5 -3
- package/dist/entries/field-validation.js +7 -5
- package/dist/entries/index.d.ts +2 -2
- package/dist/entries/lifecycle-dispatch.d.ts +5 -2
- package/dist/entries/lifecycle-dispatch.js +2 -1
- package/dist/entries/list.d.ts +4 -2
- package/dist/entries/list.js +4 -3
- package/dist/entries/repo.memory.d.ts +12 -10
- package/dist/entries/repo.memory.js +11 -10
- package/dist/entries/tool-registrations.d.ts +6 -8
- package/dist/entries/tool-registrations.js +42 -41
- package/dist/entries/types.d.ts +0 -7
- package/dist/entries/write-service.d.ts +28 -29
- package/dist/entries/write-service.js +114 -105
- package/dist/http/settings/cms-adapter.d.ts +20 -0
- package/dist/http/settings/cms-adapter.js +124 -0
- package/dist/http/settings/contracts.d.ts +135 -0
- package/dist/http/settings/contracts.js +2 -0
- package/dist/http/settings/http.d.ts +10 -0
- package/dist/http/settings/http.js +211 -0
- package/dist/http/settings/index.d.ts +5 -0
- package/dist/http/settings/index.js +4 -0
- package/dist/http/settings/shared.d.ts +68 -0
- package/dist/http/settings/shared.js +54 -0
- package/dist/http/settings/stream.d.ts +24 -0
- package/dist/http/settings/stream.js +147 -0
- package/dist/media/agent-tools.d.ts +1 -66
- package/dist/media/agent-tools.js +5 -1
- package/dist/media/blob-gc-lock.d.ts +6 -2
- package/dist/media/blob-gc-lock.js +4 -2
- package/dist/media/blob-gc.d.ts +64 -51
- package/dist/media/blob-gc.js +62 -52
- package/dist/media/blob-key.d.ts +1 -1
- package/dist/media/content-type-sniffer.d.ts +3 -1
- package/dist/media/content-type-sniffer.js +1 -1
- package/dist/media/html-attributes.d.ts +9 -5
- package/dist/media/html-attributes.js +8 -6
- package/dist/media/image-transformer.js +1 -1
- package/dist/media/image-transformer.sharp.d.ts +6 -0
- package/dist/media/image-transformer.sharp.js +12 -6
- package/dist/media/import/fetch-image.d.ts +24 -0
- package/dist/media/import/fetch-image.js +137 -0
- package/dist/media/import/index.d.ts +3 -0
- package/dist/media/import/index.js +3 -0
- package/dist/media/import/ports.d.ts +38 -0
- package/dist/media/index.d.ts +7 -1
- package/dist/media/index.js +1 -0
- package/dist/media/media-service.d.ts +14 -10
- package/dist/media/media-service.js +57 -52
- package/dist/media/ports.d.ts +3 -2
- package/dist/media/rendition-service.d.ts +3 -3
- package/dist/media/rendition-service.js +38 -37
- package/dist/media/repo.memory.d.ts +18 -7
- package/dist/media/repo.memory.js +12 -6
- package/dist/media/tool-registrations.d.ts +16 -15
- package/dist/media/tool-registrations.js +84 -72
- package/dist/media/transform-lock.d.ts +22 -12
- package/dist/media/transform-lock.js +14 -13
- package/dist/media/transform-registry.d.ts +3 -3
- package/dist/media/transform-registry.js +19 -18
- package/dist/media/transform-types.d.ts +8 -3
- package/dist/media/transform-types.js +10 -5
- package/dist/media/types.d.ts +21 -4
- package/dist/media/types.js +15 -2
- package/dist/navigation/agent-tools.d.ts +1 -80
- package/dist/navigation/agent-tools.js +15 -3
- package/dist/navigation/contracts.d.ts +1 -1
- package/dist/navigation/index.d.ts +2 -2
- package/dist/navigation/menu-service.d.ts +57 -40
- package/dist/navigation/menu-service.js +80 -69
- package/dist/navigation/ports.d.ts +1 -1
- package/dist/navigation/read-model.d.ts +3 -1
- package/dist/navigation/read-model.js +2 -2
- package/dist/navigation/reconcile.d.ts +2 -2
- package/dist/navigation/reconcile.js +2 -1
- package/dist/navigation/repo.memory.d.ts +7 -3
- package/dist/navigation/repo.memory.js +4 -2
- package/dist/navigation/resolver.d.ts +4 -1
- package/dist/navigation/resolver.js +1 -1
- package/dist/navigation/tool-registrations.d.ts +6 -8
- package/dist/navigation/tool-registrations.js +44 -42
- package/dist/navigation/types.d.ts +1 -1
- package/dist/presentation/presentation.d.ts +11 -3
- package/dist/presentation/presentation.js +13 -6
- package/dist/presentation/repo.memory.d.ts +6 -2
- package/dist/presentation/repo.memory.js +3 -2
- package/dist/settings/agent-tools.d.ts +2 -75
- package/dist/settings/agent-tools.js +49 -45
- package/dist/settings/agent-value-write-tools.d.ts +22 -0
- package/dist/settings/agent-value-write-tools.js +154 -0
- package/dist/settings/agent-writable-preferences.js +8 -20
- 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 +11 -2
- package/dist/settings/change-feed.js +4 -4
- package/dist/settings/definitions-dispatch.d.ts +8 -3
- package/dist/settings/definitions-dispatch.js +5 -5
- package/dist/settings/dictionaries/index.d.ts +5 -2
- package/dist/settings/dictionaries/index.js +2 -2
- package/dist/settings/dictionaries/settings-dialog.ar.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.bn.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.de.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.es.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.fa.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.fr.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.hi.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.hu.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.id.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.it.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ja.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ko.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.pl.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ru.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.th.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.tr.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.uk.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ur.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js +0 -1
- package/dist/settings/ensure-definitions.d.ts +5 -5
- package/dist/settings/errors.d.ts +7 -1
- package/dist/settings/errors.js +7 -1
- package/dist/settings/index.d.ts +4 -1
- package/dist/settings/index.js +1 -0
- package/dist/settings/ports.d.ts +6 -5
- package/dist/settings/principal-lookup.d.ts +9 -0
- package/dist/settings/principal-lookup.js +2 -0
- package/dist/settings/purge-service.d.ts +5 -4
- package/dist/settings/purge-service.js +3 -2
- package/dist/settings/settings.d.ts +22 -7
- package/dist/settings/settings.js +47 -26
- package/dist/settings/tool-registrations.d.ts +25 -32
- package/dist/settings/tool-registrations.js +66 -56
- package/dist/settings/types.d.ts +1 -1
- package/dist/settings/ui-tab-definitions.d.ts +1 -1
- package/dist/settings/write-service.d.ts +32 -26
- package/dist/settings/write-service.js +26 -20
- package/dist/taxonomy/agent-tools.d.ts +1 -66
- package/dist/taxonomy/agent-tools.js +25 -15
- package/dist/taxonomy/content-lookup.js +1 -1
- package/dist/taxonomy/index.d.ts +2 -2
- package/dist/taxonomy/merge-term.d.ts +7 -4
- package/dist/taxonomy/merge-term.js +7 -5
- package/dist/taxonomy/repo.memory.d.ts +42 -19
- package/dist/taxonomy/repo.memory.js +14 -12
- package/dist/taxonomy/validation-chain.d.ts +39 -18
- package/dist/taxonomy/validation-chain.js +35 -25
- package/dist/taxonomy/write-service.d.ts +103 -44
- package/dist/taxonomy/write-service.js +221 -179
- package/dist/trash/adapters/record-store.d.ts +52 -0
- package/dist/trash/adapters/record-store.js +36 -0
- package/dist/trash/cursor.d.ts +20 -0
- package/dist/trash/cursor.js +36 -0
- package/dist/trash/follow-ups.d.ts +76 -0
- package/dist/trash/follow-ups.js +46 -0
- package/dist/trash/index.d.ts +8 -0
- package/dist/trash/index.js +8 -0
- package/dist/trash/ports.d.ts +308 -0
- package/dist/trash/ports.js +2 -0
- package/dist/trash/repo.memory.d.ts +50 -0
- package/dist/trash/repo.memory.js +114 -0
- package/dist/trash/sweeper.d.ts +77 -0
- package/dist/trash/sweeper.js +127 -0
- package/dist/trash/write-service.d.ts +100 -0
- package/dist/trash/write-service.js +242 -0
- package/dist/workspace/agent-tools.d.ts +1 -59
- package/dist/workspace/agent-tools.js +27 -12
- package/dist/workspace/create.d.ts +31 -6
- package/dist/workspace/create.js +18 -5
- package/dist/workspace/delete.d.ts +18 -13
- package/dist/workspace/delete.js +20 -12
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/repo.memory.d.ts +19 -4
- package/dist/workspace/repo.memory.js +26 -4
- package/dist/workspace/tool-registrations.d.ts +2 -2
- package/dist/workspace/tool-registrations.js +13 -13
- package/dist/workspace/update.d.ts +6 -5
- package/dist/workspace/update.js +9 -8
- package/package.json +44 -38
- package/dist/identity/admin-crud-service.d.ts +0 -280
- package/dist/identity/admin-crud-service.js +0 -468
- package/dist/identity/agent-tool-input.d.ts +0 -58
- package/dist/identity/agent-tool-input.js +0 -126
- package/dist/identity/agent-tools.d.ts +0 -101
- package/dist/identity/agent-tools.js +0 -290
- package/dist/identity/auth-service.d.ts +0 -118
- package/dist/identity/auth-service.js +0 -170
- package/dist/identity/authorize.d.ts +0 -90
- package/dist/identity/authorize.js +0 -94
- package/dist/identity/grant-service.d.ts +0 -198
- package/dist/identity/grant-service.js +0 -361
- package/dist/identity/hasher.d.ts +0 -39
- package/dist/identity/hasher.js +0 -39
- package/dist/identity/index.d.ts +0 -62
- package/dist/identity/index.js +0 -54
- package/dist/identity/password-policy.d.ts +0 -57
- package/dist/identity/password-policy.js +0 -66
- package/dist/identity/permission-migrations.d.ts +0 -86
- package/dist/identity/permission-migrations.js +0 -67
- package/dist/identity/permissions.d.ts +0 -35
- package/dist/identity/permissions.js +0 -603
- package/dist/identity/ports.d.ts +0 -201
- package/dist/identity/repo.memory.d.ts +0 -164
- package/dist/identity/repo.memory.js +0 -224
- package/dist/identity/seed.d.ts +0 -54
- package/dist/identity/seed.js +0 -429
- package/dist/identity/tool-registrations.d.ts +0 -50
- package/dist/identity/tool-registrations.js +0 -419
- package/dist/identity/types.d.ts +0 -185
- package/dist/identity/types.js +0 -64
- package/dist/identity/username.d.ts +0 -12
- package/dist/identity/username.js +0 -14
- /package/dist/{identity → media/import}/ports.js +0 -0
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { adaptLegacyAuthorize } from "../core/tools/index.js";
|
|
2
|
+
import { buildSettingsValueWriteHandler } from "./agent-value-write-tools.js";
|
|
3
|
+
import { AGENT_WRITE_CONFIRMATION_SETTINGS, AGENT_WRITE_DENIED_SETTINGS, findAgentWriteRule } from "./agent-write-denylist.js";
|
|
4
|
+
import { buildDomainRegistrations, indexCatalogById, optionalString, requireInputRecord, requireNoInput, requireString, withSchemaOnRejection } from "@jini-ai/core";
|
|
5
|
+
import { requireToolPermission } from "../core/tools/index.js";
|
|
2
6
|
import { getSettingsAgentToolCatalog } from "./agent-tools.js";
|
|
3
7
|
import { AGENT_PREFERENCE_WRITE_SCOPE, AGENT_WRITABLE_PREFERENCE_IDS, resolveAgentWritablePreference, } from "./agent-writable-preferences.js";
|
|
4
8
|
import { DefinitionNotFoundError, DefinitionTombstonedError, ScopeNotAllowedError, ValueValidationFailedError, } from "./errors.js";
|
|
5
|
-
import { getEffective, resolveDefinition } from "./settings.js";
|
|
9
|
+
import { getEffective, resolveDefinition, resolveDefinitionRaw } from "./settings.js";
|
|
10
|
+
import { SCOPE_BIT } from "./types.js";
|
|
6
11
|
import { set as setSettingValue } from "./write-service.js";
|
|
7
|
-
const CATALOG_BY_ID = indexCatalogById(getSettingsAgentToolCatalog());
|
|
12
|
+
const CATALOG_BY_ID = indexCatalogById({ catalog: getSettingsAgentToolCatalog() });
|
|
8
13
|
/** Permission gating a read that names a DIFFERENT principal than the caller — mirrors a host's
|
|
9
14
|
* own HTTP admin layer's equivalent constant (kept as a literal here per this file's header; the
|
|
10
15
|
* two are data, not logic, so duplication carries no drift risk beyond a string comparison). */
|
|
@@ -31,11 +36,7 @@ async function resolveOwnOrOtherPrincipalRead(routeDeps, required) {
|
|
|
31
36
|
const target = requestedPrincipalId ?? callerPrincipalId;
|
|
32
37
|
if (target === callerPrincipalId)
|
|
33
38
|
return target;
|
|
34
|
-
await requireToolPermission(routeDeps, {
|
|
35
|
-
principalId: callerPrincipalId,
|
|
36
|
-
permission: CROSS_PRINCIPAL_SETTINGS_READ_PERMISSION,
|
|
37
|
-
entityType: "setting-value",
|
|
38
|
-
});
|
|
39
|
+
await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: callerPrincipalId, permission: CROSS_PRINCIPAL_SETTINGS_READ_PERMISSION }, { entityType: "setting-value" });
|
|
39
40
|
return requestedPrincipalId;
|
|
40
41
|
}
|
|
41
42
|
function layerValueOf(record) {
|
|
@@ -79,11 +80,15 @@ function isPreferenceShapeRejection(error) {
|
|
|
79
80
|
/**
|
|
80
81
|
* This wiring layer's OWN risk classification, authored from what each handler below actually
|
|
81
82
|
* calls. See `DerivedRiskByToolId` in the kit for why it is independent of the catalog's own
|
|
82
|
-
* `sideEffects` declaration — and note that the
|
|
83
|
+
* `sideEffects` declaration — and note that the two excluded bulk/schema writes appear NOWHERE here, which is
|
|
83
84
|
* itself the strongest of the guards: an unclassified id cannot be wired at all.
|
|
84
85
|
*/
|
|
85
86
|
export const settingsDerivedRisk = new Map([
|
|
86
|
-
// ->
|
|
87
|
+
// -> write-service.set(): one value override and one revision, with optional human confirmation.
|
|
88
|
+
["settings_set_value", "mutates-durable-state"],
|
|
89
|
+
// -> write-service.clear(): one override marked cleared and one revision; no definition/delete.
|
|
90
|
+
["settings_clear_value", "mutates-durable-state"],
|
|
91
|
+
// -> settingsRepo.listActiveDefinitions() x2 + resolveDefinitionRaw() for aliases: reads only.
|
|
87
92
|
["settings_list_definitions", "none"],
|
|
88
93
|
// -> settingsRepo.listActiveDefinitions() x2 + getEffective() per key: reads only, no write path.
|
|
89
94
|
["settings_get_effective", "none"],
|
|
@@ -97,11 +102,6 @@ export const settingsDerivedRisk = new Map([
|
|
|
97
102
|
]);
|
|
98
103
|
/** Settings catalog entries this pass does not wire, and why — see `agent-tools.ts`'s own per-entry comments for the full reasoning. */
|
|
99
104
|
const UNWIRED_SETTINGS_TOOL_IDS = new Set([
|
|
100
|
-
// EXCLUDED BY DESIGN: generic "set any setting key" — a typical human admin UI for this domain
|
|
101
|
-
// is itself an uncurated free-text/raw-JSON editor, not a fixed named list (see agent-tools.ts
|
|
102
|
-
// file header).
|
|
103
|
-
"settings_set",
|
|
104
|
-
"settings_clear",
|
|
105
105
|
// EXCLUDED BY DESIGN: bulk variant — clears every value in an operator-named namespace at a
|
|
106
106
|
// scope in one call, irreversible.
|
|
107
107
|
"settings_reset",
|
|
@@ -112,27 +112,44 @@ const UNWIRED_SETTINGS_TOOL_IDS = new Set([
|
|
|
112
112
|
]);
|
|
113
113
|
export function buildSettingsRegistrations(routeDeps) {
|
|
114
114
|
const handlers = {
|
|
115
|
+
settings_set_value: buildSettingsValueWriteHandler(routeDeps, "settings_set_value"),
|
|
116
|
+
settings_clear_value: buildSettingsValueWriteHandler(routeDeps, "settings_clear_value"),
|
|
115
117
|
settings_list_definitions: async (ctx) => {
|
|
116
|
-
requireNoInput(ctx.input);
|
|
118
|
+
requireNoInput({ input: ctx.input });
|
|
117
119
|
await routeDeps.settingsReady;
|
|
118
|
-
await requireToolPermission(routeDeps,
|
|
120
|
+
await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: ctx.principal.id, permission: "settings.read.definitions" }, { entityType: "setting-definition" });
|
|
119
121
|
const [platformDefs, siteDefs] = await Promise.all([
|
|
120
122
|
routeDeps.settingsRepo.listActiveDefinitions({ workspaceId: null }),
|
|
121
123
|
routeDeps.settingsRepo.listActiveDefinitions({ workspaceId: routeDeps.workspaceId }),
|
|
122
124
|
]);
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
+
const deniedRules = [...AGENT_WRITE_DENIED_SETTINGS, ...(routeDeps.extraDeniedSettings ?? [])];
|
|
126
|
+
const confirmationRules = [...AGENT_WRITE_CONFIRMATION_SETTINGS, ...(routeDeps.extraConfirmationSettings ?? [])];
|
|
127
|
+
// Only aliases need an extra lookup. Their target's schema/scope and BOTH coordinates'
|
|
128
|
+
// key policies must agree with the write handler; dangling/tombstoned targets fail closed.
|
|
129
|
+
const data = (await Promise.all([...platformDefs, ...siteDefs].map(async (def) => {
|
|
130
|
+
const resolved = def.status === "alias"
|
|
131
|
+
? await resolveDefinitionRaw({ repo: routeDeps.settingsRepo }, { namespace: def.namespace, key: def.key, workspaceId: routeDeps.workspaceId })
|
|
132
|
+
: def;
|
|
133
|
+
const coordinates = resolved ? [def, resolved] : [def];
|
|
134
|
+
const denied = coordinates.some((coordinate) => !!findAgentWriteRule(deniedRules, coordinate));
|
|
135
|
+
const confirmationRequired = coordinates.some((coordinate) => !!findAgentWriteRule(confirmationRules, coordinate));
|
|
136
|
+
const scopes = resolved?.scopes ?? def.scopes;
|
|
137
|
+
return { namespace: def.namespace, key: def.key, ownerKind: def.ownerKind, scopes, status: def.status, version: def.version,
|
|
138
|
+
schema: resolved?.schema ?? def.schema,
|
|
139
|
+
agentWritable: !!resolved && resolved.status !== "tombstone" && !denied && (!confirmationRequired || !!routeDeps.confirmWrite) && (scopes & (SCOPE_BIT.workspace | SCOPE_BIT.user)) !== 0,
|
|
140
|
+
confirmationRequired };
|
|
141
|
+
})))
|
|
125
142
|
.sort((a, b) => a.namespace.localeCompare(b.namespace) || a.key.localeCompare(b.key));
|
|
126
143
|
return { data };
|
|
127
144
|
},
|
|
128
145
|
settings_get_effective: async (ctx) => {
|
|
129
|
-
const input = requireInputRecord(ctx.input);
|
|
130
|
-
const namespace = requireString(input, "namespace");
|
|
146
|
+
const input = requireInputRecord({ input: ctx.input });
|
|
147
|
+
const namespace = requireString({ input: input, key: "namespace" });
|
|
131
148
|
await routeDeps.settingsReady;
|
|
132
|
-
await requireToolPermission(routeDeps,
|
|
149
|
+
await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: ctx.principal.id, permission: "settings.read" }, { entityType: "setting-value" });
|
|
133
150
|
const workspaceId = routeDeps.workspaceId;
|
|
134
151
|
const principalId = await resolveOwnOrOtherPrincipalRead(routeDeps, {
|
|
135
|
-
requestedPrincipalId: optionalString(input, "principalId"),
|
|
152
|
+
requestedPrincipalId: optionalString({ input: input, key: "principalId" }),
|
|
136
153
|
callerPrincipalId: ctx.principal.id,
|
|
137
154
|
});
|
|
138
155
|
const [platformDefs, siteDefs] = await Promise.all([
|
|
@@ -149,14 +166,14 @@ export function buildSettingsRegistrations(routeDeps) {
|
|
|
149
166
|
return { data };
|
|
150
167
|
},
|
|
151
168
|
settings_get_raw: async (ctx) => {
|
|
152
|
-
const input = requireInputRecord(ctx.input);
|
|
153
|
-
const namespace = requireString(input, "namespace");
|
|
154
|
-
const key = requireString(input, "key");
|
|
169
|
+
const input = requireInputRecord({ input: ctx.input });
|
|
170
|
+
const namespace = requireString({ input: input, key: "namespace" });
|
|
171
|
+
const key = requireString({ input: input, key: "key" });
|
|
155
172
|
await routeDeps.settingsReady;
|
|
156
|
-
await requireToolPermission(routeDeps,
|
|
173
|
+
await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: ctx.principal.id, permission: "settings.read.raw" }, { entityType: "setting-value" });
|
|
157
174
|
const workspaceId = routeDeps.workspaceId;
|
|
158
175
|
const principalId = await resolveOwnOrOtherPrincipalRead(routeDeps, {
|
|
159
|
-
requestedPrincipalId: optionalString(input, "principalId"),
|
|
176
|
+
requestedPrincipalId: optionalString({ input: input, key: "principalId" }),
|
|
160
177
|
callerPrincipalId: ctx.principal.id,
|
|
161
178
|
});
|
|
162
179
|
const definition = await resolveDefinition({ repo: routeDeps.settingsRepo }, { namespace, key, workspaceId });
|
|
@@ -176,8 +193,8 @@ export function buildSettingsRegistrations(routeDeps) {
|
|
|
176
193
|
};
|
|
177
194
|
},
|
|
178
195
|
settings_set_ui_preference: async (ctx) => {
|
|
179
|
-
const input = requireInputRecord(ctx.input);
|
|
180
|
-
const settingId = requireString(input, "setting");
|
|
196
|
+
const input = requireInputRecord({ input: ctx.input });
|
|
197
|
+
const settingId = requireString({ input: input, key: "setting" });
|
|
181
198
|
// Re-checked here even though the published schema carries the same enum. See
|
|
182
199
|
// `agent-writable-preferences.ts` on why: the enum is enforced by a caller's descriptor
|
|
183
200
|
// validation, and this is the check that still holds if a stale or bypassed descriptor lets
|
|
@@ -201,40 +218,33 @@ export function buildSettingsRegistrations(routeDeps) {
|
|
|
201
218
|
// No `requireToolPermission` call here, unlike every read above. That is deliberate and is
|
|
202
219
|
// the opposite of the reads' situation: the read paths carry no `authorize()` of their own,
|
|
203
220
|
// so this layer must supply it, whereas `write-service.set()` IS the authorization
|
|
204
|
-
// chokepoint
|
|
221
|
+
// chokepoint and derives the permission itself from the scope and target. Adding a. See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
205
222
|
// pre-check would mean this file naming a permission string that the chokepoint might later
|
|
206
223
|
// derive differently — the exact fail-open drift `deriveRequiredPermission`'s own header
|
|
207
|
-
// (Red-Team
|
|
224
|
+
// (Red-Team ) says must not be duplicated. Omitting `principalId` is what makes the. See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
208
225
|
// derived permission `settings.user.self.write`.
|
|
209
226
|
const { value: stored, revisionSeq } = await withSchemaOnRejection({
|
|
210
227
|
toolId: "settings_set_ui_preference",
|
|
211
228
|
catalog: CATALOG_BY_ID,
|
|
212
|
-
isShapeRejection: isPreferenceShapeRejection,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
})
|
|
229
|
+
isShapeRejection: ({ error }) => isPreferenceShapeRejection(error), fn: () => setSettingValue({
|
|
230
|
+
deps: toWriteDeps(routeDeps),
|
|
231
|
+
input: {
|
|
232
|
+
namespace: preference.namespace,
|
|
233
|
+
key: preference.key,
|
|
234
|
+
scope: AGENT_PREFERENCE_WRITE_SCOPE,
|
|
235
|
+
value,
|
|
236
|
+
workspaceId: routeDeps.workspaceId,
|
|
237
|
+
// `principalId` is deliberately NOT passed. `write-service.set()` falls back to
|
|
238
|
+
// `callerPrincipalId` for a user-scoped write, so the caller's own layer is the only
|
|
239
|
+
// reachable target — and the self-vs-other permission derivation sees a self-write.
|
|
240
|
+
callerPrincipalId: ctx.principal.id,
|
|
241
|
+
authWorkspaceId: routeDeps.workspaceId,
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
});
|
|
228
245
|
return { setting: settingId, value: stored, scope: AGENT_PREFERENCE_WRITE_SCOPE, revisionSeq };
|
|
229
246
|
},
|
|
230
247
|
};
|
|
231
|
-
return buildDomainRegistrations({
|
|
232
|
-
domain: "settings",
|
|
233
|
-
catalogModule: "settings/agent-tools.ts",
|
|
234
|
-
catalog: CATALOG_BY_ID,
|
|
235
|
-
handlers,
|
|
236
|
-
derivedRisk: settingsDerivedRisk,
|
|
237
|
-
unwiredToolIds: UNWIRED_SETTINGS_TOOL_IDS,
|
|
238
|
-
});
|
|
248
|
+
return buildDomainRegistrations({ domain: "settings", catalogModule: "settings/agent-tools.ts", catalog: CATALOG_BY_ID, handlers, derivedRisk: settingsDerivedRisk }, { unwiredToolIds: UNWIRED_SETTINGS_TOOL_IDS });
|
|
239
249
|
}
|
|
240
250
|
//# sourceMappingURL=tool-registrations.js.map
|
package/dist/settings/types.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { Clock, IdGenerator, JsonValue, UUID } from "@jini-ai/core/primitives";
|
|
2
|
+
import type { SettingsPrincipalLookupPort } from "./principal-lookup.js";
|
|
3
3
|
import type { SettingsRepoPort } from "./ports.js";
|
|
4
4
|
import { type DefinitionInput } from "./settings.js";
|
|
5
5
|
import { type SettingScope, type SettingValueSchema } from "./types.js";
|
|
6
6
|
/**
|
|
7
|
-
* @file `SettingsWriteService` — the single write chokepoint
|
|
7
|
+
* @file `SettingsWriteService` — the single write chokepoint.
|
|
8
8
|
*
|
|
9
9
|
* Purpose:
|
|
10
10
|
* The ONLY value/definition-mutation path. Repo write methods
|
|
@@ -12,13 +12,14 @@ import { type SettingScope, type SettingValueSchema } from "./types.js";
|
|
|
12
12
|
* be called from outside this file (Code Review enforces this as a file-
|
|
13
13
|
* boundary check).
|
|
14
14
|
*
|
|
15
|
-
* Every export here: `authorize
|
|
16
|
-
* write value/definition row + revision in one transaction
|
|
15
|
+
* Every export here: `authorize` first (fail-closed) -> validate ->
|
|
16
|
+
* write value/definition row + revision in one transaction.
|
|
17
17
|
*
|
|
18
18
|
* `deriveRequiredPermission` is the sole source of truth for the self-vs-
|
|
19
|
-
* other permission rule (behavior.spec.md §1.3) — closes Red-Team
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* other permission rule (behavior.spec.md §1.3) — closes Red-Team.
|
|
20
|
+
* target-principal checks use a host-supplied active-principal lookup over core Principal;
|
|
21
|
+
* identity and membership policy remain at the host boundary.
|
|
22
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
22
23
|
*/
|
|
23
24
|
/** Matches `core/commands/command.ts`'s `AuthorizeFn` shape structurally — no import, kept decoupled. */
|
|
24
25
|
export type AuthorizeFn = (params: {
|
|
@@ -33,22 +34,23 @@ export type AuthorizeFn = (params: {
|
|
|
33
34
|
}>;
|
|
34
35
|
export interface SettingsWriteServiceDeps {
|
|
35
36
|
repo: SettingsRepoPort;
|
|
36
|
-
clock:
|
|
37
|
-
ids:
|
|
37
|
+
clock: Clock;
|
|
38
|
+
ids: IdGenerator;
|
|
38
39
|
authorize: AuthorizeFn;
|
|
39
|
-
/**
|
|
40
|
-
principals:
|
|
40
|
+
/** Host lookup must hide disabled and out-of-workspace principals. See docs/decisions/DR-003-settings-ledger-invariants.md. */
|
|
41
|
+
principals: SettingsPrincipalLookupPort;
|
|
41
42
|
}
|
|
42
43
|
/**
|
|
43
|
-
* behavior.spec.md §1.3 /
|
|
44
|
+
* behavior.spec.md §1.3 / `[internal-invariant]` — the self-vs-other
|
|
44
45
|
* permission derivation. `targetPrincipalId` omitted or equal to the caller
|
|
45
46
|
* -> `settings.user.self.write`; any other principal -> `settings.user.write`.
|
|
46
47
|
* A divergence between this function and any other implementation of the
|
|
47
|
-
* rule is a fail-open authorization bug (Red-Team
|
|
48
|
+
* rule is a fail-open authorization bug (Red-Team ) — this is the only
|
|
48
49
|
* place the rule may be encoded.
|
|
49
50
|
*
|
|
50
51
|
* @complexity O(1), pure.
|
|
51
52
|
* @overallScore 100
|
|
53
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
52
54
|
*/
|
|
53
55
|
export declare function deriveRequiredPermission(input: {
|
|
54
56
|
scope: SettingScope;
|
|
@@ -64,7 +66,7 @@ export interface RegisterDefinitionsRequired {
|
|
|
64
66
|
authWorkspaceId: UUID;
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
|
-
/**
|
|
69
|
+
/** chokepoint write: authorize `settings.definitions.manage` -> validate -> write + revision, one per definition, each its own tx. See docs/decisions/DR-003-settings-ledger-invariants.md. */
|
|
68
70
|
export declare function registerDefinitions(required: RegisterDefinitionsRequired): Promise<{
|
|
69
71
|
registered: string[];
|
|
70
72
|
}>;
|
|
@@ -101,13 +103,14 @@ export interface SetValueRequired {
|
|
|
101
103
|
* additive: omitted, behavior is unchanged (every pre-existing caller still gets the
|
|
102
104
|
* scope-derived permission). When a domain-settings module supplies this, the chokepoint
|
|
103
105
|
* authorizes THIS permission instead of the scope-derived one — still a mandatory
|
|
104
|
-
* `authorize
|
|
106
|
+
* `authorize` call before any write ( fail-closed discipline is not relaxed, only
|
|
105
107
|
* which permission string is checked).
|
|
106
|
-
|
|
108
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
109
|
+
*/
|
|
107
110
|
requiredPermissionOverride?: string | undefined;
|
|
108
111
|
};
|
|
109
112
|
}
|
|
110
|
-
/**
|
|
113
|
+
/** chokepoint write: authorize -> validate -> value + revision, same tx. See docs/decisions/DR-003-settings-ledger-invariants.md. */
|
|
111
114
|
export declare function set(required: SetValueRequired): Promise<{
|
|
112
115
|
value: JsonValue;
|
|
113
116
|
revisionSeq: number;
|
|
@@ -121,7 +124,7 @@ export interface ClearValueRequired {
|
|
|
121
124
|
workspaceId?: UUID | undefined;
|
|
122
125
|
principalId?: UUID | undefined;
|
|
123
126
|
callerPrincipalId: UUID;
|
|
124
|
-
/** Set by `resetNamespace` when looping `clear
|
|
127
|
+
/** Set by `resetNamespace` when looping `clear` in its own reset-authorized context — bypasses the inner authorize re-check, still writes a normal revision. See docs/decisions/DR-003-settings-ledger-invariants.md. */
|
|
125
128
|
skipAuthorize?: boolean | undefined;
|
|
126
129
|
/** Set by a caller that has ALREADY opened a transaction around this write —
|
|
127
130
|
* see `resetNamespace`. Explicit rather than an ambient depth counter,
|
|
@@ -132,7 +135,7 @@ export interface ClearValueRequired {
|
|
|
132
135
|
authWorkspaceId?: UUID | undefined;
|
|
133
136
|
};
|
|
134
137
|
}
|
|
135
|
-
/**
|
|
138
|
+
/** chokepoint write: authorize -> value + revision (state='cleared'), same tx. See docs/decisions/DR-003-settings-ledger-invariants.md. */
|
|
136
139
|
export declare function clear(required: ClearValueRequired): Promise<{
|
|
137
140
|
revisionSeq: number;
|
|
138
141
|
}>;
|
|
@@ -149,15 +152,16 @@ export interface ResetNamespaceRequired {
|
|
|
149
152
|
};
|
|
150
153
|
}
|
|
151
154
|
/**
|
|
152
|
-
*
|
|
153
|
-
* the matching `settings.reset.*` permission once, then loop `clear
|
|
155
|
+
* — an explicit, human-invoked orchestrator: authorize
|
|
156
|
+
* the matching `settings.reset.*` permission once, then loop `clear` for
|
|
154
157
|
* every setting in the namespace in the reset-authorized internal context
|
|
155
158
|
* (`skipAuthorize: true`) — the outer reset permission is sufficient on its
|
|
156
159
|
* own; each inner clear still emits its own `op='clear'` revision.
|
|
157
160
|
*
|
|
158
|
-
* `revisionSeqs` collects each inner `clear
|
|
161
|
+
* `revisionSeqs` collects each inner `clear` call's own `revisionSeq` so a
|
|
159
162
|
* host's admin HTTP route can surface the full contract without a second
|
|
160
163
|
* read. Existing callers that only read `.clearedCount` are unaffected.
|
|
164
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
161
165
|
*/
|
|
162
166
|
export declare function resetNamespace(required: ResetNamespaceRequired, keysInNamespace: string[]): Promise<{
|
|
163
167
|
clearedCount: number;
|
|
@@ -177,7 +181,7 @@ export interface RenameDefinitionRequired {
|
|
|
177
181
|
};
|
|
178
182
|
}
|
|
179
183
|
/**
|
|
180
|
-
* The rename mechanism
|
|
184
|
+
* The rename mechanism : a same-tx pair —
|
|
181
185
|
* (1) UPDATE the active definition row's `(namespace,key)` to the new name,
|
|
182
186
|
* same `setting_id`/`version` (ledgered `op='alias'`); (2) INSERT a fresh v1
|
|
183
187
|
* alias marker at the OLD name pointing at the new name. Because identity
|
|
@@ -191,6 +195,7 @@ export interface RenameDefinitionRequired {
|
|
|
191
195
|
* Never touches `schema`/`defaultValue`/`scopes` -- a pure rename can never
|
|
192
196
|
* trigger `RENAME_RETYPE_CONFLICT` by construction; that guard lives in
|
|
193
197
|
* `retypeDefinition` ("no rename+retype in one op").
|
|
198
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
194
199
|
*/
|
|
195
200
|
export declare function renameDefinition(required: RenameDefinitionRequired): Promise<{
|
|
196
201
|
settingId: string;
|
|
@@ -204,7 +209,7 @@ export interface RetypeDefinitionRequired {
|
|
|
204
209
|
workspaceId: UUID | null;
|
|
205
210
|
schema: SettingValueSchema;
|
|
206
211
|
defaultValue: JsonValue | null;
|
|
207
|
-
/** A total coercer id/tag (
|
|
212
|
+
/** A total coercer id/tag ( registry in `settings.ts`) for reading values recorded under any prior version. See docs/decisions/DR-003-settings-ledger-invariants.md. */
|
|
208
213
|
coercionTag: string;
|
|
209
214
|
/**
|
|
210
215
|
* Present only to detect a combined rename+retype request;
|
|
@@ -220,7 +225,7 @@ export interface RetypeDefinitionRequired {
|
|
|
220
225
|
};
|
|
221
226
|
}
|
|
222
227
|
/**
|
|
223
|
-
* The retype mechanism
|
|
228
|
+
* The retype mechanism : a same-tx pair --
|
|
224
229
|
* (1) UPDATE the prior active version's `status` to `deprecated` FIRST (else
|
|
225
230
|
* the insert in step 2 collides with the one-active-row-per-slot
|
|
226
231
|
* invariant); (2) INSERT the new `version+1` row as `active`, same
|
|
@@ -228,6 +233,7 @@ export interface RetypeDefinitionRequired {
|
|
|
228
233
|
* (2..N) already carries a total coercer -- `coercionTag` is `null` only for
|
|
229
234
|
* version 1 by construction (`types.ts`), so this walks 2..N and requires
|
|
230
235
|
* each to be non-null.
|
|
236
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
231
237
|
*/
|
|
232
238
|
export declare function retypeDefinition(required: RetypeDefinitionRequired): Promise<{
|
|
233
239
|
settingId: string;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
import { nowIso as kernelNowIso } from "@jini-ai/core/primitives";
|
|
1
2
|
import { AliasDepthExceededError, DefinitionInvalidError, DefinitionNotFoundError, DefinitionTombstonedError, ForbiddenError, PrincipalNotFoundError, RenameRetypeConflictError, ScopeNotAllowedError, ValueValidationFailedError, } from "./errors.js";
|
|
2
3
|
import { invalidateDefinitionNamespaceCache, resolveDefinitionRaw, validateDefinitionInput, validateValueAgainstSchema, } from "./settings.js";
|
|
3
4
|
import { SCOPE_BIT } from "./types.js";
|
|
4
5
|
/**
|
|
5
|
-
* behavior.spec.md §1.3 /
|
|
6
|
+
* behavior.spec.md §1.3 / `[internal-invariant]` — the self-vs-other
|
|
6
7
|
* permission derivation. `targetPrincipalId` omitted or equal to the caller
|
|
7
8
|
* -> `settings.user.self.write`; any other principal -> `settings.user.write`.
|
|
8
9
|
* A divergence between this function and any other implementation of the
|
|
9
|
-
* rule is a fail-open authorization bug (Red-Team
|
|
10
|
+
* rule is a fail-open authorization bug (Red-Team ) — this is the only
|
|
10
11
|
* place the rule may be encoded.
|
|
11
12
|
*
|
|
12
13
|
* @complexity O(1), pure.
|
|
13
14
|
* @overallScore 100
|
|
15
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
14
16
|
*/
|
|
15
17
|
export function deriveRequiredPermission(input) {
|
|
16
18
|
if (input.scope === "global")
|
|
@@ -20,7 +22,7 @@ export function deriveRequiredPermission(input) {
|
|
|
20
22
|
const isSelf = input.targetPrincipalId == null || input.targetPrincipalId === input.callerPrincipalId;
|
|
21
23
|
return isSelf ? "settings.user.self.write" : "settings.user.write";
|
|
22
24
|
}
|
|
23
|
-
/**
|
|
25
|
+
/** chokepoint write: authorize `settings.definitions.manage` -> validate -> write + revision, one per definition, each its own tx. See docs/decisions/DR-003-settings-ledger-invariants.md. */
|
|
24
26
|
export async function registerDefinitions(required) {
|
|
25
27
|
const { deps, input } = required;
|
|
26
28
|
const authResult = await deps.authorize({
|
|
@@ -38,7 +40,7 @@ export async function registerDefinitions(required) {
|
|
|
38
40
|
if (!validation.valid)
|
|
39
41
|
throw validation.error;
|
|
40
42
|
await deps.repo.transaction(async () => {
|
|
41
|
-
const now = deps.clock
|
|
43
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
42
44
|
const settingId = deps.ids.newId();
|
|
43
45
|
await deps.repo.saveDefinition({
|
|
44
46
|
settingId,
|
|
@@ -128,10 +130,11 @@ function assertTargetWorkspaceMatchesAuth(input) {
|
|
|
128
130
|
`but the write targets workspace '${input.workspaceId}'`);
|
|
129
131
|
}
|
|
130
132
|
/**
|
|
131
|
-
*
|
|
133
|
+
* — for scope=user writes targeting another principal, verify
|
|
132
134
|
* that principal resolves to an active user whose own `workspace_id` equals
|
|
133
135
|
* the request's `workspaceId` (structural scoping — a principal
|
|
134
136
|
* belongs to exactly one workspace, not a membership join).
|
|
137
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
135
138
|
*/
|
|
136
139
|
async function assertTargetPrincipalInWorkspace(deps, input) {
|
|
137
140
|
if (input.scope !== "user")
|
|
@@ -140,12 +143,12 @@ async function assertTargetPrincipalInWorkspace(deps, input) {
|
|
|
140
143
|
return;
|
|
141
144
|
if (!input.workspaceId)
|
|
142
145
|
return;
|
|
143
|
-
const principal = await deps.principals.
|
|
144
|
-
if (!principal
|
|
146
|
+
const principal = await deps.principals.findActiveById({ workspaceId: input.workspaceId, id: input.principalId });
|
|
147
|
+
if (!principal) {
|
|
145
148
|
throw new PrincipalNotFoundError(`principal '${input.principalId}' was not found in workspace '${input.workspaceId}'`, input.principalId, input.workspaceId);
|
|
146
149
|
}
|
|
147
150
|
}
|
|
148
|
-
/**
|
|
151
|
+
/** chokepoint write: authorize -> validate -> value + revision, same tx. See docs/decisions/DR-003-settings-ledger-invariants.md. */
|
|
149
152
|
export async function set(required) {
|
|
150
153
|
const { deps, input } = required;
|
|
151
154
|
const permission = input.requiredPermissionOverride ??
|
|
@@ -171,7 +174,7 @@ export async function set(required) {
|
|
|
171
174
|
}
|
|
172
175
|
await assertTargetPrincipalInWorkspace(deps, input);
|
|
173
176
|
const result = await deps.repo.transaction(async () => {
|
|
174
|
-
const now = deps.clock
|
|
177
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
175
178
|
const revisionSeq = await deps.repo.appendRevision({
|
|
176
179
|
entityKind: "value",
|
|
177
180
|
settingId: definition.settingId,
|
|
@@ -219,7 +222,7 @@ export async function set(required) {
|
|
|
219
222
|
});
|
|
220
223
|
return result;
|
|
221
224
|
}
|
|
222
|
-
/**
|
|
225
|
+
/** chokepoint write: authorize -> value + revision (state='cleared'), same tx. See docs/decisions/DR-003-settings-ledger-invariants.md. */
|
|
223
226
|
export async function clear(required) {
|
|
224
227
|
const { deps, input } = required;
|
|
225
228
|
if (!input.skipAuthorize) {
|
|
@@ -243,7 +246,7 @@ export async function clear(required) {
|
|
|
243
246
|
const definition = await resolveScopedDefinitionOrThrow(deps, input);
|
|
244
247
|
await assertTargetPrincipalInWorkspace(deps, input);
|
|
245
248
|
const runClear = async () => {
|
|
246
|
-
const now = deps.clock
|
|
249
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
247
250
|
const revisionSeq = await deps.repo.appendRevision({
|
|
248
251
|
entityKind: "value",
|
|
249
252
|
settingId: definition.settingId,
|
|
@@ -295,15 +298,16 @@ export async function clear(required) {
|
|
|
295
298
|
return result;
|
|
296
299
|
}
|
|
297
300
|
/**
|
|
298
|
-
*
|
|
299
|
-
* the matching `settings.reset.*` permission once, then loop `clear
|
|
301
|
+
* — an explicit, human-invoked orchestrator: authorize
|
|
302
|
+
* the matching `settings.reset.*` permission once, then loop `clear` for
|
|
300
303
|
* every setting in the namespace in the reset-authorized internal context
|
|
301
304
|
* (`skipAuthorize: true`) — the outer reset permission is sufficient on its
|
|
302
305
|
* own; each inner clear still emits its own `op='clear'` revision.
|
|
303
306
|
*
|
|
304
|
-
* `revisionSeqs` collects each inner `clear
|
|
307
|
+
* `revisionSeqs` collects each inner `clear` call's own `revisionSeq` so a
|
|
305
308
|
* host's admin HTTP route can surface the full contract without a second
|
|
306
309
|
* read. Existing callers that only read `.clearedCount` are unaffected.
|
|
310
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
307
311
|
*/
|
|
308
312
|
export async function resetNamespace(required, keysInNamespace) {
|
|
309
313
|
const { deps, input } = required;
|
|
@@ -392,7 +396,7 @@ async function resolveActiveDefinitionOrThrow(deps, input, verb) {
|
|
|
392
396
|
return current;
|
|
393
397
|
}
|
|
394
398
|
/**
|
|
395
|
-
* The rename mechanism
|
|
399
|
+
* The rename mechanism : a same-tx pair —
|
|
396
400
|
* (1) UPDATE the active definition row's `(namespace,key)` to the new name,
|
|
397
401
|
* same `setting_id`/`version` (ledgered `op='alias'`); (2) INSERT a fresh v1
|
|
398
402
|
* alias marker at the OLD name pointing at the new name. Because identity
|
|
@@ -406,6 +410,7 @@ async function resolveActiveDefinitionOrThrow(deps, input, verb) {
|
|
|
406
410
|
* Never touches `schema`/`defaultValue`/`scopes` -- a pure rename can never
|
|
407
411
|
* trigger `RENAME_RETYPE_CONFLICT` by construction; that guard lives in
|
|
408
412
|
* `retypeDefinition` ("no rename+retype in one op").
|
|
413
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
409
414
|
*/
|
|
410
415
|
export async function renameDefinition(required) {
|
|
411
416
|
const { deps, input } = required;
|
|
@@ -426,7 +431,7 @@ export async function renameDefinition(required) {
|
|
|
426
431
|
throw new DefinitionInvalidError(`rename target '${input.newNamespace}.${input.newKey}' is already in use`);
|
|
427
432
|
}
|
|
428
433
|
const result = await deps.repo.transaction(async () => {
|
|
429
|
-
const now = deps.clock
|
|
434
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
430
435
|
// Step 1: move the active row to the new name -- same setting_id, same version.
|
|
431
436
|
await deps.repo.saveDefinition({ ...current, namespace: input.newNamespace, key: input.newKey, updatedAt: now });
|
|
432
437
|
// Sequential rename: retarget every prior marker pointing at the OLD name to the NEW name.
|
|
@@ -488,7 +493,7 @@ export async function renameDefinition(required) {
|
|
|
488
493
|
return result;
|
|
489
494
|
}
|
|
490
495
|
/**
|
|
491
|
-
* The retype mechanism
|
|
496
|
+
* The retype mechanism : a same-tx pair --
|
|
492
497
|
* (1) UPDATE the prior active version's `status` to `deprecated` FIRST (else
|
|
493
498
|
* the insert in step 2 collides with the one-active-row-per-slot
|
|
494
499
|
* invariant); (2) INSERT the new `version+1` row as `active`, same
|
|
@@ -496,6 +501,7 @@ export async function renameDefinition(required) {
|
|
|
496
501
|
* (2..N) already carries a total coercer -- `coercionTag` is `null` only for
|
|
497
502
|
* version 1 by construction (`types.ts`), so this walks 2..N and requires
|
|
498
503
|
* each to be non-null.
|
|
504
|
+
* See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
499
505
|
*/
|
|
500
506
|
export async function retypeDefinition(required) {
|
|
501
507
|
const { deps, input } = required;
|
|
@@ -514,7 +520,7 @@ export async function retypeDefinition(required) {
|
|
|
514
520
|
}
|
|
515
521
|
}
|
|
516
522
|
const result = await deps.repo.transaction(async () => {
|
|
517
|
-
const now = deps.clock
|
|
523
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
518
524
|
const newVersion = current.version + 1;
|
|
519
525
|
// Step 1: deprecate the prior active version first.
|
|
520
526
|
await deps.repo.saveDefinition({ ...current, status: "deprecated", updatedAt: now });
|
|
@@ -606,7 +612,7 @@ export async function reconcileDefinitionDefault(required) {
|
|
|
606
612
|
throw new ValueValidationFailedError(`cannot reconcile the default of '${input.namespace}.${input.key}': the source default does not satisfy the definition's own schema (type '${current.schema.type}')`);
|
|
607
613
|
}
|
|
608
614
|
await deps.repo.transaction(async () => {
|
|
609
|
-
const now = deps.clock
|
|
615
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
610
616
|
await deps.repo.saveDefinition({ ...current, defaultValue: input.defaultValue, updatedAt: now });
|
|
611
617
|
await deps.repo.appendRevision({
|
|
612
618
|
entityKind: "definition",
|
|
@@ -638,7 +644,7 @@ async function transitionDefinitionStatus(deps, input, target) {
|
|
|
638
644
|
await authorizeDefinitionsManage(deps, input.callerPrincipalId, input.authWorkspaceId);
|
|
639
645
|
const current = await resolveActiveDefinitionOrThrow(deps, input, target.revisionOp);
|
|
640
646
|
const result = await deps.repo.transaction(async () => {
|
|
641
|
-
const now = deps.clock
|
|
647
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
642
648
|
await deps.repo.saveDefinition({ ...current, status: target.status, updatedAt: now });
|
|
643
649
|
await deps.repo.appendRevision({
|
|
644
650
|
entityKind: "definition",
|