@sema-agent/settings-schema 7.0.1 → 8.1.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 +207 -1
- package/README.md +60 -6
- package/conformance/VERSION +1 -1
- package/conformance/cases/c01-entry-salvage-then-typo/expected.json +5 -0
- package/conformance/cases/c02-hosts-clamp-stacked/expected.json +5 -0
- package/conformance/cases/c03-literal-dotted-top-key/expected.json +41 -0
- package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +5 -0
- package/conformance/cases/c05-gate-domain-bad-entry-throws/expected.json +5 -0
- package/conformance/cases/c06-legacy-governance-lift/expected.json +12 -0
- package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +5 -0
- package/conformance/cases/c08-limits-unknown-carried-64/expected.json +77 -0
- package/conformance/cases/c09-limits-unknown-carried-65/expected.json +78 -0
- package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +5 -0
- package/conformance/cases/c11-unicode-unknown-keys/expected.json +14 -0
- package/conformance/cases/c12-prototype-name-top-keys/expected.json +5 -0
- package/conformance/cases/c13-models-field-null/expected.json +5 -0
- package/conformance/cases/c14-models-field-empty-array/expected.json +16 -0
- package/conformance/cases/c15-models-field-number/expected.json +5 -0
- package/conformance/cases/c16-deep-nesting-205/expected.json +14 -0
- package/conformance/cases/c17-empty-domain-file/expected.json +10 -0
- package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +10 -0
- package/conformance/cases/c19-wire-envelope-no-version/expected.json +10 -0
- package/conformance/cases/c20-governance-absent-lifts/expected.json +12 -0
- package/conformance/cases/c21-governance-empty-object-wins/expected.json +13 -0
- package/conformance/cases/c22-governance-null-throws/expected.json +5 -0
- package/conformance/cases/c23-mixed-validity-models/expected.json +5 -0
- package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +29 -0
- package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +33 -0
- package/conformance/cases/c26-hosts-array-1024/expected.json +7183 -0
- package/conformance/cases/c27-hosts-array-1025/expected.json +5 -0
- package/conformance/cases/c28-large-document-over-1mb/expected.json +14 -0
- package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +5 -0
- package/conformance/cases/c30-version-triple-a-first/expected.json +29 -0
- package/conformance/cases/c31-version-triple-b-changed/expected.json +29 -0
- package/conformance/cases/c32-version-triple-a-restored/expected.json +29 -0
- package/conformance/cases/c33-limits-nested-carried-key/expected.json +17 -0
- package/conformance/cases/c34-skills-content-vs-manifest/expected.json +23 -0
- package/conformance/cases/c35-portable-domain-file-is-null/expected.json +5 -0
- package/conformance/cases/c36-skills-entry-without-content/expected.json +23 -0
- package/conformance/cases/c37-skills-field-null/expected.json +5 -0
- package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +5 -0
- package/conformance/cases/c39-hidden-file-in-config-d/expected.json +5 -0
- package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +5 -0
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +5 -0
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +33 -0
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +5 -0
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +5 -0
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +5 -0
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/expected.json +5 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/expected.json +5 -0
- package/conformance/cases/c48-s935-model-headers-transported/expected.json +59 -0
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +5 -0
- package/conformance/cases/c50-s784-lane-host-legal/expected.json +44 -0
- package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +46 -0
- package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +48 -0
- package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +48 -0
- package/conformance/cases/c54-s784-lane-adb-legal/expected.json +44 -0
- package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +45 -0
- package/conformance/cases/c56-s784-lane-device-legal/expected.json +51 -0
- package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +5 -0
- package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +5 -0
- package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +5 -0
- package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +5 -0
- package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +5 -0
- package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +5 -0
- package/conformance/cases/c63-s784-host-two-replicas/expected.json +5 -0
- package/conformance/cases/c64-s784-multitenant-local-store/expected.json +5 -0
- package/conformance/cases/c65-s784-pg-without-db/expected.json +5 -0
- package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +5 -0
- package/conformance/cases/c67-s784-retired-field-loud/expected.json +5 -0
- package/conformance/cases/c68-s784-device-local-store/expected.json +5 -0
- package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +54 -0
- package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +27 -0
- package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +33 -0
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +5 -0
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +5 -0
- package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +22 -0
- package/conformance/cases/cli04-env-value-with-newline/expected.json +46 -0
- package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +53 -0
- package/conformance/cases/t01-absent-domain-key-not-null/expected.json +5 -0
- package/conformance/cases/t02-entry-salvage-vs-whole-domain-refusal/expected.json +5 -0
- package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +29 -0
- package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +5 -0
- package/conformance/cases/z01-every-domain-empty-object/expected.json +64 -0
- package/conformance/cases/z02-every-domain-unknown-key/expected.json +66 -0
- package/dist/api/auth-bridge.d.ts +2 -2
- package/dist/cli.js +58 -26
- package/dist/config-fns.js +7 -1
- package/dist/conformance/contract-adapter.d.ts +4 -2
- package/dist/conformance/contract-adapter.js +21 -7
- package/dist/conformance/corpus.js +12 -7
- package/dist/conformance/derive.d.ts +26 -0
- package/dist/conformance/derive.js +25 -2
- package/dist/conformance/index.d.ts +4 -3
- package/dist/conformance/index.js +4 -3
- package/dist/conformance/run.d.ts +3 -3
- package/dist/conformance/run.js +87 -17
- package/dist/conformance/types.d.ts +57 -17
- package/dist/conformance/types.js +4 -2
- package/dist/fleet.d.ts +417 -3
- package/dist/fleet.js +287 -12
- package/dist/hooks.d.ts +4 -4
- package/dist/types.js +3 -1
- package/package.json +2 -1
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 三步接线(消费方侧):
|
|
8
8
|
* 1. 造一个 {@link ConformanceAdapter}:`provenance` = 你**实际加载到**的那份契约包(package.json 路径、
|
|
9
|
-
* 版号、完整性串);`legs` =
|
|
9
|
+
* 版号、完整性串);`legs` = **四条腿逐条表态**:你自己的生产入口(每条带 `entry{module,export}`,生产入口的
|
|
10
|
+
* 绝对路径),或 `{ declined: "no-production-path", why }`(你的生产代码结构上没有这种读法 / 写法)。
|
|
10
11
|
* 2. `const report = await runConformance(adapter, { mode: "release" })`。
|
|
11
12
|
* 3. 断言 `report.ok`;不绿就读 `report.violations` 与每格的 `diff`(effective 的 JSON patch、告警多重集差、
|
|
12
13
|
* 元数据差三面分开)。
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
export { runConformance, materializeLocalRoot, resolveLegExpectation } from "./run.js";
|
|
17
18
|
export { createContractAdapter, type ContractAdapterOptions, type ContractModule } from "./contract-adapter.js";
|
|
18
19
|
export { loadCorpus, loadCase, kitIdentity, DEFAULT_CORPUS_DIR, PACKAGE_ROOT, type Corpus, type ConformanceCase, type CaseInput, type CaseMeta, type LegExpectationJson } from "./corpus.js";
|
|
19
|
-
export { DERIVATION_TRANSFORMS, NON_PORTABLE_DOMAINS, EPOCH_ISO, deriveWireInput, deriveLocalInput, deriveWireExpectation, deriveLocalExpectation, foreignKeysOf, toManifestDoc, type DerivationTransform, type LegExpectation, } from "./derive.js";
|
|
20
|
+
export { DERIVATION_TRANSFORMS, LEG_EXPECTATION, NON_PORTABLE_DOMAINS, EPOCH_ISO, deriveWireInput, deriveLocalInput, deriveWireExpectation, deriveLocalExpectation, foreignKeysOf, toManifestDoc, type DerivationTransform, type LegExpectation, } from "./derive.js";
|
|
20
21
|
export { canonicalize, deepEqual, diffJson, stableKey, type JsonPatchOp } from "./canonical.js";
|
|
21
22
|
export { WARNING_KINDS, projectWarning, normalizeProjection, projectionKey, projectionValue, diffWarningMultiset, findSecretLeaks, type WarningKind, type WarningProjection, } from "./warnings.js";
|
|
22
|
-
export { LEG_NAMES, REJECT_STAGES, type ConformanceAdapter, type ConformanceLeg, type ConformanceMode, type ConformanceNormalizer, type ConformanceOptions, type ConformanceProvenance, type ConformanceReport, type ConformanceResult, type ConformanceStatus, type KnownDivergence, type OutcomeDivergence, type PathDivergence, type LedgerRow, type LegEntry, type LegInput, type LegName, type LegOutcome, type RejectStage, } from "./types.js";
|
|
23
|
+
export { LEG_DECLINE_REASONS, LEG_NAMES, REJECT_STAGES, type ConformanceAdapter, type ConformanceLeg, type DomainDocsInput, type LegDeclineReason, type LegDeclined, type LegStance, type ConformanceMode, type ConformanceNormalizer, type ConformanceOptions, type ConformanceProvenance, type ConformanceReport, type ConformanceResult, type ConformanceStatus, type KnownDivergence, type OutcomeDivergence, type PathDivergence, type LedgerRow, type LegEntry, type LegInput, type LegName, type LegOutcome, type RejectStage, } from "./types.js";
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 三步接线(消费方侧):
|
|
8
8
|
* 1. 造一个 {@link ConformanceAdapter}:`provenance` = 你**实际加载到**的那份契约包(package.json 路径、
|
|
9
|
-
* 版号、完整性串);`legs` =
|
|
9
|
+
* 版号、完整性串);`legs` = **四条腿逐条表态**:你自己的生产入口(每条带 `entry{module,export}`,生产入口的
|
|
10
|
+
* 绝对路径),或 `{ declined: "no-production-path", why }`(你的生产代码结构上没有这种读法 / 写法)。
|
|
10
11
|
* 2. `const report = await runConformance(adapter, { mode: "release" })`。
|
|
11
12
|
* 3. 断言 `report.ok`;不绿就读 `report.violations` 与每格的 `diff`(effective 的 JSON patch、告警多重集差、
|
|
12
13
|
* 元数据差三面分开)。
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
export { runConformance, materializeLocalRoot, resolveLegExpectation } from "./run.js";
|
|
17
18
|
export { createContractAdapter } from "./contract-adapter.js";
|
|
18
19
|
export { loadCorpus, loadCase, kitIdentity, DEFAULT_CORPUS_DIR, PACKAGE_ROOT } from "./corpus.js";
|
|
19
|
-
export { DERIVATION_TRANSFORMS, NON_PORTABLE_DOMAINS, EPOCH_ISO, deriveWireInput, deriveLocalInput, deriveWireExpectation, deriveLocalExpectation, foreignKeysOf, toManifestDoc, } from "./derive.js";
|
|
20
|
+
export { DERIVATION_TRANSFORMS, LEG_EXPECTATION, NON_PORTABLE_DOMAINS, EPOCH_ISO, deriveWireInput, deriveLocalInput, deriveWireExpectation, deriveLocalExpectation, foreignKeysOf, toManifestDoc, } from "./derive.js";
|
|
20
21
|
export { canonicalize, deepEqual, diffJson, stableKey } from "./canonical.js";
|
|
21
22
|
export { WARNING_KINDS, projectWarning, normalizeProjection, projectionKey, projectionValue, diffWarningMultiset, findSecretLeaks, } from "./warnings.js";
|
|
22
|
-
export { LEG_NAMES, REJECT_STAGES, } from "./types.js";
|
|
23
|
+
export { LEG_DECLINE_REASONS, LEG_NAMES, REJECT_STAGES, } from "./types.js";
|
|
@@ -19,9 +19,9 @@ export declare function resolveLegExpectation(c: ConformanceCase, leg: LegName,
|
|
|
19
19
|
/**
|
|
20
20
|
* 跑一遍门。
|
|
21
21
|
*
|
|
22
|
-
* `release`:版本三方对齐(适配器加载到的包 == 语料 VERSION == kit 所在包)+
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* `release`:版本三方对齐(适配器加载到的包 == 语料 VERSION == kit 所在包)+ **每条腿都有表态**(实现,或显式声明
|
|
23
|
+
* 不承担:闭集理由词 + why;缺表态 / 形不对 = 违例)+ 每条实现了的腿都指向一个**真存在、且不在契约包自己身上**的生产入口
|
|
24
|
+
* + 禁 `only` + 至少实现一条腿 + **实现的腿必须跑** + 账本条目的形 + 零陈腐账本条目。声明不承担的腿逐格记 `skipped`(带理由)。
|
|
25
25
|
* `historical`:对旧版包跑,只记行为 diff;归一器可在场且必须自陈有损。
|
|
26
26
|
*/
|
|
27
27
|
export declare function runConformance(adapter: ConformanceAdapter, opts: ConformanceOptions): Promise<ConformanceReport>;
|
package/dist/conformance/run.js
CHANGED
|
@@ -19,7 +19,7 @@ import { diffWarningMultiset, findSecretLeaks, projectWarning } from "./warnings
|
|
|
19
19
|
import { deriveLocalExpectation, deriveLocalInput, deriveWireExpectation, deriveWireInput, toManifestDoc } from "./derive.js";
|
|
20
20
|
import { kitIdentity, loadCorpus } from "./corpus.js";
|
|
21
21
|
import { contractOwnedEntry, contractOwnedEntryWhy } from "./entry-origin.js";
|
|
22
|
-
import { LEG_NAMES, } from "./types.js";
|
|
22
|
+
import { LEG_DECLINE_REASONS, LEG_NAMES, } from "./types.js";
|
|
23
23
|
/** 一个**模块**文件的样子 —— 来源律用它挡住「随便找个存在的文件当入口」(`package.json` 也是文件)。 */
|
|
24
24
|
const MODULE_EXTENSIONS = [".ts", ".mts", ".cts", ".tsx", ".js", ".mjs", ".cjs", ".jsx"];
|
|
25
25
|
/**
|
|
@@ -77,6 +77,7 @@ function legInput(c, leg) {
|
|
|
77
77
|
const { version, updatedAt } = c.input.meta;
|
|
78
78
|
switch (leg) {
|
|
79
79
|
case "domains":
|
|
80
|
+
case "strictWrite": // 两条腿吃同一份真源(恒等,不派生入参)
|
|
80
81
|
return { leg, docs: c.input.domains, version, updatedAt };
|
|
81
82
|
case "wire":
|
|
82
83
|
return c.input.wirePayload === undefined ? deriveWireInput(c.input.domains, version, updatedAt) : { leg, payload: c.input.wirePayload };
|
|
@@ -254,6 +255,31 @@ function judge(c, leg, expected, actual, normalizer, ledger, hits) {
|
|
|
254
255
|
}
|
|
255
256
|
return { ...base, status: "diff", detail, diff };
|
|
256
257
|
}
|
|
258
|
+
function stanceOf(adapter, leg) {
|
|
259
|
+
const legs = adapter.legs;
|
|
260
|
+
const s = legs !== null && typeof legs === "object" ? legs[leg] : undefined;
|
|
261
|
+
if (s === undefined)
|
|
262
|
+
return { kind: "missing" };
|
|
263
|
+
if (s === null || typeof s !== "object")
|
|
264
|
+
return { kind: "malformed", why: "the stance is not an object" };
|
|
265
|
+
const runs = typeof s.run === "function";
|
|
266
|
+
const declines = Object.prototype.hasOwnProperty.call(s, "declined");
|
|
267
|
+
// 与类型同律(两臂互斥):声明臂上出现实现臂的任一字段(`entry` / `run`)都算「既实现又声明」。
|
|
268
|
+
const carriesImplementation = runs || Object.prototype.hasOwnProperty.call(s, "entry") || Object.prototype.hasOwnProperty.call(s, "run");
|
|
269
|
+
if (declines && carriesImplementation)
|
|
270
|
+
return { kind: "malformed", why: "it both implements the leg (`entry` / `run`) and declines it (`declined`) — 一条腿只能有一种表态" };
|
|
271
|
+
if (runs)
|
|
272
|
+
return { kind: "implemented", impl: s };
|
|
273
|
+
if (!declines)
|
|
274
|
+
return { kind: "malformed", why: "it neither implements the leg (`run` + `entry`) nor declines it (`declined` + `why`)" };
|
|
275
|
+
const { declined, why } = s;
|
|
276
|
+
if (!LEG_DECLINE_REASONS.includes(declined)) {
|
|
277
|
+
return { kind: "malformed", why: `decline reason ${JSON.stringify(declined)} is not one of ${LEG_DECLINE_REASONS.join(" | ")}` };
|
|
278
|
+
}
|
|
279
|
+
if (typeof why !== "string" || why.trim().length === 0)
|
|
280
|
+
return { kind: "malformed", why: "a declined leg has an empty `why` — 说不出「为什么结构上没有」的一句声明等于没声明" };
|
|
281
|
+
return { kind: "declined", declined: { declined: declined, why } };
|
|
282
|
+
}
|
|
257
283
|
/** release 模式的**来源律 + 版本护栏**(historical 模式不跑这几条:它就是拿旧版包比行为的)。 */
|
|
258
284
|
async function releaseViolations(adapter, opts, corpus, kit) {
|
|
259
285
|
const v = [];
|
|
@@ -307,9 +333,30 @@ async function releaseViolations(adapter, opts, corpus, kit) {
|
|
|
307
333
|
if (!Array.isArray(cases) || cases.length === 0)
|
|
308
334
|
v.push(`known divergence \`${e.ref}\` is an outcome divergence that must name at least one case — 结局形不许按前缀大包一整条腿的拒绝面`);
|
|
309
335
|
}
|
|
310
|
-
|
|
336
|
+
// **每条腿都要表态**:实现,或显式声明不承担(闭集理由词 + why)。缺表态 / 形不对 ⇒ 违例 —— 不许静默跳过,也不许默认当通过。
|
|
337
|
+
const legs = [];
|
|
338
|
+
for (const leg of LEG_NAMES) {
|
|
339
|
+
const s = stanceOf(adapter, leg);
|
|
340
|
+
switch (s.kind) {
|
|
341
|
+
case "implemented":
|
|
342
|
+
legs.push([leg, s.impl]);
|
|
343
|
+
break;
|
|
344
|
+
case "declined":
|
|
345
|
+
break;
|
|
346
|
+
case "missing":
|
|
347
|
+
v.push(`leg \`${leg}\` has no stance — 每条腿要么实现、要么显式声明不承担(\`{ declined: ${LEG_DECLINE_REASONS.map((r) => JSON.stringify(r)).join(" | ")}, why }\`);不表态不是一种表态`);
|
|
348
|
+
break;
|
|
349
|
+
case "malformed":
|
|
350
|
+
v.push(`leg \`${leg}\` has a malformed stance: ${s.why}`);
|
|
351
|
+
break;
|
|
352
|
+
default: {
|
|
353
|
+
const unhandled = s;
|
|
354
|
+
throw new Error(`conformance: unhandled stance ${JSON.stringify(unhandled)}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
311
358
|
if (legs.length === 0)
|
|
312
|
-
v.push("adapter
|
|
359
|
+
v.push("adapter implements no leg at all");
|
|
313
360
|
for (const [name, leg] of legs) {
|
|
314
361
|
const mod = leg.entry.module;
|
|
315
362
|
if (!isAbsolute(mod)) {
|
|
@@ -338,18 +385,34 @@ async function releaseViolations(adapter, opts, corpus, kit) {
|
|
|
338
385
|
}
|
|
339
386
|
return v;
|
|
340
387
|
}
|
|
388
|
+
/** 一格「没跑」的那句话:按表态说清是哪一种没跑(实现了的腿走不到这里)。 */
|
|
389
|
+
function notRunDetail(s) {
|
|
390
|
+
switch (s.kind) {
|
|
391
|
+
case "declined":
|
|
392
|
+
return `consumer declines this leg (${s.declined.declined}): ${s.declined.why}`;
|
|
393
|
+
case "missing":
|
|
394
|
+
return "no stance: this consumer neither implements nor declines this leg";
|
|
395
|
+
case "malformed":
|
|
396
|
+
return `malformed stance: ${s.why}`;
|
|
397
|
+
default: {
|
|
398
|
+
const unhandled = s;
|
|
399
|
+
throw new Error(`conformance: unhandled stance ${JSON.stringify(unhandled)}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
341
403
|
/**
|
|
342
404
|
* 跑一遍门。
|
|
343
405
|
*
|
|
344
|
-
* `release`:版本三方对齐(适配器加载到的包 == 语料 VERSION == kit 所在包)+
|
|
345
|
-
*
|
|
346
|
-
*
|
|
406
|
+
* `release`:版本三方对齐(适配器加载到的包 == 语料 VERSION == kit 所在包)+ **每条腿都有表态**(实现,或显式声明
|
|
407
|
+
* 不承担:闭集理由词 + why;缺表态 / 形不对 = 违例)+ 每条实现了的腿都指向一个**真存在、且不在契约包自己身上**的生产入口
|
|
408
|
+
* + 禁 `only` + 至少实现一条腿 + **实现的腿必须跑** + 账本条目的形 + 零陈腐账本条目。声明不承担的腿逐格记 `skipped`(带理由)。
|
|
347
409
|
* `historical`:对旧版包跑,只记行为 diff;归一器可在场且必须自陈有损。
|
|
348
410
|
*/
|
|
349
411
|
export async function runConformance(adapter, opts) {
|
|
350
412
|
const corpus = await loadCorpus(opts.corpusDir);
|
|
351
413
|
const kit = await kitIdentity();
|
|
352
414
|
const violations = opts.mode === "release" ? await releaseViolations(adapter, opts, corpus, kit) : [];
|
|
415
|
+
const stances = new Map(LEG_NAMES.map((leg) => [leg, stanceOf(adapter, leg)]));
|
|
353
416
|
const only = opts.only === undefined ? undefined : new Set(opts.only);
|
|
354
417
|
const cases = only === undefined ? corpus.cases : corpus.cases.filter((c) => only.has(c.id));
|
|
355
418
|
if (only !== undefined) {
|
|
@@ -368,17 +431,16 @@ export async function runConformance(adapter, opts) {
|
|
|
368
431
|
results.push({ caseId: c.id, leg, status: "skipped", detail: `case declares this leg not applicable: ${spec.why}` });
|
|
369
432
|
continue;
|
|
370
433
|
}
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
373
|
-
//
|
|
374
|
-
// `skipped
|
|
375
|
-
|
|
376
|
-
results.push({ caseId: c.id, leg, status: "skipped", detail: "no adapter: this consumer registered no such read leg" });
|
|
434
|
+
const stance = stances.get(leg);
|
|
435
|
+
if (stance.kind !== "implemented") {
|
|
436
|
+
// 声明不承担 ⇒ 逐格 `skipped`,detail 带理由词与 why(覆盖矩阵看得见它没跑、为什么没跑)。缺表态 / 形不对同样
|
|
437
|
+
// 没法跑 ⇒ 也记 `skipped`,但 release 门已把它记成违例(见 releaseViolations)—— 不表态不是一种表态。
|
|
438
|
+
results.push({ caseId: c.id, leg, status: "skipped", detail: notRunDetail(stance) });
|
|
377
439
|
continue;
|
|
378
440
|
}
|
|
379
441
|
let actual;
|
|
380
442
|
try {
|
|
381
|
-
actual = await impl.run(legInput(c, leg));
|
|
443
|
+
actual = await stance.impl.run(legInput(c, leg));
|
|
382
444
|
}
|
|
383
445
|
catch (e) {
|
|
384
446
|
results.push({ caseId: c.id, leg, status: "harness-error", detail: `the adapter threw instead of returning an outcome: ${e instanceof Error ? e.message : String(e)}` });
|
|
@@ -394,12 +456,12 @@ export async function runConformance(adapter, opts) {
|
|
|
394
456
|
? { ref: e.ref, kind: e.kind, leg: e.leg, pathPrefix: e.pathPrefix, ...(e.cases === undefined ? {} : { cases: e.cases }), flipWhen: e.flipWhen, hits: hits[i] }
|
|
395
457
|
: { ref: e.ref, kind: e.kind, leg: e.outcome.leg, outcome: { expected: e.outcome.expected, actual: e.outcome.actual }, cases: e.outcome.cases, flipWhen: e.flipWhen, hits: hits[i] });
|
|
396
458
|
if (opts.mode === "release") {
|
|
397
|
-
//
|
|
459
|
+
// **实现的腿必须跑**:实现了却在整套语料上一格真读数都没有,那份实现是空的。
|
|
398
460
|
for (const leg of LEG_NAMES) {
|
|
399
|
-
if (
|
|
461
|
+
if (stances.get(leg).kind !== "implemented")
|
|
400
462
|
continue;
|
|
401
463
|
if (!results.some((r) => r.leg === leg && r.status !== "skipped")) {
|
|
402
|
-
violations.push(`leg \`${leg}\` is
|
|
464
|
+
violations.push(`leg \`${leg}\` is implemented yet never ran — 整套语料上一格真读数都没有(实现的腿必须跑)`);
|
|
403
465
|
}
|
|
404
466
|
}
|
|
405
467
|
// **棘轮只降不升**:账本里一条从未命中的登记,自己就是违例 —— 债还了就得把账本删掉。
|
|
@@ -411,7 +473,15 @@ export async function runConformance(adapter, opts) {
|
|
|
411
473
|
const uniqueViolations = [...new Set(violations)];
|
|
412
474
|
return {
|
|
413
475
|
mode: opts.mode,
|
|
414
|
-
adapter: {
|
|
476
|
+
adapter: {
|
|
477
|
+
name: adapter.name,
|
|
478
|
+
provenance: adapter.provenance,
|
|
479
|
+
legs: LEG_NAMES.filter((l) => stances.get(l).kind === "implemented"),
|
|
480
|
+
declined: LEG_NAMES.flatMap((leg) => {
|
|
481
|
+
const s = stances.get(leg);
|
|
482
|
+
return s.kind === "declined" ? [{ leg, ...s.declined }] : [];
|
|
483
|
+
}),
|
|
484
|
+
},
|
|
415
485
|
corpusVersion: corpus.version,
|
|
416
486
|
kitVersion: kit.version,
|
|
417
487
|
...(opts.normalizer === undefined ? {} : { normalizer: { id: opts.normalizer.id, lossy: opts.normalizer.lossy, why: opts.normalizer.why } }),
|
|
@@ -10,21 +10,31 @@
|
|
|
10
10
|
* 复述的规范化就是第二份真源,而两份真源的分歧正是本门要抓的那类病。
|
|
11
11
|
*/
|
|
12
12
|
import type { EffectiveReadWarning, WarningProjection } from "../config-fns.js";
|
|
13
|
-
/**
|
|
13
|
+
/** 四条腿的名字。**闭集** —— 新开一条腿要同时在 `derive.ts` 的 {@link LEG_EXPECTATION}(`Record<LegName, …>`,
|
|
14
|
+
* 不表态即编译红)里说清它的期望从哪来,且每个消费方的 {@link ConformanceAdapter.legs} 都要对它表态(实现或声明不承担)。
|
|
14
15
|
*
|
|
15
|
-
* · `domains`:各域**原始文档**(center 侧存储形)→ effective。契约仓的 `buildEffective
|
|
16
|
+
* · `domains`:各域**原始文档**(center 侧存储形)→ effective。契约仓的 `buildEffective`(读:逐条抢救 + 降级 + 告警)。
|
|
17
|
+
* · `strictWrite`:同一份各域原始文档 → **严写受理**(写入席收下这份文档集与否)。契约仓的 `parseDomain`;管理面的
|
|
18
|
+
* 域写受理。结局复用 {@link LegOutcome} 的两类:全部受理 ⇒ `effective` = 严格解析出的域文档集(只含入参里的键,
|
|
19
|
+
* 不补缺席域);任何一份不受理 ⇒ `rejected{stage:"validate"}`。读腿抢救的(坏条目丢弃、整域降级、主机名夹紧)
|
|
20
|
+
* 在这条腿上是拒 —— 写入席不替运维删他的条目。
|
|
16
21
|
* · `wire`:`GET /effective` 载荷 → effective。契约仓的 `readEffectiveWire`。
|
|
17
22
|
* · `localRoot`:`config.d/` 目录快照(文件名 → 文件内容)→ effective。契约仓的 `loadLocalConfig`。 */
|
|
18
|
-
export type LegName = "domains" | "wire" | "localRoot";
|
|
19
|
-
/**
|
|
20
|
-
export declare const LEG_NAMES: readonly ["domains", "wire", "localRoot"];
|
|
21
|
-
/**
|
|
22
|
-
export
|
|
23
|
-
leg: "domains";
|
|
23
|
+
export type LegName = "domains" | "strictWrite" | "wire" | "localRoot";
|
|
24
|
+
/** 四条腿的名字表(运行期枚举用;与 {@link LegName} 同源 —— 多一员少一员编译红)。 */
|
|
25
|
+
export declare const LEG_NAMES: readonly ["domains", "strictWrite", "wire", "localRoot"];
|
|
26
|
+
/** 各域原始文档集 —— `domains` 与 `strictWrite` 两条腿**同一份**入参形(真源;一份字段表,两臂共用)。 */
|
|
27
|
+
export interface DomainDocsInput {
|
|
24
28
|
docs: Readonly<Record<string, unknown>>;
|
|
25
29
|
version: number;
|
|
26
30
|
updatedAt: string;
|
|
27
|
-
}
|
|
31
|
+
}
|
|
32
|
+
/** 一条腿的入参。腿名决定形:`domains` / `strictWrite` 吃真源本身,另两形由 harness 派生(见 derive.ts 的变换表)。 */
|
|
33
|
+
export type LegInput = ({
|
|
34
|
+
leg: "domains";
|
|
35
|
+
} & DomainDocsInput) | ({
|
|
36
|
+
leg: "strictWrite";
|
|
37
|
+
} & DomainDocsInput) | {
|
|
28
38
|
leg: "wire";
|
|
29
39
|
payload: unknown;
|
|
30
40
|
} | {
|
|
@@ -95,11 +105,36 @@ export interface LegEntry {
|
|
|
95
105
|
/** 该文件里被当作读腿入口的导出名。 */
|
|
96
106
|
export: string;
|
|
97
107
|
}
|
|
98
|
-
/** 一条登记进 kit
|
|
108
|
+
/** 一条登记进 kit 的腿:它的生产入口 + 真正跑它的那只函数。与 {@link LegDeclined} **互斥**(类型层与运行期同律):
|
|
109
|
+
* 实现臂不许同时带 `declined`。 */
|
|
99
110
|
export interface ConformanceLeg {
|
|
100
111
|
entry: LegEntry;
|
|
101
112
|
run(input: LegInput): Promise<LegOutcome> | LegOutcome;
|
|
113
|
+
declined?: never;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 「不承担这条腿」的理由。**闭集** —— 一个词就是一条理由,报告里逐格可见,release 门按词判(不认识的词 = 违例)。
|
|
117
|
+
*
|
|
118
|
+
* · `no-production-path`:这个消费方的**生产代码里结构上就没有**这种读法 / 写法 —— 例:worker 只读不写,没有严写受理
|
|
119
|
+
* (`strictWrite`);worker 不从各域原始文档建 effective,只吃 wire 与本地目录(`domains`);管理面不读 `config.d/`(`localRoot`)。
|
|
120
|
+
*
|
|
121
|
+
* 刻意**没有**「有这条路径但还没接」的词:有路径就接(那正是门要比的东西);一份结局上的已知差走 {@link KnownDivergence} 账本,
|
|
122
|
+
* 它带台账号、带翻转条件、受 stale 律管 —— 不必在这里再开一扇不受律管的门。
|
|
123
|
+
*/
|
|
124
|
+
export type LegDeclineReason = "no-production-path";
|
|
125
|
+
/** {@link LegDeclineReason} 的运行期表(release 门校验用;与类型同源)。 */
|
|
126
|
+
export declare const LEG_DECLINE_REASONS: readonly ["no-production-path"];
|
|
127
|
+
/** 一条腿的**显式不承担**声明:理由词 + 一句人读的事实(`why`,非空 —— 「为什么结构上没有」要说得出来)。
|
|
128
|
+
* 与 {@link ConformanceLeg} **互斥**:声明臂不许带 `entry` / `run`(从声明迁到实现时 spread 合并留下的旧声明,编译期就红)。 */
|
|
129
|
+
export interface LegDeclined {
|
|
130
|
+
declined: LegDeclineReason;
|
|
131
|
+
why: string;
|
|
132
|
+
entry?: never;
|
|
133
|
+
run?: never;
|
|
102
134
|
}
|
|
135
|
+
/** 消费方对一条腿的表态:实现它({@link ConformanceLeg}),或声明不承担({@link LegDeclined})。两臂互斥;没有第三种 —— 不表态不是一种表态。
|
|
136
|
+
* release 门在运行期按同一条律判(适配器可能来自 JS / `as`):带 `declined` 又带 `entry` 或 `run` = 形不对。 */
|
|
137
|
+
export type LegStance = ConformanceLeg | LegDeclined;
|
|
103
138
|
/**
|
|
104
139
|
* 消费方对金样的一条**已知合法差异**的登记位 —— 临时欠账,或成文语义。
|
|
105
140
|
*
|
|
@@ -164,14 +199,15 @@ export interface ConformanceAdapter {
|
|
|
164
199
|
name: string;
|
|
165
200
|
provenance: ConformanceProvenance;
|
|
166
201
|
/**
|
|
167
|
-
*
|
|
168
|
-
* `
|
|
169
|
-
* (
|
|
202
|
+
* **每条腿都要表态**(一条规则,四条腿同律):实现它,或显式声明不承担({@link LegDeclined},带理由词)。
|
|
203
|
+
* 类型是**全量** `Record` —— 闭集 {@link LegName} 加一员,每个消费方的适配器就编译红,直到它对新腿表态;
|
|
204
|
+
* 运行期(适配器可能来自 JS / `as`)release 门照判:缺表态 / 形不对 / 理由词不在闭集 / `why` 为空 ⇒ 违例。
|
|
205
|
+
* 声明不承担的腿逐格记 `skipped`(detail 带理由词与 `why`),并列进报告的 `adapter.declined` —— 看得见、不算通过、也不算红。
|
|
170
206
|
*
|
|
171
|
-
* release
|
|
172
|
-
*
|
|
207
|
+
* release 模式另两条律指向**实现了的腿**:①至少实现一条腿;②**实现的腿必须跑** —— 实现了却在整套语料上一格真读数都没有,
|
|
208
|
+
* 那份实现是空的,违例。
|
|
173
209
|
*/
|
|
174
|
-
legs:
|
|
210
|
+
legs: Record<LegName, LegStance>;
|
|
175
211
|
/** 已知的**合法**差异账本(见 {@link KnownDivergence})。缺席 = 零容忍,任何 diff 都阻塞。 */
|
|
176
212
|
knownDivergences?: readonly KnownDivergence[];
|
|
177
213
|
}
|
|
@@ -200,7 +236,7 @@ export interface ConformanceNormalizer {
|
|
|
200
236
|
}
|
|
201
237
|
/**
|
|
202
238
|
* 一格结局。`pass` / `expected-rejected` / `diff-ledgered` 不阻塞,`diff` / `harness-error` 红,
|
|
203
|
-
* `skipped` 既不绿也不红(
|
|
239
|
+
* `skipped` 既不绿也不红(没跑:语料声明本腿不适用,或消费方声明不承担 / 没表态 —— detail 说是哪一种)。
|
|
204
240
|
*
|
|
205
241
|
* `diff-ledgered`(3.0.0 车 FT):这一格的 effective 差**逐坐标**落在适配器的
|
|
206
242
|
* {@link KnownDivergence} 账本里 —— 一笔登记在案的合法差异,不阻塞,但单独计数、按 `ref` 列出来。
|
|
@@ -261,10 +297,14 @@ export interface LedgerRow {
|
|
|
261
297
|
/** 一次跑的报告。 */
|
|
262
298
|
export interface ConformanceReport {
|
|
263
299
|
mode: ConformanceMode;
|
|
300
|
+
/** `legs` = 实现了的腿;`declined` = 显式声明不承担的腿(理由词 + `why`)。两者之外的腿 = 没表态(release 模式违例)。 */
|
|
264
301
|
adapter: {
|
|
265
302
|
name: string;
|
|
266
303
|
provenance: ConformanceProvenance;
|
|
267
304
|
legs: readonly LegName[];
|
|
305
|
+
declined: readonly ({
|
|
306
|
+
leg: LegName;
|
|
307
|
+
} & LegDeclined)[];
|
|
268
308
|
};
|
|
269
309
|
/** 语料版本(`conformance/VERSION`)。 */
|
|
270
310
|
corpusVersion: string;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export const LEG_NAMES = ["domains", "wire", "localRoot"];
|
|
1
|
+
/** 四条腿的名字表(运行期枚举用;与 {@link LegName} 同源 —— 多一员少一员编译红)。 */
|
|
2
|
+
export const LEG_NAMES = ["domains", "strictWrite", "wire", "localRoot"];
|
|
3
3
|
/** {@link RejectStage} 的运行期表(语料校验用;与类型同源)。 */
|
|
4
4
|
export const REJECT_STAGES = ["envelope", "parse", "validate", "hash", "store"];
|
|
5
|
+
/** {@link LegDeclineReason} 的运行期表(release 门校验用;与类型同源)。 */
|
|
6
|
+
export const LEG_DECLINE_REASONS = ["no-production-path"];
|