@hasna/skills 0.10.49 → 0.10.51
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/README.md +40 -6
- package/bin/index.js +82 -50
- package/bin/maintenance.js +199 -2
- package/bin/mcp.js +53 -36
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +71 -39
- package/dist/lib/agent-discovery.d.ts +2 -2
- package/dist/lib/agent-integration.d.ts +1 -0
- package/dist/lib/claude-settings-witness.d.ts +19 -0
- package/dist/lib/codex-native-compatibility.d.ts +4 -1
- package/dist/sdk/index.js +49 -21
- package/dist/server/maintenance-entry.d.ts +14 -0
- package/dist/server/schema-census.d.ts +77 -0
- package/docs/plugin-admission.md +44 -5
- package/package.json +1 -1
package/dist/sdk/index.js
CHANGED
|
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
|
|
|
32373
32373
|
// package.json
|
|
32374
32374
|
var package_default = {
|
|
32375
32375
|
name: "@hasna/skills",
|
|
32376
|
-
version: "0.10.
|
|
32376
|
+
version: "0.10.51",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|
|
@@ -38140,6 +38140,8 @@ var BUILTIN_MODEL_SELECTIONS = new Set([
|
|
|
38140
38140
|
"claude-opus-4-6[1m]",
|
|
38141
38141
|
"claude-sonnet-4-6[1m]"
|
|
38142
38142
|
]);
|
|
38143
|
+
var CLAUDE_BUILTIN_THEMES = Object.freeze(["auto", "dark", "light", "dark-daltonized", "light-daltonized", "dark-ansi", "light-ansi"]);
|
|
38144
|
+
var BUILTIN_THEMES = new Set(CLAUDE_BUILTIN_THEMES);
|
|
38143
38145
|
function canonical(value) {
|
|
38144
38146
|
if (value.kind === "object")
|
|
38145
38147
|
return { kind: "object", entries: value.entries.map(([key, child]) => [key, canonical(child)]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0) };
|
|
@@ -38198,7 +38200,9 @@ function inferencePreference(key, child, version) {
|
|
|
38198
38200
|
function settingsDigest(text, version = 1) {
|
|
38199
38201
|
const value = parse2(text);
|
|
38200
38202
|
value.entries = value.entries.filter(([key, child]) => {
|
|
38201
|
-
if (version
|
|
38203
|
+
if (version === 4 && key === "theme" && child.kind === "string" && BUILTIN_THEMES.has(child.value))
|
|
38204
|
+
return false;
|
|
38205
|
+
if (version !== 1 && inferencePreference(key, child, version === 4 ? 3 : version))
|
|
38202
38206
|
return false;
|
|
38203
38207
|
if (key === "model") {
|
|
38204
38208
|
need(child.kind === "string", "contains an invalid model selection");
|
|
@@ -38256,6 +38260,26 @@ function upgradeClaudeSettingsWitness(previous, reviewedSettingsPath) {
|
|
|
38256
38260
|
need(settingsDigest(before, 3) === sha2562, "non-preference settings changed; explicit discovery review required");
|
|
38257
38261
|
return { path: previous.path, hashMode: "claude-settings-v3", sha256: sha2562 };
|
|
38258
38262
|
}
|
|
38263
|
+
function captureClaudeSettingsV4(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
38264
|
+
return { path, hashMode: "claude-settings-v4", sha256: settingsDigest(readNativeSettingsWitnessFile(path, budget), 4) };
|
|
38265
|
+
}
|
|
38266
|
+
function hashClaudeSettingsReplacementV4(text, budget) {
|
|
38267
|
+
need(typeof text === "string", "requires settings text");
|
|
38268
|
+
const bytes = Buffer.byteLength(text);
|
|
38269
|
+
need(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
|
|
38270
|
+
budget.remaining -= bytes;
|
|
38271
|
+
return settingsDigest(text, 4);
|
|
38272
|
+
}
|
|
38273
|
+
function upgradeClaudeSettingsWitnessV4(previous, reviewedSettingsPath) {
|
|
38274
|
+
need(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === undefined || previous.hashMode === "bytes" || previous.hashMode === "claude-settings-v1" || previous.hashMode === "claude-settings-v2" || previous.hashMode === "claude-settings-v3") && typeof previous.sha256 === "string" && /^[a-f0-9]{64}$/.test(previous.sha256), "requires an exact prior settings witness");
|
|
38275
|
+
const budget = { remaining: 2 * CLAUDE_SETTINGS_WITNESS_LIMITS.bytes };
|
|
38276
|
+
const before = readNativeSettingsWitnessFile(reviewedSettingsPath, budget), current = readNativeSettingsWitnessFile(previous.path, budget);
|
|
38277
|
+
const beforeDigest = previous.hashMode === undefined || previous.hashMode === "bytes" ? createHash6("sha256").update(before).digest("hex") : settingsDigest(before, previous.hashMode === "claude-settings-v1" ? 1 : previous.hashMode === "claude-settings-v2" ? 2 : 3);
|
|
38278
|
+
need(beforeDigest === previous.sha256, "reviewed preimage does not match the prior witness");
|
|
38279
|
+
const sha2562 = settingsDigest(current, 4);
|
|
38280
|
+
need(settingsDigest(before, 4) === sha2562, "non-preference settings changed; explicit discovery review required");
|
|
38281
|
+
return { path: previous.path, hashMode: "claude-settings-v4", sha256: sha2562 };
|
|
38282
|
+
}
|
|
38259
38283
|
function hashNativeJsonControls(text, excludedMetadata) {
|
|
38260
38284
|
need(Buffer.byteLength(text) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes, "exceeds its byte limit");
|
|
38261
38285
|
const value = parse2(text);
|
|
@@ -38402,7 +38426,8 @@ var MEASURED_CODEX_CAPABILITIES = {
|
|
|
38402
38426
|
"codex-cli 0.158.0": ["hooks"],
|
|
38403
38427
|
"codex-cli 0.159.0": ["hooks"],
|
|
38404
38428
|
"codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
|
|
38405
|
-
"codex-cli 0.160.0": ["hooks", "qualified-skill-catalog"]
|
|
38429
|
+
"codex-cli 0.160.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
|
|
38430
|
+
"codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
|
|
38406
38431
|
};
|
|
38407
38432
|
function supportsCodexNativeCapability(version, capability) {
|
|
38408
38433
|
return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
|
|
@@ -39003,6 +39028,7 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read, rules)
|
|
|
39003
39028
|
function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules, pluginSettings, plannedRules, denials = []) {
|
|
39004
39029
|
if (!supportsCodexNativeCapability(catalog?.version, "qualified-skill-catalog") || catalog.cwd !== cwd)
|
|
39005
39030
|
refuse3();
|
|
39031
|
+
const installedPluginReview = supportsCodexNativeCapability(catalog.version, "installed-plugin-review");
|
|
39006
39032
|
if (!Array.isArray(denials) || denials.length > 4096 || new Set(denials.map((item) => item?.path)).size !== denials.length || denials.some((item) => !item || Object.keys(item).sort().join(",") !== "name,path,sha256" || typeof item.name !== "string" || typeof item.path !== "string" || !/^[a-f0-9]{64}$/.test(item.sha256)))
|
|
39007
39033
|
refuse3();
|
|
39008
39034
|
const usedDenials = new Set;
|
|
@@ -39034,7 +39060,7 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules,
|
|
|
39034
39060
|
if (listed.length > 1)
|
|
39035
39061
|
refuse3();
|
|
39036
39062
|
const installed = installedPlugins.filter((plugin) => plugin.id === expectedPluginId && plugin.name === pluginName);
|
|
39037
|
-
if (
|
|
39063
|
+
if (installedPluginReview && installed.length === 1 && installed[0].installed && installed[0].localVersion === basename4(located.root) && installed[0].sourceType === "local" && codexPluginSourceIsConfigControlled(installed[0])) {
|
|
39038
39064
|
const plugin = installed[0], input = { pluginId: expectedPluginId, namespace: located.namespace, pluginParent: located.pluginParent, sourceRoot: plugin.sourcePath, sourceSha256: plugin.sourceSha256 };
|
|
39039
39065
|
if (input.sourceRoot !== located.root && !input.sourceRoot.startsWith(cache + sep3) && (lstatSync7(input.sourceRoot, { throwIfNoEntry: false }) || documents.some((path) => path.startsWith(input.sourceRoot + sep3)))) {
|
|
39040
39066
|
if (skills.some((skill) => skill.path === input.sourceRoot || skill.path.startsWith(input.sourceRoot + sep3)) || !sourceInputUnchanged(input, cache, read))
|
|
@@ -39043,7 +39069,7 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules,
|
|
|
39043
39069
|
}
|
|
39044
39070
|
}
|
|
39045
39071
|
const settings = pluginSettings && typeof pluginSettings === "object" && !Array.isArray(pluginSettings) ? pluginSettings : undefined;
|
|
39046
|
-
if (
|
|
39072
|
+
if (installedPluginReview && !listed.length && !skills.some((skill) => skill.pluginId === expectedPluginId) && installed.length === 1 && installed[0].installed && !installed[0].enabled && codexPluginSourceIsConfigControlled(installed[0]) && settings && Object.hasOwn(settings, expectedPluginId) && settings[expectedPluginId]?.enabled === false) {
|
|
39047
39073
|
if (!Array.isArray(rules))
|
|
39048
39074
|
refuse3();
|
|
39049
39075
|
const exactPathDeny = rules.some((rule) => rule?.path === document && rule?.enabled === false);
|
|
@@ -39076,12 +39102,12 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules,
|
|
|
39076
39102
|
const exactPathDeny = Array.isArray(rules) && rules.some((rule) => rule?.path === document && rule?.enabled === false);
|
|
39077
39103
|
const nativeSkill = { name: parsed.name, path: document, pluginId: expectedPluginId, enabled: true };
|
|
39078
39104
|
const denial = denials.find((item) => item.path === document);
|
|
39079
|
-
const explicitDeny = denial !== undefined && remotePluginId !== undefined &&
|
|
39105
|
+
const explicitDeny = denial !== undefined && remotePluginId !== undefined && installedPluginReview && denial.name === parsed.name && denial.sha256 === createHash8("sha256").update(read(document)).digest("hex") && Array.isArray(rules) && !rules.some((rule) => (rule?.path === document || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document && rule.enabled === false);
|
|
39080
39106
|
if (denial && !explicitDeny)
|
|
39081
39107
|
refuse3();
|
|
39082
39108
|
if (explicitDeny)
|
|
39083
39109
|
usedDenials.add(document);
|
|
39084
|
-
const plannedPathDeny =
|
|
39110
|
+
const plannedPathDeny = installedPluginReview && remotePluginId !== undefined && Array.isArray(rules) && rules.some((rule) => typeof rule?.name === "string" && rule.name.trim() === parsed.name && rule.enabled === false) && !rules.some((rule) => (rule?.path === document || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document && rule.enabled === false) && isCodexNativeSkillDisabled(nativeSkill, plannedRules);
|
|
39085
39111
|
if (!explicitDeny && (!exactPathDeny && !plannedPathDeny || !isCodexNativeSkillDisabled(nativeSkill, rules)))
|
|
39086
39112
|
refuse3();
|
|
39087
39113
|
}
|
|
@@ -40329,7 +40355,7 @@ function assertAgentPolicyCollections(policy) {
|
|
|
40329
40355
|
}
|
|
40330
40356
|
if (bridge.codexInactivePlugins !== undefined) {
|
|
40331
40357
|
const controls = array(bridge.codexInactivePlugins, 4096), ids = new Set;
|
|
40332
|
-
requireBound(controls.length === 0 || object4(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version
|
|
40358
|
+
requireBound(controls.length === 0 || object4(bridge.codexPluginSkillReview) && supportsCodexNativeCapability(bridge.codexPluginSkillReview.version, "installed-plugin-review") && typeof bridge.codexPluginSkillReview.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(bridge.codexPluginSkillReview.catalogSha256));
|
|
40333
40359
|
for (const control of controls) {
|
|
40334
40360
|
requireBound(object4(control) && Object.keys(control).every((key) => ["pluginId", "namespace", "pluginParent", "sourceType", "sourceSha256"].includes(key)));
|
|
40335
40361
|
text(control.pluginId, 1024);
|
|
@@ -40381,7 +40407,7 @@ function assertAgentPolicyCollections(policy) {
|
|
|
40381
40407
|
}
|
|
40382
40408
|
if (value.codexInstallationInputs !== undefined) {
|
|
40383
40409
|
const proof = record3(value.codexInstallationInputs, 4), inputs = array(proof.plugins, 4096), ids = new Set;
|
|
40384
|
-
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && proof.version
|
|
40410
|
+
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && supportsCodexNativeCapability(proof.version, "installed-plugin-review") && typeof proof.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(proof.catalogSha256) && inputs.length > 0 && object4(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === proof.version && bridge.codexPluginSkillReview.catalogSha256 === proof.catalogSha256);
|
|
40385
40411
|
for (const input of inputs) {
|
|
40386
40412
|
requireBound(object4(input) && Object.keys(input).every((key) => ["pluginId", "namespace", "pluginParent", "sourceRoot", "sourceSha256"].includes(key)));
|
|
40387
40413
|
text(input.pluginId, 1024);
|
|
@@ -40405,14 +40431,14 @@ function assertAgentPolicyCollections(policy) {
|
|
|
40405
40431
|
text(source.path);
|
|
40406
40432
|
requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
|
|
40407
40433
|
if (source.hashMode !== undefined)
|
|
40408
|
-
requireBound(["bytes", "path-bytes", "claude-plugin-manifest-v1", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-marketplace-registry-v2", "codex-settings-v1", "codex-settings-v2", "codex-settings-v3", "codex-settings-v4", "sumi-settings-v1"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
40434
|
+
requireBound(["bytes", "path-bytes", "claude-plugin-manifest-v1", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-settings-v4", "claude-marketplace-registry-v2", "codex-settings-v1", "codex-settings-v2", "codex-settings-v3", "codex-settings-v4", "sumi-settings-v1"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
40409
40435
|
if (source.hashMode === "claude-plugin-manifest-v1")
|
|
40410
40436
|
requireBound(agent === "claude" && value.agent === "claude" && ["automatic", "reviewed"].includes(value.method) && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute8(source.path) && resolve7(source.path) === source.path && source.path.endsWith(join14(sep4, ".claude-plugin", "plugin.json")) && source.managedPlugins === undefined);
|
|
40411
40437
|
if (source.hashMode === "sumi-settings-v1")
|
|
40412
40438
|
requireBound(agent === "sumi" && value.agent === "sumi" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute8(source.path) && resolve7(source.path) === source.path && basename5(source.path) === "sumi.json");
|
|
40413
40439
|
if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4"))
|
|
40414
40440
|
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute8(source.path) && resolve7(source.path) === source.path && basename5(source.path) === "config.toml");
|
|
40415
|
-
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
|
|
40441
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4"))
|
|
40416
40442
|
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute8(source.path) && resolve7(source.path) === source.path && basename5(source.path) === "settings.json");
|
|
40417
40443
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
|
|
40418
40444
|
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute8(source.path) && resolve7(source.path) === source.path && basename5(source.path) === "known_marketplaces.json");
|
|
@@ -43246,13 +43272,13 @@ function projected(source, changes, budget2 = discoveryByteBudget()) {
|
|
|
43246
43272
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
43247
43273
|
return changes?.has(source.path) ? (source.hashMode === "codex-settings-v4" ? hashCodexSettingsReplacementV4 : source.hashMode === "codex-settings-v3" ? hashCodexSettingsReplacementV3 : source.hashMode === "codex-settings-v2" ? hashCodexSettingsReplacementV2 : hashCodexSettingsReplacement)(changes.get(source.path), budget2) : current;
|
|
43248
43274
|
}
|
|
43249
|
-
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
|
|
43275
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4")) {
|
|
43250
43276
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
43251
43277
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
43252
|
-
const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget2).sha256;
|
|
43278
|
+
const current = (source.hashMode === "claude-settings-v4" ? captureClaudeSettingsV4 : source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget2).sha256;
|
|
43253
43279
|
if (current !== source.sha256)
|
|
43254
43280
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
43255
|
-
return changes?.has(source.path) ? (source.hashMode === "claude-settings-v3" ? hashClaudeSettingsReplacementV3 : source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget2) : current;
|
|
43281
|
+
return changes?.has(source.path) ? (source.hashMode === "claude-settings-v4" ? hashClaudeSettingsReplacementV4 : source.hashMode === "claude-settings-v3" ? hashClaudeSettingsReplacementV3 : source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget2) : current;
|
|
43256
43282
|
}
|
|
43257
43283
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
43258
43284
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256) || changes?.has(source.path))
|
|
@@ -43463,7 +43489,7 @@ function codexInstallationRoots(binding) {
|
|
|
43463
43489
|
const proof = binding.codexInstallationInputs;
|
|
43464
43490
|
if (proof === undefined)
|
|
43465
43491
|
return [];
|
|
43466
|
-
if (binding.agent !== "codex" || binding.method !== "reviewed" || proof.version
|
|
43492
|
+
if (binding.agent !== "codex" || binding.method !== "reviewed" || !supportsCodexNativeCapability(proof.version, "installed-plugin-review") || !/^[a-f0-9]{64}$/.test(proof.catalogSha256) || !Array.isArray(proof.plugins) || !proof.plugins.length)
|
|
43467
43493
|
throw new Error("Invalid native installation input proof");
|
|
43468
43494
|
const cache = dirname16(dirname16(proof.plugins[0].pluginParent));
|
|
43469
43495
|
const config = join26(dirname16(dirname16(cache)), "config.toml"), home = dirname16(dirname16(dirname16(cache)));
|
|
@@ -43487,7 +43513,7 @@ function assertMarketplaceBinding(binding, source) {
|
|
|
43487
43513
|
throw new Error("Sumi settings witnesses require explicit reviewed Sumi configuration");
|
|
43488
43514
|
if ((source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")) && (binding.agent !== "codex" || binding.method !== "reviewed" || basename8(source.path) !== "config.toml"))
|
|
43489
43515
|
throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
|
|
43490
|
-
if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
|
|
43516
|
+
if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4")) && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
|
|
43491
43517
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
43492
43518
|
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
43493
43519
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
@@ -43689,7 +43715,7 @@ function resolveAgentDiscovery(options) {
|
|
|
43689
43715
|
verifyAgentDiscovery(retained);
|
|
43690
43716
|
if (!retained.sources.some((source) => source.path === canonical7(configPath) && source.format === undefined && source.fields === undefined))
|
|
43691
43717
|
unresolved("retained review is missing the full agent configuration source");
|
|
43692
|
-
if (retained.sources.some((source) => (source.hashMode === "sumi-settings-v1" || source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")))) && source.path !== canonical7(configPath)))
|
|
43718
|
+
if (retained.sources.some((source) => (source.hashMode === "sumi-settings-v1" || source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")))) && source.path !== canonical7(configPath)))
|
|
43693
43719
|
unresolved("retained settings witness names another configuration source");
|
|
43694
43720
|
const covered = supersedesSettingsProjection(agent, retained.sources, canonical7(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical7(configPath), source)) : sources;
|
|
43695
43721
|
if (covered.some((source) => !retained.sources.some((saved) => isDeepStrictEqual6(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
|
|
@@ -43703,7 +43729,7 @@ function resolveAgentDiscovery(options) {
|
|
|
43703
43729
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
43704
43730
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
43705
43731
|
verifyAgentDiscovery(supplied);
|
|
43706
|
-
if (review.sources.some((source) => (source.hashMode === "sumi-settings-v1" || source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")))) && source.path !== canonical7(configPath)))
|
|
43732
|
+
if (review.sources.some((source) => (source.hashMode === "sumi-settings-v1" || source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")))) && source.path !== canonical7(configPath)))
|
|
43707
43733
|
throw new Error("Semantic settings witnesses must name the configured agent configuration source");
|
|
43708
43734
|
if (!review.sources.some((source) => source.path === canonical7(configPath)))
|
|
43709
43735
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -45059,7 +45085,7 @@ function planAgentIntegration(options) {
|
|
|
45059
45085
|
const { codexInstallationInputs: previousInputs, ...current } = rebound;
|
|
45060
45086
|
const inputs = codexPluginSourceInputs.map((input) => input.sourceRoot);
|
|
45061
45087
|
const directories = options.codexNativeCatalog || !previousInputs ? captureDiscoveryDirectories((binding.directories ?? []).filter((directory) => inputs.some((root2) => root2.startsWith(directory.path + sep7))).map((directory) => directory.path), inputs) : previousInputs.directories;
|
|
45062
|
-
const installed = codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version:
|
|
45088
|
+
const installed = codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version: codexPluginSkillReview.version, catalogSha256: codexPluginSkillReview.catalogSha256, plugins: codexPluginSourceInputs, ...directories?.length ? { directories } : {} } } : current;
|
|
45063
45089
|
return captureRetiredCodexDiscovery(installed, codexPluginSkills, Bun.TOML.parse(changes.find((change) => change.path === canonicalAgentPath(join27(home, ".codex/config.toml"), aliases))?.after ?? codexConfigBefore ?? "").skills?.config ?? []);
|
|
45064
45090
|
});
|
|
45065
45091
|
const nextPolicy = { ...policy, version: 1, loading: "cli", profileId, bridge: {
|
|
@@ -45374,6 +45400,8 @@ function atomicWrite2(path, content) {
|
|
|
45374
45400
|
function planAgentSettingsWitnessUpgrade(options) {
|
|
45375
45401
|
if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3, 4].includes(options.targetCodexVersion)))
|
|
45376
45402
|
throw new Error("Invalid Codex witness target version");
|
|
45403
|
+
if (options.targetClaudeVersion !== undefined && (options.agent !== "claude" || ![3, 4].includes(options.targetClaudeVersion)))
|
|
45404
|
+
throw new Error("Invalid Claude witness target version");
|
|
45377
45405
|
const home = resolve18(options.home ?? homedir3()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
45378
45406
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
45379
45407
|
if (!snapshot2 || sha4(snapshot2.text) !== options.expectedPolicySha256)
|
|
@@ -45421,7 +45449,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
45421
45449
|
replaced.push(source);
|
|
45422
45450
|
}
|
|
45423
45451
|
}
|
|
45424
|
-
const next = options.agent === "sumi" ? upgradeSumiSettingsWitness(previous, options.reviewedPreimage) : options.agent === "claude" ? upgradeClaudeSettingsWitness(previous, options.reviewedPreimage) : options.targetCodexVersion === 4 ? upgradeCodexSettingsWitnessV4(previous, options.reviewedPreimage) : options.targetCodexVersion === 3 ? upgradeCodexSettingsWitnessV3(previous, options.reviewedPreimage) : upgradeCodexSettingsWitness(previous, options.reviewedPreimage);
|
|
45452
|
+
const next = options.agent === "sumi" ? upgradeSumiSettingsWitness(previous, options.reviewedPreimage) : options.agent === "claude" ? options.targetClaudeVersion === 4 ? upgradeClaudeSettingsWitnessV4(previous, options.reviewedPreimage) : upgradeClaudeSettingsWitness(previous, options.reviewedPreimage) : options.targetCodexVersion === 4 ? upgradeCodexSettingsWitnessV4(previous, options.reviewedPreimage) : options.targetCodexVersion === 3 ? upgradeCodexSettingsWitnessV3(previous, options.reviewedPreimage) : upgradeCodexSettingsWitness(previous, options.reviewedPreimage);
|
|
45425
45453
|
const rebound = [];
|
|
45426
45454
|
for (const source of binding.sources) {
|
|
45427
45455
|
if (!replaced.includes(source)) {
|
|
@@ -45467,7 +45495,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
45467
45495
|
const upgrade = plan.settingsWitnessUpgrade;
|
|
45468
45496
|
if (!plan.managedAgentChecks || !plan.observedPolicy?.before)
|
|
45469
45497
|
throw new Error("Invalid settings witness upgrade plan");
|
|
45470
|
-
const verified = planAgentSettingsWitnessUpgrade({ agent: upgrade.agent, ...upgrade.agent === "codex" ? { targetCodexVersion: upgrade.toHashMode === "codex-settings-v4" ? 4 : upgrade.toHashMode === "codex-settings-v3" ? 3 : 2 } : {}, reviewedPreimage: upgrade.reviewedPreimage, home: plan.managedAgentChecks.home, dataDir: plan.dataDir, expectedPolicySha256: sha4(plan.observedPolicy.before), expectedSettingsSha256: upgrade.currentSettingsSha256 });
|
|
45498
|
+
const verified = planAgentSettingsWitnessUpgrade({ agent: upgrade.agent, ...upgrade.agent === "codex" ? { targetCodexVersion: upgrade.toHashMode === "codex-settings-v4" ? 4 : upgrade.toHashMode === "codex-settings-v3" ? 3 : 2 } : {}, ...upgrade.agent === "claude" && upgrade.toHashMode === "claude-settings-v4" ? { targetClaudeVersion: 4 } : {}, reviewedPreimage: upgrade.reviewedPreimage, home: plan.managedAgentChecks.home, dataDir: plan.dataDir, expectedPolicySha256: sha4(plan.observedPolicy.before), expectedSettingsSha256: upgrade.currentSettingsSha256 });
|
|
45471
45499
|
if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.settingsWitnessUpgrade) !== JSON.stringify(upgrade))
|
|
45472
45500
|
throw new Error("Settings witness upgrade plan changed");
|
|
45473
45501
|
provenDiscovery = verified.discoveryBefore[0];
|
|
@@ -9,7 +9,21 @@ type InspectionManifest = EnrollmentInspectionInput & {
|
|
|
9
9
|
expiresAt: string;
|
|
10
10
|
manifestDigest: string;
|
|
11
11
|
};
|
|
12
|
+
/** The schema census has its own authorization; no enrollment or inspection manifest can authorize it. */
|
|
13
|
+
type SchemaCensusManifest = {
|
|
14
|
+
app: "skills";
|
|
15
|
+
operation: "inspect-schema";
|
|
16
|
+
manifestVersion: 1;
|
|
17
|
+
operationId: string;
|
|
18
|
+
stationId: string;
|
|
19
|
+
keyId: string;
|
|
20
|
+
orgId: string;
|
|
21
|
+
expiresAt: string;
|
|
22
|
+
manifestDigest: string;
|
|
23
|
+
};
|
|
12
24
|
/** Inspection has its own authorization; a failed enrollment manifest cannot authorize it. */
|
|
13
25
|
export declare function readInspectionManifest(path: string, receiptPath: string): InspectionManifest;
|
|
26
|
+
/** The census has its own authorization; a failed enrollment or inspection manifest cannot authorize it. */
|
|
27
|
+
export declare function readSchemaCensusManifest(path: string, receiptPath: string): SchemaCensusManifest;
|
|
14
28
|
export declare function registerMaintenance(parent: Command): void;
|
|
15
29
|
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One fixed, closed census of the Skills database maintenance surface.
|
|
3
|
+
*
|
|
4
|
+
* Why it exists (I6-00024): a production migration is held until the exact
|
|
5
|
+
* current registry, version ledger and migration ledger are read from the live
|
|
6
|
+
* database. The enrollment inspection answers a different question (one
|
|
7
|
+
* operator key), and no existing reader returns the fixed aggregate + ledger
|
|
8
|
+
* census. This module is that reader, and it is deliberately shaped so it
|
|
9
|
+
* cannot become a general SQL console:
|
|
10
|
+
*
|
|
11
|
+
* - No target, query, schema or table input exists. The only argument is the
|
|
12
|
+
* database URL, and the operation refuses to run unless the connection
|
|
13
|
+
* resolves to the fixed public schema the Skills store uses.
|
|
14
|
+
* - Every owned-table read is schema-qualified to `public`. A connection whose
|
|
15
|
+
* search path falls through to a later schema cannot substitute another
|
|
16
|
+
* schema's tables: a missing public table refuses (CENSUS_SCHEMA_UNAVAILABLE)
|
|
17
|
+
* instead of counting rows that do not belong to the fixed target.
|
|
18
|
+
* - The transaction is READ ONLY, REPEATABLE READ, with a five-second
|
|
19
|
+
* statement timeout and a one-second lock timeout, asserted from inside the
|
|
20
|
+
* read path rather than assumed.
|
|
21
|
+
* - The output is a closed shape with per-field size bounds, a bounded
|
|
22
|
+
* migration ledger and a total serialized-size gate. Nothing is truncated
|
|
23
|
+
* silently: a census that would exceed a bound refuses instead.
|
|
24
|
+
* - Nothing imports the application store: no initialization, migrations,
|
|
25
|
+
* backfills or writes happen on this path.
|
|
26
|
+
*/
|
|
27
|
+
export interface SchemaCensusResult {
|
|
28
|
+
status: "inspected";
|
|
29
|
+
database: string;
|
|
30
|
+
role: string;
|
|
31
|
+
serverVersion: string;
|
|
32
|
+
schema: "public";
|
|
33
|
+
transaction: {
|
|
34
|
+
readOnly: true;
|
|
35
|
+
isolation: "repeatable read";
|
|
36
|
+
statementTimeoutMs: number;
|
|
37
|
+
lockTimeoutMs: number;
|
|
38
|
+
};
|
|
39
|
+
migrations: {
|
|
40
|
+
count: number;
|
|
41
|
+
versions: string[];
|
|
42
|
+
};
|
|
43
|
+
registry: {
|
|
44
|
+
rows: number;
|
|
45
|
+
};
|
|
46
|
+
versions: {
|
|
47
|
+
rows: number;
|
|
48
|
+
};
|
|
49
|
+
lifecycle: {
|
|
50
|
+
active: {
|
|
51
|
+
live: number;
|
|
52
|
+
tombstoned: number;
|
|
53
|
+
};
|
|
54
|
+
archived: {
|
|
55
|
+
live: number;
|
|
56
|
+
tombstoned: number;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
authorizationEpoch: {
|
|
60
|
+
exists: false;
|
|
61
|
+
} | {
|
|
62
|
+
exists: true;
|
|
63
|
+
nullCount: number;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
type CensusCode = "CENSUS_POSTGRES_REQUIRED" | "CENSUS_SCHEMA_UNAVAILABLE" | "CENSUS_ACCESS_DENIED" | "CENSUS_WRITE_REFUSED" | "CENSUS_TIMEOUT" | "CENSUS_CONNECTION_FAILED" | "CENSUS_TARGET_MISMATCH" | "CENSUS_INVALID_RESULT" | "CENSUS_OUTPUT_UNBOUNDED" | "CENSUS_FAILED";
|
|
67
|
+
export declare class SchemaCensusError extends Error {
|
|
68
|
+
readonly code: CensusCode;
|
|
69
|
+
constructor(code: CensusCode);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Census metadata only. Deliberately does not import or initialize the application store.
|
|
73
|
+
*
|
|
74
|
+
* @param databaseUrl explicit PostgreSQL URL; there is no implicit localhost fallback.
|
|
75
|
+
*/
|
|
76
|
+
export declare function inspectSchemaCensus(databaseUrl: string): Promise<SchemaCensusResult>;
|
|
77
|
+
export {};
|
package/docs/plugin-admission.md
CHANGED
|
@@ -239,8 +239,8 @@ Invalid values for the display preferences refuse capture and verification. Ever
|
|
|
239
239
|
other field remains bound, including unknown future fields, hooks and their
|
|
240
240
|
exact commands, permissions, native skill suppression and synchronization,
|
|
241
241
|
plugin roots, marketplaces, environment and configuration precedence. Language,
|
|
242
|
-
output style, theme
|
|
243
|
-
intentionally retained. These are narrow preference and inference-selection
|
|
242
|
+
output style, theme (v4 alone permits the built-in theme values), and
|
|
243
|
+
command-bearing status or file suggestion settings are intentionally retained. These are narrow preference and inference-selection
|
|
244
244
|
exceptions, not general permission to change Claude configuration. The native
|
|
245
245
|
bridge guard continues to check its exact commands and native Skill policy.
|
|
246
246
|
|
|
@@ -263,6 +263,43 @@ or guarantee when a running native client adopts a settings edit.
|
|
|
263
263
|
Those complementary checks retain their existing exact registration and hook
|
|
264
264
|
entry comparisons; this mode does not relax them for property-order changes.
|
|
265
265
|
|
|
266
|
+
### Explicit v4 built-in theme reviews
|
|
267
|
+
|
|
268
|
+
`claude-settings-v4` is an explicit successor to v3. It binds everything v3
|
|
269
|
+
binds, with one more omission: the top-level `theme` key, and only when its
|
|
270
|
+
value is exactly one of the built-in values `auto`, `dark`, `light`,
|
|
271
|
+
`dark-daltonized`, `light-daltonized`, `dark-ansi` or `light-ansi`. The list is
|
|
272
|
+
pinned in code (`CLAUDE_BUILTIN_THEMES`) from the
|
|
273
|
+
[`theme` setting reference](https://code.claude.com/docs/en/settings-reference#theme)
|
|
274
|
+
and the [built-in presets](https://code.claude.com/docs/en/terminal-config#match-the-color-theme),
|
|
275
|
+
read on 2026-10-07. Custom and plugin themes (`custom:<slug>`,
|
|
276
|
+
`custom:<plugin-name>:<slug>`) load theme files, so they stay bound, as does any
|
|
277
|
+
other value, type, spelling or letter case, and any `theme` key nested inside
|
|
278
|
+
another setting. `skipDangerousModePermissionPrompt`, permissions, hooks,
|
|
279
|
+
environment, `apiKeyHelper` and other helpers, plugins, marketplaces, custom
|
|
280
|
+
model values and unknown settings stay bound exactly as in v3. The digest is
|
|
281
|
+
domain-separated from v3.
|
|
282
|
+
|
|
283
|
+
Existing v1, v2 and v3 witnesses keep their meaning; nothing selects v4
|
|
284
|
+
implicitly, and a stored witness is never rewritten. A policy opts in through
|
|
285
|
+
either explicit path:
|
|
286
|
+
|
|
287
|
+
- a fresh review: `skills hook witness --kind claude-settings-v4 --path
|
|
288
|
+
<settings.json> --json` (or `captureClaudeSettingsV4(path)`), placed in the
|
|
289
|
+
reviewed discovery inputs and applied with the normal `skills hook install
|
|
290
|
+
--discovery-inputs <file>` plan/apply flow;
|
|
291
|
+
- a preserved-preimage upgrade: `skills hook rebind-settings --agent claude
|
|
292
|
+
--claude-witness-version 4 --reviewed-preimage <preserved-settings.json>
|
|
293
|
+
--expected-policy-sha256 <policy-hash> --expected-settings-sha256
|
|
294
|
+
<settings-hash>` (or `upgradeClaudeSettingsWitnessV4`). It accepts a raw, v1,
|
|
295
|
+
v2 or v3 witness, proves the preimage matches it in its original mode, and
|
|
296
|
+
refuses unless the current settings equal the preimage under v4. Without
|
|
297
|
+
`--claude-witness-version` the rebind target remains v3.
|
|
298
|
+
|
|
299
|
+
Install a runtime that recognizes `claude-settings-v4`, including any bundled
|
|
300
|
+
copy of the verifier, before a policy carries it; an older runtime refuses the
|
|
301
|
+
unknown hash mode.
|
|
302
|
+
|
|
266
303
|
## Verification and limits
|
|
267
304
|
|
|
268
305
|
Unit tests cover provenance and mapping failures, offline/revoked authorities,
|
|
@@ -367,8 +404,8 @@ witness intersecting the write still refuses instead of adopting a new inode.
|
|
|
367
404
|
|
|
368
405
|
## Codex bundled cleanup controls
|
|
369
406
|
|
|
370
|
-
Qualified native skill review supports a narrow subset of Codex 0.159.2
|
|
371
|
-
0.160.0's bundled cleanup hooks. The identities `browser`, `chrome`, `chrome-dev`,
|
|
407
|
+
Qualified native skill review supports a narrow subset of Codex 0.159.2,
|
|
408
|
+
0.160.0 and 0.160.1's bundled cleanup hooks. The identities `browser`, `chrome`, `chrome-dev`,
|
|
372
409
|
`chrome-internal` and `computer-use` in `openai-bundled` may call
|
|
373
410
|
`node_repl.turn_ended`; `unified-computer-use` may call `cua_repl.turn_ended`.
|
|
374
411
|
Only `Interrupt`, `SubagentStop` and `Stop` are accepted, with one group and one
|
|
@@ -384,4 +421,6 @@ apply and subsequent discovery checks. This review preserves native cleanup;
|
|
|
384
421
|
it neither enables plugins nor grants hook trust, browser authority or signing.
|
|
385
422
|
The upstream contracts are
|
|
386
423
|
[0.159.2 bundled hooks](https://github.com/openai/codex/blob/ff6aec96948b70d94983af2641a6b67c94faeff5/codex-rs/plugin/src/bundled_hooks.rs)
|
|
387
|
-
and [0.160.0 bundled hooks](https://github.com/openai/codex/blob/a956835d020762cb2b570053af06f643a11c0ecc/codex-rs/plugin/src/bundled_hooks.rs)
|
|
424
|
+
and [0.160.0 bundled hooks](https://github.com/openai/codex/blob/a956835d020762cb2b570053af06f643a11c0ecc/codex-rs/plugin/src/bundled_hooks.rs);
|
|
425
|
+
0.160.1 carries the identical file
|
|
426
|
+
([0.160.1 bundled hooks](https://github.com/openai/codex/blob/d27764b82f7118f674371e6d6e76271d9d606edb/codex-rs/plugin/src/bundled_hooks.rs)).
|