@lifeaitools/clauth 1.30.17 → 1.30.18
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.
|
@@ -13,6 +13,7 @@ const ACTIONS = new Set(["start", "stop", "restart", "reconcile", "test", "promo
|
|
|
13
13
|
const DEFAULT_HEALTH_RECONCILE_INTERVAL_MS = 10000;
|
|
14
14
|
const DEFAULT_HEALTH_TIMEOUT_MS = 2500;
|
|
15
15
|
const HEALTH_RECONCILE_COOLDOWN_MS = 15000;
|
|
16
|
+
const DOCUMENTATION_FIELDS = ["architecture", "operator_guide", "install", "runbook", "tool_reference", "release", "agent_context"];
|
|
16
17
|
|
|
17
18
|
export function getSupervisorPort() {
|
|
18
19
|
return Number(process.env.CLAUTH_SUPERVISOR_PORT || DEFAULT_SUPERVISOR_PORT);
|
|
@@ -188,6 +189,25 @@ function normalizeDestination(destination) {
|
|
|
188
189
|
return value;
|
|
189
190
|
}
|
|
190
191
|
|
|
192
|
+
function normalizeDocumentation(value) {
|
|
193
|
+
if (value == null) return null;
|
|
194
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("documentation must be an object");
|
|
195
|
+
const documentation = {};
|
|
196
|
+
for (const field of DOCUMENTATION_FIELDS) {
|
|
197
|
+
if (value[field] == null) continue;
|
|
198
|
+
if (typeof value[field] !== "string" || !value[field].trim()) {
|
|
199
|
+
throw new Error(`documentation.${field} must be a non-empty path`);
|
|
200
|
+
}
|
|
201
|
+
const ref = value[field].trim().replaceAll("\\", "/");
|
|
202
|
+
if (ref.startsWith("/") || ref.includes("..") || /^[a-z]+:/i.test(ref)) {
|
|
203
|
+
throw new Error(`documentation.${field} must be a repository-relative path`);
|
|
204
|
+
}
|
|
205
|
+
documentation[field] = ref;
|
|
206
|
+
}
|
|
207
|
+
if (!documentation.architecture) throw new Error("documentation.architecture is required");
|
|
208
|
+
return documentation;
|
|
209
|
+
}
|
|
210
|
+
|
|
191
211
|
function localhostHealth(pathOrUrl, port) {
|
|
192
212
|
if (!pathOrUrl) return null;
|
|
193
213
|
if (/^https?:\/\//i.test(pathOrUrl)) {
|
|
@@ -237,6 +257,7 @@ export function validatePluginManifest(manifest, sourcePath = "") {
|
|
|
237
257
|
id,
|
|
238
258
|
version,
|
|
239
259
|
publisher: String(manifest.publisher || "unknown"),
|
|
260
|
+
documentation: normalizeDocumentation(manifest.documentation),
|
|
240
261
|
// Only trusted managed manifests may opt into automatic startup. User
|
|
241
262
|
// plugins remain awaiting_enable until an operator explicitly enables them.
|
|
242
263
|
core: manifest.core === true,
|
|
@@ -66,6 +66,11 @@ function baseManifest(id, overrides = {}) {
|
|
|
66
66
|
restart: ["node", "--version"],
|
|
67
67
|
}],
|
|
68
68
|
test: { command: ["node", "--version"], port: "auto", health: "/health", selfTest: [["node", "--version"]] },
|
|
69
|
+
documentation: {
|
|
70
|
+
architecture: "docs/systems/example/ARCHITECTURE.md",
|
|
71
|
+
operator_guide: "docs/systems/example/OPERATE.md",
|
|
72
|
+
tool_reference: ".claude/context/mcp-endpoint-design.md",
|
|
73
|
+
},
|
|
69
74
|
...overrides,
|
|
70
75
|
};
|
|
71
76
|
}
|
|
@@ -77,6 +82,8 @@ test("validatePluginManifest accepts LIFEAI plugin contract with isolated test c
|
|
|
77
82
|
assert.equal(plugin.surfaces[0].destination, "local/clauth/pm2");
|
|
78
83
|
assert.equal(plugin.surfaces[0].lifecycle_owner, "clauth");
|
|
79
84
|
assert.equal(plugin.surfaces[0].health, "http://127.0.0.1:39111/health");
|
|
85
|
+
assert.equal(plugin.documentation.architecture, "docs/systems/example/ARCHITECTURE.md");
|
|
86
|
+
assert.equal(plugin.documentation.operator_guide, "docs/systems/example/OPERATE.md");
|
|
80
87
|
});
|
|
81
88
|
|
|
82
89
|
test("validatePluginManifest accepts empty test command arrays from the v1 template", () => {
|
|
@@ -92,6 +99,15 @@ test("validatePluginManifest rejects invalid manifests and non-local health URLs
|
|
|
92
99
|
assert.throws(() => validatePluginManifest(baseManifest("bad", {
|
|
93
100
|
surfaces: [{ id: "bad", health: "https://example.com/health" }],
|
|
94
101
|
})), /localhost-only/);
|
|
102
|
+
assert.throws(() => validatePluginManifest(baseManifest("bad-docs", {
|
|
103
|
+
documentation: { architecture: "../outside.md" },
|
|
104
|
+
})), /repository-relative/);
|
|
105
|
+
assert.throws(() => validatePluginManifest(baseManifest("bad-docs", {
|
|
106
|
+
documentation: { operator_guide: "docs/guide.md" },
|
|
107
|
+
})), /documentation.architecture is required/);
|
|
108
|
+
assert.throws(() => validatePluginManifest(baseManifest("bad-docs", {
|
|
109
|
+
documentation: { architecture: "https://example.com/architecture.md" },
|
|
110
|
+
})), /repository-relative/);
|
|
95
111
|
});
|
|
96
112
|
|
|
97
113
|
test("discovery merges managed and user roots without silent managed-id shadowing", () => withTempSupervisor((root) => {
|