@openparachute/vault 0.5.1 → 0.5.2-rc.2

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.
@@ -56,20 +56,60 @@ function makeWorker(opts: {
56
56
  fetchImpl: typeof fetch;
57
57
  retention?: "keep" | "until_transcribed" | "never";
58
58
  maxAttempts?: number;
59
+ // Override the store the worker sees (race-injection tests wrap `store`).
60
+ store?: Store;
61
+ logger?: { error: (...args: unknown[]) => void; info?: (...args: unknown[]) => void };
59
62
  }) {
63
+ const workerStore = opts.store ?? (store as unknown as Store);
60
64
  return startTranscriptionWorker({
61
65
  vaultList: () => ["default"],
62
- getStore: () => store as unknown as Store,
66
+ getStore: () => workerStore,
63
67
  scribeUrl: "http://scribe.test",
64
68
  resolveAssetsDir: () => assetsRoot,
65
69
  getAudioRetention: () => opts.retention ?? "keep",
66
70
  pollIntervalMs: 10_000_000, // never auto-fire; tests drive ticks manually
67
71
  maxAttempts: opts.maxAttempts ?? 3,
68
72
  fetchImpl: opts.fetchImpl,
69
- logger: silentLogger,
73
+ logger: opts.logger ?? silentLogger,
70
74
  });
71
75
  }
72
76
 
77
+ /**
78
+ * Wrap a store so the first `interfereTimes` `updateNote` calls carrying an
79
+ * `if_updated_at` precondition fire `userEdit()` (a concurrent user write that
80
+ * bumps `updated_at`) just before delegating — forcing the precondition stale
81
+ * exactly that many times. Non-OC writes (and the last-resort no-precondition
82
+ * write) pass straight through. Drives the vault#435 worker race tests.
83
+ *
84
+ * NOTE: duplicated in src/vault.test.ts (routes-layer race tests) — keep in sync.
85
+ */
86
+ function withRace(
87
+ base: Store,
88
+ interfereTimes: number,
89
+ userEdit: () => Promise<void>,
90
+ ): Store {
91
+ let fired = 0;
92
+ return new Proxy(base, {
93
+ get(target, prop, receiver) {
94
+ if (prop === "updateNote") {
95
+ return async (id: string, updates: any) => {
96
+ if (updates?.if_updated_at !== undefined && fired < interfereTimes) {
97
+ fired++;
98
+ // bun:sqlite stamps `updated_at` at ms granularity. Sleep so the
99
+ // concurrent user write lands at a strictly-greater timestamp than
100
+ // the precondition the worker captured — making the conflict
101
+ // deterministic rather than racing inside the same millisecond.
102
+ await new Promise((r) => setTimeout(r, 5));
103
+ await userEdit();
104
+ }
105
+ return (target as any).updateNote(id, updates);
106
+ };
107
+ }
108
+ return Reflect.get(target, prop, receiver);
109
+ },
110
+ }) as Store;
111
+ }
112
+
73
113
  describe("transcription worker", () => {
74
114
  test("happy path: replaces _Transcript pending._ and clears stub marker", async () => {
75
115
  const note = await store.createNote(
@@ -305,8 +345,10 @@ describe("transcription worker", () => {
305
345
  });
306
346
 
307
347
  test("FIFO: oldest pending is processed first", async () => {
308
- await store.createNote("s", { id: "f1", metadata: { transcribe_stub: true } });
309
- await store.createNote("s", { id: "f2", metadata: { transcribe_stub: true } });
348
+ // Seed the placeholder body so the transcript patches in place (the real
349
+ // capture shape); ordering is what this test verifies.
350
+ await store.createNote("_Transcript pending._", { id: "f1", metadata: { transcribe_stub: true } });
351
+ await store.createNote("_Transcript pending._", { id: "f2", metadata: { transcribe_stub: true } });
310
352
  seedAudio("memos/first.webm");
311
353
  seedAudio("memos/second.webm");
312
354
  await store.addAttachment("f1", "memos/first.webm", "audio/webm", {
@@ -513,6 +555,173 @@ describe("transcription worker", () => {
513
555
  });
514
556
  });
515
557
 
558
+ // ---------------------------------------------------------------------------
559
+ // Optimistic concurrency on the worker's note writes (vault#435).
560
+ //
561
+ // The worker patches the memo note in two read-modify-write cycles — the
562
+ // success path (replace the placeholder/marker with the transcript) and the
563
+ // failure path (`applyFailureMarker`). Without an `if_updated_at` precondition
564
+ // a user edit landing between read and write is silently clobbered (the
565
+ // static-write/stale-read class of vault#208). These tests inject a concurrent
566
+ // user PATCH right before the worker's OC write to assert:
567
+ // (a) the user's edit is NOT clobbered,
568
+ // (b) the worker RE-APPLIES the transcript/marker correctly on fresh content,
569
+ // (c) the double-conflict policy (worker = resilient): last-resort apply when
570
+ // still safe, safe-skip otherwise — never crash the sweep.
571
+ // ---------------------------------------------------------------------------
572
+ describe("transcription worker — optimistic concurrency (vault#435)", () => {
573
+ test("success patch: single race → re-applies transcript onto the user's fresh content", async () => {
574
+ // Placeholder body so the transcript would normally surgical-replace in
575
+ // place. The user appends a line mid-flight; the worker's first OC write
576
+ // conflicts, it re-reads, and the surgical replace lands on the FRESH
577
+ // content — preserving the user's append.
578
+ await store.createNote(
579
+ "# memo\n\n_Transcript pending._\n",
580
+ { id: "oc-succ-1", metadata: { transcribe_stub: true } },
581
+ );
582
+ seedAudio("memos/ocs1.webm");
583
+ await store.addAttachment("oc-succ-1", "memos/ocs1.webm", "audio/webm", {
584
+ transcribe_status: "pending",
585
+ });
586
+
587
+ const raceStore = withRace(store as unknown as Store, 1, async () => {
588
+ await store.updateNote("oc-succ-1", { append: "\n\nUSER EDIT" });
589
+ });
590
+ const worker = makeWorker({
591
+ store: raceStore,
592
+ fetchImpl: mkFetchMock([{ text: "the transcript" }]),
593
+ });
594
+ try {
595
+ await worker.tick();
596
+ } finally {
597
+ await worker.stop();
598
+ }
599
+
600
+ const note = await store.getNote("oc-succ-1");
601
+ // (b) transcript replaced the placeholder in place; (a) user edit survives.
602
+ expect(note!.content).toBe("# memo\n\nthe transcript\n\n\nUSER EDIT");
603
+ expect((note!.metadata as any)?.transcribe_stub).toBeUndefined();
604
+ const [att] = await store.getAttachments("oc-succ-1");
605
+ expect(att.metadata?.transcribe_status).toBe("done");
606
+ expect(att.metadata?.transcript).toBe("the transcript");
607
+ });
608
+
609
+ test("failure marker: single race → re-applies marker onto the user's fresh content", async () => {
610
+ // maxAttempts=1 so a single scribe failure is terminal → applyFailureMarker.
611
+ await store.createNote(
612
+ "# memo\n\n_Transcript pending._\n",
613
+ { id: "oc-fail-1", metadata: { transcribe_stub: true } },
614
+ );
615
+ seedAudio("memos/ocf1.webm");
616
+ await store.addAttachment("oc-fail-1", "memos/ocf1.webm", "audio/webm", {
617
+ transcribe_status: "pending",
618
+ });
619
+
620
+ const raceStore = withRace(store as unknown as Store, 1, async () => {
621
+ await store.updateNote("oc-fail-1", { append: "\n\nUSER EDIT" });
622
+ });
623
+ const worker = makeWorker({
624
+ store: raceStore,
625
+ maxAttempts: 1,
626
+ fetchImpl: mkFetchMock([{ error: "scribe down", status: 500 }]),
627
+ });
628
+ try {
629
+ await worker.tick();
630
+ } finally {
631
+ await worker.stop();
632
+ }
633
+
634
+ const note = await store.getNote("oc-fail-1");
635
+ // (b) marker replaced the placeholder in place; (a) user edit survives.
636
+ expect(note!.content).toBe("# memo\n\n_Transcription unavailable._\n\n\nUSER EDIT");
637
+ expect((note!.metadata as any)?.transcribe_stub).toBeUndefined();
638
+ const [att] = await store.getAttachments("oc-fail-1");
639
+ expect(att.metadata?.transcribe_status).toBe("failed");
640
+ });
641
+
642
+ test("double conflict + still safe → last-resort apply (append path stays applicable)", async () => {
643
+ // No placeholder in the body → the transform takes the APPEND branch, which
644
+ // is always safe against fresh content. Interfere on BOTH the first write
645
+ // and the retry write → the worker falls back to the precondition-less
646
+ // last-resort apply (safe because the stub survives + append is safe).
647
+ await store.createNote(
648
+ "user prose with no marker",
649
+ { id: "oc-dbl-safe", metadata: { transcribe_stub: true } },
650
+ );
651
+ seedAudio("memos/ocd1.webm");
652
+ await store.addAttachment("oc-dbl-safe", "memos/ocd1.webm", "audio/webm", {
653
+ transcribe_status: "pending",
654
+ });
655
+
656
+ let edits = 0;
657
+ const raceStore = withRace(store as unknown as Store, 2, async () => {
658
+ edits++;
659
+ await store.updateNote("oc-dbl-safe", { append: ` e${edits}` });
660
+ });
661
+ const worker = makeWorker({
662
+ store: raceStore,
663
+ fetchImpl: mkFetchMock([{ text: "TRANSCRIPT" }]),
664
+ });
665
+ try {
666
+ await worker.tick();
667
+ } finally {
668
+ await worker.stop();
669
+ }
670
+
671
+ const note = await store.getNote("oc-dbl-safe");
672
+ // (a) both user appends survive; (c) transcript still appended (last-resort).
673
+ expect(note!.content).toBe("user prose with no marker e1 e2\n\nTRANSCRIPT");
674
+ expect((note!.metadata as any)?.transcribe_stub).toBeUndefined();
675
+ });
676
+
677
+ test("double conflict + unsafe (user cleared the stub) → safe-skip, note untouched", async () => {
678
+ // The racing user edit clears `transcribe_stub` → the last-resort safety
679
+ // gate refuses to blind-write. The worker skips the note write entirely
680
+ // and the user's content is left exactly as they left it. The attachment
681
+ // transcript is still recorded (we never throw work away).
682
+ await store.createNote(
683
+ "# memo\n\n_Transcript pending._\n",
684
+ { id: "oc-dbl-skip", metadata: { transcribe_stub: true } },
685
+ );
686
+ seedAudio("memos/ocd2.webm");
687
+ await store.addAttachment("oc-dbl-skip", "memos/ocd2.webm", "audio/webm", {
688
+ transcribe_status: "pending",
689
+ });
690
+
691
+ let edit = 0;
692
+ const raceStore = withRace(store as unknown as Store, 2, async () => {
693
+ edit++;
694
+ // Second interference clears the stub (user opts out mid-flight).
695
+ if (edit >= 2) {
696
+ await store.updateNote("oc-dbl-skip", {
697
+ content: "I took this note over",
698
+ metadata: {},
699
+ });
700
+ } else {
701
+ await store.updateNote("oc-dbl-skip", { append: " edit1" });
702
+ }
703
+ });
704
+ const worker = makeWorker({
705
+ store: raceStore,
706
+ fetchImpl: mkFetchMock([{ text: "WOULD CLOBBER" }]),
707
+ });
708
+ try {
709
+ await worker.tick();
710
+ } finally {
711
+ await worker.stop();
712
+ }
713
+
714
+ const note = await store.getNote("oc-dbl-skip");
715
+ // (c) safe-skip: the worker did NOT overwrite the user's takeover content.
716
+ expect(note!.content).toBe("I took this note over");
717
+ expect(note!.content).not.toContain("WOULD CLOBBER");
718
+ // Transcript still durable on the attachment row.
719
+ const [att] = await store.getAttachments("oc-dbl-skip");
720
+ expect(att.metadata?.transcribe_status).toBe("done");
721
+ expect(att.metadata?.transcript).toBe("WOULD CLOBBER");
722
+ });
723
+ });
724
+
516
725
  describe("transcription worker — auth + context", () => {
517
726
  test("attaches multipart context part when getContextPredicates returns entries", async () => {
518
727
  await store.createNote("stub", { id: "ctx1", metadata: { transcribe_stub: true } });
@@ -702,7 +911,8 @@ describe("transcription worker — hook-driven", () => {
702
911
  });
703
912
 
704
913
  test("attachment:created event triggers a cycle before the sweep fires", async () => {
705
- await hookedStore.createNote("stub", { id: "h1", metadata: { transcribe_stub: true } });
914
+ // Placeholder body so the transcript patches in place (real capture shape).
915
+ await hookedStore.createNote("_Transcript pending._", { id: "h1", metadata: { transcribe_stub: true } });
706
916
  seedAudio("memos/h1.webm");
707
917
 
708
918
  let callCount = 0;
@@ -1117,3 +1327,259 @@ describe("transcription worker — auto-origin (vault#353)", () => {
1117
1327
  }
1118
1328
  });
1119
1329
  });
1330
+
1331
+ // ---------------------------------------------------------------------------
1332
+ // finding F — never destroy content on the legacy in-body memo path. These
1333
+ // fixtures carry the `![[<audio>]]` embed (the REAL production capture shape
1334
+ // from recorder.ts memoNoteContent), which the pre-existing failure tests all
1335
+ // omitted — which is why the data-loss branch went uncaught.
1336
+ // ---------------------------------------------------------------------------
1337
+
1338
+ describe("transcription worker — legacy in-body memo content safety (finding F)", () => {
1339
+ // Canonical capture body: header + _Recorded_ + placeholder + ![[embed]].
1340
+ const captureBody = (audio: string) =>
1341
+ `# 🎙️ Voice memo\n\n_Recorded sometime._\n\n_Transcript pending._\n\n![[${audio}]]\n`;
1342
+
1343
+ test("failure with placeholder present → marker replaces placeholder, embed intact", async () => {
1344
+ await store.createNote(captureBody("memos/f1.webm"), {
1345
+ id: "f-marker-1",
1346
+ metadata: { transcribe_stub: true },
1347
+ });
1348
+ seedAudio("memos/f1.webm");
1349
+ await store.addAttachment("f-marker-1", "memos/f1.webm", "audio/webm", {
1350
+ transcribe_status: "pending",
1351
+ transcribe_attempts: 2,
1352
+ });
1353
+
1354
+ const worker = makeWorker({
1355
+ fetchImpl: mkFetchMock([{ error: "scribe down", status: 500 }]),
1356
+ maxAttempts: 3,
1357
+ });
1358
+ try {
1359
+ await worker.tick();
1360
+ } finally {
1361
+ await worker.stop();
1362
+ }
1363
+
1364
+ const note = await store.getNote("f-marker-1");
1365
+ expect(note!.content).toBe(
1366
+ "# 🎙️ Voice memo\n\n_Recorded sometime._\n\n_Transcription unavailable._\n\n![[memos/f1.webm]]\n",
1367
+ );
1368
+ // Embed survives — the whole point.
1369
+ expect(note!.content).toContain("![[memos/f1.webm]]");
1370
+ expect((note!.metadata as any)?.transcribe_stub).toBeUndefined();
1371
+ });
1372
+
1373
+ test("failure with placeholder ABSENT (user-edited body + embed) → marker APPENDED, content preserved", async () => {
1374
+ // The data-loss regression: user edited the note while pending, removing
1375
+ // the _Transcript pending._ placeholder but keeping their text + the
1376
+ // embed. The old code full-replaced the body with the bare marker here,
1377
+ // destroying both. Now we append.
1378
+ const editedBody = "# 🎙️ My trip notes\n\nWent to the coast today.\n\n![[memos/f2.webm]]\n";
1379
+ await store.createNote(editedBody, {
1380
+ id: "f-marker-2",
1381
+ metadata: { transcribe_stub: true },
1382
+ });
1383
+ seedAudio("memos/f2.webm");
1384
+ await store.addAttachment("f-marker-2", "memos/f2.webm", "audio/webm", {
1385
+ transcribe_status: "pending",
1386
+ transcribe_attempts: 2,
1387
+ });
1388
+
1389
+ const worker = makeWorker({
1390
+ fetchImpl: mkFetchMock([{ error: "scribe down", status: 500 }]),
1391
+ maxAttempts: 3,
1392
+ });
1393
+ try {
1394
+ await worker.tick();
1395
+ } finally {
1396
+ await worker.stop();
1397
+ }
1398
+
1399
+ const note = await store.getNote("f-marker-2");
1400
+ // User text + embed preserved; marker appended.
1401
+ expect(note!.content).toBe(`${editedBody}\n\n_Transcription unavailable._`);
1402
+ expect(note!.content).toContain("Went to the coast today.");
1403
+ expect(note!.content).toContain("![[memos/f2.webm]]");
1404
+ expect((note!.metadata as any)?.transcribe_stub).toBeUndefined();
1405
+ });
1406
+
1407
+ test("double terminal failure → exactly one marker (idempotent, no stacking)", async () => {
1408
+ // First failure writes the marker + clears the stub. Re-arm the stub and
1409
+ // fail again — the marker must NOT stack. (Re-arming models a retry that
1410
+ // fails again.)
1411
+ await store.createNote(captureBody("memos/f3.webm"), {
1412
+ id: "f-marker-3",
1413
+ metadata: { transcribe_stub: true },
1414
+ });
1415
+ seedAudio("memos/f3.webm");
1416
+ await store.addAttachment("f-marker-3", "memos/f3.webm", "audio/webm", {
1417
+ transcribe_status: "pending",
1418
+ transcribe_attempts: 2,
1419
+ });
1420
+
1421
+ const worker1 = makeWorker({
1422
+ fetchImpl: mkFetchMock([{ error: "down", status: 500 }]),
1423
+ maxAttempts: 3,
1424
+ });
1425
+ try { await worker1.tick(); } finally { await worker1.stop(); }
1426
+
1427
+ // Re-arm stub + reset attachment to pending (what a retry does), then fail again.
1428
+ const note1 = await store.getNote("f-marker-3");
1429
+ await store.updateNote("f-marker-3", {
1430
+ metadata: { ...((note1!.metadata as any) ?? {}), transcribe_stub: true },
1431
+ skipUpdatedAt: true,
1432
+ });
1433
+ const [att] = await store.getAttachments("f-marker-3");
1434
+ await store.setAttachmentMetadata(att.id, {
1435
+ ...(att.metadata ?? {}),
1436
+ transcribe_status: "pending",
1437
+ transcribe_attempts: 2,
1438
+ });
1439
+
1440
+ const worker2 = makeWorker({
1441
+ fetchImpl: mkFetchMock([{ error: "down again", status: 500 }]),
1442
+ maxAttempts: 3,
1443
+ });
1444
+ try { await worker2.tick(); } finally { await worker2.stop(); }
1445
+
1446
+ const note = await store.getNote("f-marker-3");
1447
+ const occurrences = note!.content.split("_Transcription unavailable._").length - 1;
1448
+ expect(occurrences).toBe(1);
1449
+ expect(note!.content).toContain("![[memos/f3.webm]]");
1450
+ });
1451
+
1452
+ test("success with placeholder absent (stub set) → transcript appended, content preserved", async () => {
1453
+ // Mirror of the failure-append case, on the success path. User edited the
1454
+ // note (cleared the placeholder) but kept the stub. The old code
1455
+ // full-replaced the body with the bare transcript; now we append.
1456
+ const editedBody = "# 🎙️ My trip notes\n\nWent to the coast today.\n\n![[memos/f4.webm]]\n";
1457
+ await store.createNote(editedBody, {
1458
+ id: "f-success-1",
1459
+ metadata: { transcribe_stub: true },
1460
+ });
1461
+ seedAudio("memos/f4.webm");
1462
+ await store.addAttachment("f-success-1", "memos/f4.webm", "audio/webm", {
1463
+ transcribe_status: "pending",
1464
+ });
1465
+
1466
+ const worker = makeWorker({
1467
+ fetchImpl: mkFetchMock([{ text: "the spoken transcript" }]),
1468
+ });
1469
+ try {
1470
+ await worker.tick();
1471
+ } finally {
1472
+ await worker.stop();
1473
+ }
1474
+
1475
+ const note = await store.getNote("f-success-1");
1476
+ expect(note!.content).toBe(`${editedBody}\n\nthe spoken transcript`);
1477
+ expect(note!.content).toContain("Went to the coast today.");
1478
+ expect(note!.content).toContain("![[memos/f4.webm]]");
1479
+ expect((note!.metadata as any)?.transcribe_stub).toBeUndefined();
1480
+ });
1481
+
1482
+ test("first-try success with placeholder present + embed → transcript replaces placeholder in place", async () => {
1483
+ // Pins the canonical first-try-success shape the retry round-trip must match.
1484
+ await store.createNote(captureBody("memos/f5.webm"), {
1485
+ id: "f-success-2",
1486
+ metadata: { transcribe_stub: true },
1487
+ });
1488
+ seedAudio("memos/f5.webm");
1489
+ await store.addAttachment("f-success-2", "memos/f5.webm", "audio/webm", {
1490
+ transcribe_status: "pending",
1491
+ });
1492
+
1493
+ const worker = makeWorker({
1494
+ fetchImpl: mkFetchMock([{ text: "the spoken words" }]),
1495
+ });
1496
+ try {
1497
+ await worker.tick();
1498
+ } finally {
1499
+ await worker.stop();
1500
+ }
1501
+
1502
+ const note = await store.getNote("f-success-2");
1503
+ expect(note!.content).toBe(
1504
+ "# 🎙️ Voice memo\n\n_Recorded sometime._\n\nthe spoken words\n\n![[memos/f5.webm]]\n",
1505
+ );
1506
+ });
1507
+
1508
+ test("transcript containing `$&` round-trips byte-identical (no String.replace injection) — first-try success", async () => {
1509
+ // Regression: String.replace with a STRING replacement treats `$&` etc. as
1510
+ // special. A transcript with `$&` must land verbatim in the body, not as
1511
+ // the matched marker text.
1512
+ const dollarTranscript = "it matched $& exactly, and $1 too, plus $` and $'";
1513
+ await store.createNote(captureBody("memos/f6.webm"), {
1514
+ id: "f-dollar-1",
1515
+ metadata: { transcribe_stub: true },
1516
+ });
1517
+ seedAudio("memos/f6.webm");
1518
+ await store.addAttachment("f-dollar-1", "memos/f6.webm", "audio/webm", {
1519
+ transcribe_status: "pending",
1520
+ });
1521
+
1522
+ const worker = makeWorker({
1523
+ fetchImpl: mkFetchMock([{ text: dollarTranscript }]),
1524
+ });
1525
+ try {
1526
+ await worker.tick();
1527
+ } finally {
1528
+ await worker.stop();
1529
+ }
1530
+
1531
+ const note = await store.getNote("f-dollar-1");
1532
+ expect(note!.content).toBe(
1533
+ `# 🎙️ Voice memo\n\n_Recorded sometime._\n\n${dollarTranscript}\n\n![[memos/f6.webm]]\n`,
1534
+ );
1535
+ // Belt-and-suspenders: the literal `$&` must be present verbatim.
1536
+ expect(note!.content).toContain("it matched $& exactly");
1537
+ });
1538
+
1539
+ test("transcript containing `$&` round-trips byte-identical on the RETRY path (marker → transcript)", async () => {
1540
+ // Same injection guard, but exercised on the retry surgical-replace: the
1541
+ // transcript replaces `_Transcription unavailable._` in place.
1542
+ const dollarTranscript = "retry text with $& and $0 and $$ literal";
1543
+ await store.createNote(captureBody("memos/f7.webm"), {
1544
+ id: "f-dollar-2",
1545
+ metadata: { transcribe_stub: true },
1546
+ });
1547
+ seedAudio("memos/f7.webm");
1548
+ await store.addAttachment("f-dollar-2", "memos/f7.webm", "audio/webm", {
1549
+ transcribe_status: "pending",
1550
+ transcribe_attempts: 2, // one more failure → terminal at maxAttempts=3
1551
+ });
1552
+
1553
+ // Phase 1: terminal failure → marker replaces placeholder.
1554
+ const worker1 = makeWorker({
1555
+ fetchImpl: mkFetchMock([{ error: "down", status: 500 }]),
1556
+ maxAttempts: 3,
1557
+ });
1558
+ try { await worker1.tick(); } finally { await worker1.stop(); }
1559
+ const failed = await store.getNote("f-dollar-2");
1560
+ expect(failed!.content).toContain("_Transcription unavailable._");
1561
+
1562
+ // Re-arm stub + reset attachment (what the retry route does).
1563
+ await store.updateNote("f-dollar-2", {
1564
+ metadata: { ...((failed!.metadata as any) ?? {}), transcribe_stub: true },
1565
+ skipUpdatedAt: true,
1566
+ });
1567
+ const [att] = await store.getAttachments("f-dollar-2");
1568
+ await store.setAttachmentMetadata(att.id, {
1569
+ ...(att.metadata ?? {}),
1570
+ transcribe_status: "pending",
1571
+ });
1572
+
1573
+ // Phase 2: success with a `$&`-bearing transcript → replaces the marker.
1574
+ const worker2 = makeWorker({
1575
+ fetchImpl: mkFetchMock([{ text: dollarTranscript }]),
1576
+ });
1577
+ try { await worker2.tick(); } finally { await worker2.stop(); }
1578
+
1579
+ const note = await store.getNote("f-dollar-2");
1580
+ expect(note!.content).toBe(
1581
+ `# 🎙️ Voice memo\n\n_Recorded sometime._\n\n${dollarTranscript}\n\n![[memos/f7.webm]]\n`,
1582
+ );
1583
+ expect(note!.content).toContain("retry text with $& and $0 and $$ literal");
1584
+ });
1585
+ });