@sema-agent/settings-schema 10.1.1 → 11.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 +469 -1
- package/README.md +144 -66
- package/bin/settings-schema.mjs +10 -12
- package/conformance/VERSION +1 -1
- package/conformance/baseline.json +3 -1
- package/conformance/cases/c01-entry-salvage-then-typo/expected.json +3 -1
- package/conformance/cases/c02-hosts-clamp-stacked/expected.json +12 -5
- package/conformance/cases/c02-hosts-clamp-stacked/meta.json +1 -1
- package/conformance/cases/c03-literal-dotted-top-key/expected.json +22 -2
- package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +3 -1
- package/conformance/cases/c06-legacy-governance-lift/expected.json +11 -101
- package/conformance/cases/c06-legacy-governance-lift/meta.json +1 -1
- package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +3 -1
- package/conformance/cases/c08-limits-unknown-carried-64/expected.json +203 -2
- package/conformance/cases/c09-limits-unknown-carried-65/expected.json +204 -2
- package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +3 -1
- package/conformance/cases/c11-unicode-unknown-keys/expected.json +20 -2
- package/conformance/cases/c12-prototype-name-top-keys/expected.json +3 -1
- package/conformance/cases/c13-models-field-null/expected.json +3 -1
- package/conformance/cases/c14-models-field-empty-array/expected.json +6 -1
- package/conformance/cases/c15-models-field-number/expected.json +3 -1
- package/conformance/cases/c16-deep-nesting-205/expected.json +14 -2
- package/conformance/cases/c17-empty-domain-file/expected.json +3 -1
- package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +3 -1
- package/conformance/cases/c19-wire-envelope-no-version/expected.json +3 -1
- package/conformance/cases/c20-governance-absent-lifts/expected.json +11 -86
- package/conformance/cases/c20-governance-absent-lifts/meta.json +1 -1
- package/conformance/cases/c21-governance-empty-object-wins/expected.json +6 -5
- package/conformance/cases/c21-governance-empty-object-wins/input.json +1 -4
- package/conformance/cases/c21-governance-empty-object-wins/meta.json +1 -1
- package/conformance/cases/c23-mixed-validity-models/expected.json +3 -1
- package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +22 -2
- package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +6 -1
- package/conformance/cases/c26-hosts-array-1024/expected.json +3 -1
- package/conformance/cases/c27-hosts-array-1025/expected.json +6 -7175
- package/conformance/cases/c27-hosts-array-1025/meta.json +1 -1
- package/conformance/cases/c28-large-document-over-1mb/expected.json +14 -2
- package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +3 -1
- package/conformance/cases/c30-version-triple-a-first/expected.json +6 -1
- package/conformance/cases/c31-version-triple-b-changed/expected.json +6 -1
- package/conformance/cases/c32-version-triple-a-restored/expected.json +6 -1
- package/conformance/cases/c33-limits-nested-carried-key/expected.json +18 -2
- package/conformance/cases/c34-skills-content-vs-manifest/expected.json +19 -3
- package/conformance/cases/c35-portable-domain-file-is-null/expected.json +3 -1
- package/conformance/cases/c36-skills-entry-without-content/expected.json +6 -2
- package/conformance/cases/c37-skills-field-null/expected.json +6 -2
- package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +3 -1
- package/conformance/cases/c39-hidden-file-in-config-d/expected.json +3 -1
- package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +11 -4
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +9 -3
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +39 -2
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +4 -1
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +3 -1
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +19 -10
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/meta.json +1 -1
- package/conformance/cases/{c46-s811-host-name-url-userinfo-characterization → c46-s811-host-name-url-userinfo-withheld}/expected.json +12 -5
- package/conformance/cases/c46-s811-host-name-url-userinfo-withheld/meta.json +9 -0
- package/conformance/cases/{c47-s811-roles-key-url-userinfo-characterization → c47-s811-roles-key-url-userinfo-withheld}/expected.json +23 -3
- package/conformance/cases/{c47-s811-roles-key-url-userinfo-characterization → c47-s811-roles-key-url-userinfo-withheld}/input.json +6 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-withheld/meta.json +10 -0
- package/conformance/cases/c48-s935-model-headers-transported/expected.json +6 -1
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +3 -1
- package/conformance/cases/c50-s784-lane-host-legal/expected.json +3 -1
- package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +3 -1
- package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +3 -1
- package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +3 -1
- package/conformance/cases/c54-s784-lane-adb-legal/expected.json +3 -1
- package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +3 -1
- package/conformance/cases/c56-s784-lane-device-legal/expected.json +3 -1
- package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +3 -1
- package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +3 -1
- package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +3 -1
- package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +3 -1
- package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +3 -1
- package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +3 -1
- package/conformance/cases/c63-s784-host-two-replicas/expected.json +3 -1
- package/conformance/cases/c64-s784-multitenant-local-store/expected.json +3 -1
- package/conformance/cases/c65-s784-pg-without-db/expected.json +3 -1
- package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +3 -1
- package/conformance/cases/c67-s784-retired-field-loud/expected.json +3 -1
- package/conformance/cases/c67-s784-retired-field-loud/meta.json +1 -1
- package/conformance/cases/c68-s784-device-local-store/expected.json +3 -1
- package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +3 -1
- package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +3 -1
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/expected.json +119 -0
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/input.json +24 -0
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/meta.json +10 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/expected.json +111 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/input.json +26 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/meta.json +6 -0
- package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +3 -1
- package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +3 -1
- package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +6 -1
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +11 -1
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +11 -1
- package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +16 -2
- package/conformance/cases/cli04-env-value-with-newline/expected.json +3 -1
- package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +6 -1
- package/conformance/cases/t01-absent-domain-key-not-null/expected.json +3 -1
- package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +29 -2
- package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +3 -1
- package/conformance/cases/z01-every-domain-empty-object/expected.json +8 -4
- package/conformance/cases/z01-every-domain-empty-object/input.json +0 -1
- package/conformance/cases/z01-every-domain-empty-object/meta.json +1 -1
- package/conformance/cases/z02-every-domain-unknown-key/expected.json +163 -23
- package/conformance/cases/z02-every-domain-unknown-key/input.json +0 -3
- package/conformance/cases/z02-every-domain-unknown-key/meta.json +1 -1
- package/dist/api/auth-bridge.d.ts +51 -181
- package/dist/api/auth-bridge.js +58 -33
- package/dist/api/auth.d.ts +37 -146
- package/dist/api/auth.js +16 -26
- package/dist/api/feedback.d.ts +87 -0
- package/dist/api/feedback.js +66 -0
- package/dist/api/pull-token.d.ts +32 -0
- package/dist/api/pull-token.js +88 -0
- package/dist/api/scopes.d.ts +82 -174
- package/dist/api/scopes.js +23 -18
- package/dist/atomic-write.d.ts +14 -0
- package/dist/atomic-write.js +60 -0
- package/dist/bundle.d.ts +15 -7
- package/dist/bundle.js +28 -27
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +149 -80
- package/dist/config-d-file.d.ts +14 -0
- package/dist/config-d-file.js +67 -0
- package/dist/config-fns.d.ts +87 -186
- package/dist/config-fns.js +207 -262
- package/dist/conformance/contract-adapter.d.ts +9 -2
- package/dist/conformance/contract-adapter.js +15 -7
- package/dist/conformance/corpus.d.ts +3 -0
- package/dist/conformance/corpus.js +5 -0
- package/dist/conformance/derive.d.ts +3 -0
- package/dist/conformance/index.d.ts +2 -2
- package/dist/conformance/index.js +2 -2
- package/dist/conformance/run.js +67 -22
- package/dist/conformance/types.d.ts +31 -15
- package/dist/conformance/types.js +5 -0
- package/dist/conformance/warnings.d.ts +1 -1
- package/dist/conformance/warnings.js +1 -4
- package/dist/cross-domain.d.ts +66 -22
- package/dist/cross-domain.js +164 -93
- package/dist/cross-session.d.ts +29 -11
- package/dist/default-model.d.ts +43 -0
- package/dist/default-model.js +34 -0
- package/dist/execution-lane-index.d.ts +4 -8
- package/dist/execution-lane-index.js +4 -8
- package/dist/execution-lanes.d.ts +31 -8
- package/dist/execution-lanes.js +23 -5
- package/dist/file-edit.d.ts +3 -3
- package/dist/file-edit.js +11 -18
- package/dist/file-store.d.ts +7 -6
- package/dist/file-store.js +37 -50
- package/dist/fleet.d.ts +364 -762
- package/dist/fleet.js +277 -224
- package/dist/hooks.d.ts +1151 -4737
- package/dist/hooks.js +59 -15
- package/dist/index.d.ts +10 -4
- package/dist/index.js +13 -7
- package/dist/integer.d.ts +11 -0
- package/dist/integer.js +11 -0
- package/dist/issue-path.d.ts +10 -0
- package/dist/issue-path.js +10 -0
- package/dist/local-load.d.ts +1 -1
- package/dist/local-load.js +4 -14
- package/dist/loopback.d.ts +26 -0
- package/dist/loopback.js +54 -0
- package/dist/migration-receipt.d.ts +42 -0
- package/dist/migration-receipt.js +12 -0
- package/dist/one-time-migrations.d.ts +62 -0
- package/dist/one-time-migrations.js +828 -0
- package/dist/prompts-digest.d.ts +1 -1
- package/dist/prompts-digest.js +3 -2
- package/dist/reader.d.ts +2 -2
- package/dist/remote-exec.d.ts +45 -187
- package/dist/remote-exec.js +16 -10
- package/dist/resolve-roster.d.ts +18 -3
- package/dist/resolve-roster.js +28 -25
- package/dist/retired-envs.d.ts +24 -0
- package/dist/retired-envs.js +114 -0
- package/dist/retired-form.d.ts +31 -0
- package/dist/retired-form.js +28 -0
- package/dist/safety-merge-spec.d.ts +131 -191
- package/dist/safety-merge-spec.js +18 -14
- package/dist/scheduler-lifetime.d.ts +5 -0
- package/dist/scheduler-lifetime.js +7 -0
- package/dist/scheduler-store-migration.d.ts +14 -0
- package/dist/scheduler-store-migration.js +14 -0
- package/dist/scheduler-store-node.d.ts +28 -12
- package/dist/scheduler-store-node.js +32 -23
- package/dist/scheduler-store.d.ts +47 -14
- package/dist/scheduler-store.js +32 -61
- package/dist/secret-refs.d.ts +3 -3
- package/dist/secret-refs.js +1 -1
- package/dist/secret-scan.d.ts +14 -24
- package/dist/secret-scan.js +45 -23
- package/dist/sha256-core.d.ts +16 -0
- package/dist/sha256-core.js +138 -0
- package/dist/sha256.d.ts +6 -1
- package/dist/sha256.js +8 -98
- package/dist/skills-manifest.js +1 -1
- package/dist/types.d.ts +1509 -13536
- package/dist/types.js +526 -438
- package/dist/url-shape.d.ts +34 -0
- package/dist/url-shape.js +61 -0
- package/dist/web-admin-credential.d.ts +96 -0
- package/dist/{center-credential.js → web-admin-credential.js} +49 -46
- package/dist/web-admin-env.d.ts +33 -0
- package/dist/web-admin-env.js +33 -0
- package/dist/worker-row-verdict.d.ts +16 -0
- package/dist/worker-row-verdict.js +31 -0
- package/dist/workers-rows.d.ts +34 -0
- package/dist/workers-rows.js +42 -0
- package/dist/zod-unwrap.d.ts +11 -0
- package/dist/zod-unwrap.js +25 -0
- package/package.json +17 -9
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/meta.json +0 -6
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/meta.json +0 -6
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/expected.json +0 -49
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/input.json +0 -26
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/meta.json +0 -4
- package/conformance/migrate-workers/m02-g5-idempotent/expected.json +0 -38
- package/conformance/migrate-workers/m02-g5-idempotent/input.json +0 -33
- package/conformance/migrate-workers/m02-g5-idempotent/meta.json +0 -5
- package/conformance/migrate-workers/m03-g5-refused-no-lane/expected.json +0 -71
- package/conformance/migrate-workers/m03-g5-refused-no-lane/input.json +0 -42
- package/conformance/migrate-workers/m03-g5-refused-no-lane/meta.json +0 -4
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/expected.json +0 -52
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/input.json +0 -30
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/meta.json +0 -4
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/expected.json +0 -82
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/input.json +0 -68
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/meta.json +0 -4
- package/dist/center-credential.d.ts +0 -116
- package/dist/issue-text.d.ts +0 -10
- package/dist/issue-text.js +0 -8
- package/dist/migrate-workers.d.ts +0 -71
- package/dist/migrate-workers.js +0 -351
- package/dist/migrate.d.ts +0 -255
- package/dist/migrate.js +0 -542
- /package/conformance/cases/{c46-s811-host-name-url-userinfo-characterization → c46-s811-host-name-url-userinfo-withheld}/input.json +0 -0
package/dist/cross-domain.js
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
|
+
import { formatConfigPath } from "./config-fns.js";
|
|
2
|
+
/** What a dangling reference failed to find —— the closed set (S-1075, 11.0.0): one word per referenced domain. */
|
|
3
|
+
export const REF_ISSUE_KINDS = ["unknown_model", "unknown_roster", "unknown_worker", "unknown_skill", "unknown_mcp", "unknown_plugin"];
|
|
4
|
+
/** The domain a {@link RefIssueKind}'s target lives in. */
|
|
5
|
+
export const REF_ISSUE_TARGET_DOMAIN = {
|
|
6
|
+
unknown_model: "models",
|
|
7
|
+
unknown_roster: "rosters",
|
|
8
|
+
unknown_worker: "workers",
|
|
9
|
+
unknown_skill: "skills",
|
|
10
|
+
unknown_mcp: "mcp",
|
|
11
|
+
unknown_plugin: "plugins",
|
|
12
|
+
};
|
|
13
|
+
/** The domains whose documents carry cross-domain references (by name) —— the referrer side of the judge. */
|
|
14
|
+
export const REF_REFERRER_DOMAINS = ["rosters", "workers", "models", "collab", "systems", "entitlement"];
|
|
15
|
+
/** The noun a kind's sentence uses for its target. */
|
|
16
|
+
function refTargetNoun(kind) {
|
|
17
|
+
switch (kind) {
|
|
18
|
+
case "unknown_model":
|
|
19
|
+
return "model";
|
|
20
|
+
case "unknown_roster":
|
|
21
|
+
return "roster";
|
|
22
|
+
case "unknown_worker":
|
|
23
|
+
return "worker";
|
|
24
|
+
case "unknown_skill":
|
|
25
|
+
return "skill";
|
|
26
|
+
case "unknown_mcp":
|
|
27
|
+
return "mcp server";
|
|
28
|
+
case "unknown_plugin":
|
|
29
|
+
return "plugin";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** How a referrer is named in the sentence (its name through `show`). */
|
|
33
|
+
function refReferrerLabel(r, show) {
|
|
34
|
+
const named = (noun) => (r.name === undefined ? noun : `${noun} "${show(r.name)}"`);
|
|
35
|
+
switch (r.domain) {
|
|
36
|
+
case "rosters":
|
|
37
|
+
return named("roster");
|
|
38
|
+
case "workers":
|
|
39
|
+
return named("worker");
|
|
40
|
+
case "models":
|
|
41
|
+
return named("models");
|
|
42
|
+
case "collab":
|
|
43
|
+
return named("collab template");
|
|
44
|
+
case "systems":
|
|
45
|
+
return named("system");
|
|
46
|
+
case "entitlement":
|
|
47
|
+
return named("tier");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** The ONE rendering of a {@link RefIssue}'s sentence: `<referrer> → unknown <noun> "<target>" (at <path>)`. `show` maps each
|
|
51
|
+
* user-written value (the referrer's name, the target); the default shows them as written — that is how `message` is minted. */
|
|
52
|
+
export function renderRefIssue(issue, show = (v) => v) {
|
|
53
|
+
return `${refReferrerLabel(issue.referrer, show)} → unknown ${refTargetNoun(issue.kind)} "${show(issue.target)}" (at ${formatConfigPath(issue.path)})`;
|
|
54
|
+
}
|
|
55
|
+
/** The one minting point of a {@link RefIssue}. */
|
|
56
|
+
function danglingRef(path, kind, referrer, target) {
|
|
57
|
+
const operands = { path, code: "custom", kind, referrer, target };
|
|
58
|
+
return { ...operands, message: renderRefIssue(operands) };
|
|
59
|
+
}
|
|
1
60
|
/** A resolver over the reader, optionally shadowed by pending (same-operation) writes keyed by domain.
|
|
2
61
|
* Widened from the fat ConfigStore to the narrow {@link ConfigReader} (it only calls `getDomain`) so a TOC
|
|
3
62
|
* file source can use it without the full store.
|
|
@@ -12,131 +71,143 @@ export function createSiblingResolver(store, pending) {
|
|
|
12
71
|
return store.getDomain(domain);
|
|
13
72
|
};
|
|
14
73
|
}
|
|
15
|
-
/** The domains
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
74
|
+
/** The domains a write must be judged for —— the closed set as a RUNTIME VALUE (4.0.0): every domain that HOLDS cross-domain
|
|
75
|
+
* references ({@link REF_REFERRER_DOMAINS}) and every domain those references POINT INTO (the targets of
|
|
76
|
+
* {@link REF_ISSUE_TARGET_DOMAIN}) —— 11.0.0 (S-1066 ③) added the three target-only domains `skills` / `mcp` / `plugins`: deleting
|
|
77
|
+
* a skill a tier still names is a dangling reference made by the skills write, so that write is judged too. A consumer that has
|
|
78
|
+
* to ask "is this domain ref-checked?" at runtime (a config-rollback path deciding whether to run the guard before writing)
|
|
79
|
+
* asks this constant (or {@link isRefCheckedDomain}) instead of writing a list — a hand copy drifts silently (two rollback routes
|
|
80
|
+
* in the admin console each once carried a five-member copy that had lost `entitlement`).
|
|
21
81
|
*
|
|
22
|
-
* `satisfies
|
|
23
|
-
*
|
|
24
|
-
export const REF_CHECKED_DOMAINS = ["rosters", "workers", "models", "collab", "systems", "entitlement"];
|
|
82
|
+
* `satisfies` keeps every member a real domain key; a pin checks it equals referrers ∪ targets. {@link RefCheckedDomain} is
|
|
83
|
+
* DERIVED from this value. */
|
|
84
|
+
export const REF_CHECKED_DOMAINS = ["rosters", "workers", "models", "collab", "systems", "entitlement", "skills", "mcp", "plugins"];
|
|
25
85
|
/** Runtime narrowing to {@link RefCheckedDomain}, same idiom as `isDomainName`: use it instead of hard-coding
|
|
26
86
|
* the set when filtering an arbitrary domain key (a batch payload's keys, a rollback target). */
|
|
27
87
|
export function isRefCheckedDomain(d) {
|
|
28
88
|
return typeof d === "string" && REF_CHECKED_DOMAINS.includes(d);
|
|
29
89
|
}
|
|
30
|
-
/**
|
|
31
|
-
*
|
|
32
|
-
|
|
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). */
|
|
39
|
-
export async function refIntegrityIssues(domain, value, resolve) {
|
|
40
|
-
const issues = [];
|
|
90
|
+
/** Every by-name reference `domain`'s document makes —— the ONE place the reference graph is written down (which field of which
|
|
91
|
+
* domain names what). Exhaustive over {@link RefReferrerDomain}: a new referrer domain is a compile error until it is listed. */
|
|
92
|
+
function referencesFrom(domain, docs, push) {
|
|
41
93
|
if (domain === "rosters") {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
r.models.forEach((name, j) => {
|
|
45
|
-
if (!catalog.has(name))
|
|
46
|
-
issues.push({ path: ["rosters", i, "models", j], message: `roster "${r.name}" references unknown model "${name}" (not in the models catalog)`, code: "custom" });
|
|
47
|
-
});
|
|
94
|
+
(docs.rosters?.rosters ?? []).forEach((r, i) => {
|
|
95
|
+
const referrer = { domain, name: r.name };
|
|
96
|
+
r.models.forEach((name, j) => push({ path: ["rosters", i, "models", j], kind: "unknown_model", referrer, target: name }));
|
|
48
97
|
for (const [role, target] of Object.entries(r.roles ?? {})) {
|
|
49
|
-
if (target && "model" in target
|
|
50
|
-
|
|
98
|
+
if (target && "model" in target)
|
|
99
|
+
push({ path: ["rosters", i, "roles", role], kind: "unknown_model", referrer, target: target.model });
|
|
51
100
|
}
|
|
52
|
-
// 0.6.3 markers (primaryModel/cheapModel) reference the catalog like a {model}-form role does
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (!catalog.has(name))
|
|
60
|
-
issues.push({ path: ["rosters", i, "atModelAllowlist", j], message: `roster "${r.name}" atModelAllowlist "${name}" not in the models catalog`, code: "custom" });
|
|
61
|
-
});
|
|
101
|
+
// 0.6.3 markers (primaryModel/cheapModel) reference the catalog like a {model}-form role does.
|
|
102
|
+
if (r.primaryModel)
|
|
103
|
+
push({ path: ["rosters", i, "primaryModel"], kind: "unknown_model", referrer, target: r.primaryModel });
|
|
104
|
+
if (r.cheapModel)
|
|
105
|
+
push({ path: ["rosters", i, "cheapModel"], kind: "unknown_model", referrer, target: r.cheapModel });
|
|
106
|
+
if (r.atMention?.mode === "only")
|
|
107
|
+
r.atMention.models.forEach((name, j) => push({ path: ["rosters", i, "atMention", "models", j], kind: "unknown_model", referrer, target: name }));
|
|
62
108
|
});
|
|
63
109
|
}
|
|
64
110
|
else if (domain === "workers") {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
issues.push({ path: ["workers", i, "roster"], message: `worker "${w.name}" references unknown roster "${w.roster}"`, code: "custom" });
|
|
111
|
+
(docs.workers?.workers ?? []).forEach((w, i) => {
|
|
112
|
+
if (w.roster)
|
|
113
|
+
push({ path: ["workers", i, "roster"], kind: "unknown_roster", referrer: { domain, name: w.name }, target: w.roster });
|
|
69
114
|
});
|
|
70
115
|
}
|
|
71
116
|
else if (domain === "models") {
|
|
72
|
-
// The roles map +
|
|
73
|
-
|
|
74
|
-
const m
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (target && "model" in target && !catalog.has(target.model))
|
|
78
|
-
issues.push({ path: ["models", "roles", role], message: `models.roles.${role} → unknown model "${target.model}" (not in the catalog)`, code: "custom" });
|
|
117
|
+
// The roles map + `atMention.models` reference the catalog of the SAME document (they ship together in the models domain).
|
|
118
|
+
const m = docs.models;
|
|
119
|
+
for (const [role, target] of Object.entries(m?.roles ?? {})) {
|
|
120
|
+
if (target && "model" in target)
|
|
121
|
+
push({ path: ["models", "roles", role], kind: "unknown_model", referrer: { domain }, target: target.model });
|
|
79
122
|
}
|
|
80
|
-
(m
|
|
81
|
-
|
|
82
|
-
issues.push({ path: ["models", "atModelAllowlist", j], message: `atModelAllowlist "${name}" not in the models catalog`, code: "custom" });
|
|
83
|
-
});
|
|
123
|
+
if (m?.atMention?.mode === "only")
|
|
124
|
+
m.atMention.models.forEach((name, j) => push({ path: ["models", "atMention", "models", j], kind: "unknown_model", referrer: { domain }, target: name }));
|
|
84
125
|
}
|
|
85
126
|
else if (domain === "collab") {
|
|
86
|
-
// collab (0.7.0
|
|
87
|
-
|
|
88
|
-
// modelRef is the legal "跟随用户当前模型" state, not a dangle. This is the SAVE-time guard; a model
|
|
89
|
-
// deleted/renamed LATER still dangles → the web renders the 悬空 destructive badge (§11-6), same as
|
|
90
|
-
// groupBindings.
|
|
91
|
-
const catalog = new Set(((await resolve("models"))?.models ?? []).map((x) => x.name));
|
|
92
|
-
value.templates.forEach((t, i) => {
|
|
127
|
+
// collab (0.7.0): a slot member's `modelRef` is a models-CATALOG name. ABSENT modelRef is the legal "跟随用户当前模型" state.
|
|
128
|
+
(docs.collab?.templates ?? []).forEach((t, i) => {
|
|
93
129
|
t.slots.forEach((s, j) => {
|
|
94
130
|
s.members.forEach((mem, k) => {
|
|
95
|
-
if (mem.modelRef
|
|
96
|
-
|
|
131
|
+
if (mem.modelRef)
|
|
132
|
+
push({ path: ["collab", "templates", i, "slots", j, "members", k, "modelRef"], kind: "unknown_model", referrer: { domain, name: t.id }, target: mem.modelRef });
|
|
97
133
|
});
|
|
98
134
|
});
|
|
99
135
|
});
|
|
100
136
|
}
|
|
101
137
|
else if (domain === "systems") {
|
|
102
|
-
// systems.callsWorkers → workers
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
value.systems.forEach((sys, i) => {
|
|
106
|
-
sys.callsWorkers.forEach((name, j) => {
|
|
107
|
-
if (!workers.has(name))
|
|
108
|
-
issues.push({ path: ["systems", i, "callsWorkers", j], message: `system "${sys.name}" references unknown worker "${name}"`, code: "custom" });
|
|
109
|
-
});
|
|
138
|
+
// systems.callsWorkers → workers (a dangling name would mint a credential routed at nobody).
|
|
139
|
+
(docs.systems?.systems ?? []).forEach((sys, i) => {
|
|
140
|
+
sys.callsWorkers.forEach((name, j) => push({ path: ["systems", i, "callsWorkers", j], kind: "unknown_worker", referrer: { domain, name: sys.name }, target: name }));
|
|
110
141
|
});
|
|
111
142
|
}
|
|
112
143
|
else if (domain === "entitlement") {
|
|
113
|
-
// A tier references FIVE
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (t.roster && !rosterNames.has(t.roster))
|
|
124
|
-
issues.push({ path: ["entitlement", "tiers", i, "roster"], message: `tier "${t.id}" references unknown roster "${t.roster}"`, code: "custom" });
|
|
125
|
-
(t.models ?? []).forEach((name, j) => { if (!modelCatalog.has(name))
|
|
126
|
-
issues.push({ path: ["entitlement", "tiers", i, "models", j], message: `tier "${t.id}" model "${name}" not in the models catalog`, code: "custom" }); });
|
|
127
|
-
(t.visibleSkills ?? []).forEach((name, j) => { if (!skillNames.has(name))
|
|
128
|
-
issues.push({ path: ["entitlement", "tiers", i, "visibleSkills", j], message: `tier "${t.id}" visibleSkill "${name}" not in skills`, code: "custom" }); });
|
|
129
|
-
(t.visibleMcp ?? []).forEach((name, j) => { if (!mcpNames.has(name))
|
|
130
|
-
issues.push({ path: ["entitlement", "tiers", i, "visibleMcp", j], message: `tier "${t.id}" visibleMcp "${name}" not in mcp`, code: "custom" }); });
|
|
131
|
-
(t.visiblePlugins ?? []).forEach((name, j) => { if (!pluginNames.has(name))
|
|
132
|
-
issues.push({ path: ["entitlement", "tiers", i, "visiblePlugins", j], message: `tier "${t.id}" visiblePlugin "${name}" not in plugins`, code: "custom" }); });
|
|
144
|
+
// A tier references FIVE domains by name; a dangling one otherwise resolves silently (a typo'd roster → an empty set → the
|
|
145
|
+
// principal is silently denied).
|
|
146
|
+
(docs.entitlement?.tiers ?? []).forEach((t, i) => {
|
|
147
|
+
const referrer = { domain, name: t.id };
|
|
148
|
+
if (t.roster)
|
|
149
|
+
push({ path: ["entitlement", "tiers", i, "roster"], kind: "unknown_roster", referrer, target: t.roster });
|
|
150
|
+
(t.models ?? []).forEach((name, j) => push({ path: ["entitlement", "tiers", i, "models", j], kind: "unknown_model", referrer, target: name }));
|
|
151
|
+
(t.visibleSkills ?? []).forEach((name, j) => push({ path: ["entitlement", "tiers", i, "visibleSkills", j], kind: "unknown_skill", referrer, target: name }));
|
|
152
|
+
(t.visibleMcp ?? []).forEach((name, j) => push({ path: ["entitlement", "tiers", i, "visibleMcp", j], kind: "unknown_mcp", referrer, target: name }));
|
|
153
|
+
(t.visiblePlugins ?? []).forEach((name, j) => push({ path: ["entitlement", "tiers", i, "visiblePlugins", j], kind: "unknown_plugin", referrer, target: name }));
|
|
133
154
|
});
|
|
134
155
|
}
|
|
135
156
|
else {
|
|
136
|
-
// Exhaustiveness guard: a new
|
|
137
|
-
// domain's validation. `never` makes that a compile error; the throw is the runtime backstop.
|
|
157
|
+
// Exhaustiveness guard: a new referrer domain MUST add its own branch above. `never` makes that a compile error.
|
|
138
158
|
const unhandled = domain;
|
|
139
|
-
throw new Error(`
|
|
159
|
+
throw new Error(`referencesFrom: unhandled domain "${String(unhandled)}"`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** The names a target domain defines. */
|
|
163
|
+
function namesIn(kind, docs) {
|
|
164
|
+
switch (kind) {
|
|
165
|
+
case "unknown_model":
|
|
166
|
+
return new Set((docs.models?.models ?? []).map((m) => m.name));
|
|
167
|
+
case "unknown_roster":
|
|
168
|
+
return new Set((docs.rosters?.rosters ?? []).map((r) => r.name));
|
|
169
|
+
case "unknown_worker":
|
|
170
|
+
return new Set((docs.workers?.workers ?? []).map((w) => w.name));
|
|
171
|
+
case "unknown_skill":
|
|
172
|
+
return new Set((docs.skills?.skills ?? []).map((sk) => sk.name));
|
|
173
|
+
case "unknown_mcp":
|
|
174
|
+
return new Set((docs.mcp?.servers ?? []).map((sv) => sv.name));
|
|
175
|
+
case "unknown_plugin":
|
|
176
|
+
return new Set((docs.plugins?.plugins ?? []).map((pl) => pl.name));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* **THE reference judge** (11.0.0, S-1066 ③; pure, synchronous): every by-name cross-domain reference in a document set that does
|
|
181
|
+
* not resolve, as a {@link RefIssue} (`path` = where the reference sits, `path[0]` = the referrer's domain). Both seats read this
|
|
182
|
+
* one conclusion: the write seat ({@link refIntegrityIssues}, refusing) and the read side (`resolveEffectiveForWorker`'s
|
|
183
|
+
* `refIssues`, reporting what it drops). Before 11.0.0 the write seat judged forward only and the read side judged on its own,
|
|
184
|
+
* silently.
|
|
185
|
+
*/
|
|
186
|
+
export function danglingRefs(docs) {
|
|
187
|
+
const issues = [];
|
|
188
|
+
const names = new Map();
|
|
189
|
+
const known = (kind) => names.get(kind) ?? names.set(kind, namesIn(kind, docs)).get(kind);
|
|
190
|
+
for (const domain of REF_REFERRER_DOMAINS) {
|
|
191
|
+
referencesFrom(domain, docs, (ref) => {
|
|
192
|
+
if (!known(ref.kind).has(ref.target))
|
|
193
|
+
issues.push(danglingRef(ref.path, ref.kind, ref.referrer, ref.target));
|
|
194
|
+
});
|
|
140
195
|
}
|
|
141
196
|
return issues;
|
|
142
197
|
}
|
|
198
|
+
/** Cross-domain ref integrity for a write of one {@link RefCheckedDomain} value (the write seat): the judge's conclusion on the
|
|
199
|
+
* document set AFTER the write ({@link danglingRefs} over `value` + every other ref-checked domain through `resolve`), narrowed
|
|
200
|
+
* to the references this write is answerable for —— the ones its own document holds (forward: a roster naming a model that is not
|
|
201
|
+
* in the catalog) and the ones pointing into it (reverse, 11.0.0: deleting a model a roster still names; the issue names the
|
|
202
|
+
* roster, `path` points at the reference). Empty = ok.
|
|
203
|
+
*
|
|
204
|
+
* BATCH-AWARE through `resolve` (a sibling being written in the same batch shadows the stored one, see
|
|
205
|
+
* {@link createSiblingResolver}). A reference that already dangled before this write and still involves this domain is reported
|
|
206
|
+
* too — the same as the forward half always did (a roster write re-judges every roster). */
|
|
207
|
+
export async function refIntegrityIssues(domain, value, resolve) {
|
|
208
|
+
const docs = { [domain]: value };
|
|
209
|
+
for (const other of REF_CHECKED_DOMAINS)
|
|
210
|
+
if (other !== domain)
|
|
211
|
+
docs[other] = await resolve(other);
|
|
212
|
+
return danglingRefs(docs).filter((i) => i.referrer.domain === domain || REF_ISSUE_TARGET_DOMAIN[i.kind] === domain);
|
|
213
|
+
}
|
package/dist/cross-session.d.ts
CHANGED
|
@@ -49,7 +49,13 @@ export type CrossSessionInboundValue = (typeof CROSS_SESSION_INBOUND_VALUES)[num
|
|
|
49
49
|
/** 设置 UI 的「不设」拼写(见头注)——两键共用同一个字面量。 */
|
|
50
50
|
export declare const CROSS_SESSION_INBOUND_UNSET: "default";
|
|
51
51
|
/** 文件层受理面:四个显式值 + `"default"`(未设)。 */
|
|
52
|
-
export declare const CrossSessionInboundSetting: z.ZodEnum<
|
|
52
|
+
export declare const CrossSessionInboundSetting: z.ZodEnum<{
|
|
53
|
+
default: "default";
|
|
54
|
+
wake: "wake";
|
|
55
|
+
"next-turn": "next-turn";
|
|
56
|
+
hold: "hold";
|
|
57
|
+
refuse: "refuse";
|
|
58
|
+
}>;
|
|
53
59
|
export type CrossSessionInboundSettingValue = z.infer<typeof CrossSessionInboundSetting>;
|
|
54
60
|
/** 四值闭集(CC 逐字)。`"never"` = 不设期限。 */
|
|
55
61
|
export declare const DIALOG_EXPIRY_VALUES: readonly ["60s", "5m", "10m", "never"];
|
|
@@ -57,7 +63,13 @@ export type DialogExpiryValue = (typeof DIALOG_EXPIRY_VALUES)[number];
|
|
|
57
63
|
/** 未设时生效的值(CC 缺省)。 */
|
|
58
64
|
export declare const DIALOG_EXPIRY_DEFAULT: DialogExpiryValue;
|
|
59
65
|
/** 文件层受理面:四个值 + `"default"`(未设 ⇒ 落 {@link DIALOG_EXPIRY_DEFAULT})。 */
|
|
60
|
-
export declare const DialogExpirySetting: z.ZodEnum<
|
|
66
|
+
export declare const DialogExpirySetting: z.ZodEnum<{
|
|
67
|
+
never: "never";
|
|
68
|
+
default: "default";
|
|
69
|
+
"60s": "60s";
|
|
70
|
+
"5m": "5m";
|
|
71
|
+
"10m": "10m";
|
|
72
|
+
}>;
|
|
61
73
|
export type DialogExpirySettingValue = z.infer<typeof DialogExpirySetting>;
|
|
62
74
|
/**
|
|
63
75
|
* 整份 `settings.json` 里**本片段负责的两键**。刻意 `passthrough`:本片段不是整份 settings 的闭集门
|
|
@@ -65,15 +77,21 @@ export type DialogExpirySettingValue = z.infer<typeof DialogExpirySetting>;
|
|
|
65
77
|
* 值必合法**。
|
|
66
78
|
*/
|
|
67
79
|
export declare const CrossSessionSettings: z.ZodObject<{
|
|
68
|
-
crossSessionInbound: z.ZodOptional<z.ZodEnum<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
dialogExpiry: z.ZodOptional<z.ZodEnum<
|
|
76
|
-
|
|
80
|
+
crossSessionInbound: z.ZodOptional<z.ZodEnum<{
|
|
81
|
+
default: "default";
|
|
82
|
+
wake: "wake";
|
|
83
|
+
"next-turn": "next-turn";
|
|
84
|
+
hold: "hold";
|
|
85
|
+
refuse: "refuse";
|
|
86
|
+
}>>;
|
|
87
|
+
dialogExpiry: z.ZodOptional<z.ZodEnum<{
|
|
88
|
+
never: "never";
|
|
89
|
+
default: "default";
|
|
90
|
+
"60s": "60s";
|
|
91
|
+
"5m": "5m";
|
|
92
|
+
"10m": "10m";
|
|
93
|
+
}>>;
|
|
94
|
+
}, z.core.$loose>;
|
|
77
95
|
export type CrossSessionSettings = z.infer<typeof CrossSessionSettings>;
|
|
78
96
|
/** 一层读数:`ok:false` = 词表外(调用方响亮拒);`ok:true` 无 `value` = 未设;有 `value` = 显式值。 */
|
|
79
97
|
export type SettingLayerRead<T> = {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「目录的缺省模型是哪一条」—— **一只**纯函数(11.0.0,S-1070)。server 的 apply 与 dry-run、壳的本地池读口都调它,不再各写一份链;
|
|
3
|
+
* `roles.default` 是 `{select}` 形时它不答名(交回 `select`,由引擎解),所以本包与引擎对同一份配置不会各答一个。
|
|
4
|
+
*
|
|
5
|
+
* 缺省模型只有一个位:`models.roles.default`(core 角色链的根;N-7 起 `models.default` 结构键退役 —— 那时两位各答一个、主任务跑前者、
|
|
6
|
+
* `default` 角色解到后者)。本模块纯、零 I/O、浏览器安全。
|
|
7
|
+
*/
|
|
8
|
+
import type { ModelsConfig, RoleTarget } from "./types.js";
|
|
9
|
+
/** core 对 `default` 角色的档语义:从它的缺省档 `pro` 起**只向低档**扫 —— pro → flash → lite,绝不向上抓 max / ultra
|
|
10
|
+
* (core `ROLE_TIER_DEFAULTS.default` + `DEFAULT_TIER_ORDER`;钉对 devDependency 里精确钉住的那一版 core)。 */
|
|
11
|
+
export declare const DEFAULT_ROLE_TIER_LADDER: readonly ["pro", "flash", "lite"];
|
|
12
|
+
/** 命名链每一级的来源词(闭集)。 */
|
|
13
|
+
export type CatalogDefaultSource = "roles.default" | `tierGroup.${(typeof DEFAULT_ROLE_TIER_LADDER)[number]}` | "fallback";
|
|
14
|
+
/**
|
|
15
|
+
* 解析的结果 —— 纯数据,判别联合(`kind` 闭集,两词):
|
|
16
|
+
* · `named` —— 命名链选中的目录名 `name`、哪一级选的 `source`、哪几级点了名却不可用 `dangling`(调用方据此告警,解析口自己不出声);
|
|
17
|
+
* · `select` —— `roles.default` 是 `{select}` 形:它不点名模型,按目录条目的**属性**(档 / 能力 / 偏好)挑。挑哪一条是引擎的选择语义
|
|
18
|
+
* (core `resolveTaskModel` 对 `default` 角色的解法),本包不复刻、不猜、也不跳到命名链的下一级 —— `select` 原样交回,调用方交给引擎解。
|
|
19
|
+
* 引擎要在与 `has` **同一可用集**(启用 ∧ 本机可服务)上解:交给它一份更宽的目录,它就可能挑中一条 `has` 说不可用的条目。
|
|
20
|
+
* 它是第一级,前面没有可悬空的级,所以不带 `dangling`。
|
|
21
|
+
*/
|
|
22
|
+
export type CatalogDefaultModel = {
|
|
23
|
+
readonly kind: "named";
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly source: CatalogDefaultSource;
|
|
26
|
+
readonly dangling: readonly {
|
|
27
|
+
readonly source: Exclude<CatalogDefaultSource, "fallback">;
|
|
28
|
+
readonly name: string;
|
|
29
|
+
}[];
|
|
30
|
+
} | {
|
|
31
|
+
readonly kind: "select";
|
|
32
|
+
readonly select: Extract<RoleTarget, {
|
|
33
|
+
select: unknown;
|
|
34
|
+
}>["select"];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* 缺省模型由 `roles.default` 的形分两路(一条规则:点名的走命名链,不点名的交回引擎):
|
|
38
|
+
* · `{select}` ⇒ `{ kind: "select", select }`(见 {@link CatalogDefaultModel});
|
|
39
|
+
* · `{model}` 或缺席 ⇒ 命名链 —— 第一个**可用**的候选:`roles.default` 的 `{model}` → 活动档位组的 pro / flash / lite 绑定
|
|
40
|
+
* ({@link DEFAULT_ROLE_TIER_LADDER})→ `fallback`(调用方的兜底,通常是第一条可用条目)。`has(name)` = 调用方的「这条目录名此刻可用」
|
|
41
|
+
* (启用 ∧ 本机可服务);点了名但不可用的一级记进 `dangling`、落下一级,绝不静默指错模型。
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveCatalogDefaultModel(models: Pick<ModelsConfig, "roles" | "tierGroups" | "activeTierGroup">, has: (name: string) => boolean, fallback: string): CatalogDefaultModel;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { resolveActiveTiers } from "./config-fns.js";
|
|
2
|
+
/** core 对 `default` 角色的档语义:从它的缺省档 `pro` 起**只向低档**扫 —— pro → flash → lite,绝不向上抓 max / ultra
|
|
3
|
+
* (core `ROLE_TIER_DEFAULTS.default` + `DEFAULT_TIER_ORDER`;钉对 devDependency 里精确钉住的那一版 core)。 */
|
|
4
|
+
export const DEFAULT_ROLE_TIER_LADDER = ["pro", "flash", "lite"];
|
|
5
|
+
/**
|
|
6
|
+
* 缺省模型由 `roles.default` 的形分两路(一条规则:点名的走命名链,不点名的交回引擎):
|
|
7
|
+
* · `{select}` ⇒ `{ kind: "select", select }`(见 {@link CatalogDefaultModel});
|
|
8
|
+
* · `{model}` 或缺席 ⇒ 命名链 —— 第一个**可用**的候选:`roles.default` 的 `{model}` → 活动档位组的 pro / flash / lite 绑定
|
|
9
|
+
* ({@link DEFAULT_ROLE_TIER_LADDER})→ `fallback`(调用方的兜底,通常是第一条可用条目)。`has(name)` = 调用方的「这条目录名此刻可用」
|
|
10
|
+
* (启用 ∧ 本机可服务);点了名但不可用的一级记进 `dangling`、落下一级,绝不静默指错模型。
|
|
11
|
+
*/
|
|
12
|
+
export function resolveCatalogDefaultModel(models, has, fallback) {
|
|
13
|
+
const target = models.roles?.default;
|
|
14
|
+
const named = target !== undefined && "model" in target ? target.model : undefined;
|
|
15
|
+
const select = target !== undefined && "select" in target ? target.select : undefined;
|
|
16
|
+
// 判序与引擎同一只真值判(core `partsModel`:`model` 为真 ⇒ 按名;否则 `select` 为真 ⇒ 按属性挑):schema 读过的值只有一形,这一判序只对
|
|
17
|
+
// 绕过 schema 递进来的两形并在(含 `model: ""`)起作用 —— 那时也与引擎同答。
|
|
18
|
+
if (!named && select)
|
|
19
|
+
return { kind: "select", select };
|
|
20
|
+
const tiers = resolveActiveTiers(models) ?? {};
|
|
21
|
+
const candidates = [
|
|
22
|
+
{ source: "roles.default", name: named },
|
|
23
|
+
...DEFAULT_ROLE_TIER_LADDER.map((t) => ({ source: `tierGroup.${t}`, name: tiers[t] })),
|
|
24
|
+
];
|
|
25
|
+
const dangling = [];
|
|
26
|
+
for (const c of candidates) {
|
|
27
|
+
if (c.name === undefined || c.name === "")
|
|
28
|
+
continue;
|
|
29
|
+
if (has(c.name))
|
|
30
|
+
return { kind: "named", name: c.name, source: c.source, dangling };
|
|
31
|
+
dangling.push({ source: c.source, name: c.name });
|
|
32
|
+
}
|
|
33
|
+
return { kind: "named", name: fallback, source: "fallback", dangling };
|
|
34
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 包内私有:车道表({@link EXECUTION_LANES})的两只**派生索引** +
|
|
3
|
-
* 供 `types.ts`(写闸)与 `
|
|
2
|
+
* 包内私有:车道表({@link EXECUTION_LANES})的两只**派生索引** + 「迁移能搬哪些键」的**一份**键集,
|
|
3
|
+
* 供 `types.ts`(写闸)与 `one-time-migrations.ts`(一次性迁移 SS-M4)同读。
|
|
4
4
|
* 不进主桶、不进 `package.json` 的 `exports`(与 `secret-scan.ts` 同一姿势)—— 消费方只拿表本身,索引是本包的实现细节。
|
|
5
5
|
* 索引只从表派生,不另存任何词:表改了,这里跟着变。
|
|
6
6
|
*
|
|
7
|
-
* ## 迁移指路(7.0.1)
|
|
7
|
+
* ## 迁移指路(7.0.1;11.0.0 起指路句住 `retired-form.ts`)
|
|
8
8
|
* 拒因要不要带迁移指路只有一条规则:**这个键「旧形 ⇒ 迁移能搬」就带,搬不了的保留名(`PORT` / `MODEL_*` …)不带**。
|
|
9
9
|
* 「迁移能搬」= 迁移自己识别的键集(车道键 {@link isLaneEnvKey} ∪ 店键 {@link STORE_ENV_KEYS}),写闸读的是**同一只谓词**
|
|
10
10
|
* ({@link isMigratableEnvKey}),不是第二张手抄的表 —— 迁移多认一个键而写闸没跟,或反过来,不会静默分叉
|
|
11
|
-
* (`test/migration-signpost-
|
|
11
|
+
* (`test/migration-signpost-11.0.0.test.ts` 用真跑迁移的读数逐个保留名对拍)。
|
|
12
12
|
*/
|
|
13
13
|
import { type ExecutionLane, type ExecutionLaneRow } from "./execution-lanes.js";
|
|
14
14
|
/** 一条车道的表行。词表与表行由 `execution-lanes.ts` 的 `satisfies Record<ExecutionLane, …>` 一一对应 ——
|
|
@@ -32,7 +32,3 @@ export declare const STORE_ENV_KEYS: readonly ["DB_BACKEND", "MEMORY_ENGINE", "M
|
|
|
32
32
|
export declare function isLaneEnvKey(name: string): boolean;
|
|
33
33
|
/** 一个名字是不是迁移会处理的 6.x env 键(搬进结构块 / 丢弃 / 点名它拒):车道块的键 ∪ 店键。写闸拒因带不带迁移指路只看它。 */
|
|
34
34
|
export declare function isMigratableEnvKey(name: string): boolean;
|
|
35
|
-
/** 迁移命令的唯一拼写(design/327 §3.4 原句):`npx` 全名形 —— 没装本包的运维粘贴即可跑;裸 bin 名 `settings-schema …` 在没装本包的机器上是 `command not found`。 */
|
|
36
|
-
export declare const MIGRATE_WORKERS_COMMAND: "npx @sema-agent/settings-schema migrate-workers";
|
|
37
|
-
/** 拒因里的迁移指路 —— **一句话、一个常量**:保留名闸(`env` / `secretEnv` 里的 {@link isMigratableEnvKey} 键)与删除项预处理(七个被删旧键)引用同一个。 */
|
|
38
|
-
export declare const MIGRATION_SIGNPOST: string;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 包内私有:车道表({@link EXECUTION_LANES})的两只**派生索引** +
|
|
3
|
-
* 供 `types.ts`(写闸)与 `
|
|
2
|
+
* 包内私有:车道表({@link EXECUTION_LANES})的两只**派生索引** + 「迁移能搬哪些键」的**一份**键集,
|
|
3
|
+
* 供 `types.ts`(写闸)与 `one-time-migrations.ts`(一次性迁移 SS-M4)同读。
|
|
4
4
|
* 不进主桶、不进 `package.json` 的 `exports`(与 `secret-scan.ts` 同一姿势)—— 消费方只拿表本身,索引是本包的实现细节。
|
|
5
5
|
* 索引只从表派生,不另存任何词:表改了,这里跟着变。
|
|
6
6
|
*
|
|
7
|
-
* ## 迁移指路(7.0.1)
|
|
7
|
+
* ## 迁移指路(7.0.1;11.0.0 起指路句住 `retired-form.ts`)
|
|
8
8
|
* 拒因要不要带迁移指路只有一条规则:**这个键「旧形 ⇒ 迁移能搬」就带,搬不了的保留名(`PORT` / `MODEL_*` …)不带**。
|
|
9
9
|
* 「迁移能搬」= 迁移自己识别的键集(车道键 {@link isLaneEnvKey} ∪ 店键 {@link STORE_ENV_KEYS}),写闸读的是**同一只谓词**
|
|
10
10
|
* ({@link isMigratableEnvKey}),不是第二张手抄的表 —— 迁移多认一个键而写闸没跟,或反过来,不会静默分叉
|
|
11
|
-
* (`test/migration-signpost-
|
|
11
|
+
* (`test/migration-signpost-11.0.0.test.ts` 用真跑迁移的读数逐个保留名对拍)。
|
|
12
12
|
*/
|
|
13
13
|
import { EXECUTION_LANES } from "./execution-lanes.js";
|
|
14
14
|
/** 一条车道的表行。词表与表行由 `execution-lanes.ts` 的 `satisfies Record<ExecutionLane, …>` 一一对应 ——
|
|
@@ -40,7 +40,3 @@ export function isLaneEnvKey(name) {
|
|
|
40
40
|
export function isMigratableEnvKey(name) {
|
|
41
41
|
return isLaneEnvKey(name) || STORE_ENV_KEYS.some((k) => k === name);
|
|
42
42
|
}
|
|
43
|
-
/** 迁移命令的唯一拼写(design/327 §3.4 原句):`npx` 全名形 —— 没装本包的运维粘贴即可跑;裸 bin 名 `settings-schema …` 在没装本包的机器上是 `command not found`。 */
|
|
44
|
-
export const MIGRATE_WORKERS_COMMAND = "npx @sema-agent/settings-schema migrate-workers";
|
|
45
|
-
/** 拒因里的迁移指路 —— **一句话、一个常量**:保留名闸(`env` / `secretEnv` 里的 {@link isMigratableEnvKey} 键)与删除项预处理(七个被删旧键)引用同一个。 */
|
|
46
|
-
export const MIGRATION_SIGNPOST = `lift a stored 6.x spec once with migrateWorkersDomain(doc) (on the raw document, ahead of parseDomain) or \`${MIGRATE_WORKERS_COMMAND} <file>\``;
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* ## 为什么在这里
|
|
6
6
|
* 车道词、每条车道读哪些 env、哪条车道分租户、存储后端词 —— 这几张表以前在四处各抄一份(引擎的能力表、
|
|
7
|
-
*
|
|
8
|
-
* ·
|
|
9
|
-
* ·
|
|
7
|
+
* 部署面的车道清单、web-admin 表单、本包 `RemoteExecSpec`),任何一处改了别处都不红。从本版起**只有这一份**:
|
|
8
|
+
* · web-admin 写闸 = `WorkerSpec.safeParse`(本包 schema 读这张表判 `runtime.execution` / `runtime.store`);
|
|
9
|
+
* · web-admin 渲染器按这张表把结构块渲成 env(唯一写者);
|
|
10
10
|
* · 引擎提货时从这张表派生自己的车道词表 / 必需键表 / 租户分离位(引擎只留运行期事实,按导入联合类型穷举);
|
|
11
11
|
* · 部署面车道清单由这张表生成。
|
|
12
12
|
* 写闸与起服读同一张表 ⇒ 不会分叉。
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* · 车道词与次序:`src/execution-lane-caps.ts` `EXECUTION_LANE_WORDS`(:335)去掉 `in-process` =
|
|
16
16
|
* `REMOTE_EXEC_PROVIDER_WORDS`(:366)。次序照抄,于是引擎改派生之后,拒启句里的 `valid:` 列表与出路列表逐字不变。
|
|
17
17
|
* · `tenantSeparation`:同文件 `executionLaneCaps` 每臂的 `tenantSeparation` 位(:209 host / :230 e2b / :249 k8s /
|
|
18
|
-
* :269 ssh / :288 adb / :308 local-docker / :327 device)
|
|
18
|
+
* :269 ssh / :288 adb / :308 local-docker / :327 device)。`tenantSeparationBasis`(11.0.0):分租户的那几臂凭什么分 ——
|
|
19
|
+
* e2b / k8s / local-docker 每任务一只隔离目标(`isolation`),device 是发起者自己的设备(`principal-binding`)。
|
|
19
20
|
* · `requiredEnv ∪ requiredSecretEnv`:`src/config.ts` `REQUIRED_ENVS_BY_LANE`(:1054)逐字;按 `src/config-catalog.ts`
|
|
20
21
|
* 该键那一行的值类型分栏 —— 类型 `secret` / `opaque`(目录自己「绝不回显值」的两类)进 `*SecretEnv`,其余进 `*Env`。
|
|
21
22
|
* · `optionalEnv ∪ optionalSecretEnv`:`src/config.ts` `parseOrchestrationDomain` 的 `REMOTE_EXEC` 三元链里**该车道那一臂**
|
|
@@ -31,25 +32,47 @@
|
|
|
31
32
|
* 仍回 completed + 零工具,就是那个病换了个合法名字。要无手 worker 先另立设计、定用户面行为再给词。
|
|
32
33
|
* · `remote-docker`:引擎有一只没接线的适配器工厂,但没有任何读点能把这个词变成一条在跑的车道
|
|
33
34
|
* (`REMOTE_EXEC=remote-docker` 起服即拒;远端 docker 守护进程今天的真形 = `local-docker` + `DOCKER_HOST`)。
|
|
35
|
+
* 它在退役词墓碑 {@link RETIRED_LANE_WORDS} 里:读到即拒,拒句带出路。
|
|
34
36
|
*/
|
|
35
37
|
/** 可声明的执行车道闭集(= 引擎 `REMOTE_EXEC` 收的词,逐字同序)。 */
|
|
36
38
|
export declare const EXECUTION_LANE_WORDS: readonly ["host", "e2b", "k8s", "ssh", "adb", "local-docker", "device"];
|
|
37
39
|
export type ExecutionLane = (typeof EXECUTION_LANE_WORDS)[number];
|
|
40
|
+
/**
|
|
41
|
+
* 已退役的车道**值** —— 墓碑(与退役 env 名的墓碑表 `RETIRED_ENVS` 同一条线:永久、只增不减、一跳 —— 去向恒是现行车道词)。
|
|
42
|
+
* 一行 = `[旧词, 去向车道, 怎么配出旧词当年要的那件事]`。读到旧词的每一处(worker 规格的 `runtime.execution.lane`、车道文件
|
|
43
|
+
* `config.d/remote-exec.json` 的 `provider`、引擎的 `REMOTE_EXEC`)拒,拒句取 {@link retiredLaneWordRefusal} —— 出路在源头给,一句。
|
|
44
|
+
*/
|
|
45
|
+
export type RetiredLaneWord = readonly [old: string, to: ExecutionLane, how: string];
|
|
46
|
+
export declare const RETIRED_LANE_WORDS: readonly RetiredLaneWord[];
|
|
47
|
+
/** 一个车道值是不是退役词:是 ⇒ 带出路的整句;否(现行词、不认识的词、非串)⇒ `undefined`。纯函数。 */
|
|
48
|
+
export declare function retiredLaneWordRefusal(word: unknown): string | undefined;
|
|
38
49
|
/** 车道分不分租户(引擎能力表的 `tenantSeparation` 位,两档,没有「不表态」)。
|
|
39
50
|
* `supported` = 每个租户的命令落在各自的目标上;`single-user-only` = 所有任务落同一个共享目标 ⇒ 只许单用户 worker。 */
|
|
40
51
|
export declare const TENANT_SEPARATION_WORDS: readonly ["supported", "single-user-only"];
|
|
41
52
|
export type TenantSeparation = (typeof TENANT_SEPARATION_WORDS)[number];
|
|
53
|
+
/** 分租户车道**凭什么**分租户(两档):`isolation` = 每个任务一只隔离的目标(microVM / pod / 容器);`principal-binding` = 目标绑在
|
|
54
|
+
* 发起者身上(设备 ↔ principal 绑定),目标本身不隔离。只有 `tenantSeparation: "supported"` 的车道有依据。 */
|
|
55
|
+
export declare const TENANT_SEPARATION_BASES: readonly ["isolation", "principal-binding"];
|
|
56
|
+
export type TenantSeparationBasis = (typeof TENANT_SEPARATION_BASES)[number];
|
|
57
|
+
/** 车道的租户分离位与它的依据 —— 判别联合:分租户 ⇔ 依据在场(类型保证,不靠一句注释)。消费方的规则读依据列,不点名车道。 */
|
|
58
|
+
export type LaneTenancy = {
|
|
59
|
+
readonly tenantSeparation: "supported";
|
|
60
|
+
readonly tenantSeparationBasis: TenantSeparationBasis;
|
|
61
|
+
} | {
|
|
62
|
+
readonly tenantSeparation: "single-user-only";
|
|
63
|
+
readonly tenantSeparationBasis?: never;
|
|
64
|
+
};
|
|
42
65
|
/** 一条车道的表行。四栏 env 名两两不交、跨车道也不交(`test/execution-lanes-7.0.0.test.ts` 钉)。
|
|
43
66
|
* - `requiredEnv` / `optionalEnv`:非密坐标 —— 写在 `runtime.execution.coords`(字面值);
|
|
44
|
-
* - `requiredSecretEnv` / `optionalSecretEnv`:密钥 —— 写在 `runtime.execution.secretRefs`(服务变量名 → 宿主 env-NAME,绝不写值)
|
|
45
|
-
|
|
67
|
+
* - `requiredSecretEnv` / `optionalSecretEnv`:密钥 —— 写在 `runtime.execution.secretRefs`(服务变量名 → 宿主 env-NAME,绝不写值);
|
|
68
|
+
* - 租户分离位 + 依据:见 {@link LaneTenancy}。 */
|
|
69
|
+
export type ExecutionLaneRow = {
|
|
46
70
|
readonly lane: ExecutionLane;
|
|
47
|
-
readonly tenantSeparation: TenantSeparation;
|
|
48
71
|
readonly requiredEnv: readonly string[];
|
|
49
72
|
readonly requiredSecretEnv: readonly string[];
|
|
50
73
|
readonly optionalEnv: readonly string[];
|
|
51
74
|
readonly optionalSecretEnv: readonly string[];
|
|
52
|
-
}
|
|
75
|
+
} & LaneTenancy;
|
|
53
76
|
/** 车道表(次序 = {@link EXECUTION_LANE_WORDS})。整张表深冻结 —— 它是唯一正本,消费方拿到的不许被改。 */
|
|
54
77
|
export declare const EXECUTION_LANES: readonly ExecutionLaneRow[];
|
|
55
78
|
/** 存储后端(→ `DB_BACKEND`)。= server `src/config-types.ts` `DB_BACKEND_WORDS`(:67)逐字同序。 */
|