@sema-agent/settings-schema 3.0.0 → 4.0.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 +327 -1
- package/README.md +16 -8
- package/conformance/VERSION +1 -1
- package/conformance/cases/t01-absent-domain-key-not-null/expected.json +93 -0
- package/conformance/cases/t01-absent-domain-key-not-null/input.json +29 -0
- package/conformance/cases/t01-absent-domain-key-not-null/meta.json +6 -0
- package/conformance/cases/t02-entry-salvage-vs-whole-domain-refusal/expected.json +16 -0
- package/conformance/cases/t02-entry-salvage-vs-whole-domain-refusal/input.json +32 -0
- package/conformance/cases/t02-entry-salvage-vs-whole-domain-refusal/meta.json +6 -0
- package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +107 -0
- package/conformance/cases/t03-prototype-name-key-not-swallowed/input.json +25 -0
- package/conformance/cases/t03-prototype-name-key-not-swallowed/meta.json +6 -0
- package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +105 -0
- package/conformance/cases/t04-secret-literal-never-in-warning-value/input.json +25 -0
- package/conformance/cases/t04-secret-literal-never-in-warning-value/meta.json +14 -0
- package/conformance/historical/settings-schema@1.0.0.json +84 -3
- package/conformance/historical/settings-schema@1.10.0.json +84 -3
- package/conformance/historical/settings-schema@1.5.0.json +84 -3
- package/conformance/historical/settings-schema@2.0.0.json +83 -2
- package/conformance/historical/settings-schema@2.1.0.json +83 -2
- package/dist/api/auth.d.ts +21 -0
- package/dist/api/auth.js +21 -0
- package/dist/config-fns.d.ts +76 -11
- package/dist/config-fns.js +81 -26
- package/dist/conformance/contract-adapter.js +37 -0
- package/dist/conformance/entry-origin.d.ts +24 -0
- package/dist/conformance/entry-origin.js +86 -0
- package/dist/conformance/run.js +6 -6
- package/dist/conformance/types.d.ts +5 -1
- package/dist/conformance/warnings.d.ts +7 -48
- package/dist/conformance/warnings.js +17 -40
- package/dist/cross-domain.d.ts +27 -7
- package/dist/cross-domain.js +31 -11
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +2 -2
- package/dist/node.d.ts +1 -0
- package/dist/node.js +1 -0
- package/dist/prompts-digest.d.ts +16 -0
- package/dist/prompts-digest.js +48 -0
- package/dist/scheduler-store-node.d.ts +7 -19
- package/dist/scheduler-store-node.js +8 -26
- package/dist/scheduler-store.d.ts +1 -1
- package/dist/scheduler-store.js +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.js +3 -3
- package/package.json +3 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 来源律的**判别式**(§2d-2 同义反复护栏)——「这条腿的入口,其实就是契约包自己吗?」
|
|
3
|
+
*
|
|
4
|
+
* 成案:消费方登记的腿必须是**自己生产代码**的入口。把契约包的函数注册成腿,门证明的只是「包等于包」:
|
|
5
|
+
* 一次纯粹的同义反复,而且看起来全绿。判据落在**入口坐标**上,不在「你调了谁」上 —— 后者机器证不了
|
|
6
|
+
* (`docs/CONFORMANCE.md` 逐字写明了这条诚实边界)。
|
|
7
|
+
*
|
|
8
|
+
* **一只判别式,两处执法**(单铸,不许两处各写一遍「哪儿算契约包自己」)。两形是个**闭集**
|
|
9
|
+
* ({@link CONTRACT_OWNED_FORMS}),而**哪一形归哪一处**由一句话定:**工厂只拒在任何模式下都错的那一形**,
|
|
10
|
+
* 另一形归**看得见 `mode`** 的那道门。
|
|
11
|
+
* · `own-build-output` —— 入口落在**正在跑这段码的这份**契约包自己的构建产物 `<包根>/dist/` 下。
|
|
12
|
+
* 这是「拿自己比自己」,`release` 与 `historical` 两个模式下都是同义反复 ⇒ {@link createContractAdapter}
|
|
13
|
+
* 造适配器时**当场拒**(运行期拒,不是告警)。
|
|
14
|
+
* · `another-installed-copy` —— 入口落在**另一份**装好的契约包(`node_modules/<包名>/`)里。它在 `release`
|
|
15
|
+
* 下是同义反复,在 `historical` 下却**正当**(那个模式就是拿旧版包比行为,而旧版包常常正好装在
|
|
16
|
+
* `node_modules/<包名>/` 这个布局下)⇒ 工厂**不拒**(工厂看不见 `mode`,不越权),由 `runConformance`
|
|
17
|
+
* 的 release 门记成违例。
|
|
18
|
+
* 手写的适配器字面量(接入文档的默认配方)根本走不到工厂 ⇒ 那道门**两形都判**。
|
|
19
|
+
*
|
|
20
|
+
* 为什么 `own-build-output` 咬 `dist/` 而不是整个包根:契约仓**自证**时,腿的入口是本仓的 `src/*.ts`
|
|
21
|
+
* (那对契约仓来说就是它的生产入口,是这只工厂存在的两个理由之一),而 `src/` 不随包发 —— 消费方手上
|
|
22
|
+
* 只有 `dist/`。于是同一条判据既拦住了消费方的同义反复,又放行了契约仓自证;historical 模式拿
|
|
23
|
+
* **另一份**旧版包当入口,那份不是「自己」,照样放行。
|
|
24
|
+
*/
|
|
25
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
26
|
+
import { join, resolve, sep } from "node:path";
|
|
27
|
+
import { PACKAGE_ROOT } from "./corpus.js";
|
|
28
|
+
/** 契约包在盘上的两种样子(闭集):**我自己**,与**另一份**。 */
|
|
29
|
+
export const CONTRACT_OWNED_FORMS = ["own-build-output", "another-installed-copy"];
|
|
30
|
+
let cachedName;
|
|
31
|
+
/** 本包的名字 —— 从**自己**的 package.json 读(不手抄;`kitIdentity()` 读的是同一个文件)。读不出来就抛:
|
|
32
|
+
* 一只判不了「哪儿是契约包自己」的判别式,静默放行等于把来源律整条关掉。 */
|
|
33
|
+
function contractPackageName() {
|
|
34
|
+
if (cachedName === undefined) {
|
|
35
|
+
const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
36
|
+
if (typeof pkg.name !== "string" || pkg.name.length === 0) {
|
|
37
|
+
throw new Error("conformance: 契约包自己的 package.json 没有 `name` —— 来源律判别式判不了「哪儿是契约包自己」");
|
|
38
|
+
}
|
|
39
|
+
cachedName = pkg.name;
|
|
40
|
+
}
|
|
41
|
+
return cachedName;
|
|
42
|
+
}
|
|
43
|
+
/** 一条路径的**归一形**(判据咬的是**文件**,不是字符串):点段 / 双斜杠先由 `resolve` 抹平,再尽力解一次
|
|
44
|
+
* 软链(`npm link` 出来的树就是软链)。文件不在盘上 ⇒ 解不了,那就只用字面归一形 —— 这不是放行:
|
|
45
|
+
* 「入口不存在」本来就由 release 门单独点名,这里少一个候选形不会让任何一形逃掉。 */
|
|
46
|
+
function canonicalForms(p) {
|
|
47
|
+
const lexical = resolve(p);
|
|
48
|
+
try {
|
|
49
|
+
const real = realpathSync(lexical);
|
|
50
|
+
return real === lexical ? [lexical] : [lexical, real];
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return [lexical];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 这个入口坐标落在契约包自己身上吗?命中回形与坐标,没命中回 `undefined`。
|
|
58
|
+
*
|
|
59
|
+
* ⚠️ **两形的判定顺序是承重的**:消费方装好的那份 kit **自己就住在 `node_modules/<包名>/` 里**,它的
|
|
60
|
+
* `dist/` 同时命中两形。「是不是**我自己**」必须先判 —— 否则最该拒的那一形(消费方拿自己比自己)会被
|
|
61
|
+
* 判成「另一份」而被工厂放行。先 `dist` 后 `node_modules`,两形于是不相交:`another-installed-copy`
|
|
62
|
+
* 字面意思就是**另一份**装好的契约包(historical 模式的正当目标)。
|
|
63
|
+
*
|
|
64
|
+
* ⚠️ **判之前先归一**(两侧都归):`<包根>/./dist/x.js` 与 `<包根>//dist/x.js` 与一条指向 `<包根>/dist`
|
|
65
|
+
* 的软链,指的都是同一个文件。release 门会先 `realpath` 再判,但 `historical` 模式根本不跑那道门 ——
|
|
66
|
+
* 工厂这一侧是那条路上唯一的闸,所以归一得在**判别式里**做,两处执法才咬同一个判据。
|
|
67
|
+
*/
|
|
68
|
+
export function contractOwnedEntry(modulePath) {
|
|
69
|
+
const candidates = canonicalForms(modulePath);
|
|
70
|
+
const ownDists = canonicalForms(join(PACKAGE_ROOT, "dist")).map((d) => d + sep);
|
|
71
|
+
for (const anchor of ownDists) {
|
|
72
|
+
if (candidates.some((c) => c.startsWith(anchor)))
|
|
73
|
+
return { form: "own-build-output", anchor };
|
|
74
|
+
}
|
|
75
|
+
const installed = `${sep}node_modules${sep}${contractPackageName().split("/").join(sep)}${sep}`;
|
|
76
|
+
if (candidates.some((c) => c.includes(installed)))
|
|
77
|
+
return { form: "another-installed-copy", anchor: installed };
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
/** 拒绝句/违例句共用的那一段:你落在哪儿、合法的是哪儿。两处文案同源,消费方读到的是同一句话。 */
|
|
81
|
+
export function contractOwnedEntryWhy(leg, modulePath, owned) {
|
|
82
|
+
return (`leg \`${leg}\` entry.module sits inside the contract package (${modulePath}) —— 命中 ${owned.form} @ ${owned.anchor};` +
|
|
83
|
+
`那条腿证明的是「包等于包」。合法的入口 = **你自己生产代码**里那个真入口文件的绝对路径` +
|
|
84
|
+
`(消费方:对着你自己的装配层求 \`fileURLToPath(import.meta.url)\`;契约仓自证:本仓 \`src/*.ts\`;` +
|
|
85
|
+
`historical 模式:另一份旧版包的 dist)。见 docs/CONFORMANCE.md 的来源律一节。`);
|
|
86
|
+
}
|
package/dist/conformance/run.js
CHANGED
|
@@ -18,12 +18,10 @@ import { canonicalize, deepEqual, diffJson, stableKey } from "./canonical.js";
|
|
|
18
18
|
import { diffWarningMultiset, findSecretLeaks, projectWarning } from "./warnings.js";
|
|
19
19
|
import { deriveLocalExpectation, deriveLocalInput, deriveWireExpectation, deriveWireInput, toManifestDoc } from "./derive.js";
|
|
20
20
|
import { kitIdentity, loadCorpus } from "./corpus.js";
|
|
21
|
+
import { contractOwnedEntry, contractOwnedEntryWhy } from "./entry-origin.js";
|
|
21
22
|
import { LEG_NAMES, } from "./types.js";
|
|
22
23
|
/** 一个**模块**文件的样子 —— 来源律用它挡住「随便找个存在的文件当入口」(`package.json` 也是文件)。 */
|
|
23
24
|
const MODULE_EXTENSIONS = [".ts", ".mts", ".cts", ".tsx", ".js", ".mjs", ".cjs", ".jsx"];
|
|
24
|
-
/** 契约包装在 `node_modules` 里的样子 —— 来源律用它判「这条腿其实就是契约包自己」。按**包名**拼,
|
|
25
|
-
* 不手抄(包名从 kit 自己的 package.json 来)。 */
|
|
26
|
-
const contractInNodeModules = (packageName) => `${sep}node_modules${sep}${packageName.split("/").join(sep)}${sep}`;
|
|
27
25
|
/**
|
|
28
26
|
* 把一份 `config.d/` 快照落成真目录 —— **kit 单铸**,消费方零复述(每个消费方各写一遍落盘逻辑,
|
|
29
27
|
* mtime 怎么设、目录怎么建就会各不相同,而 `updatedAt` 正是按 mtime 算的)。
|
|
@@ -330,9 +328,11 @@ async function releaseViolations(adapter, opts, corpus, kit) {
|
|
|
330
328
|
catch {
|
|
331
329
|
v.push(`leg \`${name}\` entry.module does not exist: ${mod}`);
|
|
332
330
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
331
|
+
// 来源律判别式 = `./entry-origin.js` 那一只(工厂当场拒的同一只)。点段 / 双斜杠 / 软链的归一在
|
|
332
|
+
// 判别式**里面**做(两处执法咬同一个判据),所以这里不再自己解一遍。
|
|
333
|
+
const owned = contractOwnedEntry(mod);
|
|
334
|
+
if (owned !== undefined)
|
|
335
|
+
v.push(contractOwnedEntryWhy(name, mod, owned));
|
|
336
336
|
if (leg.entry.export.trim().length === 0)
|
|
337
337
|
v.push(`leg \`${name}\` entry.export is empty`);
|
|
338
338
|
}
|
|
@@ -81,7 +81,11 @@ export interface ConformanceProvenance {
|
|
|
81
81
|
* 同义反复,而且看起来全绿。`module` 必须是消费方**生产代码**里那个真入口文件的绝对路径,`export`
|
|
82
82
|
* 是它导出的那个名字。
|
|
83
83
|
*
|
|
84
|
-
*
|
|
84
|
+
* 4.0.0 起这条律**有牙且两处执法**:判别式单铸在 `./entry-origin.js`(两形闭集 —— **我自己**的构建产物 /
|
|
85
|
+
* **另一份**装好的包)。`createContractAdapter` 看不见 `mode`,只拒在任何模式下都错的那一形(拿自己比自己),
|
|
86
|
+
* **当场拒**;另一形在 `historical` 下正当,连同手写的适配器字面量(走不到工厂)一起由 `release` 门记成违例。
|
|
87
|
+
*
|
|
88
|
+
* **门的诚实边界**:kit 能核的是「这个文件真的存在」「它不落在契约包自己身上」。它**不能**
|
|
85
89
|
* 证明这个文件内部没有直接转调契约函数 —— 那由消费方半场的**变异自证**负责(消费方对自己的折叠/投影层
|
|
86
90
|
* 注入一处变异 ⇒ kit 必红)。本字段是护栏,不是证明,docs/CONFORMANCE.md 逐字写明这一点。
|
|
87
91
|
*/
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 告警投影的**多重集比对** —— kit 的第二个单铸点。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* **投影本身不在这里**(4.0.0):`WarningProjection` / `projectWarning` / `WARNING_KINDS` 的属主是告警联合
|
|
5
|
+
* 自己的模块 `../config-fns`,本文件只再导出(kit 的进口逐字不变)。投影 = 每臂减掉 `error`,**从联合派生**;
|
|
6
|
+
* 为什么要剥 `error`:它带的是 zod 的错误对象/文案,可经脱敏、可随 zod 小版本改字 —— 比文案就是比实现。
|
|
7
|
+
* `error` 因此不进比对,但也不是就这么放过去了:{@link findSecretLeaks} 先断言它不含语料自带的秘密清单。
|
|
7
8
|
*
|
|
8
9
|
* 为什么是**多重集**而不是集合:同一个 kind 在同一个域上发两次(两条坏条目、两处未知键路径)是有信息的
|
|
9
10
|
* —— 去重会让「报了一条」与「报了两条」同形,而那恰是消费方最容易漏折的一类。
|
|
@@ -11,51 +12,9 @@
|
|
|
11
12
|
* 坐标({@link ConfigPath})按**结构**比:段数组进 JSON 编码,`["a.b"]` 与 `["a","b"]`、`["1"]` 与 `[1]`
|
|
12
13
|
* 在编码里都还分得开。kit 内**零** `formatConfigPath` 调用 —— 出门形是给人看的,不是比对用的。
|
|
13
14
|
*/
|
|
14
|
-
import type
|
|
15
|
+
import { type ConfigPath, type EffectiveReadWarning, type WarningProjection } from "../config-fns.js";
|
|
15
16
|
import type { ConformanceNormalizer } from "./types.js";
|
|
16
|
-
|
|
17
|
-
export declare const WARNING_KINDS: readonly ["hosts-grandfathered", "domain-defaulted", "unknown-keys-dropped", "unknown-keys-carried", "legacy-governance-lifted", "entry-dropped", "unread-config-file"];
|
|
18
|
-
export type WarningKind = (typeof WARNING_KINDS)[number];
|
|
19
|
-
/** 一条告警的投影 —— 每臂列全该 kind 的**非 `error`** 字段。 */
|
|
20
|
-
export type WarningProjection = {
|
|
21
|
-
kind: "hosts-grandfathered";
|
|
22
|
-
domain: "hosts";
|
|
23
|
-
droppedNames: readonly string[];
|
|
24
|
-
truncatedFrom?: number;
|
|
25
|
-
} | {
|
|
26
|
-
kind: "domain-defaulted";
|
|
27
|
-
domain: string;
|
|
28
|
-
} | {
|
|
29
|
-
kind: "unknown-keys-dropped";
|
|
30
|
-
domain: string;
|
|
31
|
-
keys: readonly ConfigPath[];
|
|
32
|
-
truncatedFrom?: number;
|
|
33
|
-
} | {
|
|
34
|
-
kind: "unknown-keys-carried";
|
|
35
|
-
domain: string;
|
|
36
|
-
keys: readonly ConfigPath[];
|
|
37
|
-
truncatedFrom?: number;
|
|
38
|
-
} | {
|
|
39
|
-
kind: "legacy-governance-lifted";
|
|
40
|
-
domain: "governance";
|
|
41
|
-
keys: readonly ConfigPath[];
|
|
42
|
-
} | {
|
|
43
|
-
kind: "entry-dropped";
|
|
44
|
-
domain: string;
|
|
45
|
-
path: ConfigPath;
|
|
46
|
-
} | {
|
|
47
|
-
kind: "unread-config-file";
|
|
48
|
-
file: string;
|
|
49
|
-
domain?: string;
|
|
50
|
-
why: string;
|
|
51
|
-
};
|
|
52
|
-
/**
|
|
53
|
-
* 一条告警 → 它的投影。**闭集穷举**:新 kind 不在这里表态就编译红(#157 的闭集纪律)。
|
|
54
|
-
*
|
|
55
|
-
* `truncatedFrom` 缺席时**不铸这个键**(不是铸 `undefined`)—— 规范化会把 `undefined` 值剔掉,两种写法
|
|
56
|
-
* 最终同形,但不铸更诚实:「没有截断」与「截断了 0 条」不该在中间形上分成两件事。
|
|
57
|
-
*/
|
|
58
|
-
export declare function projectWarning(w: EffectiveReadWarning): WarningProjection;
|
|
17
|
+
export { WARNING_KINDS, projectWarning, type WarningKind, type WarningProjection } from "../config-fns.js";
|
|
59
18
|
/**
|
|
60
19
|
* 一条投影的**比对归一形**:①坐标过一遍映射(historical 模式把段拼成旧版的点号串;无归一器时是恒等);
|
|
61
20
|
* ②`keys` / `droppedNames` 这两个**集合字段**按袋归一。第二处闭集穷举:哪些 kind 带坐标、带在哪个字段上,
|
|
@@ -1,46 +1,23 @@
|
|
|
1
|
-
import { canonicalize, stableKey } from "./canonical.js";
|
|
2
|
-
/** {@link EffectiveReadWarning} 的 `kind` 闭集(覆盖律按它计数;新 kind 不登记 ⇒ 下面的 switch 编译红)。 */
|
|
3
|
-
export const WARNING_KINDS = [
|
|
4
|
-
"hosts-grandfathered",
|
|
5
|
-
"domain-defaulted",
|
|
6
|
-
"unknown-keys-dropped",
|
|
7
|
-
"unknown-keys-carried",
|
|
8
|
-
"legacy-governance-lifted",
|
|
9
|
-
"entry-dropped",
|
|
10
|
-
"unread-config-file",
|
|
11
|
-
];
|
|
12
1
|
/**
|
|
13
|
-
*
|
|
2
|
+
* 告警投影的**多重集比对** —— kit 的第二个单铸点。
|
|
3
|
+
*
|
|
4
|
+
* **投影本身不在这里**(4.0.0):`WarningProjection` / `projectWarning` / `WARNING_KINDS` 的属主是告警联合
|
|
5
|
+
* 自己的模块 `../config-fns`,本文件只再导出(kit 的进口逐字不变)。投影 = 每臂减掉 `error`,**从联合派生**;
|
|
6
|
+
* 为什么要剥 `error`:它带的是 zod 的错误对象/文案,可经脱敏、可随 zod 小版本改字 —— 比文案就是比实现。
|
|
7
|
+
* `error` 因此不进比对,但也不是就这么放过去了:{@link findSecretLeaks} 先断言它不含语料自带的秘密清单。
|
|
14
8
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
9
|
+
* 为什么是**多重集**而不是集合:同一个 kind 在同一个域上发两次(两条坏条目、两处未知键路径)是有信息的
|
|
10
|
+
* —— 去重会让「报了一条」与「报了两条」同形,而那恰是消费方最容易漏折的一类。
|
|
11
|
+
*
|
|
12
|
+
* 坐标({@link ConfigPath})按**结构**比:段数组进 JSON 编码,`["a.b"]` 与 `["a","b"]`、`["1"]` 与 `[1]`
|
|
13
|
+
* 在编码里都还分得开。kit 内**零** `formatConfigPath` 调用 —— 出门形是给人看的,不是比对用的。
|
|
17
14
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// 两臂字段相同但**不合并**:合并后 `kind` 塌成两员的联合,TS 就再也认不出它属于投影联合的哪一员
|
|
25
|
-
// (真编译红过一次)。逐 kind 一臂,是这张表本来的样子。
|
|
26
|
-
case "unknown-keys-dropped":
|
|
27
|
-
return { kind: w.kind, domain: w.domain, keys: w.keys, ...(w.truncatedFrom === undefined ? {} : { truncatedFrom: w.truncatedFrom }) };
|
|
28
|
-
case "unknown-keys-carried":
|
|
29
|
-
return { kind: w.kind, domain: w.domain, keys: w.keys, ...(w.truncatedFrom === undefined ? {} : { truncatedFrom: w.truncatedFrom }) };
|
|
30
|
-
case "legacy-governance-lifted":
|
|
31
|
-
return { kind: w.kind, domain: w.domain, keys: w.keys };
|
|
32
|
-
case "entry-dropped":
|
|
33
|
-
return { kind: w.kind, domain: w.domain, path: w.path };
|
|
34
|
-
case "unread-config-file":
|
|
35
|
-
return { kind: w.kind, file: w.file, ...(w.domain === undefined ? {} : { domain: w.domain }), why: w.why };
|
|
36
|
-
default: {
|
|
37
|
-
const unhandled = w;
|
|
38
|
-
// 运行期到不了(本联合由契约包铸造);到了就让它红 —— 一条不认识的告警被静默投影成空,
|
|
39
|
-
// 正是「门看起来全绿」的那种假绿。
|
|
40
|
-
throw new Error(`conformance: unknown EffectiveReadWarning kind — ${JSON.stringify(unhandled)}`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
15
|
+
import { projectWarning } from "../config-fns.js";
|
|
16
|
+
import { canonicalize, stableKey } from "./canonical.js";
|
|
17
|
+
// 投影的**属主在 `../config-fns`**(告警联合自己的模块;4.0.0 起)—— kit 只再导出,进口不断。
|
|
18
|
+
// 4.0.0 之前这三件住在本文件里,于是「一条告警有哪些字段」在舰队里有两个属主:契约那边加一个字段,
|
|
19
|
+
// 这边的手写投影不动,而**没有一格会红**。现在投影从联合逐臂派生(`Omit<臂, "error">`),那条缝没了。
|
|
20
|
+
export { WARNING_KINDS, projectWarning } from "../config-fns.js";
|
|
44
21
|
/** 一只**袋**:元素按各自的稳定编码排序,重数保留。比对前归一用,不改变「报了几条」这件事。 */
|
|
45
22
|
const bag = (items) => [...items].sort((a, b) => (stableKey(a) < stableKey(b) ? -1 : stableKey(a) > stableKey(b) ? 1 : 0));
|
|
46
23
|
/**
|
package/dist/cross-domain.d.ts
CHANGED
|
@@ -23,16 +23,36 @@ export type SiblingResolver = <K extends keyof DomainConfig>(domain: K) => Promi
|
|
|
23
23
|
* Widened from the fat ConfigStore to the narrow {@link ConfigReader} (it only calls `getDomain`) so a TOC
|
|
24
24
|
* file source can use it without the full store.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
26
|
+
* 2.0.0 renamed this from `siblingResolver` (BREAKING, zero aliases): the naming law is `build*` returns
|
|
27
27
|
* plain DATA and `create*` returns a LIVE object — this returns a closure, so it was on the wrong side of
|
|
28
28
|
* the line and carried neither prefix. A consumer's compile error IS the notice; the fix is the new name. */
|
|
29
29
|
export declare function createSiblingResolver(store: ConfigReader, pending?: Partial<{
|
|
30
30
|
[K in keyof DomainConfig]: DomainConfig[K];
|
|
31
31
|
}>): SiblingResolver;
|
|
32
|
-
/** The domains whose writes carry cross-domain model/roster references we validate at save time
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
32
|
+
/** The domains whose writes carry cross-domain model/roster references we validate at save time — the closed
|
|
33
|
+
* set as a RUNTIME VALUE (4.0.0). A consumer that has to ask "is this domain ref-checked?" at runtime (a
|
|
34
|
+
* config-rollback path deciding whether to run the guard before writing) could only hand-copy the list
|
|
35
|
+
* before, and a hand copy drifts silently: two rollback routes in the admin console each carried a
|
|
36
|
+
* five-member copy that had lost `entitlement`, so an entitlement rollback skipped the guard entirely with
|
|
37
|
+
* nothing red anywhere. Ask this constant (or {@link isRefCheckedDomain}) instead of writing a list.
|
|
38
|
+
*
|
|
39
|
+
* `satisfies readonly (keyof DomainConfig)[]` keeps every member a real domain key; {@link RefCheckedDomain}
|
|
40
|
+
* is DERIVED from this value, so there is exactly one place the set is written down. */
|
|
41
|
+
export declare const REF_CHECKED_DOMAINS: readonly ["rosters", "workers", "models", "collab", "systems", "entitlement"];
|
|
42
|
+
/** The domains whose writes carry cross-domain model/roster references we validate at save time.
|
|
43
|
+
* DERIVED from {@link REF_CHECKED_DOMAINS} (4.0.0) — a hand-written union next to the value would be a
|
|
44
|
+
* second copy of the same set, free to disagree with it. */
|
|
45
|
+
export type RefCheckedDomain = (typeof REF_CHECKED_DOMAINS)[number];
|
|
46
|
+
/** Runtime narrowing to {@link RefCheckedDomain}, same idiom as `isDomainName`: use it instead of hard-coding
|
|
47
|
+
* the set when filtering an arbitrary domain key (a batch payload's keys, a rollback target). */
|
|
48
|
+
export declare function isRefCheckedDomain(d: unknown): d is RefCheckedDomain;
|
|
49
|
+
/** Cross-domain ref integrity for one {@link RefCheckedDomain} value. Every issue `path` starts with the
|
|
50
|
+
* DOMAIN KEY (e.g. `["rosters", i, "models", j]`, `["collab", "templates", i, …]`) so a caller can prefix a
|
|
51
|
+
* batch index and get a coordinate that is unambiguous across arms. Empty = ok. Runtime resolution stays
|
|
52
|
+
* tolerant (falls back); this is purely a save-time guard — a precise 400 at save time is friendlier than a
|
|
53
|
+
* silently-wrong roster.
|
|
54
|
+
*
|
|
55
|
+
* 4.0.0 made that first sentence TRUE: the `collab` arm used to start its paths at `"templates"` and the
|
|
56
|
+
* `entitlement` arm at `"tiers"`, so a caller could not read `path[0]` as the domain. See the CHANGELOG entry
|
|
57
|
+
* for the migration (BREAKING, value shape). */
|
|
38
58
|
export declare function refIntegrityIssues(domain: RefCheckedDomain, value: unknown, resolve: SiblingResolver): Promise<RefIssue[]>;
|
package/dist/cross-domain.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Widened from the fat ConfigStore to the narrow {@link ConfigReader} (it only calls `getDomain`) so a TOC
|
|
3
3
|
* file source can use it without the full store.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* 2.0.0 renamed this from `siblingResolver` (BREAKING, zero aliases): the naming law is `build*` returns
|
|
6
6
|
* plain DATA and `create*` returns a LIVE object — this returns a closure, so it was on the wrong side of
|
|
7
7
|
* the line and carried neither prefix. A consumer's compile error IS the notice; the fix is the new name. */
|
|
8
8
|
export function createSiblingResolver(store, pending) {
|
|
@@ -12,10 +12,30 @@ export function createSiblingResolver(store, pending) {
|
|
|
12
12
|
return store.getDomain(domain);
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
/** The domains whose writes carry cross-domain model/roster references we validate at save time — the closed
|
|
16
|
+
* set as a RUNTIME VALUE (4.0.0). A consumer that has to ask "is this domain ref-checked?" at runtime (a
|
|
17
|
+
* config-rollback path deciding whether to run the guard before writing) could only hand-copy the list
|
|
18
|
+
* before, and a hand copy drifts silently: two rollback routes in the admin console each carried a
|
|
19
|
+
* five-member copy that had lost `entitlement`, so an entitlement rollback skipped the guard entirely with
|
|
20
|
+
* nothing red anywhere. Ask this constant (or {@link isRefCheckedDomain}) instead of writing a list.
|
|
21
|
+
*
|
|
22
|
+
* `satisfies readonly (keyof DomainConfig)[]` keeps every member a real domain key; {@link RefCheckedDomain}
|
|
23
|
+
* is DERIVED from this value, so there is exactly one place the set is written down. */
|
|
24
|
+
export const REF_CHECKED_DOMAINS = ["rosters", "workers", "models", "collab", "systems", "entitlement"];
|
|
25
|
+
/** Runtime narrowing to {@link RefCheckedDomain}, same idiom as `isDomainName`: use it instead of hard-coding
|
|
26
|
+
* the set when filtering an arbitrary domain key (a batch payload's keys, a rollback target). */
|
|
27
|
+
export function isRefCheckedDomain(d) {
|
|
28
|
+
return typeof d === "string" && REF_CHECKED_DOMAINS.includes(d);
|
|
29
|
+
}
|
|
30
|
+
/** Cross-domain ref integrity for one {@link RefCheckedDomain} value. Every issue `path` starts with the
|
|
31
|
+
* DOMAIN KEY (e.g. `["rosters", i, "models", j]`, `["collab", "templates", i, …]`) so a caller can prefix a
|
|
32
|
+
* batch index and get a coordinate that is unambiguous across arms. Empty = ok. Runtime resolution stays
|
|
33
|
+
* tolerant (falls back); this is purely a save-time guard — a precise 400 at save time is friendlier than a
|
|
34
|
+
* silently-wrong roster.
|
|
35
|
+
*
|
|
36
|
+
* 4.0.0 made that first sentence TRUE: the `collab` arm used to start its paths at `"templates"` and the
|
|
37
|
+
* `entitlement` arm at `"tiers"`, so a caller could not read `path[0]` as the domain. See the CHANGELOG entry
|
|
38
|
+
* for the migration (BREAKING, value shape). */
|
|
19
39
|
export async function refIntegrityIssues(domain, value, resolve) {
|
|
20
40
|
const issues = [];
|
|
21
41
|
if (domain === "rosters") {
|
|
@@ -73,7 +93,7 @@ export async function refIntegrityIssues(domain, value, resolve) {
|
|
|
73
93
|
t.slots.forEach((s, j) => {
|
|
74
94
|
s.members.forEach((mem, k) => {
|
|
75
95
|
if (mem.modelRef && !catalog.has(mem.modelRef))
|
|
76
|
-
issues.push({ path: ["templates", i, "slots", j, "members", k, "modelRef"], message: `collab template "${t.id}" slot "${s.id}" → unknown model "${mem.modelRef}" (not in the catalog)`, code: "custom" });
|
|
96
|
+
issues.push({ path: ["collab", "templates", i, "slots", j, "members", k, "modelRef"], message: `collab template "${t.id}" slot "${s.id}" → unknown model "${mem.modelRef}" (not in the catalog)`, code: "custom" });
|
|
77
97
|
});
|
|
78
98
|
});
|
|
79
99
|
});
|
|
@@ -101,15 +121,15 @@ export async function refIntegrityIssues(domain, value, resolve) {
|
|
|
101
121
|
const pluginNames = new Set((await resolve("plugins"))?.plugins.map((p) => p.name) ?? []);
|
|
102
122
|
value.tiers.forEach((t, i) => {
|
|
103
123
|
if (t.roster && !rosterNames.has(t.roster))
|
|
104
|
-
issues.push({ path: ["tiers", i, "roster"], message: `tier "${t.id}" references unknown roster "${t.roster}"`, code: "custom" });
|
|
124
|
+
issues.push({ path: ["entitlement", "tiers", i, "roster"], message: `tier "${t.id}" references unknown roster "${t.roster}"`, code: "custom" });
|
|
105
125
|
(t.models ?? []).forEach((name, j) => { if (!modelCatalog.has(name))
|
|
106
|
-
issues.push({ path: ["tiers", i, "models", j], message: `tier "${t.id}" model "${name}" not in the models catalog`, code: "custom" }); });
|
|
126
|
+
issues.push({ path: ["entitlement", "tiers", i, "models", j], message: `tier "${t.id}" model "${name}" not in the models catalog`, code: "custom" }); });
|
|
107
127
|
(t.visibleSkills ?? []).forEach((name, j) => { if (!skillNames.has(name))
|
|
108
|
-
issues.push({ path: ["tiers", i, "visibleSkills", j], message: `tier "${t.id}" visibleSkill "${name}" not in skills`, code: "custom" }); });
|
|
128
|
+
issues.push({ path: ["entitlement", "tiers", i, "visibleSkills", j], message: `tier "${t.id}" visibleSkill "${name}" not in skills`, code: "custom" }); });
|
|
109
129
|
(t.visibleMcp ?? []).forEach((name, j) => { if (!mcpNames.has(name))
|
|
110
|
-
issues.push({ path: ["tiers", i, "visibleMcp", j], message: `tier "${t.id}" visibleMcp "${name}" not in mcp`, code: "custom" }); });
|
|
130
|
+
issues.push({ path: ["entitlement", "tiers", i, "visibleMcp", j], message: `tier "${t.id}" visibleMcp "${name}" not in mcp`, code: "custom" }); });
|
|
111
131
|
(t.visiblePlugins ?? []).forEach((name, j) => { if (!pluginNames.has(name))
|
|
112
|
-
issues.push({ path: ["tiers", i, "visiblePlugins", j], message: `tier "${t.id}" visiblePlugin "${name}" not in plugins`, code: "custom" }); });
|
|
132
|
+
issues.push({ path: ["entitlement", "tiers", i, "visiblePlugins", j], message: `tier "${t.id}" visiblePlugin "${name}" not in plugins`, code: "custom" }); });
|
|
113
133
|
});
|
|
114
134
|
}
|
|
115
135
|
else {
|
package/dist/hooks.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* DRIFT: this package OWNS the hooks contract, so it owns noticing that CC moved. `test/upstream-drift.test.ts`
|
|
11
11
|
* compares {@link HOOK_EVENTS} against a CC anchor snapshot that `scripts/extract-cc-hook-events.mjs` extracts
|
|
12
12
|
* from the CLI bundle — a word CC has and this set does not is a RED test naming it, instead of a consumer
|
|
13
|
-
* discovering it the hard way (the 1.3.0 / 1.6.0 / 1.8.0 /
|
|
13
|
+
* discovering it the hard way (the 1.3.0 / 1.6.0 / 1.8.0 / 2.0.0 pattern).
|
|
14
14
|
*
|
|
15
15
|
* CONSUMERS
|
|
16
16
|
* - service (the worker server): `TaskRequest.settings.hooks` wire face = `HooksConfig` below; the worker-side
|
|
@@ -35,7 +35,7 @@ export declare const CC_198_HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse",
|
|
|
35
35
|
* Post-CC-2.1.198 additions (sema superset; CC added them after 198).
|
|
36
36
|
* APPEND-ONLY: new members go at the tail (1.3.0 `DirectoryAdded`, CC 2.1.219 — CC's own tuple places it
|
|
37
37
|
* between FileChanged and MessageDisplay; the declaration order here is not normative, the enum is a set).
|
|
38
|
-
*
|
|
38
|
+
* 2.0.0 appends the model-switch pair (`PreModelSwitch` / `PostModelSwitch`, CC 2.1.259 — absent from the
|
|
39
39
|
* 2.1.250 corpus, so 250→259 is when they landed upstream).
|
|
40
40
|
*
|
|
41
41
|
* 🔴 This list is no longer maintained by hand-reading a CLI bundle: `test/upstream-drift.test.ts` fails
|
package/dist/hooks.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* DRIFT: this package OWNS the hooks contract, so it owns noticing that CC moved. `test/upstream-drift.test.ts`
|
|
11
11
|
* compares {@link HOOK_EVENTS} against a CC anchor snapshot that `scripts/extract-cc-hook-events.mjs` extracts
|
|
12
12
|
* from the CLI bundle — a word CC has and this set does not is a RED test naming it, instead of a consumer
|
|
13
|
-
* discovering it the hard way (the 1.3.0 / 1.6.0 / 1.8.0 /
|
|
13
|
+
* discovering it the hard way (the 1.3.0 / 1.6.0 / 1.8.0 / 2.0.0 pattern).
|
|
14
14
|
*
|
|
15
15
|
* CONSUMERS
|
|
16
16
|
* - service (the worker server): `TaskRequest.settings.hooks` wire face = `HooksConfig` below; the worker-side
|
|
@@ -64,7 +64,7 @@ export const CC_198_HOOK_EVENTS = [
|
|
|
64
64
|
* Post-CC-2.1.198 additions (sema superset; CC added them after 198).
|
|
65
65
|
* APPEND-ONLY: new members go at the tail (1.3.0 `DirectoryAdded`, CC 2.1.219 — CC's own tuple places it
|
|
66
66
|
* between FileChanged and MessageDisplay; the declaration order here is not normative, the enum is a set).
|
|
67
|
-
*
|
|
67
|
+
* 2.0.0 appends the model-switch pair (`PreModelSwitch` / `PostModelSwitch`, CC 2.1.259 — absent from the
|
|
68
68
|
* 2.1.250 corpus, so 250→259 is when they landed upstream).
|
|
69
69
|
*
|
|
70
70
|
* 🔴 This list is no longer maintained by hand-reading a CLI bundle: `test/upstream-drift.test.ts` fails
|
package/dist/node.d.ts
CHANGED
package/dist/node.js
CHANGED
|
@@ -9,3 +9,4 @@ export * from "./file-edit.js";
|
|
|
9
9
|
export * from "./bundle.js";
|
|
10
10
|
export * from "./local-load.js";
|
|
11
11
|
export * from "./scheduler-store-node.js"; // R7 self-wake scheduler store — fs binding of the ./scheduler-store contract
|
|
12
|
+
export * from "./prompts-digest.js"; // prompts 下发包的内容摘要(4.0.0 从 scheduler-store-node 搬来 —— 与调度存储无关;出口路径不变)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** [1057]① prompts contentDigest:canonical JSON(stableStringify 同姿势:键序稳定+深度护栏)全量 sha256,
|
|
2
|
+
* **非盐化**(三方可独立复算)。入参=下发形的 `{sections, scenarioOverrides?}`(packId/contentDigest
|
|
3
|
+
* 自身不入 hash)。 */
|
|
4
|
+
export declare function promptsContentDigest(payload: {
|
|
5
|
+
sections: unknown;
|
|
6
|
+
scenarioOverrides?: unknown;
|
|
7
|
+
}): {
|
|
8
|
+
contentDigest: string;
|
|
9
|
+
packId: string;
|
|
10
|
+
/** [1062]③ 顺带:逐 section 对 `text` 铸的 contentHash(与 core 1.315 校验语义同刻度=
|
|
11
|
+
* sha256(text));元素无 string text 位=undefined。center 物化流程=先把这些写回
|
|
12
|
+
* declaration.contentHash,再对含 hash 的终形铸整包 digest(本函数对入参原样铸,两步自洽)。 */
|
|
13
|
+
sectionHashes: Array<string | undefined>;
|
|
14
|
+
};
|
|
15
|
+
/** 逐 section 对账锚(core 1.315 `PromptTextDeclaration.contentHash` 同刻度):sha256(text)。 */
|
|
16
|
+
export declare function sectionContentHash(text: string): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sema-agent/settings-schema/node — prompts 下发包的**内容摘要**([1057]①/[1062]③)。
|
|
3
|
+
*
|
|
4
|
+
* 这两只是跨仓**对账锚**:center 铸、server 透传、core 的 manifest 归因**独立复算**,三方算出同一个值才
|
|
5
|
+
* 对得上账([1050]② operator 对账位)。所以它们只有一件事要守:**一个刻度**。
|
|
6
|
+
*
|
|
7
|
+
* 4.0.0 从 `scheduler-store-node.ts` 搬到这里(不 breaking —— 仍挂在 `./node` 这条 subpath 上,消费方的
|
|
8
|
+
* import 一个字不用改)。两条理由:①它与调度存储(`scheduled_tasks.json` 的原子读写 + 跨进程变更原语)
|
|
9
|
+
* 零关系,住在一起只是历史;②搬家顺手把**两个** sha256 铸点合成一只 —— 此前整包 digest 与逐 section
|
|
10
|
+
* contentHash 各自 `createHash('sha256')` 一次、各自拼一次 `sha256:` 前缀,哪天有人只改一处就成了两个刻度,
|
|
11
|
+
* 而没有一格会红(门:`test/prompts-digest-4.0.0.test.ts` 逐条看着铸点数与出门前缀数)。
|
|
12
|
+
*
|
|
13
|
+
* 摘要用本包**纯实现**的 sha256(`./sha256.ts`),不是 `node:crypto` —— 那份实现与
|
|
14
|
+
* `createHash("sha256").update(s,"utf8").digest("hex")` 逐字节相同(`test/sha256.test.ts` 逐例压着,
|
|
15
|
+
* 本模块的门再压一遍摘要值本身),于是整个包的 sha256 只有**一份实现**。
|
|
16
|
+
*/
|
|
17
|
+
import { stableStringify } from './hash.js';
|
|
18
|
+
import { sha256Hex } from './sha256.js';
|
|
19
|
+
/**
|
|
20
|
+
* 本模块**唯一**的 sha256 铸点:一段 utf8 文本 → `{ hex, anchor }`。`anchor` = 出门形 `sha256:<hex>`
|
|
21
|
+
* (自描述算法,与 skills 的内容地址同形),`hex` 给需要裸 hex 的那一处(packId 取前 12 位)。
|
|
22
|
+
* 两个出门形从**同一次**摘要派生 —— 这正是本模块存在的理由。
|
|
23
|
+
*/
|
|
24
|
+
function contentAnchor(text) {
|
|
25
|
+
const hex = sha256Hex(text);
|
|
26
|
+
return { hex, anchor: `sha256:${hex}` };
|
|
27
|
+
}
|
|
28
|
+
/** [1057]① prompts contentDigest:canonical JSON(stableStringify 同姿势:键序稳定+深度护栏)全量 sha256,
|
|
29
|
+
* **非盐化**(三方可独立复算)。入参=下发形的 `{sections, scenarioOverrides?}`(packId/contentDigest
|
|
30
|
+
* 自身不入 hash)。 */
|
|
31
|
+
export function promptsContentDigest(payload) {
|
|
32
|
+
const canonical = stableStringify({
|
|
33
|
+
sections: payload.sections,
|
|
34
|
+
...(payload.scenarioOverrides !== undefined ? { scenarioOverrides: payload.scenarioOverrides } : {}),
|
|
35
|
+
});
|
|
36
|
+
const { hex, anchor } = contentAnchor(canonical);
|
|
37
|
+
const sectionHashes = Array.isArray(payload.sections)
|
|
38
|
+
? payload.sections.map((s) => {
|
|
39
|
+
const t = s?.text;
|
|
40
|
+
return typeof t === 'string' ? sectionContentHash(t) : undefined;
|
|
41
|
+
})
|
|
42
|
+
: [];
|
|
43
|
+
return { contentDigest: anchor, packId: `center:${hex.slice(0, 12)}`, sectionHashes };
|
|
44
|
+
}
|
|
45
|
+
/** 逐 section 对账锚(core 1.315 `PromptTextDeclaration.contentHash` 同刻度):sha256(text)。 */
|
|
46
|
+
export function sectionContentHash(text) {
|
|
47
|
+
return contentAnchor(text).anchor;
|
|
48
|
+
}
|
|
@@ -13,8 +13,13 @@ export declare function loadSchedulerStore(path: string): SchedulerRecord[];
|
|
|
13
13
|
* concurrent reader never sees a partial file). Throws on IO failure (caller maps to a domain error).
|
|
14
14
|
*/
|
|
15
15
|
export declare function saveSchedulerStore(path: string, records: SchedulerRecord[]): void;
|
|
16
|
-
/** A filesystem-backed {@link SchedulerStore} bound to a path (convenience for the daemon/backend constructors).
|
|
17
|
-
|
|
16
|
+
/** A filesystem-backed {@link SchedulerStore} bound to a path (convenience for the daemon/backend constructors).
|
|
17
|
+
*
|
|
18
|
+
* 4.0.0 BREAKING 改名(S-571 B5,**零别名**):旧名 `fileSchedulerStore` 已删。它返回一只带 `load`/`save`
|
|
19
|
+
* 的**活对象** ⇒ 按本组工厂命名法(`build*` 出纯数据、`create*` 出活对象)该叫 `create*`;2.0.0 那次照人眼
|
|
20
|
+
* 改只改到了 `createSiblingResolver`,漏了这处 —— 现在由 `test/factory-naming-4.0.0.test.ts` 的门④ 机器看着。
|
|
21
|
+
* 消费方改调 `createFileSchedulerStore`(改名是硬 breaking:消费方的 tsc 红 / 运行期 undefined 就是通知本身)。 */
|
|
22
|
+
export declare function createFileSchedulerStore(path: string): SchedulerStore;
|
|
18
23
|
/** Outcome of {@link mutateSchedulerStore}. `conflict_exhausted` = a concurrent writer kept changing the file
|
|
19
24
|
* faster than the bounded replays; nothing was written on that final attempt (earlier attempts never wrote). */
|
|
20
25
|
export type SchedulerStoreMutation = {
|
|
@@ -53,20 +58,3 @@ export type SchedulerStoreMutation = {
|
|
|
53
58
|
export declare function mutateSchedulerStore(path: string, fn: (records: SchedulerRecord[]) => SchedulerRecord[] | null, opts?: {
|
|
54
59
|
retries?: number;
|
|
55
60
|
}): SchedulerStoreMutation;
|
|
56
|
-
/** [1057]① prompts contentDigest(node 绑定;browser-safe 纯层不带 crypto):
|
|
57
|
-
* canonical JSON(stableStringify 同姿势:键序稳定+深度护栏)全量 sha256,**非盐化**
|
|
58
|
-
* ([1050]② operator 对账位——center 产、server 透传、core manifest 归因三方可独立复算)。
|
|
59
|
-
* 入参=下发形的 {sections, scenarioOverrides?}(packId/contentDigest 自身不入 hash)。 */
|
|
60
|
-
export declare function promptsContentDigest(payload: {
|
|
61
|
-
sections: unknown;
|
|
62
|
-
scenarioOverrides?: unknown;
|
|
63
|
-
}): {
|
|
64
|
-
contentDigest: string;
|
|
65
|
-
packId: string;
|
|
66
|
-
/** [1062]③ 顺带:逐 section 对 `text` 铸的 contentHash(与 core 1.315 校验语义同刻度=
|
|
67
|
-
* sha256(text));元素无 string text 位=undefined。center 物化流程=先把这些写回
|
|
68
|
-
* declaration.contentHash,再对含 hash 的终形铸整包 digest(本函数对入参原样铸,两步自洽)。 */
|
|
69
|
-
sectionHashes: Array<string | undefined>;
|
|
70
|
-
};
|
|
71
|
-
/** 逐 section 对账锚(core 1.315 `PromptTextDeclaration.contentHash` 同刻度):sha256(text)。 */
|
|
72
|
-
export declare function sectionContentHash(text: string): string;
|
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
|
|
10
10
|
import { dirname } from 'node:path';
|
|
11
|
-
import { randomBytes
|
|
11
|
+
import { randomBytes } from 'node:crypto';
|
|
12
12
|
import { parseSchedulerStore, serializeSchedulerStore } from './scheduler-store.js';
|
|
13
|
-
import { stableStringify } from './hash.js';
|
|
14
13
|
/**
|
|
15
14
|
* Load the durable store. A MISSING file (ENOENT) ⇒ `[]` (fail-soft, first-run/never-written case); any OTHER
|
|
16
15
|
* read failure (EACCES/EIO/EISDIR/…) RETHROWS — only absence means "empty", not "unreadable for any reason"
|
|
@@ -39,8 +38,13 @@ export function saveSchedulerStore(path, records) {
|
|
|
39
38
|
writeFileSync(tmp, serializeSchedulerStore(records), { mode: 0o600 });
|
|
40
39
|
renameSync(tmp, path); // atomic over the target
|
|
41
40
|
}
|
|
42
|
-
/** A filesystem-backed {@link SchedulerStore} bound to a path (convenience for the daemon/backend constructors).
|
|
43
|
-
|
|
41
|
+
/** A filesystem-backed {@link SchedulerStore} bound to a path (convenience for the daemon/backend constructors).
|
|
42
|
+
*
|
|
43
|
+
* 4.0.0 BREAKING 改名(S-571 B5,**零别名**):旧名 `fileSchedulerStore` 已删。它返回一只带 `load`/`save`
|
|
44
|
+
* 的**活对象** ⇒ 按本组工厂命名法(`build*` 出纯数据、`create*` 出活对象)该叫 `create*`;2.0.0 那次照人眼
|
|
45
|
+
* 改只改到了 `createSiblingResolver`,漏了这处 —— 现在由 `test/factory-naming-4.0.0.test.ts` 的门④ 机器看着。
|
|
46
|
+
* 消费方改调 `createFileSchedulerStore`(改名是硬 breaking:消费方的 tsc 红 / 运行期 undefined 就是通知本身)。 */
|
|
47
|
+
export function createFileSchedulerStore(path) {
|
|
44
48
|
return {
|
|
45
49
|
load: () => loadSchedulerStore(path),
|
|
46
50
|
save: (records) => saveSchedulerStore(path, records),
|
|
@@ -95,25 +99,3 @@ export function mutateSchedulerStore(path, fn, opts) {
|
|
|
95
99
|
return { ok: true, records: next, retries: attempt };
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
|
-
/** [1057]① prompts contentDigest(node 绑定;browser-safe 纯层不带 crypto):
|
|
99
|
-
* canonical JSON(stableStringify 同姿势:键序稳定+深度护栏)全量 sha256,**非盐化**
|
|
100
|
-
* ([1050]② operator 对账位——center 产、server 透传、core manifest 归因三方可独立复算)。
|
|
101
|
-
* 入参=下发形的 {sections, scenarioOverrides?}(packId/contentDigest 自身不入 hash)。 */
|
|
102
|
-
export function promptsContentDigest(payload) {
|
|
103
|
-
const canonical = stableStringify({
|
|
104
|
-
sections: payload.sections,
|
|
105
|
-
...(payload.scenarioOverrides !== undefined ? { scenarioOverrides: payload.scenarioOverrides } : {}),
|
|
106
|
-
});
|
|
107
|
-
const hex = createHash('sha256').update(canonical, 'utf8').digest('hex');
|
|
108
|
-
const sectionHashes = Array.isArray(payload.sections)
|
|
109
|
-
? payload.sections.map((s) => {
|
|
110
|
-
const t = s?.text;
|
|
111
|
-
return typeof t === 'string' ? sectionContentHash(t) : undefined;
|
|
112
|
-
})
|
|
113
|
-
: [];
|
|
114
|
-
return { contentDigest: `sha256:${hex}`, packId: `center:${hex.slice(0, 12)}`, sectionHashes };
|
|
115
|
-
}
|
|
116
|
-
/** 逐 section 对账锚(core 1.315 `PromptTextDeclaration.contentHash` 同刻度):sha256(text)。 */
|
|
117
|
-
export function sectionContentHash(text) {
|
|
118
|
-
return `sha256:${createHash('sha256').update(text, 'utf8').digest('hex')}`;
|
|
119
|
-
}
|
|
@@ -60,7 +60,7 @@ export declare function isSchedulerRecord(v: unknown): v is SchedulerRecord;
|
|
|
60
60
|
* `switch` gains an arm to write).
|
|
61
61
|
* - `malformed-record` — the record failed {@link isSchedulerRecord} and was dropped; `record` = the
|
|
62
62
|
* raw (unvalidated) value, for diagnostics.
|
|
63
|
-
* - `durable-scope-rekeyed` — the durable-scope compat arm rewrote this row's `scope` (
|
|
63
|
+
* - `durable-scope-rekeyed` — the durable-scope compat arm rewrote this row's `scope` (2.0.0; before that
|
|
64
64
|
* the rewrite happened silently every parse). */
|
|
65
65
|
export type SchedulerParseWarning = {
|
|
66
66
|
index: number;
|