@schift-io/knowledge-scope 0.1.0 → 0.2.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/dist/index.js CHANGED
@@ -4199,6 +4199,10 @@ var SearchProviderSchema = exports_external.object({
4199
4199
  kind: exports_external.literal("schift_search"),
4200
4200
  indexRef: KnowledgeScopeIdentifierSchema
4201
4201
  }).strict();
4202
+ var LocalDocumentsProviderSchema = exports_external.object({
4203
+ kind: exports_external.literal("local_documents"),
4204
+ indexRef: KnowledgeScopeIdentifierSchema
4205
+ }).strict();
4202
4206
  var WebProviderSchema = exports_external.object({
4203
4207
  kind: exports_external.literal("web_search"),
4204
4208
  provider: exports_external.enum(["customer", "schift"])
@@ -4207,6 +4211,7 @@ var QueryProviderSchema = exports_external.discriminatedUnion("kind", [
4207
4211
  RecordsProviderSchema,
4208
4212
  ConnectorProviderSchema,
4209
4213
  SearchProviderSchema,
4214
+ LocalDocumentsProviderSchema,
4210
4215
  WebProviderSchema
4211
4216
  ]).readonly();
4212
4217
  var QueryCapabilitySchema = exports_external.object({
@@ -4315,6 +4320,7 @@ var CandidateProviderEvidenceSchema = exports_external.discriminatedUnion("kind"
4315
4320
  auditPersisted: exports_external.boolean()
4316
4321
  }).strict(),
4317
4322
  SearchProviderSchema,
4323
+ LocalDocumentsProviderSchema,
4318
4324
  WebProviderSchema
4319
4325
  ]).readonly();
4320
4326
  var CandidateEnvelopeSchema = exports_external.object({
@@ -4416,6 +4422,7 @@ var bindingMatchesCapability = (binding, capability) => {
4416
4422
  case "open_connector_action":
4417
4423
  return binding.providerRef === capability.provider.actionId && binding.connectorRef === capability.provider.connectorRef;
4418
4424
  case "schift_search":
4425
+ case "local_documents":
4419
4426
  return binding.providerRef === capability.provider.indexRef && binding.connectorRef === undefined;
4420
4427
  case "web_search":
4421
4428
  return binding.providerRef === capability.provider.provider && binding.connectorRef === undefined;
@@ -4559,6 +4566,10 @@ var ProviderEvidenceSchema = exports_external.discriminatedUnion("kind", [
4559
4566
  kind: exports_external.literal("schift_search"),
4560
4567
  indexRef: KnowledgeScopeIdentifierSchema
4561
4568
  }).strict(),
4569
+ exports_external.object({
4570
+ kind: exports_external.literal("local_documents"),
4571
+ indexRef: KnowledgeScopeIdentifierSchema
4572
+ }).strict(),
4562
4573
  exports_external.object({
4563
4574
  kind: exports_external.literal("web_search"),
4564
4575
  provider: exports_external.enum(["customer", "schift"])
@@ -4688,6 +4699,8 @@ var providerEvidenceDenial = (capability, binding, candidate) => {
4688
4699
  return evidence.auditPersisted ? undefined : "connector_audit_missing";
4689
4700
  case "schift_search":
4690
4701
  return evidence.kind === "schift_search" && evidence.indexRef === capability.provider.indexRef ? undefined : "provider_evidence_mismatch";
4702
+ case "local_documents":
4703
+ return evidence.kind === "local_documents" && evidence.indexRef === capability.provider.indexRef ? undefined : "provider_evidence_mismatch";
4691
4704
  case "web_search":
4692
4705
  return evidence.kind === "web_search" && evidence.provider === capability.provider.provider ? undefined : "provider_evidence_mismatch";
4693
4706
  }
@@ -5595,21 +5608,170 @@ var createHttpProviderExecutionPort = (options) => ({
5595
5608
  return parsed.data;
5596
5609
  });
5597
5610
  }
5611
+ case "local_documents":
5598
5612
  case "web_search":
5599
5613
  throw new HttpProviderAdapterError("invalid_configuration");
5600
5614
  }
5601
5615
  }
5602
5616
  });
5603
5617
 
5618
+ // src/local-documents/files.ts
5619
+ import { constants } from "node:fs";
5620
+ import { lstat, open, opendir, realpath } from "node:fs/promises";
5621
+ import { dirname, extname, join, relative, resolve, sep } from "node:path";
5622
+ class LocalDocumentError extends Error {
5623
+ code;
5624
+ name = "LocalDocumentError";
5625
+ constructor(code) {
5626
+ super({
5627
+ local_source_invalid: "Select regular UTF-8 Markdown (.md) or text (.txt) files; symlinks, hardlinks and binary files are unsupported.",
5628
+ local_limit_exceeded: "Local documents exceed the supported file, byte, or chunk limits.",
5629
+ local_snapshot_invalid: "Local document snapshot is missing, unsafe, or corrupt; ingest the source again.",
5630
+ local_scope_invalid: "Local documents require the mounted tenant and document binding; filters and narrowed scopes are unsupported."
5631
+ }[code]);
5632
+ this.code = code;
5633
+ }
5634
+ }
5635
+ var LOCAL_LIMITS = { files: 100, fileBytes: 1048576, totalBytes: 8388608, snapshotBytes: 16777216, chunks: 4000, entries: 2000, depth: 16, chunkCharacters: 2000 };
5636
+ var identityMatches = (left, right) => left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs && right.nlink === 1;
5637
+ var checkDirectories = async (directories) => {
5638
+ for (const directory of directories) {
5639
+ const current = await lstat(directory.path);
5640
+ if (!current.isDirectory() || current.isSymbolicLink() || current.dev !== directory.dev || current.ino !== directory.ino || await realpath(directory.path) !== directory.path)
5641
+ throw new LocalDocumentError("local_source_invalid");
5642
+ }
5643
+ };
5644
+ var mapLocalFileError = (error, ownerOnly) => {
5645
+ if (error instanceof Error && "code" in error && typeof error.code === "string" && ["ENOENT", "ELOOP", "EACCES", "EPERM", "ENOTDIR", "EISDIR"].includes(error.code)) {
5646
+ throw new LocalDocumentError(ownerOnly ? "local_snapshot_invalid" : "local_source_invalid");
5647
+ }
5648
+ throw error;
5649
+ };
5650
+ var readBounded = async (source, maxBytes, ownerOnly = false) => {
5651
+ try {
5652
+ return await readFileContents(source, maxBytes, ownerOnly);
5653
+ } catch (error) {
5654
+ return mapLocalFileError(error, ownerOnly);
5655
+ }
5656
+ };
5657
+ var readFileContents = async (source, maxBytes, ownerOnly) => {
5658
+ if (!constants.O_NOFOLLOW)
5659
+ throw new LocalDocumentError("local_source_invalid");
5660
+ const path = typeof source === "string" ? source : source.path;
5661
+ if (typeof source !== "string") {
5662
+ await checkDirectories(source.ancestors);
5663
+ const currentPath = await realpath(path);
5664
+ const inside = relative(source.root, currentPath);
5665
+ if (currentPath !== path || inside === ".." || inside.startsWith(`..${sep}`))
5666
+ throw new LocalDocumentError("local_source_invalid");
5667
+ }
5668
+ const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
5669
+ try {
5670
+ const before = await handle.stat();
5671
+ if (typeof source !== "string") {
5672
+ if (!identityMatches(source.identity, before))
5673
+ throw new LocalDocumentError("local_source_invalid");
5674
+ await checkDirectories(source.ancestors);
5675
+ }
5676
+ if (!before.isFile() || ownerOnly && ((before.mode & 63) !== 0 || process.getuid !== undefined && before.uid !== process.getuid()))
5677
+ throw new LocalDocumentError("local_snapshot_invalid");
5678
+ if (before.size > maxBytes)
5679
+ throw new LocalDocumentError("local_limit_exceeded");
5680
+ const buffer = Buffer.alloc(maxBytes + 1);
5681
+ let length = 0;
5682
+ while (length < buffer.length) {
5683
+ const { bytesRead } = await handle.read(buffer, length, buffer.length - length, length);
5684
+ if (bytesRead === 0)
5685
+ break;
5686
+ length += bytesRead;
5687
+ }
5688
+ const after = await handle.stat();
5689
+ if (length > maxBytes)
5690
+ throw new LocalDocumentError("local_limit_exceeded");
5691
+ if (before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs || typeof source !== "string" && !identityMatches(source.identity, after))
5692
+ throw new LocalDocumentError("local_source_invalid");
5693
+ if (typeof source !== "string")
5694
+ await checkDirectories(source.ancestors);
5695
+ const text = decodeUtf8Strict(buffer.subarray(0, length));
5696
+ if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/u.test(text))
5697
+ throw new LocalDocumentError("local_source_invalid");
5698
+ return text;
5699
+ } finally {
5700
+ await handle.close();
5701
+ }
5702
+ };
5703
+ var collectFiles = async (source) => {
5704
+ try {
5705
+ return await collectSourceFiles(source);
5706
+ } catch (error) {
5707
+ return mapLocalFileError(error, false);
5708
+ }
5709
+ };
5710
+ var collectSourceFiles = async (source) => {
5711
+ const selected = resolve(source);
5712
+ const selectedMetadata = await lstat(selected);
5713
+ if (selectedMetadata.isSymbolicLink())
5714
+ throw new LocalDocumentError("local_source_invalid");
5715
+ const root = await realpath(selected);
5716
+ const rootMetadata = await lstat(root);
5717
+ if (selectedMetadata.dev !== rootMetadata.dev || selectedMetadata.ino !== rootMetadata.ino)
5718
+ throw new LocalDocumentError("local_source_invalid");
5719
+ const boundary = rootMetadata.isDirectory() ? root : dirname(root);
5720
+ const parentMetadata = await lstat(dirname(boundary));
5721
+ const parents = [{ path: dirname(boundary), dev: parentMetadata.dev, ino: parentMetadata.ino }];
5722
+ const files = [];
5723
+ let entries = 1;
5724
+ const visit = async (path, depth, ancestors) => {
5725
+ await checkDirectories(ancestors);
5726
+ if (entries > LOCAL_LIMITS.entries || depth > LOCAL_LIMITS.depth)
5727
+ throw new LocalDocumentError("local_limit_exceeded");
5728
+ const metadata = await lstat(path);
5729
+ if (metadata.isSymbolicLink())
5730
+ throw new LocalDocumentError("local_source_invalid");
5731
+ if (metadata.isDirectory()) {
5732
+ const directories = [...ancestors, { path, dev: metadata.dev, ino: metadata.ino }];
5733
+ await checkDirectories(directories);
5734
+ const directory = await opendir(path);
5735
+ for await (const child of directory) {
5736
+ entries += 1;
5737
+ if (entries > LOCAL_LIMITS.entries)
5738
+ throw new LocalDocumentError("local_limit_exceeded");
5739
+ if (child.name.startsWith(".") || child.name === "node_modules")
5740
+ continue;
5741
+ await visit(join(path, child.name), depth + 1, directories);
5742
+ }
5743
+ await checkDirectories(directories);
5744
+ return;
5745
+ }
5746
+ if (!metadata.isFile())
5747
+ throw new LocalDocumentError("local_source_invalid");
5748
+ if (![".md", ".txt"].includes(extname(path).toLowerCase())) {
5749
+ if (depth === 0)
5750
+ throw new LocalDocumentError("local_source_invalid");
5751
+ return;
5752
+ }
5753
+ if (metadata.nlink !== 1 || await realpath(path) !== path)
5754
+ throw new LocalDocumentError("local_source_invalid");
5755
+ files.push({ path, root: boundary, identity: metadata, ancestors });
5756
+ if (files.length > LOCAL_LIMITS.files)
5757
+ throw new LocalDocumentError("local_limit_exceeded");
5758
+ };
5759
+ const boundaryMetadata = await lstat(boundary);
5760
+ await visit(root, 0, rootMetadata.isDirectory() ? parents : [...parents, { path: boundary, dev: boundaryMetadata.dev, ino: boundaryMetadata.ino }]);
5761
+ if (files.length === 0)
5762
+ throw new LocalDocumentError("local_source_invalid");
5763
+ return files.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
5764
+ };
5765
+
5604
5766
  // src/application.ts
5605
5767
  import { randomUUID as randomUUID2 } from "node:crypto";
5606
5768
 
5607
5769
  // src/authorization.ts
5608
5770
  import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
5609
- import { constants } from "node:fs";
5610
- import { chmod, link, lstat, mkdir, open, rm } from "node:fs/promises";
5771
+ import { constants as constants2 } from "node:fs";
5772
+ import { chmod, link, lstat as lstat2, mkdir, open as open2, rm } from "node:fs/promises";
5611
5773
  import { homedir } from "node:os";
5612
- import { join } from "node:path";
5774
+ import { join as join2 } from "node:path";
5613
5775
  var AuthorizationSubjectSchema = CandidateEnvelopeSchema.unwrap().pick({
5614
5776
  srn: true,
5615
5777
  sourceId: true,
@@ -5652,18 +5814,18 @@ var authorizationSubjectFromCandidate = (candidate) => AuthorizationSubjectSchem
5652
5814
  });
5653
5815
  var hasCode = (value, code) => value !== null && typeof value === "object" && ("code" in value) && value.code === code;
5654
5816
  var assertOwnerOnly = async (path, directory) => {
5655
- const metadata = await lstat(path);
5817
+ const metadata = await lstat2(path);
5656
5818
  if (metadata.isSymbolicLink() || (metadata.mode & 63) !== 0 || (directory ? !metadata.isDirectory() : !metadata.isFile())) {
5657
5819
  throw productError("state_permissions_invalid");
5658
5820
  }
5659
5821
  };
5660
5822
  var readOwnerOnlyKey = async (path) => {
5661
- if (typeof constants.O_NOFOLLOW !== "number" || constants.O_NOFOLLOW === 0) {
5823
+ if (typeof constants2.O_NOFOLLOW !== "number" || constants2.O_NOFOLLOW === 0) {
5662
5824
  throw productError("state_permissions_invalid");
5663
5825
  }
5664
5826
  let handle;
5665
5827
  try {
5666
- handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
5828
+ handle = await open2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
5667
5829
  } catch (error) {
5668
5830
  if (hasCode(error, "ELOOP"))
5669
5831
  throw productError("state_permissions_invalid");
@@ -5688,8 +5850,8 @@ class KnowledgeScopeAuthorization {
5688
5850
  faults;
5689
5851
  constructor(options = {}) {
5690
5852
  const environment = options.environment ?? process.env;
5691
- this.home = options.home ?? environment["SCHIFT_KS_HOME"] ?? join(homedir(), ".schift", "knowledge-scope");
5692
- this.keyPath = join(this.home, "authorization.key");
5853
+ this.home = options.home ?? environment["SCHIFT_KS_HOME"] ?? join2(homedir(), ".schift", "knowledge-scope");
5854
+ this.keyPath = join2(this.home, "authorization.key");
5693
5855
  this.faults = options.faults;
5694
5856
  }
5695
5857
  async issue(subject) {
@@ -5727,8 +5889,8 @@ class KnowledgeScopeAuthorization {
5727
5889
  return key;
5728
5890
  }
5729
5891
  async installKeyIfAbsent() {
5730
- const temporaryPath = join(this.home, `.authorization-key-${process.pid}-${randomUUID()}.tmp`);
5731
- const handle = await open(temporaryPath, "wx", 384);
5892
+ const temporaryPath = join2(this.home, `.authorization-key-${process.pid}-${randomUUID()}.tmp`);
5893
+ const handle = await open2(temporaryPath, "wx", 384);
5732
5894
  try {
5733
5895
  await handle.writeFile(randomBytes(32));
5734
5896
  await handle.sync();
@@ -5741,7 +5903,7 @@ class KnowledgeScopeAuthorization {
5741
5903
  throw error;
5742
5904
  return;
5743
5905
  }
5744
- const directory = await open(this.home, "r");
5906
+ const directory = await open2(this.home, "r");
5745
5907
  try {
5746
5908
  await directory.sync();
5747
5909
  } finally {
@@ -6108,6 +6270,8 @@ var providerReference = (capability) => {
6108
6270
  return capability.provider.actionId;
6109
6271
  case "schift_search":
6110
6272
  return capability.provider.indexRef;
6273
+ case "local_documents":
6274
+ return capability.provider.indexRef;
6111
6275
  case "web_search":
6112
6276
  return capability.provider.provider;
6113
6277
  }
@@ -6454,6 +6618,8 @@ var createKnowledgeScopeApi = (options) => {
6454
6618
  return errorResponse(error.code, error.status);
6455
6619
  if (error instanceof HttpProviderAdapterError)
6456
6620
  return errorResponse(error.code, 502);
6621
+ if (error instanceof LocalDocumentError)
6622
+ return errorResponse(error.code, error.code === "local_snapshot_invalid" ? 503 : 400);
6457
6623
  if (error instanceof KnowledgeScopeProductError)
6458
6624
  return errorResponse(error.code, 400);
6459
6625
  if (error instanceof SyntaxError || error instanceof URIError)
@@ -6513,11 +6679,11 @@ var serveKnowledgeScopeApi = async (options) => {
6513
6679
  writeNodeResponse(outgoing, errorResponse(code, status));
6514
6680
  });
6515
6681
  });
6516
- await new Promise((resolve, reject) => {
6682
+ await new Promise((resolve2, reject) => {
6517
6683
  server.once("error", reject);
6518
6684
  server.listen(options.port ?? 8787, host, () => {
6519
6685
  server.off("error", reject);
6520
- resolve();
6686
+ resolve2();
6521
6687
  });
6522
6688
  });
6523
6689
  const address = server.address();
@@ -6594,8 +6760,8 @@ var createRemoteKnowledgeScopeApplication = (apiUrl, request = globalThis.fetch,
6594
6760
  };
6595
6761
  var isJsonObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
6596
6762
  // src/cli.ts
6597
- import { mkdir as mkdir3, open as open2 } from "node:fs/promises";
6598
- import { basename, resolve as resolve2 } from "node:path";
6763
+ import { mkdir as mkdir5, open as open4 } from "node:fs/promises";
6764
+ import { basename as basename2, resolve as resolve4 } from "node:path";
6599
6765
 
6600
6766
  // src/cli-options.ts
6601
6767
  class CliUsageError extends Error {
@@ -6612,12 +6778,13 @@ var specifications = {
6612
6778
  lock: { count: 1, values: [] },
6613
6779
  mount: { count: 1, values: ["--bindings", "--api-url"] },
6614
6780
  inspect: { count: 1, values: ["--api-url"] },
6781
+ query: { count: 1, values: ["--query", "--api-url"] },
6615
6782
  run: { count: 2, values: ["--input", "--api-url"] },
6616
6783
  "run-batch": { count: 1, values: ["--input", "--api-url"] },
6617
6784
  admit: { count: 1, values: ["--candidate", "--api-url"] },
6618
6785
  unmount: { count: 1, values: ["--expected-revision", "--api-url"] },
6619
6786
  serve: { count: 0, values: ["--host", "--port"] },
6620
- quickstart: { count: 1, values: ["--index", "--tenant", "--query"] },
6787
+ quickstart: { count: 1, values: ["--source", "--index", "--tenant", "--query"] },
6621
6788
  doctor: { count: 1, values: ["--query"], flags: ["--probe"] }
6622
6789
  };
6623
6790
  var parseCliOptions = (command, args) => {
@@ -6652,12 +6819,14 @@ var parseCliOptions = (command, args) => {
6652
6819
  throw new CliUsageError("argument_invalid");
6653
6820
  if (command === "doctor" && seen.has("--probe") !== seen.has("--query"))
6654
6821
  throw new CliUsageError("argument_invalid");
6822
+ if (command === "quickstart" && seen.has("--source") && seen.has("--index"))
6823
+ throw new CliUsageError("argument_invalid");
6655
6824
  return positional;
6656
6825
  };
6657
6826
 
6658
6827
  // src/quickstart.ts
6659
6828
  import { mkdir as mkdir2, writeFile } from "node:fs/promises";
6660
- import { join as join2, resolve } from "node:path";
6829
+ import { join as join3, resolve as resolve2 } from "node:path";
6661
6830
 
6662
6831
  // src/onboarding-config.ts
6663
6832
  class OnboardingError extends Error {
@@ -6704,7 +6873,7 @@ var quickstart = async (request, dependencies, environment) => {
6704
6873
  });
6705
6874
  if (!definition.success)
6706
6875
  throw new OnboardingError("argument_invalid");
6707
- const directory = resolve(request.directory);
6876
+ const directory = resolve2(request.directory);
6708
6877
  try {
6709
6878
  await mkdir2(directory, { mode: 448 });
6710
6879
  } catch (error) {
@@ -6712,20 +6881,20 @@ var quickstart = async (request, dependencies, environment) => {
6712
6881
  throw new OnboardingError("directory_exists");
6713
6882
  throw error;
6714
6883
  }
6715
- const pack = join2(directory, "pack");
6716
- const bindingsPath = join2(directory, "bindings.json");
6717
- const inputPath = join2(directory, "input.json");
6884
+ const pack = join3(directory, "pack");
6885
+ const bindingsPath = join3(directory, "bindings.json");
6886
+ const inputPath = join3(directory, "input.json");
6718
6887
  let installationId;
6719
6888
  let artifactsComplete = false;
6720
6889
  const recovery = () => installationId === undefined ? ["schift-ks", "mount", pack, "--bindings", bindingsPath] : ["schift-ks", "run", installationId, "search", "--input", inputPath];
6721
6890
  try {
6722
- await mkdir2(join2(pack, "schemas"), { recursive: true, mode: 448 });
6891
+ await mkdir2(join3(pack, "schemas"), { recursive: true, mode: 448 });
6723
6892
  const bindings = { scopeAuthority: scopeAuthority.data, sourceBindings: [{ sourceId: "project-documents", sourceClass: "document", authority: "approved", permissionMode: "mirrored", providerRef: request.index, operationIds: ["search"] }] };
6724
6893
  const input = { effectiveScope: { tenant: request.tenant }, input: { query: request.query } };
6725
6894
  const files = {
6726
- [join2(pack, "scope.json")]: json(definition.data),
6727
- [join2(pack, "schemas/input.json")]: { type: "object", required: ["query"], properties: { query: { type: "string", minLength: 1, maxLength: 8192 } }, additionalProperties: false },
6728
- [join2(pack, "schemas/result.json")]: { type: "object", required: ["text"], properties: { text: { type: "string", minLength: 1 } }, additionalProperties: true },
6895
+ [join3(pack, "scope.json")]: json(definition.data),
6896
+ [join3(pack, "schemas/input.json")]: { type: "object", required: ["query"], properties: { query: { type: "string", minLength: 1, maxLength: 8192 } }, additionalProperties: false },
6897
+ [join3(pack, "schemas/result.json")]: { type: "object", required: ["text"], properties: { text: { type: "string", minLength: 1 } }, additionalProperties: true },
6729
6898
  [bindingsPath]: json(bindings),
6730
6899
  [inputPath]: input
6731
6900
  };
@@ -6736,7 +6905,7 @@ var quickstart = async (request, dependencies, environment) => {
6736
6905
  artifactsComplete = true;
6737
6906
  const mounted = KnowledgeScopeMountSchema.parse(await dependencies.embedded.mount(await dependencies.authoring.mountPayload(pack, bindingsPath)));
6738
6907
  installationId = mounted.installationId;
6739
- await writeFile(join2(directory, "installation.json"), `${JSON.stringify(mounted)}
6908
+ await writeFile(join3(directory, "installation.json"), `${JSON.stringify(mounted)}
6740
6909
  `, { flag: "wx", mode: 384 });
6741
6910
  const result = await dependencies.embedded.run({ installationId, operationId: "search", ...input });
6742
6911
  return { directory, installationId, operationId: "search", result, recovery: recovery(), status: "completed" };
@@ -6746,6 +6915,266 @@ var quickstart = async (request, dependencies, environment) => {
6746
6915
  }
6747
6916
  };
6748
6917
 
6918
+ // src/local-quickstart.ts
6919
+ import { mkdir as mkdir4, writeFile as writeFile2 } from "node:fs/promises";
6920
+ import { join as join5, resolve as resolve3 } from "node:path";
6921
+
6922
+ // src/local-documents/index.ts
6923
+ import { mkdir as mkdir3, lstat as lstat3, open as open3, link as link2, rm as rm2 } from "node:fs/promises";
6924
+ import { basename, join as join4 } from "node:path";
6925
+ import { fileURLToPath } from "node:url";
6926
+ import { homedir as homedir2 } from "node:os";
6927
+ import { randomUUID as randomUUID3 } from "node:crypto";
6928
+
6929
+ // src/local-documents/snapshot.ts
6930
+ import { createHash as createHash3 } from "node:crypto";
6931
+ import { pathToFileURL } from "node:url";
6932
+ var DocumentSchema = exports_external.object({ uri: exports_external.string().url(), text: exports_external.string().max(LOCAL_LIMITS.fileBytes), revision: exports_external.string().regex(/^sha256:[a-f0-9]{64}$/) }).strict().readonly();
6933
+ var SnapshotSchema = exports_external.object({ version: exports_external.literal(1), capturedAt: exports_external.string().datetime(), documents: exports_external.array(DocumentSchema).min(1).max(LOCAL_LIMITS.files).readonly() }).strict().readonly();
6934
+ var digest2 = (value) => createHash3("sha256").update(value).digest("hex");
6935
+ var snapshotId = (snapshot) => `local:${digest2(canonicalJson2(snapshot))}`;
6936
+ var buildSnapshot = async (source) => {
6937
+ const documents = [];
6938
+ let bytes = 0;
6939
+ for (const file of await collectFiles(source)) {
6940
+ const text = await readBounded(file, LOCAL_LIMITS.fileBytes);
6941
+ bytes += Buffer.byteLength(text);
6942
+ if (bytes > LOCAL_LIMITS.totalBytes)
6943
+ throw new LocalDocumentError("local_limit_exceeded");
6944
+ documents.push({ uri: pathToFileURL(file.path).href, text, revision: `sha256:${digest2(text)}` });
6945
+ }
6946
+ return { version: 1, capturedAt: new Date().toISOString(), documents };
6947
+ };
6948
+ var chunksFor = (snapshot) => {
6949
+ const chunks = [];
6950
+ let bytes = 0;
6951
+ for (const document of snapshot.documents) {
6952
+ const uri = new URL(document.uri);
6953
+ if (uri.protocol !== "file:" || uri.host !== "" || uri.search !== "" || uri.hash !== "" || document.revision !== `sha256:${digest2(document.text)}`)
6954
+ throw new LocalDocumentError("local_snapshot_invalid");
6955
+ bytes += Buffer.byteLength(document.text);
6956
+ if (bytes > LOCAL_LIMITS.totalBytes)
6957
+ throw new LocalDocumentError("local_limit_exceeded");
6958
+ const lines = document.text.split(/\r?\n/u);
6959
+ let text = "";
6960
+ let start = 1;
6961
+ let end = 1;
6962
+ const flush = () => {
6963
+ if (text.trim().length > 0)
6964
+ chunks.push({ document, text, lineStart: start, lineEnd: end, id: `chunk:${digest2(`${document.uri}:${start}:${end}:${text}`)}` });
6965
+ text = "";
6966
+ if (chunks.length > LOCAL_LIMITS.chunks)
6967
+ throw new LocalDocumentError("local_limit_exceeded");
6968
+ };
6969
+ lines.forEach((line, offset) => {
6970
+ if (line.length > LOCAL_LIMITS.chunkCharacters)
6971
+ throw new LocalDocumentError("local_limit_exceeded");
6972
+ if (text.length + line.length + 1 > LOCAL_LIMITS.chunkCharacters)
6973
+ flush();
6974
+ if (text.length === 0)
6975
+ start = offset + 1;
6976
+ text += `${text.length === 0 ? "" : `
6977
+ `}${line}`;
6978
+ end = offset + 1;
6979
+ });
6980
+ flush();
6981
+ }
6982
+ return chunks;
6983
+ };
6984
+ var lexicalTerms = (text) => {
6985
+ const words = text.normalize("NFKC").toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
6986
+ return [...new Set(words.flatMap((word) => /[가-힣]/u.test(word) && word.length > 2 ? [word, ...Array.from({ length: word.length - 1 }, (_, index) => word.slice(index, index + 2))] : [word]))];
6987
+ };
6988
+
6989
+ // src/local-documents/index.ts
6990
+ var QuerySchema = exports_external.object({ query: exports_external.string().min(1).max(8192) }).strict();
6991
+ var assertPrivateDirectory = async (path) => {
6992
+ await mkdir3(path, { recursive: true, mode: 448 });
6993
+ const info = await lstat3(path);
6994
+ if (!info.isDirectory() || info.isSymbolicLink() || (info.mode & 63) !== 0 || process.getuid !== undefined && info.uid !== process.getuid())
6995
+ throw new LocalDocumentError("local_snapshot_invalid");
6996
+ };
6997
+
6998
+ class LocalDocumentStore {
6999
+ home;
7000
+ constructor(options = {}) {
7001
+ this.home = options.home ?? (options.environment ?? process.env)["SCHIFT_KS_HOME"] ?? join4(homedir2(), ".schift", "knowledge-scope");
7002
+ }
7003
+ async directory() {
7004
+ try {
7005
+ await assertPrivateDirectory(this.home);
7006
+ const directory = join4(this.home, "local-documents");
7007
+ await assertPrivateDirectory(directory);
7008
+ return directory;
7009
+ } catch (error) {
7010
+ return mapLocalFileError(error, true);
7011
+ }
7012
+ }
7013
+ async ingest(sourcePath) {
7014
+ const snapshot = await buildSnapshot(sourcePath);
7015
+ const chunks = chunksFor(snapshot);
7016
+ if (chunks.length === 0)
7017
+ throw new LocalDocumentError("local_source_invalid");
7018
+ const indexRef = snapshotId(snapshot);
7019
+ const directory = await this.directory();
7020
+ const temporary = join4(directory, `.ingest-${randomUUID3()}`);
7021
+ const serialized = canonicalJson2(snapshot);
7022
+ if (Buffer.byteLength(serialized) > LOCAL_LIMITS.snapshotBytes)
7023
+ throw new LocalDocumentError("local_limit_exceeded");
7024
+ const handle = await open3(temporary, "wx", 384);
7025
+ try {
7026
+ await handle.writeFile(serialized, "utf8");
7027
+ await handle.sync();
7028
+ try {
7029
+ await link2(temporary, join4(directory, `${indexRef.slice(6)}.json`));
7030
+ } catch (error) {
7031
+ if (!(error instanceof Error && ("code" in error) && error.code === "EEXIST"))
7032
+ throw error;
7033
+ }
7034
+ } finally {
7035
+ await handle.close();
7036
+ await rm2(temporary, { force: true });
7037
+ }
7038
+ return { indexRef, documentCount: snapshot.documents.length, chunkCount: chunks.length };
7039
+ }
7040
+ async read(indexRef) {
7041
+ if (!/^local:[a-f0-9]{64}$/u.test(indexRef))
7042
+ throw new LocalDocumentError("local_snapshot_invalid");
7043
+ const directory = await this.directory();
7044
+ const text = await readBounded(join4(directory, `${indexRef.slice(6)}.json`), LOCAL_LIMITS.snapshotBytes, true);
7045
+ const parsed = SnapshotSchema.safeParse(parseJsonText(text, "local snapshot", { maxBytes: LOCAL_LIMITS.snapshotBytes }));
7046
+ if (!parsed.success || snapshotId(parsed.data) !== indexRef)
7047
+ throw new LocalDocumentError("local_snapshot_invalid");
7048
+ return parsed.data;
7049
+ }
7050
+ async execute(context) {
7051
+ const { capability, binding, request, mount } = context;
7052
+ if (mount.state !== "mounted" || !mount.sourceBindings.some((entry) => canonicalJson2(entry) === canonicalJson2(binding)) || capability.provider.kind !== "local_documents" || binding.sourceClass !== "document" || binding.providerRef !== capability.provider.indexRef || binding.connectorRef !== undefined || binding.permissionMode !== "static" || !binding.operationIds.includes(capability.operationId) || request.operationId !== capability.operationId || request.installationId !== mount.installationId || request.effectiveScope.tenant !== mount.scopeAuthority.tenant || request.effectiveScope.namespace !== undefined || request.effectiveScope.subject !== undefined || request.effectiveScope.session !== undefined || (capability.requiredProviderScopes?.length ?? 0) > 0 || Object.keys(request.filters ?? {}).length > 0)
7053
+ throw new LocalDocumentError("local_scope_invalid");
7054
+ const query = QuerySchema.safeParse(request.input);
7055
+ if (!query.success || !context.validateInput(request.input).valid)
7056
+ throw new LocalDocumentError("local_source_invalid");
7057
+ const snapshot = await this.read(capability.provider.indexRef);
7058
+ const terms = lexicalTerms(query.data.query);
7059
+ const ranked = chunksFor(snapshot).map((chunk) => {
7060
+ const tokens = new Set(lexicalTerms(chunk.text));
7061
+ return { chunk, score: terms.filter((term) => tokens.has(term)).length };
7062
+ }).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.chunk.id.localeCompare(b.chunk.id));
7063
+ const results = [];
7064
+ let bytes = 0;
7065
+ for (const { chunk, score } of ranked.slice(0, Math.min(capability.limits?.maxRows ?? 8, 100))) {
7066
+ const sourcePath = fileURLToPath(chunk.document.uri);
7067
+ const documentId = `doc:${digest2(chunk.document.uri)}`;
7068
+ const payload = { text: chunk.text, sourceId: binding.sourceId, documentId, sourcePath, chunkId: chunk.id, lineStart: chunk.lineStart, lineEnd: chunk.lineEnd, score };
7069
+ if (!context.validateResult(payload).valid)
7070
+ throw new LocalDocumentError("local_source_invalid");
7071
+ const result = ProviderResultSchema.parse({
7072
+ resultId: chunk.id,
7073
+ srn: `srn:local:${chunk.id.slice(6)}`,
7074
+ revision: chunk.document.revision,
7075
+ freshness: snapshot.capturedAt,
7076
+ payload,
7077
+ citation: { uri: `schift://local-documents/${capability.provider.indexRef}/${documentId}#L${chunk.lineStart}-L${chunk.lineEnd}`, label: `${basename(sourcePath)}:L${chunk.lineStart}-L${chunk.lineEnd}` },
7078
+ providerScopes: [],
7079
+ providerEvidence: { kind: "local_documents", indexRef: capability.provider.indexRef }
7080
+ });
7081
+ bytes += Buffer.byteLength(canonicalJson2(result));
7082
+ if (bytes > Math.min(capability.limits?.maxResultBytes ?? 1048576, 8388608))
7083
+ throw new LocalDocumentError("local_limit_exceeded");
7084
+ results.push(result);
7085
+ }
7086
+ return results;
7087
+ }
7088
+ }
7089
+
7090
+ // src/local-quickstart.ts
7091
+ var localQuickstart = async (request, dependencies) => {
7092
+ const importer = dependencies.localDocuments;
7093
+ if (importer === undefined)
7094
+ throw new OnboardingError("local_import_unavailable");
7095
+ const scopeAuthority = ScopeAuthoritySchema.safeParse({ organizationId: "local-organization", tenant: request.tenant });
7096
+ if (!scopeAuthority.success || request.query.trim().length === 0 || request.query.length > 8192)
7097
+ throw new OnboardingError("argument_invalid");
7098
+ const directory = resolve3(request.directory);
7099
+ try {
7100
+ await mkdir4(directory, { mode: 448 });
7101
+ } catch (error) {
7102
+ if (error !== null && typeof error === "object" && "code" in error && error.code === "EEXIST")
7103
+ throw new OnboardingError("directory_exists", { nextAction: "Choose a new workspace directory; your existing files were not changed." });
7104
+ throw error;
7105
+ }
7106
+ const pack = join5(directory, "pack");
7107
+ const bindingsPath = join5(directory, "bindings.json");
7108
+ let installationId;
7109
+ let artifactsComplete = false;
7110
+ try {
7111
+ const imported = await importer.ingest(request.source);
7112
+ const definition = KnowledgeScopeDefinitionSchema.parse({
7113
+ packId: "local-project",
7114
+ version: "0.1.0",
7115
+ responsibility: "project-context",
7116
+ scope: { root: "tenant", descendants: ["namespace", "subject", "session"] },
7117
+ authority: { allowed: ["read", "draft"], forbidden: ["send", "approve", "mutate_source", "workflow"], precedence: ["primary", "operational", "approved", "observed", "derived"] },
7118
+ capabilities: [{ operationId: "search", provider: { kind: "local_documents", indexRef: imported.indexRef }, inputSchemaRef: "schemas/input.json", resultSchemaRef: "schemas/result.json", limits: { maxRows: 8, maxResultBytes: 131072 } }],
7119
+ contextPolicy: { mustConsider: [{ id: "project-evidence", minEvidence: 1, selector: { sourceClasses: ["document"] } }], mayConsider: [], mustNotUse: [{ id: "derived-context", selector: { authorities: ["derived"] } }] },
7120
+ evidence: { requireCitation: true, freshness: { defaultMaxAgeSeconds: 86400 }, coverageAssertions: ["project-evidence"] }
7121
+ });
7122
+ const bindings = { scopeAuthority: scopeAuthority.data, sourceBindings: [{ sourceId: "project-documents", sourceClass: "document", authority: "approved", permissionMode: "static", providerRef: imported.indexRef, operationIds: ["search"] }] };
7123
+ const input = { effectiveScope: { tenant: request.tenant }, input: { query: request.query } };
7124
+ await mkdir4(join5(pack, "schemas"), { recursive: true, mode: 448 });
7125
+ const files = {
7126
+ [join5(pack, "scope.json")]: parseJsonText(JSON.stringify(definition), "local definition"),
7127
+ [join5(pack, "schemas/input.json")]: { type: "object", required: ["query"], properties: { query: { type: "string", minLength: 1, maxLength: 8192 } }, additionalProperties: false },
7128
+ [join5(pack, "schemas/result.json")]: { type: "object", required: ["text"], properties: { text: { type: "string", minLength: 1 } }, additionalProperties: true },
7129
+ [bindingsPath]: bindings,
7130
+ [join5(directory, "input.json")]: input
7131
+ };
7132
+ for (const [path, value] of Object.entries(files))
7133
+ await writeFile2(path, `${canonicalJson2(value)}
7134
+ `, { flag: "wx", mode: 384 });
7135
+ await dependencies.authoring.lock(pack);
7136
+ artifactsComplete = true;
7137
+ const mounted = KnowledgeScopeMountSchema.parse(await dependencies.embedded.mount(await dependencies.authoring.mountPayload(pack, bindingsPath)));
7138
+ installationId = mounted.installationId;
7139
+ await writeFile2(join5(directory, "installation.json"), `${JSON.stringify(mounted)}
7140
+ `, { flag: "wx", mode: 384 });
7141
+ const result = await dependencies.embedded.run({ installationId, operationId: "search", expectedRevision: mounted.revision, ...input });
7142
+ return {
7143
+ directory,
7144
+ installationId,
7145
+ tenant: request.tenant,
7146
+ operationId: "search",
7147
+ documentCount: imported.documentCount,
7148
+ chunkCount: imported.chunkCount,
7149
+ result,
7150
+ nextQuery: ["schift-ks", "query", installationId, "--query", "Your next question"],
7151
+ status: "completed",
7152
+ storage: "local_snapshot",
7153
+ note: "Documents stay on this computer. Search uses keywords, not semantic embeddings. Re-import into a new workspace to refresh the snapshot."
7154
+ };
7155
+ } catch (error) {
7156
+ const code = error instanceof LocalDocumentError || error instanceof OnboardingError || error instanceof KnowledgeScopeProductError || error instanceof KnowledgeScopeApiError ? error.code : "local_quickstart_failed";
7157
+ const recovery = installationId !== undefined ? ["schift-ks", "query", installationId, "--query", request.query] : artifactsComplete ? ["schift-ks", "mount", pack, "--bindings", bindingsPath] : ["schift-ks", "quickstart", "<new-workspace>", "--source", "<file-or-directory>", "--query", "<question>"];
7158
+ throw new OnboardingError(code, { directory, artifactsComplete, recovery, nextAction: artifactsComplete ? "Use the recovery command to retry; your source files were not changed." : "Check that your source contains readable .md or .txt files, then retry with a new workspace. Your source files were not changed.", ...installationId === undefined ? {} : { installationId } });
7159
+ }
7160
+ };
7161
+
7162
+ // src/local-query.ts
7163
+ var queryProject = async (request, application) => {
7164
+ if (request.query.trim().length === 0 || request.query.length > 8192)
7165
+ throw new OnboardingError("argument_invalid");
7166
+ const { mount } = exports_external.object({ mount: KnowledgeScopeMountSchema }).parse(await application.inspect(request.installationId));
7167
+ if (mount.installationId !== request.installationId)
7168
+ throw new OnboardingError("installation_mismatch");
7169
+ return application.run({
7170
+ installationId: mount.installationId,
7171
+ operationId: "search",
7172
+ effectiveScope: { tenant: mount.scopeAuthority.tenant },
7173
+ expectedRevision: mount.revision,
7174
+ input: { query: request.query }
7175
+ });
7176
+ };
7177
+
6749
7178
  // src/doctor-config.ts
6750
7179
  var ActionsSchema = exports_external.array(exports_external.object({ connectorRef: exports_external.string().min(1), connectorAlias: exports_external.string().min(1), actionId: exports_external.string().min(1) }).strict()).min(1);
6751
7180
  var doctorConfiguration = (inspection, environment) => {
@@ -6754,6 +7183,10 @@ var doctorConfiguration = (inspection, environment) => {
6754
7183
  for (const capability of definition.capabilities.filter((entry) => mount.sourceBindings.some((binding) => binding.operationIds.includes(entry.operationId)))) {
6755
7184
  let scopeEnvironment;
6756
7185
  switch (capability.provider.kind) {
7186
+ case "local_documents":
7187
+ if ((capability.requiredProviderScopes?.length ?? 0) > 0)
7188
+ issues.push("local_documents_provider_scopes_unsupported");
7189
+ break;
6757
7190
  case "schift_search":
6758
7191
  issues.push(...searchConfiguration(environment));
6759
7192
  if (environment["SCHIFT_KS_SEARCH_ORGANIZATION_ID"] !== mount.scopeAuthority.organizationId)
@@ -6804,8 +7237,8 @@ var doctor = async (request, application, environment) => {
6804
7237
  throw new OnboardingError("argument_invalid");
6805
7238
  const inspection = InspectionSchema.parse(await application.inspect(request.installationId));
6806
7239
  const { definition, lock, mount } = inspection;
6807
- const digest2 = await digestKnowledgeScopeDefinition(definition);
6808
- const integrity = lock.definitionDigest === digest2 && mount.definitionDigest === digest2 && mount.installationId === request.installationId;
7240
+ const digest3 = await digestKnowledgeScopeDefinition(definition);
7241
+ const integrity = lock.definitionDigest === digest3 && mount.definitionDigest === digest3 && mount.installationId === request.installationId;
6809
7242
  const issues = doctorConfiguration(inspection, environment);
6810
7243
  const configured = integrity && mount.state === "mounted" && issues.length === 0;
6811
7244
  const report = { installationId: mount.installationId, integrity, state: mount.state, environment: [...new Set(issues)], configured };
@@ -6813,7 +7246,7 @@ var doctor = async (request, application, environment) => {
6813
7246
  return { ...report, status: configured ? "configured" : "attention_required", evidenceVerified: false };
6814
7247
  if (!configured)
6815
7248
  throw new OnboardingError("configuration_invalid", { ...report });
6816
- const capability = definition.capabilities.find((entry) => entry.provider.kind === "schift_search" && mount.sourceBindings.some((binding) => binding.operationIds.includes(entry.operationId)));
7249
+ const capability = definition.capabilities.find((entry) => (entry.provider.kind === "schift_search" || entry.provider.kind === "local_documents") && mount.sourceBindings.some((binding) => binding.operationIds.includes(entry.operationId)));
6817
7250
  if (capability === undefined)
6818
7251
  throw new OnboardingError("probe_operation_unavailable");
6819
7252
  const result = await application.run({ installationId: mount.installationId, operationId: capability.operationId, effectiveScope: { tenant: mount.scopeAuthority.tenant }, expectedRevision: mount.revision, input: { query: request.query } });
@@ -6821,10 +7254,12 @@ var doctor = async (request, application, environment) => {
6821
7254
  };
6822
7255
 
6823
7256
  // src/cli.ts
6824
- var COMMANDS = ["init", "validate", "lock", "mount", "inspect", "run", "run-batch", "admit", "unmount", "serve", "quickstart", "doctor"];
7257
+ var COMMANDS = ["init", "validate", "lock", "mount", "inspect", "run", "run-batch", "admit", "unmount", "serve", "quickstart", "query", "doctor"];
6825
7258
  var HELP = {
6826
7259
  bin: "schift-ks",
6827
7260
  commands: COMMANDS,
7261
+ start: { example: "schift-ks quickstart ./my-project --source ./notes.md --query 'What is the refund policy?'", supported: [".md", ".txt", "directory"], accountRequired: false, behavior: "Imports a local snapshot; no upload or model calls." },
7262
+ followUp: "schift-ks query <installation-id> --query 'Your next question'",
6828
7263
  serve: { apiTokenEnvironment: "SCHIFT_KS_API_TOKEN", loopbackOnly: true, tokenRequired: true }
6829
7264
  };
6830
7265
  var option = (args, name) => {
@@ -6865,7 +7300,7 @@ var candidateBatch = (value) => {
6865
7300
  throw new CliUsageError("input_invalid");
6866
7301
  };
6867
7302
  var initialDefinition = (directory) => {
6868
- const inferred = basename(resolve2(directory)).toLowerCase().replace(/[^a-z0-9._:-]+/g, "-").replace(/^-+|-+$/g, "");
7303
+ const inferred = basename2(resolve4(directory)).toLowerCase().replace(/[^a-z0-9._:-]+/g, "-").replace(/^-+|-+$/g, "");
6869
7304
  const packId = inferred.length >= 2 ? inferred.slice(0, 128) : "example-scope";
6870
7305
  return {
6871
7306
  authority: {
@@ -6891,8 +7326,8 @@ var initialDefinition = (directory) => {
6891
7326
  };
6892
7327
  };
6893
7328
  var initialize = async (directory) => {
6894
- await mkdir3(directory, { recursive: true, mode: 448 });
6895
- const file = await open2(resolve2(directory, "scope.json"), "wx", 384);
7329
+ await mkdir5(directory, { recursive: true, mode: 448 });
7330
+ const file = await open4(resolve4(directory, "scope.json"), "wx", 384);
6896
7331
  try {
6897
7332
  await file.writeFile(`${canonicalJson2(initialDefinition(directory))}
6898
7333
  `, "utf8");
@@ -6913,8 +7348,14 @@ var execute = async (argv, dependencies) => {
6913
7348
  const apiUrl = option(args, "--api-url");
6914
7349
  const application = apiUrl === undefined ? dependencies.embedded : dependencies.remote(apiUrl);
6915
7350
  switch (command) {
6916
- case "quickstart":
7351
+ case "quickstart": {
7352
+ const source = option(args, "--source");
7353
+ if (source !== undefined)
7354
+ return localQuickstart({ directory: positional[0] ?? "", source, tenant: option(args, "--tenant") ?? "local-tenant", query: requiredOption(args, "--query") }, dependencies);
6917
7355
  return quickstart({ directory: positional[0] ?? "", index: requiredOption(args, "--index"), tenant: requiredOption(args, "--tenant"), query: requiredOption(args, "--query") }, dependencies, dependencies.environment ?? {});
7356
+ }
7357
+ case "query":
7358
+ return queryProject({ installationId: positional[0] ?? "", query: requiredOption(args, "--query") }, application);
6918
7359
  case "doctor":
6919
7360
  return doctor({ installationId: positional[0] ?? "", ...args.includes("--probe") ? { query: requiredOption(args, "--query") } : {} }, application, dependencies.environment ?? {});
6920
7361
  case "init":
@@ -6995,7 +7436,7 @@ var execute = async (argv, dependencies) => {
6995
7436
  }
6996
7437
  };
6997
7438
  var errorCode = (error) => {
6998
- if (error instanceof OnboardingError || error instanceof CliUsageError || error instanceof KnowledgeScopeApiError || error instanceof KnowledgeScopeProductError || error instanceof HttpProviderAdapterError) {
7439
+ if (error instanceof LocalDocumentError || error instanceof OnboardingError || error instanceof CliUsageError || error instanceof KnowledgeScopeApiError || error instanceof KnowledgeScopeProductError || error instanceof HttpProviderAdapterError) {
6999
7440
  return error.code;
7000
7441
  }
7001
7442
  return "internal_error";
@@ -7010,16 +7451,16 @@ var runKnowledgeScopeCli = async (argv, dependencies, streams) => {
7010
7451
  }
7011
7452
  };
7012
7453
  // src/main.ts
7013
- import { constants as constants4 } from "node:fs";
7014
- import { open as open5 } from "node:fs/promises";
7454
+ import { constants as constants5 } from "node:fs";
7455
+ import { open as open7 } from "node:fs/promises";
7015
7456
 
7016
7457
  // src/portable.ts
7017
- import { constants as constants2 } from "node:fs";
7018
- import { lstat as lstat2, mkdir as mkdir4, open as open3, readdir, rename, rm as rm2 } from "node:fs/promises";
7019
- import { dirname, join as join3, relative, resolve as resolve3, sep } from "node:path";
7020
- import { randomUUID as randomUUID3 } from "node:crypto";
7458
+ import { constants as constants3 } from "node:fs";
7459
+ import { lstat as lstat4, mkdir as mkdir6, open as open5, readdir, rename, rm as rm3 } from "node:fs/promises";
7460
+ import { dirname as dirname2, join as join6, relative as relative2, resolve as resolve5, sep as sep2 } from "node:path";
7461
+ import { randomUUID as randomUUID4 } from "node:crypto";
7021
7462
  var normalizeRelativePath = (path) => {
7022
- const normalized = path.split(sep).join("/");
7463
+ const normalized = path.split(sep2).join("/");
7023
7464
  if (normalized.length === 0 || normalized.startsWith("/") || normalized.includes("\\") || normalized.split("/").some((part) => part === "" || part === "." || part === "..")) {
7024
7465
  throw productError("path_invalid");
7025
7466
  }
@@ -7027,12 +7468,12 @@ var normalizeRelativePath = (path) => {
7027
7468
  };
7028
7469
  var hasCode2 = (value, code) => value !== null && typeof value === "object" && ("code" in value) && value.code === code;
7029
7470
  var readNoFollowText = async (path, maxBytes = DEFAULT_MAX_JSON_BYTES) => {
7030
- if (typeof constants2.O_NOFOLLOW !== "number" || constants2.O_NOFOLLOW === 0) {
7471
+ if (typeof constants3.O_NOFOLLOW !== "number" || constants3.O_NOFOLLOW === 0) {
7031
7472
  throw productError("path_invalid");
7032
7473
  }
7033
7474
  let handle;
7034
7475
  try {
7035
- handle = await open3(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
7476
+ handle = await open5(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
7036
7477
  } catch (error) {
7037
7478
  if (hasCode2(error, "ELOOP"))
7038
7479
  throw productError("path_invalid");
@@ -7062,18 +7503,18 @@ var readNoFollowText = async (path, maxBytes = DEFAULT_MAX_JSON_BYTES) => {
7062
7503
  }
7063
7504
  };
7064
7505
  var walkJsonFiles = async (root, current = root) => {
7065
- const metadata = await lstat2(current);
7506
+ const metadata = await lstat4(current);
7066
7507
  if (metadata.isSymbolicLink())
7067
7508
  throw productError("path_invalid");
7068
7509
  if (!metadata.isDirectory())
7069
7510
  throw productError("path_invalid");
7070
7511
  const paths = [];
7071
7512
  for (const entry of await readdir(current, { withFileTypes: true })) {
7072
- const absolute = join3(current, entry.name);
7073
- const entryMetadata = await lstat2(absolute);
7513
+ const absolute = join6(current, entry.name);
7514
+ const entryMetadata = await lstat4(absolute);
7074
7515
  if (entryMetadata.isSymbolicLink())
7075
7516
  throw productError("path_invalid");
7076
- const path = normalizeRelativePath(relative(root, absolute));
7517
+ const path = normalizeRelativePath(relative2(root, absolute));
7077
7518
  if (entryMetadata.isDirectory()) {
7078
7519
  if (path !== ".schift")
7079
7520
  paths.push(...await walkJsonFiles(root, absolute));
@@ -7088,8 +7529,8 @@ var walkJsonFiles = async (root, current = root) => {
7088
7529
  };
7089
7530
  var readJsonFile = async (root, path) => {
7090
7531
  const normalized = normalizeRelativePath(path);
7091
- const absolute = resolve3(root, normalized);
7092
- const prefix = `${resolve3(root)}${sep}`;
7532
+ const absolute = resolve5(root, normalized);
7533
+ const prefix = `${resolve5(root)}${sep2}`;
7093
7534
  if (!absolute.startsWith(prefix))
7094
7535
  throw productError("path_invalid");
7095
7536
  const text = await readNoFollowText(absolute).catch((error) => {
@@ -7108,7 +7549,7 @@ var declaredPaths = (definition) => [
7108
7549
  ]))
7109
7550
  ].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
7110
7551
  var loadPortableScope = async (directory, options = {}) => {
7111
- const root = resolve3(directory);
7552
+ const root = resolve5(directory);
7112
7553
  const discovered = await walkJsonFiles(root);
7113
7554
  if (!discovered.includes("scope.json"))
7114
7555
  throw productError("portable_file_missing");
@@ -7128,7 +7569,7 @@ var loadPortableScope = async (directory, options = {}) => {
7128
7569
  compileSchemaSubset(files[path]);
7129
7570
  if (options.readLock === false)
7130
7571
  return { directory: root, definition: parsedDefinition.data, files };
7131
- const lockPath = join3(root, "scope.lock.json");
7572
+ const lockPath = join6(root, "scope.lock.json");
7132
7573
  try {
7133
7574
  const rawLock = parseJsonText(await readNoFollowText(lockPath), "scope.lock.json");
7134
7575
  const parsedLock = KnowledgeScopeLockSchema.safeParse(rawLock);
@@ -7145,10 +7586,10 @@ var loadPortableScope = async (directory, options = {}) => {
7145
7586
  var createPortableLock = async (directory) => {
7146
7587
  const portable = await loadPortableScope(directory, { readLock: false });
7147
7588
  const lock = await buildKnowledgeScopeLock(portable.definition, portable.files);
7148
- const lockPath = join3(portable.directory, "scope.lock.json");
7149
- const temporaryPath = join3(dirname(lockPath), `.scope-lock-${randomUUID3()}.tmp`);
7150
- await mkdir4(dirname(lockPath), { recursive: true });
7151
- const handle = await open3(temporaryPath, "wx", 420);
7589
+ const lockPath = join6(portable.directory, "scope.lock.json");
7590
+ const temporaryPath = join6(dirname2(lockPath), `.scope-lock-${randomUUID4()}.tmp`);
7591
+ await mkdir6(dirname2(lockPath), { recursive: true });
7592
+ const handle = await open5(temporaryPath, "wx", 420);
7152
7593
  try {
7153
7594
  await handle.writeFile(`${canonicalJson2(lock)}
7154
7595
  `, "utf8");
@@ -7156,7 +7597,7 @@ var createPortableLock = async (directory) => {
7156
7597
  await rename(temporaryPath, lockPath);
7157
7598
  } finally {
7158
7599
  await handle.close();
7159
- await rm2(temporaryPath, { force: true });
7600
+ await rm3(temporaryPath, { force: true });
7160
7601
  }
7161
7602
  return lock;
7162
7603
  };
@@ -7168,11 +7609,11 @@ var verifyPortableLock = async (directory) => {
7168
7609
  };
7169
7610
 
7170
7611
  // src/state-store.ts
7171
- import { constants as constants3 } from "node:fs";
7172
- import { chmod as chmod2, link as link2, lstat as lstat3, mkdir as mkdir5, open as open4, rename as rename2, rm as rm3 } from "node:fs/promises";
7173
- import { homedir as homedir2 } from "node:os";
7174
- import { dirname as dirname2, join as join4 } from "node:path";
7175
- import { randomUUID as randomUUID4 } from "node:crypto";
7612
+ import { constants as constants4 } from "node:fs";
7613
+ import { chmod as chmod2, link as link3, lstat as lstat5, mkdir as mkdir7, open as open6, rename as rename2, rm as rm4 } from "node:fs/promises";
7614
+ import { homedir as homedir3 } from "node:os";
7615
+ import { dirname as dirname3, join as join7 } from "node:path";
7616
+ import { randomUUID as randomUUID5 } from "node:crypto";
7176
7617
 
7177
7618
  // src/state-contract.ts
7178
7619
  var StateJsonValueSchema = exports_external.lazy(() => exports_external.union([
@@ -7238,21 +7679,21 @@ var hasCode3 = (value, code) => {
7238
7679
  return value.code === code;
7239
7680
  };
7240
7681
  var assertOwnerOnly2 = async (path, expectedDirectory) => {
7241
- const metadata = await lstat3(path);
7682
+ const metadata = await lstat5(path);
7242
7683
  if (metadata.isSymbolicLink() || (metadata.mode & 63) !== 0 || (expectedDirectory ? !metadata.isDirectory() : !metadata.isFile())) {
7243
7684
  throw productError("state_permissions_invalid");
7244
7685
  }
7245
7686
  };
7246
7687
  var noFollowReadFlags = () => {
7247
- if (typeof constants3.O_NOFOLLOW !== "number" || constants3.O_NOFOLLOW === 0) {
7688
+ if (typeof constants4.O_NOFOLLOW !== "number" || constants4.O_NOFOLLOW === 0) {
7248
7689
  throw productError("state_permissions_invalid");
7249
7690
  }
7250
- return constants3.O_RDONLY | constants3.O_NOFOLLOW;
7691
+ return constants4.O_RDONLY | constants4.O_NOFOLLOW;
7251
7692
  };
7252
7693
  var readOwnerOnlyFile = async (path, maxBytes) => {
7253
7694
  let handle;
7254
7695
  try {
7255
- handle = await open4(path, noFollowReadFlags());
7696
+ handle = await open6(path, noFollowReadFlags());
7256
7697
  } catch (error) {
7257
7698
  if (hasCode3(error, "ELOOP"))
7258
7699
  throw productError("state_permissions_invalid");
@@ -7290,13 +7731,13 @@ class KnowledgeScopeStateStore {
7290
7731
  faults;
7291
7732
  constructor(options = {}) {
7292
7733
  const environment = options.environment ?? process.env;
7293
- this.home = options.home ?? environment["SCHIFT_KS_HOME"] ?? join4(homedir2(), ".schift", "knowledge-scope");
7294
- this.statePath = join4(this.home, "state.json");
7295
- this.lockPath = join4(this.home, "state.lock");
7734
+ this.home = options.home ?? environment["SCHIFT_KS_HOME"] ?? join7(homedir3(), ".schift", "knowledge-scope");
7735
+ this.statePath = join7(this.home, "state.json");
7736
+ this.lockPath = join7(this.home, "state.lock");
7296
7737
  this.faults = options.faults;
7297
7738
  }
7298
7739
  async initialize() {
7299
- const created = await mkdir5(this.home, { recursive: true, mode: 448 });
7740
+ const created = await mkdir7(this.home, { recursive: true, mode: 448 });
7300
7741
  if (created === undefined)
7301
7742
  await assertOwnerOnly2(this.home, true);
7302
7743
  else
@@ -7314,21 +7755,21 @@ class KnowledgeScopeStateStore {
7314
7755
  }
7315
7756
  }
7316
7757
  async createInitialState() {
7317
- const temporaryPath = join4(dirname2(this.statePath), `.state-init-${process.pid}-${randomUUID4()}.tmp`);
7318
- const handle = await open4(temporaryPath, "wx", 384);
7758
+ const temporaryPath = join7(dirname3(this.statePath), `.state-init-${process.pid}-${randomUUID5()}.tmp`);
7759
+ const handle = await open6(temporaryPath, "wx", 384);
7319
7760
  try {
7320
7761
  await handle.writeFile(serializeState(EMPTY_KNOWLEDGE_SCOPE_STATE), "utf8");
7321
7762
  await handle.sync();
7322
7763
  if (this.faults !== undefined)
7323
7764
  await this.faults.beforeRename();
7324
7765
  try {
7325
- await link2(temporaryPath, this.statePath);
7766
+ await link3(temporaryPath, this.statePath);
7326
7767
  } catch (error) {
7327
7768
  if (!hasCode3(error, "EEXIST"))
7328
7769
  throw error;
7329
7770
  return;
7330
7771
  }
7331
- const directory = await open4(this.home, "r");
7772
+ const directory = await open6(this.home, "r");
7332
7773
  try {
7333
7774
  await directory.sync();
7334
7775
  } finally {
@@ -7336,7 +7777,7 @@ class KnowledgeScopeStateStore {
7336
7777
  }
7337
7778
  } finally {
7338
7779
  await handle.close();
7339
- await rm3(temporaryPath, { force: true });
7780
+ await rm4(temporaryPath, { force: true });
7340
7781
  }
7341
7782
  }
7342
7783
  async read() {
@@ -7368,13 +7809,13 @@ class KnowledgeScopeStateStore {
7368
7809
  return mutation.value;
7369
7810
  } finally {
7370
7811
  await lockHandle.close();
7371
- await rm3(this.lockPath, { force: true });
7812
+ await rm4(this.lockPath, { force: true });
7372
7813
  }
7373
7814
  }
7374
7815
  async acquireLock() {
7375
7816
  let handle;
7376
7817
  try {
7377
- handle = await open4(this.lockPath, "wx", 384);
7818
+ handle = await open6(this.lockPath, "wx", 384);
7378
7819
  } catch (error) {
7379
7820
  if (!hasCode3(error, "EEXIST"))
7380
7821
  throw error;
@@ -7390,28 +7831,28 @@ class KnowledgeScopeStateStore {
7390
7831
  throw productError("lock_conflict");
7391
7832
  }
7392
7833
  try {
7393
- const owner = { pid: process.pid, createdAt: Date.now(), nonce: randomUUID4() };
7834
+ const owner = { pid: process.pid, createdAt: Date.now(), nonce: randomUUID5() };
7394
7835
  await handle.writeFile(`${canonicalJson2(owner)}
7395
7836
  `, "utf8");
7396
7837
  await handle.sync();
7397
7838
  return handle;
7398
7839
  } catch (error) {
7399
7840
  await handle.close();
7400
- await rm3(this.lockPath, { force: true });
7841
+ await rm4(this.lockPath, { force: true });
7401
7842
  throw error;
7402
7843
  }
7403
7844
  }
7404
7845
  async writeAtomically(state) {
7405
7846
  const serialized = serializeState(state);
7406
- const temporaryPath = join4(dirname2(this.statePath), `.state-${process.pid}-${randomUUID4()}.tmp`);
7407
- const handle = await open4(temporaryPath, "wx", 384);
7847
+ const temporaryPath = join7(dirname3(this.statePath), `.state-${process.pid}-${randomUUID5()}.tmp`);
7848
+ const handle = await open6(temporaryPath, "wx", 384);
7408
7849
  try {
7409
7850
  await handle.writeFile(serialized, "utf8");
7410
7851
  await handle.sync();
7411
7852
  if (this.faults !== undefined)
7412
7853
  await this.faults.beforeRename();
7413
7854
  await rename2(temporaryPath, this.statePath);
7414
- const directory = await open4(this.home, "r");
7855
+ const directory = await open6(this.home, "r");
7415
7856
  try {
7416
7857
  await directory.sync();
7417
7858
  } finally {
@@ -7420,7 +7861,7 @@ class KnowledgeScopeStateStore {
7420
7861
  await chmod2(this.statePath, 384);
7421
7862
  } finally {
7422
7863
  await handle.close();
7423
- await rm3(temporaryPath, { force: true });
7864
+ await rm4(temporaryPath, { force: true });
7424
7865
  }
7425
7866
  }
7426
7867
  }
@@ -7435,11 +7876,11 @@ var toJsonValue = (value) => {
7435
7876
  var isJsonObject3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
7436
7877
  var hasCode4 = (value, code) => value !== null && typeof value === "object" && ("code" in value) && value.code === code;
7437
7878
  var readNoFollowText2 = async (path) => {
7438
- if (typeof constants4.O_NOFOLLOW !== "number" || constants4.O_NOFOLLOW === 0)
7879
+ if (typeof constants5.O_NOFOLLOW !== "number" || constants5.O_NOFOLLOW === 0)
7439
7880
  throw productError("path_invalid");
7440
7881
  let handle;
7441
7882
  try {
7442
- handle = await open5(path, constants4.O_RDONLY | constants4.O_NOFOLLOW);
7883
+ handle = await open7(path, constants5.O_RDONLY | constants5.O_NOFOLLOW);
7443
7884
  } catch (error) {
7444
7885
  if (hasCode4(error, "ELOOP"))
7445
7886
  throw productError("path_invalid");
@@ -7582,16 +8023,21 @@ var createCliDependencies = (options = {}) => {
7582
8023
  const apiToken = environment["SCHIFT_KS_API_TOKEN"];
7583
8024
  const storageOptions = { environment, ...options.home === undefined ? {} : { home: options.home } };
7584
8025
  const store = new KnowledgeScopeStateStore(storageOptions);
8026
+ const localDocuments = new LocalDocumentStore(storageOptions);
8027
+ const httpProvider = environmentProvider(environment);
7585
8028
  const authorization = new KnowledgeScopeAuthorization(storageOptions);
7586
8029
  const application = new KnowledgeScopeApplication({
7587
8030
  store,
7588
8031
  authorization,
7589
- provider: options.provider ?? environmentProvider(environment)
8032
+ provider: options.provider ?? {
8033
+ execute: (context) => context.capability.provider.kind === "local_documents" ? localDocuments.execute(context) : httpProvider.execute(context)
8034
+ }
7590
8035
  });
7591
8036
  const embedded = applicationPort(application);
7592
8037
  return {
7593
8038
  environment,
7594
8039
  authoring,
8040
+ localDocuments,
7595
8041
  embedded,
7596
8042
  remote: (apiUrl) => createRemoteKnowledgeScopeApplication(apiUrl, globalThis.fetch, apiToken),
7597
8043
  readJson,
@@ -7824,11 +8270,11 @@ var createKnowledgeScopeClient = (options) => {
7824
8270
  };
7825
8271
  };
7826
8272
  // src/evaluation.ts
7827
- import { createHash as createHash3 } from "node:crypto";
8273
+ import { createHash as createHash4 } from "node:crypto";
7828
8274
 
7829
8275
  // src/evaluation/contracts.ts
7830
8276
  var id = exports_external.string().min(1).max(256);
7831
- var digest2 = exports_external.string().regex(/^sha256:[a-f0-9]{64}$/);
8277
+ var digest3 = exports_external.string().regex(/^sha256:[a-f0-9]{64}$/);
7832
8278
  var decision = exports_external.enum(["ready", "insufficient_evidence"]);
7833
8279
  var evidence = exports_external.object({ evidenceId: id, citation: exports_external.string().min(1).max(4096) }).strict();
7834
8280
  var unique = (values) => new Set(values).size === values.length;
@@ -7836,7 +8282,7 @@ var evidenceList = exports_external.array(evidence).max(100).refine((rows) => un
7836
8282
  var capturedEvidenceList = exports_external.array(evidence.extend({ citation: exports_external.string().max(4096).nullable() })).max(100).refine((rows) => unique(rows.map((row) => row.evidenceId)), "Duplicate captured evidence IDs");
7837
8283
  var question = exports_external.object({
7838
8284
  questionId: id,
7839
- inputFingerprint: digest2,
8285
+ inputFingerprint: digest3,
7840
8286
  expectedDecision: decision,
7841
8287
  expectedEvidence: evidenceList,
7842
8288
  forbiddenEvidenceIds: exports_external.array(id).max(100).refine(unique, "Duplicate forbidden evidence IDs")
@@ -7844,18 +8290,18 @@ var question = exports_external.object({
7844
8290
  var retrievalDatasetSchema = exports_external.object({
7845
8291
  datasetId: id,
7846
8292
  provenance: exports_external.enum(["synthetic", "customer_provided"]),
7847
- packDigest: digest2,
7848
- sourceSnapshotDigest: digest2,
8293
+ packDigest: digest3,
8294
+ sourceSnapshotDigest: digest3,
7849
8295
  questions: exports_external.array(question).min(1).max(1000).refine((rows) => unique(rows.map((row) => row.questionId)), "Duplicate question IDs")
7850
8296
  }).strict();
7851
8297
  var retrievalCaptureSchema = exports_external.object({
7852
8298
  adapter: id,
7853
- datasetFingerprint: digest2,
7854
- packDigest: digest2,
7855
- sourceSnapshotDigest: digest2,
8299
+ datasetFingerprint: digest3,
8300
+ packDigest: digest3,
8301
+ sourceSnapshotDigest: digest3,
7856
8302
  results: exports_external.array(exports_external.object({
7857
8303
  questionId: id,
7858
- inputFingerprint: digest2,
8304
+ inputFingerprint: digest3,
7859
8305
  decision,
7860
8306
  items: capturedEvidenceList
7861
8307
  }).strict()).max(1000).refine((rows) => unique(rows.map((row) => row.questionId)), "Duplicate captured question IDs")
@@ -7868,7 +8314,7 @@ var retrievalEvaluationSchema = exports_external.object({
7868
8314
  var count = exports_external.number().int().min(0).max(1e5);
7869
8315
  var evaluationQuestionReportSchema = exports_external.object({
7870
8316
  questionId: id,
7871
- inputFingerprint: digest2,
8317
+ inputFingerprint: digest3,
7872
8318
  expectedEvidenceCount: count,
7873
8319
  retrievedAtKCount: count,
7874
8320
  matchedAtKCount: count,
@@ -7883,10 +8329,10 @@ var retrievalReportSchema = exports_external.object({
7883
8329
  datasetId: id,
7884
8330
  provenance: exports_external.enum(["synthetic", "customer_provided"]),
7885
8331
  adapter: id,
7886
- datasetFingerprint: digest2,
7887
- inputFingerprint: digest2,
7888
- packDigest: digest2,
7889
- sourceSnapshotDigest: digest2,
8332
+ datasetFingerprint: digest3,
8333
+ inputFingerprint: digest3,
8334
+ packDigest: digest3,
8335
+ sourceSnapshotDigest: digest3,
7890
8336
  topK: exports_external.number().int().min(1).max(100),
7891
8337
  questions: exports_external.array(evaluationQuestionReportSchema).min(1).max(1000).refine((rows) => unique(rows.map((row) => row.questionId)), "Duplicate report question IDs"),
7892
8338
  metrics: exports_external.object({
@@ -7907,7 +8353,7 @@ class RetrievalEvaluationError extends Error {
7907
8353
  }
7908
8354
 
7909
8355
  // src/evaluation.ts
7910
- var hash = (value) => `sha256:${createHash3("sha256").update(canonicalJson2(value)).digest("hex")}`;
8356
+ var hash = (value) => `sha256:${createHash4("sha256").update(canonicalJson2(value)).digest("hex")}`;
7911
8357
  var byId = (left, right) => left.questionId < right.questionId ? -1 : left.questionId > right.questionId ? 1 : 0;
7912
8358
  var ratio = (numerator, denominator) => denominator === 0 ? null : numerator / denominator;
7913
8359
  function datasetFingerprint(dataset) {
@@ -8030,6 +8476,9 @@ export {
8030
8476
  RetrievalEvaluationError,
8031
8477
  MAX_CANDIDATES,
8032
8478
  MAX_BINDINGS,
8479
+ LocalDocumentStore,
8480
+ LocalDocumentError,
8481
+ LOCAL_LIMITS,
8033
8482
  KnowledgeScopeStateStore,
8034
8483
  KnowledgeScopeProductError,
8035
8484
  KnowledgeScopeClientError,