@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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.30.17",
3
+ "version": "1.30.18",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {