@openparachute/vault 0.7.0-rc.5 → 0.7.0-rc.6

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/src/vault.test.ts CHANGED
@@ -979,7 +979,7 @@ describe("scoped MCP wrapper", async () => {
979
979
  closeAllStores();
980
980
  });
981
981
 
982
- test("create-note with schema tag auto-populates defaults", async () => {
982
+ test("create-note with schema tag applies explicit defaults; undeclared fields stay absent (vault#553 Decision B)", async () => {
983
983
  const { generateScopedMcpTools } = await import("./mcp-tools.ts");
984
984
  const { writeVaultConfig } = await import("./config.ts");
985
985
  const { getVaultStore, closeAllStores } = await import("./vault-store.ts");
@@ -995,8 +995,8 @@ describe("scoped MCP wrapper", async () => {
995
995
  await vaultStore.upsertTagSchema("person", {
996
996
  description: "A person",
997
997
  fields: {
998
- first_appeared: { type: "string", description: "When" },
999
- relationship: { type: "string", description: "How" },
998
+ first_appeared: { type: "string", description: "When", default: "unknown" },
999
+ relationship: { type: "string", description: "How" }, // no default — stays absent
1000
1000
  },
1001
1001
  });
1002
1002
 
@@ -1004,19 +1004,19 @@ describe("scoped MCP wrapper", async () => {
1004
1004
  const createNote = tools.find((t) => t.name === "create-note")!;
1005
1005
  const queryNotes = tools.find((t) => t.name === "query-notes")!;
1006
1006
 
1007
- // Create a note tagged person with no metadata — defaults auto-populated
1007
+ // Create a note tagged person with no metadata — the EXPLICIT default
1008
+ // auto-populates; the field with no `default` stays absent.
1008
1009
  const result = await createNote.execute({
1009
1010
  content: "Alice",
1010
1011
  tags: ["person"],
1011
1012
  }) as any;
1012
1013
  expect(result.content).toBe("Alice");
1013
1014
 
1014
- // Verify defaults were written
1015
1015
  const fresh = await queryNotes.execute({ id: result.id }) as any;
1016
- expect(fresh.metadata.first_appeared).toBe("");
1017
- expect(fresh.metadata.relationship).toBe("");
1016
+ expect(fresh.metadata.first_appeared).toBe("unknown");
1017
+ expect(fresh.metadata.relationship).toBeUndefined();
1018
1018
 
1019
- // Create with explicit metadata — preserved
1019
+ // Create with explicit metadata — preserved (overrides the default).
1020
1020
  const result2 = await createNote.execute({
1021
1021
  content: "Bob",
1022
1022
  tags: ["person"],
@@ -1029,7 +1029,7 @@ describe("scoped MCP wrapper", async () => {
1029
1029
  closeAllStores();
1030
1030
  });
1031
1031
 
1032
- test("update-note tags.add with schema auto-populates defaults", async () => {
1032
+ test("update-note tags.add with schema applies explicit defaults; undeclared fields stay absent (vault#553 Decision B)", async () => {
1033
1033
  const { generateScopedMcpTools } = await import("./mcp-tools.ts");
1034
1034
  const { writeVaultConfig } = await import("./config.ts");
1035
1035
  const { getVaultStore, closeAllStores: close } = await import("./vault-store.ts");
@@ -1045,16 +1045,16 @@ describe("scoped MCP wrapper", async () => {
1045
1045
  await vaultStore.upsertTagSchema("person", {
1046
1046
  description: "A person",
1047
1047
  fields: {
1048
- first_appeared: { type: "string", description: "When" },
1049
- relationship: { type: "string", description: "How" },
1048
+ first_appeared: { type: "string", description: "When", default: "unknown" },
1049
+ relationship: { type: "string", description: "How" }, // no default — stays absent
1050
1050
  },
1051
1051
  });
1052
1052
  await vaultStore.upsertTagSchema("project", {
1053
1053
  description: "A project",
1054
1054
  fields: {
1055
- status: { type: "string", enum: ["active", "completed", "abandoned"], description: "Status" },
1056
- active: { type: "boolean", description: "Is active" },
1057
- priority: { type: "integer", description: "Priority level" },
1055
+ status: { type: "string", enum: ["active", "completed", "abandoned"], description: "Status", default: "active" },
1056
+ active: { type: "boolean", description: "Is active", default: false },
1057
+ priority: { type: "integer", description: "Priority level" }, // no default — stays absent
1058
1058
  },
1059
1059
  });
1060
1060
  const tools = generateScopedMcpTools(vaultName);
@@ -1066,10 +1066,11 @@ describe("scoped MCP wrapper", async () => {
1066
1066
  const note = await createNote.execute({ content: "Alice" }) as any;
1067
1067
  await updateNote.execute({ id: note.id, tags: { add: ["person"] }, force: true });
1068
1068
  const after = await queryNotes.execute({ id: note.id }) as any;
1069
- expect(after.metadata.first_appeared).toBe("");
1070
- expect(after.metadata.relationship).toBe("");
1069
+ expect(after.metadata.first_appeared).toBe("unknown");
1070
+ expect(after.metadata.relationship).toBeUndefined();
1071
1071
 
1072
- // Tag note that already has partial metadata — only missing fields populated
1072
+ // Tag note that already has partial metadata — only missing fields WITH a
1073
+ // declared default get populated; the field with no default stays absent.
1073
1074
  const note2 = await createNote.execute({
1074
1075
  content: "Bob",
1075
1076
  metadata: { first_appeared: "2023-11" },
@@ -1077,22 +1078,22 @@ describe("scoped MCP wrapper", async () => {
1077
1078
  await updateNote.execute({ id: note2.id, tags: { add: ["person"] }, force: true });
1078
1079
  const after2 = await queryNotes.execute({ id: note2.id }) as any;
1079
1080
  expect(after2.metadata.first_appeared).toBe("2023-11"); // preserved
1080
- expect(after2.metadata.relationship).toBe(""); // added
1081
+ expect(after2.metadata.relationship).toBeUndefined(); // no default — stays absent
1081
1082
 
1082
- // Tag with #project — enum defaults to first value, boolean to false, integer to 0
1083
+ // Tag with #project — declared defaults land; `priority` (no default) stays absent.
1083
1084
  const note4 = await createNote.execute({ content: "My Project" }) as any;
1084
1085
  await updateNote.execute({ id: note4.id, tags: { add: ["project"] }, force: true });
1085
1086
  const after4 = await queryNotes.execute({ id: note4.id }) as any;
1086
1087
  expect(after4.metadata.status).toBe("active");
1087
1088
  expect(after4.metadata.active).toBe(false);
1088
- expect(after4.metadata.priority).toBe(0);
1089
+ expect(after4.metadata.priority).toBeUndefined();
1089
1090
 
1090
- // Multiple schema tags at once — all defaults merged
1091
+ // Multiple schema tags at once — all EXPLICIT defaults merged.
1091
1092
  const note5 = await createNote.execute({ content: "Multi" }) as any;
1092
1093
  await updateNote.execute({ id: note5.id, tags: { add: ["person", "project"] }, force: true });
1093
1094
  const after5 = await queryNotes.execute({ id: note5.id }) as any;
1094
- expect(after5.metadata.first_appeared).toBe("");
1095
- expect(after5.metadata.relationship).toBe("");
1095
+ expect(after5.metadata.first_appeared).toBe("unknown");
1096
+ expect(after5.metadata.relationship).toBeUndefined();
1096
1097
  expect(after5.metadata.status).toBe("active");
1097
1098
  expect(after5.metadata.active).toBe(false);
1098
1099
 
@@ -1566,9 +1567,12 @@ describe("scoped MCP wrapper", async () => {
1566
1567
  });
1567
1568
 
1568
1569
  const vaultStore = getVaultStore(vaultName);
1570
+ // vault#553 Decision B: backfill is explicit-`default`-only — declare
1571
+ // one so this test still exercises an actual defaults-write (and can
1572
+ // assert it lands without bumping updatedAt).
1569
1573
  await vaultStore.upsertTagSchema("person", {
1570
1574
  description: "A person",
1571
- fields: { name: { type: "string" } },
1575
+ fields: { name: { type: "string", default: "unknown" } },
1572
1576
  });
1573
1577
 
1574
1578
  const tools = generateScopedMcpTools(vaultName);
@@ -1581,7 +1585,7 @@ describe("scoped MCP wrapper", async () => {
1581
1585
  await updateNote.execute({ id: note.id, tags: { add: ["person"] }, force: true });
1582
1586
  const after = await queryNotes.execute({ id: note.id }) as any;
1583
1587
  expect(after.updatedAt).toBe(originalUpdatedAt);
1584
- expect(after.metadata.name).toBe("");
1588
+ expect(after.metadata.name).toBe("unknown");
1585
1589
 
1586
1590
  close();
1587
1591
  });
@@ -2199,8 +2203,10 @@ describe("HTTP /notes", async () => {
2199
2203
  // mapped over the pre-defaults in-memory objects, so default-filled
2200
2204
  // metadata was missing from `POST /api/notes` responses.
2201
2205
  test("POST /notes response reflects post-applySchemaDefaults state (vault#316)", async () => {
2206
+ // vault#553 Decision B: backfill is explicit-`default`-only — declare one
2207
+ // so this test still exercises the post-defaults re-read mechanism.
2202
2208
  await store.upsertTagSchema("task", {
2203
- fields: { priority: { type: "string", enum: ["high", "low"] } },
2209
+ fields: { priority: { type: "string", enum: ["high", "low"], default: "high" } },
2204
2210
  });
2205
2211
 
2206
2212
  // Single: default lands in the returned metadata and agrees with disk.
@@ -2211,7 +2217,7 @@ describe("HTTP /notes", async () => {
2211
2217
  );
2212
2218
  expect(single.status).toBe(201);
2213
2219
  const singleBody = await single.json() as any;
2214
- expect(singleBody.metadata?.priority).toBe("high"); // first enum value
2220
+ expect(singleBody.metadata?.priority).toBe("high"); // explicit schema default
2215
2221
  const onDisk = await store.getNoteByPath("Inbox/task-1");
2216
2222
  expect((onDisk!.metadata as any)?.priority).toBe("high");
2217
2223