oasis_test 0.1.128 → 0.1.129

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.
@@ -23,6 +23,8 @@ const CONTENT_HASH_PENDING = "pending";
23
23
  const USAGE = "Usage: outline-kb <command> [arguments] [--collection ID] [--root ID]. Run outline-kb <command> --help for the exact contract.";
24
24
  const WRITE_COMMANDS = new Set(["init", "create", "update", "append", "import-file", "import-url", "import-wechat", "apply"]);
25
25
  const QUERY_COMMANDS = new Set(["status", "search", "read", "read-many", "search-and-read"]);
26
+ const INGEST_FORBIDDEN_COMMANDS = new Set(["init", "create", "update", "append", "apply", "lint"]);
27
+ const MAINTENANCE_ALLOWED_COMMANDS = new Set(["status", "validate-maintenance-plan"]);
26
28
  const WECHAT_ARTICLE_HOSTS = new Set(["mp.weixin.qq.com", "weixin.qq.com"]);
27
29
  const WECHAT_FETCH_HEADERS = Object.freeze({
28
30
  "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0 Safari/537.36",
@@ -57,6 +59,32 @@ const PLAN_EXAMPLE = {
57
59
  { type: "append", path: "log", contentFile: "log.md" },
58
60
  ],
59
61
  };
62
+ const MAINTENANCE_PLAN_SCHEMA = {
63
+ $schema: "https://json-schema.org/draft/2020-12/schema",
64
+ type: "object",
65
+ required: ["jobId", "operations"],
66
+ additionalProperties: false,
67
+ properties: {
68
+ jobId: { type: "string", minLength: 1 },
69
+ sourceHash: { type: "string" },
70
+ operations: {
71
+ type: "array",
72
+ items: {
73
+ oneOf: [
74
+ { type: "object", required: ["type", "path", "contentFile"], properties: { type: { const: "create" }, path: { type: "string" }, contentFile: { type: "string" } }, additionalProperties: false },
75
+ { type: "object", required: ["type", "path", "contentFile"], properties: { type: { const: "update" }, path: { type: "string" }, contentFile: { type: "string" } }, additionalProperties: false },
76
+ ],
77
+ },
78
+ },
79
+ },
80
+ };
81
+ const MAINTENANCE_PLAN_EXAMPLE = {
82
+ jobId: "knowledge-maintenance-20260831-001",
83
+ operations: [
84
+ { type: "create", path: "wiki/concepts/example", contentFile: "concept.md" },
85
+ { type: "update", path: "wiki/synthesis/existing", contentFile: "synthesis.md" },
86
+ ],
87
+ };
60
88
  const HELP_FAILURE_EXAMPLE = { ok: false, error: { code: "USAGE", message: "missing required argument" } };
61
89
  const PLAN_FAILURE_EXAMPLES = [
62
90
  { ok: false, error: { code: "PLAN_FILE_NOT_FOUND", message: "Plan file not found", details: { path: "/workspace/kb-plan/plan.json" } } },
@@ -79,6 +107,7 @@ const COMMAND_HELP = {
79
107
  "import-url": { usage: "outline-kb import-url URL --path raw/PATH --collection ID [--root ID]", description: "Fetch a public URL and import immutable raw evidence. WeChat article URLs are routed to the dedicated parser automatically." },
80
108
  "import-wechat": { usage: "outline-kb import-wechat WECHAT_URL --path raw/PATH --collection ID [--root ID]", description: "Capture a WeChat Official Account article with #js_content and publisher metadata validation." },
81
109
  "validate-plan": { usage: "outline-kb validate-plan PLAN.json --collection ID [--root ID]", description: "Validate local files, plan schema and every CAS precondition without writing.", pathResolution: "Every contentFile is resolved relative to the PLAN.json directory.", planSchema: PLAN_SCHEMA, example: PLAN_EXAMPLE, failureExamples: PLAN_FAILURE_EXAMPLES },
110
+ "validate-maintenance-plan": { usage: "outline-kb validate-maintenance-plan PLAN.json", description: "Validate local maintenance Candidate files without reading or writing Outline. Available only in the system-owned network-maintenance profile.", pathResolution: "Every contentFile is resolved relative to the PLAN.json directory.", planSchema: MAINTENANCE_PLAN_SCHEMA, example: MAINTENANCE_PLAN_EXAMPLE, failureExamples: PLAN_FAILURE_EXAMPLES },
82
111
  apply: { usage: "outline-kb apply PLAN.json --collection ID [--root ID]", description: "Preflight all local files and the complete plan, apply it, then return deterministic lint.", pathResolution: "Every contentFile is resolved relative to the PLAN.json directory.", planSchema: PLAN_SCHEMA, example: PLAN_EXAMPLE, failureExamples: PLAN_FAILURE_EXAMPLES },
83
112
  lint: { usage: "outline-kb lint --collection ID [--root ID]", description: "Full-library deterministic health check. Do not run inside Query." },
84
113
  };
@@ -89,6 +118,7 @@ function successExample(command) {
89
118
  }
90
119
  if (command === "read") return { ok: true, document: { id: "document-id", title: "Example", path: "wiki/concepts/example", url: "https://outline.example/doc/example", text: "..." } };
91
120
  if (command === "validate-plan") return { ok: true, jobId: PLAN_EXAMPLE.jobId, sourceHash: null, operations: PLAN_EXAMPLE.operations };
121
+ if (command === "validate-maintenance-plan") return { ok: true, jobId: MAINTENANCE_PLAN_EXAMPLE.jobId, sourceHash: null, operations: MAINTENANCE_PLAN_EXAMPLE.operations };
92
122
  if (command === "apply") return { ok: true, jobId: PLAN_EXAMPLE.jobId, results: [], lint: { ok: true, errors: [], warnings: [] }, status: "succeeded" };
93
123
  return { ok: true };
94
124
  }
@@ -822,12 +852,15 @@ export class OutlineWiki {
822
852
  const tree = snapshot ?? await this.snapshot();
823
853
  const pathOf = new Map(tree.documents.map((document) => [document.id, document.path]));
824
854
  const prefix = under ? `${normalizePath(under)}/` : null;
855
+ const explicitRaw = under ? (normalizePath(under) === "raw" || normalizePath(under).startsWith("raw/")) : false;
825
856
  return matches
826
857
  .map((document) => {
827
858
  const path = pathOf.get(document.id);
828
859
  return path === undefined ? null : { ...document, path };
829
860
  })
830
- .filter((document) => document && (!under || document.path === normalizePath(under) || document.path.startsWith(prefix)))
861
+ .filter((document) => document
862
+ && (explicitRaw || !(document.path === "raw" || document.path.startsWith("raw/")))
863
+ && (!under || document.path === normalizePath(under) || document.path.startsWith(prefix)))
831
864
  .slice(0, Math.max(1, Math.min(Number(limit) || 10, 20)));
832
865
  }
833
866
 
@@ -1068,8 +1101,21 @@ export class OutlineWiki {
1068
1101
  if (operation.type === "update") results.push({ path: operation.path, ...(await this.update(operation.path, operation.content, operation.expectedUpdatedAt, tree)) });
1069
1102
  if (operation.type === "append") results.push({ path: operation.path, ...(await this.appendLog(operation.content, validated.jobId, tree)) });
1070
1103
  }
1071
- const lint = await this.lint(tree);
1072
- return { jobId: validated.jobId, results, lint, status: lint.errors.length ? "needs_repair" : "succeeded" };
1104
+ const fullLint = await this.lint(tree);
1105
+ const changedPaths = validated.operations.map((operation) => operation.path);
1106
+ const changesKnowledge = changedPaths.some((path) => path === "raw" || path.startsWith("raw/") || path === "wiki" || path.startsWith("wiki/"));
1107
+ const changesSources = changedPaths.some((path) => path.startsWith("wiki/sources/"));
1108
+ const touchesCurrentChange = (issue) => {
1109
+ const issuePath = typeof issue?.path === "string" ? normalizePath(issue.path) : "";
1110
+ if (!issuePath) return true;
1111
+ if (changesKnowledge && issue.code === "MISSING_REQUIRED") return true;
1112
+ if (changesSources && issue.code === "SOURCE_COUNT_MISMATCH") return true;
1113
+ return changedPaths.some((path) => path === issuePath || path.startsWith(`${issuePath}/`) || issuePath.startsWith(`${path}/`));
1114
+ };
1115
+ const errors = fullLint.errors.filter(touchesCurrentChange);
1116
+ const healthErrors = fullLint.errors.filter((issue) => !touchesCurrentChange(issue));
1117
+ const lint = { ...fullLint, ok: errors.length === 0, errors, ...(healthErrors.length ? { healthErrors } : {}) };
1118
+ return { jobId: validated.jobId, results, lint, status: lint.ok ? "succeeded" : "needs_repair" };
1073
1119
  }
1074
1120
 
1075
1121
  async lint(snapshot = null) {
@@ -1266,16 +1312,17 @@ async function readPlanContent(contentFile, planFile, operation, operationIndex)
1266
1312
  }
1267
1313
  }
1268
1314
 
1269
- async function preflightPlanFiles(plan, planFile = null) {
1315
+ async function preflightPlanFiles(plan, planFile = null, options = {}) {
1270
1316
  if (!plan || typeof plan !== "object" || Array.isArray(plan) || !Array.isArray(plan.operations)) {
1271
1317
  throw new OutlineKbError("INVALID_PLAN", "Plan must contain an operations array");
1272
1318
  }
1273
1319
  if (!plan.jobId || typeof plan.jobId !== "string") throw new OutlineKbError("INVALID_PLAN", "Plan requires jobId");
1274
1320
  const seen = new Set();
1275
1321
  const operations = [];
1322
+ const allowedOperationTypes = options.allowedOperationTypes ?? new Set(["create", "update", "append"]);
1276
1323
  for (let index = 0; index < plan.operations.length; index++) {
1277
1324
  const operation = plan.operations[index];
1278
- if (!operation || !["create", "update", "append"].includes(operation.type)) {
1325
+ if (!operation || !allowedOperationTypes.has(operation.type)) {
1279
1326
  throw new OutlineKbError("INVALID_PLAN", `Unsupported operation at index ${index}`);
1280
1327
  }
1281
1328
  const path = normalizePath(operation.path);
@@ -1287,7 +1334,7 @@ async function preflightPlanFiles(plan, planFile = null) {
1287
1334
  if (path === "log" && operation.type !== "append") throw new OutlineKbError("LOG_APPEND_ONLY", "Plan may only append log");
1288
1335
  if (operation.type === "append" && path !== "log") throw new OutlineKbError("INVALID_PLAN", "append is only supported for log");
1289
1336
  const content = await readPlanContent(operation.contentFile, planFile, operation, index);
1290
- if (operation.type !== "append") assertDocument(path, content);
1337
+ if (operation.type !== "append" && options.validateMetadata !== false) assertDocument(path, content);
1291
1338
  operations.push({ ...operation, path, content });
1292
1339
  }
1293
1340
  return { jobId: plan.jobId, sourceHash: plan.sourceHash ?? null, operations };
@@ -1342,10 +1389,31 @@ async function run(argv, env = process.env) {
1342
1389
  if (env.OUTLINE_KB_READ_ONLY === "1" && WRITE_COMMANDS.has(command)) {
1343
1390
  throw new OutlineKbError("READ_ONLY", `outline-kb ${command} is disabled for a read-only knowledge query`);
1344
1391
  }
1392
+ if (env.OUTLINE_KB_INGEST_MODE === "1" && INGEST_FORBIDDEN_COMMANDS.has(command)) {
1393
+ throw new OutlineKbError("INGEST_COMMAND_DISABLED", `outline-kb ${command} is disabled until the ingest Candidate is approved`);
1394
+ }
1395
+ if (command === "validate-maintenance-plan" && env.OUTLINE_KB_MAINTENANCE_MODE !== "1") {
1396
+ throw new OutlineKbError("MAINTENANCE_MODE_REQUIRED", "validate-maintenance-plan is only available to a system-owned knowledge maintenance session");
1397
+ }
1398
+ if (env.OUTLINE_KB_MAINTENANCE_MODE === "1" && !MAINTENANCE_ALLOWED_COMMANDS.has(command)) {
1399
+ throw new OutlineKbError("MAINTENANCE_COMMAND_DISABLED", `outline-kb ${command ?? "<missing>"} is disabled for a knowledge maintenance Candidate session`);
1400
+ }
1345
1401
  let planInput = null;
1346
- if (command === "validate-plan" || command === "apply") {
1402
+ if (command === "validate-plan" || command === "validate-maintenance-plan" || command === "apply") {
1347
1403
  const loaded = await readPlanFile(need(positional[0], `${command} requires a JSON plan file`));
1348
- planInput = { ...loaded, preflight: await preflightPlanFiles(loaded.plan, loaded.path) };
1404
+ const systemManagedMetadata = command === "validate-maintenance-plan" || (command === "validate-plan"
1405
+ && env.OUTLINE_KB_INGEST_MODE === "1"
1406
+ && env.OASIS_KNOWLEDGE_SYSTEM_METADATA === "1");
1407
+ planInput = {
1408
+ ...loaded,
1409
+ preflight: await preflightPlanFiles(loaded.plan, loaded.path, {
1410
+ validateMetadata: !systemManagedMetadata,
1411
+ ...(command === "validate-maintenance-plan" ? { allowedOperationTypes: new Set(["create", "update"]) } : {}),
1412
+ }),
1413
+ };
1414
+ }
1415
+ if (command === "validate-maintenance-plan") {
1416
+ return { ok: true, ...planInput.preflight };
1349
1417
  }
1350
1418
  const client = new OutlineApiClient({ baseUrl: need(env.OUTLINE_BASE_URL, "OUTLINE_BASE_URL is required"), apiToken: need(env.OUTLINE_API_TOKEN, "OUTLINE_API_TOKEN is required") });
1351
1419
  if (command === "status") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test",
3
- "version": "0.1.128",
3
+ "version": "0.1.129",
4
4
  "description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
5
5
  "bin": {
6
6
  "oasis": "./dist/index.js"
@@ -26,6 +26,6 @@
26
26
  "node": ">=20"
27
27
  },
28
28
  "oasisRelease": {
29
- "sourceHead": "3c7ad5a874aa215ba7cee828b8cab4ab0a499e87"
29
+ "sourceHead": "d5063bab558443d535d4c4370a53ed4e169e445e"
30
30
  }
31
31
  }