@hasna-internal/kai-settings 0.1.1-rc.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.
- package/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +45 -0
- package/README.zh.md +45 -0
- package/lib/index.js +638 -0
- package/lib/invariant.js +180 -0
- package/lib/types/index.d.ts +343 -0
- package/lib/types/index.js +688 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +41 -0
- package/lib/types/redact.d.ts +40 -0
- package/lib/types/redact.js +78 -0
- package/lib/types/types.d.ts +46 -0
- package/lib/types/types.js +10 -0
- package/package.json +51 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@hasna-internal/kai-settings`.
|
|
3
|
+
* @module @hasna-internal/kai-settings/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "settings-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@hasna-internal/kai-settings`.
|
|
3
|
+
* @module @hasna-internal/kai-settings/invariant
|
|
4
|
+
*/
|
|
5
|
+
import { deepEqualJson } from "./index.js";
|
|
6
|
+
const PACKAGE_NAME = '@hasna-internal/kai-settings';
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
export const name = 'settings-invariant';
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
export const inject = ['invariants'];
|
|
11
|
+
/**
|
|
12
|
+
* Install the commit-event contract: `settings/updated` fires only for a
|
|
13
|
+
* currently registered namespace, only when the resolved value changed, and
|
|
14
|
+
* only with the service's authoritative resolved value — all judged with the
|
|
15
|
+
* seam's own equality predicate.
|
|
16
|
+
*/
|
|
17
|
+
const install = (ctx, fail) => {
|
|
18
|
+
ctx.on('settings/updated', (ns, next, prev) => {
|
|
19
|
+
const settings = ctx.get('settings');
|
|
20
|
+
if (settings === undefined) {
|
|
21
|
+
fail(`settings/updated for "${ns}" emitted without a live settings service`);
|
|
22
|
+
}
|
|
23
|
+
const current = settings.get(ns);
|
|
24
|
+
if (current === undefined) {
|
|
25
|
+
fail(`settings/updated for "${ns}" emitted while the namespace is unregistered`);
|
|
26
|
+
}
|
|
27
|
+
if (!deepEqualJson(current, next)) {
|
|
28
|
+
fail(`settings/updated for "${ns}" does not match the authoritative resolved value`);
|
|
29
|
+
}
|
|
30
|
+
if (deepEqualJson(next, prev)) {
|
|
31
|
+
fail(`settings/updated for "${ns}" emitted without a resolved-value change`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Register this package's invariant companion.
|
|
37
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
38
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
39
|
+
*/
|
|
40
|
+
export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
41
|
+
//# sourceMappingURL=invariant.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural secret redaction for settings values. `role('secret')` fields are
|
|
3
|
+
* removed from a value before it crosses a wire boundary; a sidecar records
|
|
4
|
+
* each schema-declared secret position and whether it currently holds a value,
|
|
5
|
+
* so a configuration surface can render a write-only input without ever
|
|
6
|
+
* receiving the secret itself.
|
|
7
|
+
* @module @hasna-internal/kai-settings/redact
|
|
8
|
+
*/
|
|
9
|
+
import type z from '@deepseek-ai/schemastery';
|
|
10
|
+
/** One schema-declared secret position inside a redacted value. */
|
|
11
|
+
export interface RedactedSecret {
|
|
12
|
+
/** Path from the section root to the removed field (concrete dict keys and array indexes included). */
|
|
13
|
+
path: string[];
|
|
14
|
+
/** Whether the field held a value before redaction. */
|
|
15
|
+
set: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** A value with every `role('secret')` field removed, plus the removal record. */
|
|
18
|
+
export interface RedactedValue {
|
|
19
|
+
/** Detached copy of the input with secret fields absent. */
|
|
20
|
+
value: unknown;
|
|
21
|
+
/**
|
|
22
|
+
* Every reachable secret position: object properties always (even unset, so
|
|
23
|
+
* a form knows the slot exists), dict entries and array items only where the
|
|
24
|
+
* value has them.
|
|
25
|
+
*/
|
|
26
|
+
secrets: RedactedSecret[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Remove every `role('secret')` field a schema declares from a value. The
|
|
30
|
+
* walker follows `object`, `dict`, and `array` containers; a secret must be
|
|
31
|
+
* declared directly on a field reachable through those containers (a secret
|
|
32
|
+
* buried inside a union branch or transform is not reachable and must not be
|
|
33
|
+
* modeled that way). The input is never mutated.
|
|
34
|
+
* @param schema - live schemastery schema describing the value.
|
|
35
|
+
* @param value - the value to strip; `undefined` yields an empty record with
|
|
36
|
+
* object-property secret slots still enumerated.
|
|
37
|
+
* @returns the stripped detached value and the ordered secret positions.
|
|
38
|
+
*/
|
|
39
|
+
export declare function redactSecrets(schema: z<never>, value: unknown): RedactedValue;
|
|
40
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural secret redaction for settings values. `role('secret')` fields are
|
|
3
|
+
* removed from a value before it crosses a wire boundary; a sidecar records
|
|
4
|
+
* each schema-declared secret position and whether it currently holds a value,
|
|
5
|
+
* so a configuration surface can render a write-only input without ever
|
|
6
|
+
* receiving the secret itself.
|
|
7
|
+
* @module @hasna-internal/kai-settings/redact
|
|
8
|
+
*/
|
|
9
|
+
/** Whether a value is a plain data object the walker may recurse into. */
|
|
10
|
+
function isRecord(value) {
|
|
11
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function walk(node, value, path, secrets) {
|
|
14
|
+
if (node === undefined)
|
|
15
|
+
return value;
|
|
16
|
+
if (node.meta?.role === 'secret') {
|
|
17
|
+
secrets.push({ path, set: value !== undefined });
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
switch (node.type) {
|
|
21
|
+
case 'object': {
|
|
22
|
+
const properties = node.dict ?? {};
|
|
23
|
+
const source = isRecord(value) ? value : undefined;
|
|
24
|
+
const rebuilt = {};
|
|
25
|
+
if (source !== undefined) {
|
|
26
|
+
for (const [key, entry] of Object.entries(source)) {
|
|
27
|
+
if (key in properties)
|
|
28
|
+
continue;
|
|
29
|
+
rebuilt[key] = entry;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
33
|
+
const stripped = walk(child, source?.[key], [...path, key], secrets);
|
|
34
|
+
if (stripped !== undefined)
|
|
35
|
+
rebuilt[key] = stripped;
|
|
36
|
+
}
|
|
37
|
+
return source === undefined && Object.keys(rebuilt).length === 0 ? value : rebuilt;
|
|
38
|
+
}
|
|
39
|
+
case 'dict': {
|
|
40
|
+
if (!isRecord(value))
|
|
41
|
+
return value;
|
|
42
|
+
const rebuilt = {};
|
|
43
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
44
|
+
const stripped = walk(node.inner, entry, [...path, key], secrets);
|
|
45
|
+
if (stripped !== undefined)
|
|
46
|
+
rebuilt[key] = stripped;
|
|
47
|
+
}
|
|
48
|
+
return rebuilt;
|
|
49
|
+
}
|
|
50
|
+
case 'array': {
|
|
51
|
+
if (!Array.isArray(value))
|
|
52
|
+
return value;
|
|
53
|
+
return value.map((entry, index) => walk(node.inner, entry, [...path, String(index)], secrets));
|
|
54
|
+
}
|
|
55
|
+
default:
|
|
56
|
+
// TODO(settings-wire-redaction): Fail closed instead — a secret reachable
|
|
57
|
+
// only through a union, intersection, or transform is returned verbatim
|
|
58
|
+
// here, with nothing recording that it was missed.
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Remove every `role('secret')` field a schema declares from a value. The
|
|
64
|
+
* walker follows `object`, `dict`, and `array` containers; a secret must be
|
|
65
|
+
* declared directly on a field reachable through those containers (a secret
|
|
66
|
+
* buried inside a union branch or transform is not reachable and must not be
|
|
67
|
+
* modeled that way). The input is never mutated.
|
|
68
|
+
* @param schema - live schemastery schema describing the value.
|
|
69
|
+
* @param value - the value to strip; `undefined` yields an empty record with
|
|
70
|
+
* object-property secret slots still enumerated.
|
|
71
|
+
* @returns the stripped detached value and the ordered secret positions.
|
|
72
|
+
*/
|
|
73
|
+
export function redactSecrets(schema, value) {
|
|
74
|
+
const secrets = [];
|
|
75
|
+
const stripped = walk(schema, value, [], secrets);
|
|
76
|
+
return { value: stripped, secrets };
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-safe type surface of the user-settings seam: the namespace brand, the
|
|
3
|
+
* commit-origin union, and the seam's Cordis event declarations. Types only —
|
|
4
|
+
* no runtime code, and nothing here reaches a Host-only symbol, so a Client
|
|
5
|
+
* compilation face reads exactly the signatures the Host emits.
|
|
6
|
+
*
|
|
7
|
+
* @module @hasna-internal/kai-settings/types
|
|
8
|
+
*/
|
|
9
|
+
import type { Branded } from '@hasna-internal/kai-brand';
|
|
10
|
+
/** Nominal id of one registered settings namespace. */
|
|
11
|
+
export type SettingsNamespace = Branded<'SettingsNamespace'>;
|
|
12
|
+
/** Origin of one committed settings change. */
|
|
13
|
+
export type SettingsUpdateSource = 'update' | 'provider';
|
|
14
|
+
declare module '@deepseek-ai/cordis' {
|
|
15
|
+
interface Events {
|
|
16
|
+
/**
|
|
17
|
+
* Committed change to one registered namespace's resolved value. Emitted
|
|
18
|
+
* after the provider persisted (for `update`) or published (`provider`)
|
|
19
|
+
* the change; never emitted when the resolved value is deep-equal.
|
|
20
|
+
* Listener failures are contained and logged — a sync throw and an async
|
|
21
|
+
* rejection alike — except `INVARIANT`-coded failures, which rethrow
|
|
22
|
+
* after every listener ran; that rethrow reaches the emitter only from
|
|
23
|
+
* synchronous listeners, so invariant checks on this event must not be
|
|
24
|
+
* async functions.
|
|
25
|
+
* @param ns - the namespace whose resolved value changed.
|
|
26
|
+
* @param next - the new resolved value.
|
|
27
|
+
* @param prev - the previous resolved value.
|
|
28
|
+
* @param source - whether the change entered through `update()` or the provider.
|
|
29
|
+
* @mode emit
|
|
30
|
+
*/
|
|
31
|
+
'settings/updated'(ns: SettingsNamespace, next: unknown, prev: unknown, source: SettingsUpdateSource): void;
|
|
32
|
+
/**
|
|
33
|
+
* One registered namespace's RAW user section changed, whether or not the
|
|
34
|
+
* resolved value did. `settings/updated` is the consumer-facing event and
|
|
35
|
+
* stays deep-equal-gated; this one exists for configuration surfaces,
|
|
36
|
+
* which must learn that a field went from inherited to overridden (same
|
|
37
|
+
* resolved value, different meaning) and that their held revision is
|
|
38
|
+
* stale. Listener containment matches `settings/updated`.
|
|
39
|
+
* @param ns - the namespace whose stored section changed.
|
|
40
|
+
* @param revision - the namespace's new revision.
|
|
41
|
+
* @mode emit
|
|
42
|
+
*/
|
|
43
|
+
'settings/document-updated'(ns: SettingsNamespace, revision: number): void;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-safe type surface of the user-settings seam: the namespace brand, the
|
|
3
|
+
* commit-origin union, and the seam's Cordis event declarations. Types only —
|
|
4
|
+
* no runtime code, and nothing here reaches a Host-only symbol, so a Client
|
|
5
|
+
* compilation face reads exactly the signatures the Host emits.
|
|
6
|
+
*
|
|
7
|
+
* @module @hasna-internal/kai-settings/types
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasna-internal/kai-settings",
|
|
3
|
+
"description": "Abstract user-settings seam (ctx.settings) for the DeepSeek Harness",
|
|
4
|
+
"version": "0.1.1-rc.2",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/settings/settings"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./types": {
|
|
26
|
+
"types": "./lib/types/types.d.ts",
|
|
27
|
+
"default": "./lib/types/types.js"
|
|
28
|
+
},
|
|
29
|
+
"./src/*": "./src/*",
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"lib/index.js",
|
|
34
|
+
"lib/invariant.js",
|
|
35
|
+
"lib/types/**/*.js",
|
|
36
|
+
"lib/types/**/*.d.ts"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@hasna-internal/kai-invariants": "^0.1.1-rc.2",
|
|
41
|
+
"@hasna-internal/kai-brand": "^0.1.1-rc.2",
|
|
42
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
43
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@hasna-internal/kai-brand": "^0.1.1-rc.2",
|
|
47
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
48
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
49
|
+
"@hasna-internal/kai-invariants": "^0.1.1-rc.2"
|
|
50
|
+
}
|
|
51
|
+
}
|