@remnic/core 9.69.7 → 9.69.8
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/access-admin-ops-surface.d.ts +1 -1
- package/dist/access-admin-ops-surface.js +3 -3
- package/dist/access-authorization-probe.d.ts +1 -1
- package/dist/access-authorization-probe.js +4 -4
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.js +3 -3
- package/dist/access-cli.js +22 -22
- package/dist/access-extraction-force-flush.d.ts +1 -1
- package/dist/access-extraction-force-flush.js +3 -3
- package/dist/access-http-lcm-compaction.d.ts +1 -1
- package/dist/access-http-lifecycle-flush.d.ts +1 -1
- package/dist/access-http-offline-stream.d.ts +1 -1
- package/dist/access-http-query.js +3 -3
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +13 -13
- package/dist/access-identity-continuity-surface.js +3 -3
- package/dist/access-lcm-surface.d.ts +1 -1
- package/dist/access-lcm-surface.js +3 -3
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.js +9 -9
- package/dist/access-namespace-preflight.js +3 -3
- package/dist/access-observe-write-surface.d.ts +1 -1
- package/dist/access-observe-write-surface.js +3 -3
- package/dist/access-operations-batch.js +4 -4
- package/dist/access-operations.d.ts +6 -6
- package/dist/access-operations.js +8 -8
- package/dist/access-recall-concurrency.d.ts +1 -1
- package/dist/access-recall-concurrency.js +3 -3
- package/dist/access-recall-response.d.ts +1 -1
- package/dist/access-recall-response.js +3 -3
- package/dist/access-recall-surface.d.ts +1 -1
- package/dist/access-recall-surface.js +3 -3
- package/dist/access-schema.d.ts +89 -89
- package/dist/access-service-helpers.d.ts +1 -1
- package/dist/access-service.d.ts +1 -1
- package/dist/access-service.js +3 -3
- package/dist/access-surface-catalog.d.ts +1 -1
- package/dist/access-surface-catalog.js +5 -5
- package/dist/buffer.js +3 -3
- package/dist/{chunk-J2TT3RV6.js → chunk-3FXXFLVK.js} +2 -2
- package/dist/{chunk-DZNNWPQ7.js → chunk-3QWF54M4.js} +2 -2
- package/dist/{chunk-GH7UF5BV.js → chunk-44HBCMOA.js} +8 -8
- package/dist/{chunk-IE7N2YK6.js → chunk-4PJYD2L3.js} +2 -2
- package/dist/{chunk-MWLBRFLY.js → chunk-5PTS43CH.js} +2 -2
- package/dist/{chunk-N27L5IY2.js → chunk-754GA2UE.js} +2 -2
- package/dist/{chunk-EG6NUYY3.js → chunk-DKZR56A3.js} +2 -2
- package/dist/{chunk-XHGDFN7A.js → chunk-EN75MUTO.js} +2 -2
- package/dist/{chunk-OMA2B7GY.js → chunk-G4MCAHMF.js} +2 -2
- package/dist/{chunk-KLLSFJBJ.js → chunk-IIO4C3YL.js} +2 -2
- package/dist/{chunk-TB2MJRI6.js → chunk-J3NGQ5SC.js} +2 -2
- package/dist/chunk-J3NGQ5SC.js.map +1 -0
- package/dist/{chunk-MWHSWK4W.js → chunk-J55NAXDH.js} +5 -5
- package/dist/{chunk-UJMOG5LL.js → chunk-JAU4MRKB.js} +2 -2
- package/dist/{chunk-S6DX26S3.js → chunk-KAVQ5GYY.js} +4 -3
- package/dist/{chunk-S6DX26S3.js.map → chunk-KAVQ5GYY.js.map} +1 -1
- package/dist/{chunk-GTZLPFAT.js → chunk-KLL5SPER.js} +2 -2
- package/dist/{chunk-6EE5LJSZ.js → chunk-KZMFU7YL.js} +2 -2
- package/dist/{chunk-E6Z4LWNJ.js → chunk-LVYO2HZJ.js} +2 -2
- package/dist/{chunk-APGIIJRE.js → chunk-M5GBTETG.js} +2 -2
- package/dist/{chunk-N5VXPTXC.js → chunk-PEOFTEUC.js} +7 -7
- package/dist/{chunk-UJICQ77Y.js → chunk-UCNQV43K.js} +2 -2
- package/dist/{chunk-ZNXPGZJQ.js → chunk-VGTXHTWU.js} +2 -2
- package/dist/{chunk-QAITDA5B.js → chunk-YJPFTF57.js} +4 -4
- package/dist/{cli-CFpMhqz5.d.ts → cli-Bvy4jdBu.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +15 -15
- package/dist/external-wiki-access.d.ts +1 -1
- package/dist/external-wiki-access.js +4 -4
- package/dist/external-wiki-mcp-tools.d.ts +1 -1
- package/dist/extraction.js +4 -4
- package/dist/index.d.ts +680 -680
- package/dist/index.js +22 -22
- package/dist/location/index.js +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +1 -1
- package/dist/memory-subject-cli.d.ts +1 -1
- package/dist/memory-subject.d.ts +1 -1
- package/dist/orchestration/maintenance.js +4 -4
- package/dist/orchestrator.js +22 -22
- package/dist/{public-http-C9-CHc40.d.ts → public-http-2MyshWxl.d.ts} +1 -1
- package/dist/reconcile/cursor.js +7 -2
- package/dist/reconcile/cursor.js.map +1 -1
- package/dist/schemas.d.ts +176 -176
- package/dist/shared-context/manager.d.ts +6 -6
- package/dist/source-agent-qualifier.js +3 -3
- package/dist/support-passport/index.d.ts +4 -4
- package/dist/support-passport/index.js +6 -6
- package/dist/transfer/export-okf.d.ts +2 -1
- package/dist/transfer/export-okf.js +3 -1
- package/dist/transfer/types.d.ts +78 -78
- package/dist/who-knows-cli.d.ts +1 -1
- package/dist/who-knows.d.ts +1 -1
- package/package.json +2 -2
- package/src/location/matching.test.ts +31 -1
- package/src/location/matching.ts +6 -1
- package/src/reconcile/cursor.test.ts +18 -0
- package/src/reconcile/cursor.ts +13 -2
- package/src/transfer/export-okf.test.ts +22 -0
- package/src/transfer/export-okf.ts +6 -2
- package/dist/chunk-TB2MJRI6.js.map +0 -1
- package/dist/{access-service-DcfM0Vf0.d.ts → access-service-LbwWl05g.d.ts} +64 -64
- /package/dist/{chunk-J2TT3RV6.js.map → chunk-3FXXFLVK.js.map} +0 -0
- /package/dist/{chunk-DZNNWPQ7.js.map → chunk-3QWF54M4.js.map} +0 -0
- /package/dist/{chunk-GH7UF5BV.js.map → chunk-44HBCMOA.js.map} +0 -0
- /package/dist/{chunk-IE7N2YK6.js.map → chunk-4PJYD2L3.js.map} +0 -0
- /package/dist/{chunk-MWLBRFLY.js.map → chunk-5PTS43CH.js.map} +0 -0
- /package/dist/{chunk-N27L5IY2.js.map → chunk-754GA2UE.js.map} +0 -0
- /package/dist/{chunk-EG6NUYY3.js.map → chunk-DKZR56A3.js.map} +0 -0
- /package/dist/{chunk-XHGDFN7A.js.map → chunk-EN75MUTO.js.map} +0 -0
- /package/dist/{chunk-OMA2B7GY.js.map → chunk-G4MCAHMF.js.map} +0 -0
- /package/dist/{chunk-KLLSFJBJ.js.map → chunk-IIO4C3YL.js.map} +0 -0
- /package/dist/{chunk-MWHSWK4W.js.map → chunk-J55NAXDH.js.map} +0 -0
- /package/dist/{chunk-UJMOG5LL.js.map → chunk-JAU4MRKB.js.map} +0 -0
- /package/dist/{chunk-GTZLPFAT.js.map → chunk-KLL5SPER.js.map} +0 -0
- /package/dist/{chunk-6EE5LJSZ.js.map → chunk-KZMFU7YL.js.map} +0 -0
- /package/dist/{chunk-E6Z4LWNJ.js.map → chunk-LVYO2HZJ.js.map} +0 -0
- /package/dist/{chunk-APGIIJRE.js.map → chunk-M5GBTETG.js.map} +0 -0
- /package/dist/{chunk-N5VXPTXC.js.map → chunk-PEOFTEUC.js.map} +0 -0
- /package/dist/{chunk-UJICQ77Y.js.map → chunk-UCNQV43K.js.map} +0 -0
- /package/dist/{chunk-ZNXPGZJQ.js.map → chunk-VGTXHTWU.js.map} +0 -0
- /package/dist/{chunk-QAITDA5B.js.map → chunk-YJPFTF57.js.map} +0 -0
package/src/location/matching.ts
CHANGED
|
@@ -233,7 +233,12 @@ export function locationTagSlug(placeId: string): string {
|
|
|
233
233
|
const slug = placeId
|
|
234
234
|
.toLowerCase()
|
|
235
235
|
.replace(/[^a-z0-9]+/g, "-")
|
|
236
|
-
|
|
236
|
+
// The collapse above leaves at most one dash at each edge, so single-char
|
|
237
|
+
// strips are complete. The old /^-+|-+$/ alternation backtracked
|
|
238
|
+
// quadratically on unsanitized dash runs (CodeQL js/polynomial-redos);
|
|
239
|
+
// single-character anchors are linear.
|
|
240
|
+
.replace(/^-/, "")
|
|
241
|
+
.replace(/-$/, "");
|
|
237
242
|
return slug.slice(0, TAG_LIMITS.maxTagLength - LOCATION_TAG_PREFIX.length);
|
|
238
243
|
}
|
|
239
244
|
|
|
@@ -199,3 +199,21 @@ test("converge refresh obligations survive restart and clear independently", asy
|
|
|
199
199
|
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
200
200
|
}
|
|
201
201
|
});
|
|
202
|
+
|
|
203
|
+
test("normalizeConvergePeerUrl: non-URL peers trim trailing slashes", () => {
|
|
204
|
+
assert.equal(normalizeConvergePeerUrl("local///"), "local");
|
|
205
|
+
assert.equal(normalizeConvergePeerUrl("peer-a/"), "peer-a");
|
|
206
|
+
assert.equal(normalizeConvergePeerUrl("peer-a"), "peer-a");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("normalizeConvergePeerUrl: stays linear on long unparseable slash runs", () => {
|
|
210
|
+
// No scheme, so the URL constructor throws and the fallback branch runs;
|
|
211
|
+
// the trailing "a" means no suffix of slashes can match, which is the
|
|
212
|
+
// backtracking worst case for the old /\/+$/ trim.
|
|
213
|
+
const peerUrl = "/".repeat(50_000) + "a";
|
|
214
|
+
const startedAt = Date.now();
|
|
215
|
+
const normalized = normalizeConvergePeerUrl(peerUrl);
|
|
216
|
+
const elapsedMs = Date.now() - startedAt;
|
|
217
|
+
assert.ok(elapsedMs < 1000, `normalizeConvergePeerUrl took ${elapsedMs}ms on a ${peerUrl.length}-char slash run`);
|
|
218
|
+
assert.equal(normalized, peerUrl);
|
|
219
|
+
});
|
package/src/reconcile/cursor.ts
CHANGED
|
@@ -27,6 +27,17 @@ export interface ConvergeCursorState {
|
|
|
27
27
|
pendingRefreshes?: ConvergeRefreshTarget[];
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Linear trailing-slash trim. `/\/+$/` backtracks quadratically on long
|
|
32
|
+
* slash runs that do not end in a slash (CodeQL js/polynomial-redos); a
|
|
33
|
+
* bounded character loop cannot.
|
|
34
|
+
*/
|
|
35
|
+
function stripTrailingSlashes(value: string): string {
|
|
36
|
+
let end = value.length;
|
|
37
|
+
while (end > 0 && value[end - 1] === "/") end -= 1;
|
|
38
|
+
return end === value.length ? value : value.slice(0, end);
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
export function normalizeConvergePeerUrl(peerUrl: string): string {
|
|
31
42
|
const trimmed = peerUrl.trim();
|
|
32
43
|
try {
|
|
@@ -35,10 +46,10 @@ export function normalizeConvergePeerUrl(peerUrl: string): string {
|
|
|
35
46
|
url.password = "";
|
|
36
47
|
url.search = "";
|
|
37
48
|
url.hash = "";
|
|
38
|
-
url.pathname = url.pathname
|
|
49
|
+
url.pathname = stripTrailingSlashes(url.pathname);
|
|
39
50
|
return url.toString().replace(/\/$/, "");
|
|
40
51
|
} catch {
|
|
41
|
-
return trimmed
|
|
52
|
+
return stripTrailingSlashes(trimmed);
|
|
42
53
|
}
|
|
43
54
|
}
|
|
44
55
|
|
|
@@ -9,6 +9,7 @@ import { StorageManager } from "../storage.js";
|
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_OKF_LOG_MAX_ENTRIES,
|
|
11
11
|
exportOkfBundle,
|
|
12
|
+
firstHeading,
|
|
12
13
|
OKF_EXPORT_VERSION,
|
|
13
14
|
OKF_LOG_TRUNCATION_MARKER,
|
|
14
15
|
parseIncludeStatus,
|
|
@@ -238,3 +239,24 @@ test("--out that is a file is rejected before anything is written", async () =>
|
|
|
238
239
|
await rm(root, { recursive: true, force: true });
|
|
239
240
|
}
|
|
240
241
|
});
|
|
242
|
+
|
|
243
|
+
test("firstHeading: pins H1 extraction semantics", () => {
|
|
244
|
+
assert.equal(firstHeading("# Title\nbody"), "Title");
|
|
245
|
+
assert.equal(firstHeading("## h2 skipped\n# Real"), "Real");
|
|
246
|
+
assert.equal(firstHeading("no heading\nnone"), undefined);
|
|
247
|
+
assert.equal(firstHeading("# spaced title "), "spaced title");
|
|
248
|
+
// `.` cannot span \u2028, so that line is skipped and a later H1 wins.
|
|
249
|
+
assert.equal(firstHeading("# a\u2028b\n# ok"), "ok");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("firstHeading: stays linear on whitespace-heavy blocked headings", () => {
|
|
253
|
+
// The \u2028 after the first non-space blocks the match (the regex must
|
|
254
|
+
// fail), and the long whitespace run forces every \s+/capture split point
|
|
255
|
+
// to be tried — the pre-fix superlinear case.
|
|
256
|
+
const content = "# " + " ".repeat(25_000) + "a\u2028b\nplain";
|
|
257
|
+
const startedAt = Date.now();
|
|
258
|
+
const heading = firstHeading(content);
|
|
259
|
+
const elapsedMs = Date.now() - startedAt;
|
|
260
|
+
assert.ok(elapsedMs < 1000, `firstHeading took ${elapsedMs}ms`);
|
|
261
|
+
assert.equal(heading, undefined);
|
|
262
|
+
});
|
|
@@ -276,9 +276,13 @@ function boldAction(raw: string): string {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
|
|
279
|
-
function firstHeading(content: string): string | undefined {
|
|
279
|
+
export function firstHeading(content: string): string | undefined {
|
|
280
280
|
for (const line of content.split("\n")) {
|
|
281
|
-
|
|
281
|
+
// `\S` makes the capture boundary disjoint from `\s+`, so the regex
|
|
282
|
+
// cannot backtrack across split points — `\s+(.+)` was superlinear on
|
|
283
|
+
// whitespace-heavy lines (CodeQL js/polynomial-redos). `.` still excludes
|
|
284
|
+
// \r\u2028\u2029, preserving the skip-line behavior on those lines.
|
|
285
|
+
const match = /^#\s+(\S.*)$/.exec(line.trim());
|
|
282
286
|
if (match) return match[1]!.trim();
|
|
283
287
|
}
|
|
284
288
|
return undefined;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/location/intervals.ts","../src/location/matching.ts","../src/location/store.ts","../src/location/tagging.ts"],"sourcesContent":["/**\n * Location interval math (issue #2044).\n *\n * Pure, deterministic helpers over UTC ISO-8601 instants: half-open\n * [start, end) day windows (reusing the DST-correct activity day-window), IANA\n * local-day bucketing, overlap, observation→segment dwell, and dominant-place\n * selection with stable tie-breaking. No IO, no clocks, no randomness.\n */\n\nimport { activityDayWindow, isValidActivityDate } from \"../activity/digest.js\";\nimport type { LocationObservation, LocationPlace, LocationSegment } from \"./types.js\";\n\nexport function isValidLocationDate(date: string): boolean {\n return isValidActivityDate(date);\n}\n\n/** Half-open [start, end) UTC ISO bounds of a local day. */\nexport function locationDayWindow(date: string, timezone: string): { startUtc: string; endUtc: string } {\n if (!isValidActivityDate(date)) {\n throw new RangeError(`Invalid location date \"${date}\"; expected a real YYYY-MM-DD day.`);\n }\n return activityDayWindow(date, timezone);\n}\n\nfunction assertFiniteInstant(iso: string, what: string): number {\n const ms = Date.parse(iso);\n if (typeof iso !== \"string\" || !Number.isFinite(ms)) {\n throw new RangeError(`${what} must be a UTC ISO-8601 instant; got \"${String(iso)}\"`);\n }\n return ms;\n}\n\n/** The IANA local calendar day (YYYY-MM-DD) an instant falls in. */\nexport function localDayKey(instantUtc: string, timezone: string): string {\n assertFiniteInstant(instantUtc, \"instantUtc\");\n return new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(new Date(instantUtc));\n}\n\nfunction compareObservations(a: LocationObservation, b: LocationObservation): number {\n if (a.observedAtUtc !== b.observedAtUtc) {\n return a.observedAtUtc < b.observedAtUtc ? -1 : 1;\n }\n // Total comparator (§12): equal-key observations fall back to place id so\n // ordering — and therefore dwell attribution — is deterministic.\n if (a.place.id !== b.place.id) {\n return a.place.id < b.place.id ? -1 : 1;\n }\n return 0;\n}\n\n/** Bucket observations into their IANA local days, sorted within each day. */\nexport function bucketObservationsByDay(\n observations: readonly LocationObservation[],\n timezone: string,\n): Map<string, LocationObservation[]> {\n const buckets = new Map<string, LocationObservation[]>();\n for (const observation of observations) {\n assertFiniteInstant(observation.observedAtUtc, \"observation observedAtUtc\");\n const key = localDayKey(observation.observedAtUtc, timezone);\n const bucket = buckets.get(key);\n if (bucket === undefined) buckets.set(key, [observation]);\n else bucket.push(observation);\n }\n for (const bucket of buckets.values()) bucket.sort(compareObservations);\n return buckets;\n}\n\n/** Overlap in ms of two half-open intervals; 0 when disjoint. */\nexport function intervalOverlapMs(\n a: { startUtc: string; endUtc: string },\n b: { startUtc: string; endUtc: string },\n): number {\n const overlap =\n Math.min(Date.parse(a.endUtc), Date.parse(b.endUtc)) - Math.max(Date.parse(a.startUtc), Date.parse(b.startUtc));\n return Math.max(0, overlap);\n}\n\nfunction placeForSegment(place: LocationPlace, retainCoordinates: boolean): LocationPlace {\n if (retainCoordinates || place.latitude === undefined || place.longitude === undefined) {\n return place;\n }\n return {\n id: place.id,\n label: place.label,\n ...(place.kind === undefined ? {} : { kind: place.kind }),\n };\n}\n\n/**\n * Derive place-visit segments from a day's observations: each observation\n * covers [its instant, the next observation's instant ∩ window end). The last\n * observation is capped by the window end, so dwell never crosses the day\n * boundary. Out-of-window observations are a provider contract violation and\n * are rejected loudly rather than silently reshaping the day. Coordinates are\n * stripped unless `retainCoordinates` is set.\n */\nexport function observationSegments(\n observations: readonly LocationObservation[],\n window: { startUtc: string; endUtc: string },\n options: { retainCoordinates: boolean },\n): LocationSegment[] {\n const startMs = assertFiniteInstant(window.startUtc, \"window startUtc\");\n const endMs = assertFiniteInstant(window.endUtc, \"window endUtc\");\n if (startMs >= endMs) {\n throw new RangeError(\"location window must satisfy startUtc < endUtc\");\n }\n const ordered = [...observations].sort(compareObservations);\n const segments: LocationSegment[] = [];\n for (let index = 0; index < ordered.length; index += 1) {\n const current = ordered[index]!;\n const currentMs = Date.parse(current.observedAtUtc);\n if (currentMs < startMs || currentMs >= endMs) {\n throw new RangeError(\n `location observation at \"${current.observedAtUtc}\" falls outside the requested [${window.startUtc}, ${window.endUtc}) window`,\n );\n }\n const nextMs =\n index + 1 < ordered.length ? Date.parse(ordered[index + 1]!.observedAtUtc) : endMs;\n const segmentEndMs = Math.min(nextMs, endMs);\n if (currentMs >= segmentEndMs) continue;\n segments.push({\n startUtc: new Date(currentMs).toISOString(),\n endUtc: new Date(segmentEndMs).toISOString(),\n place: placeForSegment(current.place, options.retainCoordinates),\n ...(current.confidence !== undefined ? { confidence: current.confidence } : {}),\n });\n }\n return segments;\n}\n\n/** Total time a place was occupied (union of its segment intervals, ms). */\nexport interface PlaceDuration {\n place: LocationPlace;\n totalMs: number;\n /** Earliest segment start contributing to this place's total. */\n firstStartUtc: string;\n}\n\nfunction unionIntervalMs(segments: readonly LocationSegment[]): number {\n // Merge overlapping [start, end) intervals per place so two providers\n // observing the same visit do not double-count its duration.\n const bounds = segments\n .map((segment) => ({ start: Date.parse(segment.startUtc), end: Date.parse(segment.endUtc) }))\n .sort((a, b) => (a.start !== b.start ? a.start - b.start : a.end - b.end));\n let total = 0;\n let sweepStart: number | null = null;\n let sweepEnd = 0;\n for (const bound of bounds) {\n if (sweepStart === null || bound.start > sweepEnd) {\n if (sweepStart !== null) total += sweepEnd - sweepStart;\n sweepStart = bound.start;\n sweepEnd = bound.end;\n } else if (bound.end > sweepEnd) {\n sweepEnd = bound.end;\n }\n }\n if (sweepStart !== null) total += sweepEnd - sweepStart;\n return total;\n}\n\n/** Per-place totals across every segment given (multi-source safe). */\nexport function placeDurations(segments: readonly LocationSegment[]): PlaceDuration[] {\n const byPlace = new Map<string, { place: LocationPlace; segments: LocationSegment[]; firstStartUtc: string }>();\n for (const segment of segments) {\n const existing = byPlace.get(segment.place.id);\n if (existing === undefined) {\n byPlace.set(segment.place.id, { place: segment.place, segments: [segment], firstStartUtc: segment.startUtc });\n continue;\n }\n existing.segments.push(segment);\n if (segment.startUtc < existing.firstStartUtc) existing.firstStartUtc = segment.startUtc;\n // Two providers may label the same place id differently; keep the\n // lexicographically smallest label so rendering stays deterministic.\n if (segment.place.label < existing.place.label) existing.place = segment.place;\n }\n const durations: PlaceDuration[] = [...byPlace.values()].map((entry) => ({\n place: entry.place,\n totalMs: unionIntervalMs(entry.segments),\n firstStartUtc: entry.firstStartUtc,\n }));\n durations.sort((a, b) => {\n if (a.place.id !== b.place.id) return a.place.id < b.place.id ? -1 : 1;\n return 0;\n });\n return durations;\n}\n\nexport interface DominantPlaceResult {\n place: LocationPlace;\n totalMs: number;\n}\n\n/**\n * The place with the longest total occupancy. Ties are stable and\n * deterministic: earlier first start wins, then the lexicographically smaller\n * place id — never insertion order.\n */\nexport function dominantPlace(segments: readonly LocationSegment[]): DominantPlaceResult | null {\n const durations = placeDurations(segments);\n let best: PlaceDuration | null = null;\n for (const duration of durations) {\n if (best === null) {\n best = duration;\n continue;\n }\n if (duration.totalMs !== best.totalMs) {\n if (duration.totalMs > best.totalMs) best = duration;\n continue;\n }\n if (duration.firstStartUtc !== best.firstStartUtc) {\n if (duration.firstStartUtc < best.firstStartUtc) best = duration;\n continue;\n }\n if (duration.place.id < best.place.id) best = duration;\n }\n return best === null ? null : { place: best.place, totalMs: best.totalMs };\n}\n","/**\n * Core location matcher (issue #2046) — the ONE place that decides whether a\n * memory or wearable conversation gets provider location context.\n *\n * Pure and deterministic: no IO, no clocks, no randomness. Inputs are UTC\n * ISO-8601 instants and half-open [start, end) intervals everywhere.\n *\n * Rules (issue #2046 / umbrella #2043 decision D):\n * 1. A valid memory interval is required — derived ONLY from the memory's own\n * bi-temporal frontmatter (`valid_at` / `observedAt` / `invalid_at`), never\n * from wall-clock time or message order. Untimed memories stay untagged.\n * 2. Intervals longer than 24h describe long-lived facts, not events at a\n * place; they stay untagged.\n * 3. A place is tagged only when it is DOMINANT (strictly the longest overlap\n * among candidate places; ties are conflicts and leave the memory untagged)\n * and meets `minimumOverlapSeconds` and `minimumConfidence`.\n * 4. Only resolved named places participate (`kind` home/work/poi): transit\n * legs and unnamed visits never tag a memory.\n * 5. No address is ever inferred from text, coordinates, IP, or proximity —\n * only provider-supplied place metadata is echoed, and coordinates only\n * when `retainCoordinates` is enabled.\n *\n * Provider-owned metadata contract: the tag is `location:<stable-place-slug>`\n * and the opaque provider place id lives in structured attributes. Every\n * field the matcher writes is provider-owned; a memory that carries\n * location-ish metadata WITHOUT the `locationSource` marker is treated as\n * manual/user-authored and is never touched.\n */\n\nimport { TAG_LIMITS, STRUCTURED_ATTRIBUTE_LIMITS } from \"../write-envelope.js\";\nimport type { LocationPlace, LocationSegment } from \"./types.js\";\n\n/** Maximum memory span eligible for location tagging. */\nexport const LOCATION_MATCH_MAX_MEMORY_SPAN_MS = 24 * 60 * 60 * 1000;\n\n/** Prefix of provider-owned location tags. */\nexport const LOCATION_TAG_PREFIX = \"location:\";\n\n/** Structured-attribute keys owned by the tagging pipeline (never manual). */\nexport const LOCATION_ATTRIBUTE_KEYS = [\n \"locationSource\",\n \"locationPlaceId\",\n \"locationPlace\",\n \"locationConfidence\",\n \"locationVisitStart\",\n \"locationVisitEnd\",\n \"locationLatitude\",\n \"locationLongitude\",\n] as const;\n\n/** Place kinds eligible for tagging: resolved, named places only. */\nconst TAGGABLE_PLACE_KINDS: Record<string, true> = { home: true, work: true, poi: true };\n\nexport interface LocationTagPolicy {\n minimumOverlapSeconds: number;\n minimumConfidence: number;\n retainCoordinates: boolean;\n}\n\n/** The memory side of a match: a half-open interval, or a single instant. */\nexport type MemoryLocationWindow =\n | { kind: \"interval\"; startUtc: string; endUtc: string }\n | { kind: \"instant\"; atUtc: string };\n\n/** Why a window was rejected before matching. */\nexport type WindowRejectReason = \"untimed\" | \"span-too-long\";\n\nexport interface WindowResult {\n window?: MemoryLocationWindow;\n /** Local days (YYYY-MM-DD in the tag policy timezone) the window touches. */\n days?: string[];\n rejected?: WindowRejectReason;\n}\n\n/**\n * Derive the match window from a memory's own frontmatter. `validAt` (the\n * event start; assumed copies carry the ingestion anchor) opens the window;\n * `invalid_at` closes it. An end-only memory (no start bound) is untimed.\n */\nexport function memoryLocationWindow(\n frontmatter: { valid_at?: string; observedAt?: string; invalid_at?: string },\n localDay: (instantUtc: string) => string,\n): WindowResult {\n const start = frontmatter.valid_at ?? frontmatter.observedAt;\n if (start === undefined || Number.isNaN(Date.parse(start))) {\n return { rejected: \"untimed\" };\n }\n const end = frontmatter.invalid_at;\n if (end === undefined || Number.isNaN(Date.parse(end)) || Date.parse(end) <= Date.parse(start)) {\n return { window: { kind: \"instant\", atUtc: start }, days: [localDay(start)] };\n }\n if (Date.parse(end) - Date.parse(start) > LOCATION_MATCH_MAX_MEMORY_SPAN_MS) {\n return { rejected: \"span-too-long\" };\n }\n // The interval is half-open [start, end): its last instant is end-1ms, so a\n // memory ending exactly at local midnight never touches the next day.\n const lastInstant = new Date(Date.parse(end) - 1).toISOString();\n const days = [localDay(start)];\n const endDay = localDay(lastInstant);\n if (endDay !== days[0]) days.push(endDay);\n return { window: { kind: \"interval\", startUtc: start, endUtc: end }, days };\n}\n\n/** One source's segments for a day, keyed by the configured source id. */\nexport interface LocationSourceSegments {\n sourceId: string;\n segments: LocationSegment[];\n}\n\nexport interface LocationMatch {\n sourceId: string;\n place: LocationPlace;\n /** Overlap (ms) with the window — interval mode; see `visitMs` otherwise. */\n overlapMs: number;\n /** Total dwell (ms) of the dominant place inside the window's day(s). */\n visitMs: number;\n /** Minimum contributing segment confidence, when any source reported one. */\n confidence?: number;\n /** Envelope [start, end) of the segments that produced the match. */\n visitStartUtc: string;\n visitEndUtc: string;\n}\n\nexport type MatchOutcome =\n | { status: \"matched\"; match: LocationMatch }\n | { status: \"unmatched\"; reason: \"no-overlap\" | \"ambiguous\" | \"below-minimum-overlap\" | \"below-minimum-confidence\" };\n\ninterface PlaceAccumulator {\n sourceId: string;\n place: LocationPlace;\n overlapMs: number;\n visitMs: number;\n confidence: number | undefined;\n startUtc: string;\n endUtc: string;\n}\n\nfunction overlapMsOf(\n window: MemoryLocationWindow,\n segment: { startUtc: string; endUtc: string },\n): number {\n if (window.kind === \"instant\") {\n const at = Date.parse(window.atUtc);\n return at >= Date.parse(segment.startUtc) && at < Date.parse(segment.endUtc) ? 1 : 0;\n }\n return Math.max(\n 0,\n Math.min(Date.parse(window.endUtc), Date.parse(segment.endUtc)) -\n Math.max(Date.parse(window.startUtc), Date.parse(segment.startUtc)),\n );\n}\n\n/**\n * The dominant qualifying place for a window, across every source's\n * segments. Deterministic: strict overlap comparison, ties (conflicts) are\n * rejected, and equal-place contributions merge across sources.\n */\nexport function matchDominantPlace(\n window: MemoryLocationWindow,\n sources: readonly LocationSourceSegments[],\n policy: LocationTagPolicy,\n): MatchOutcome {\n const byPlace = new Map<string, PlaceAccumulator>();\n for (const source of sources) {\n for (const segment of source.segments) {\n if (TAGGABLE_PLACE_KINDS[segment.place.kind ?? \"\"] !== true) continue;\n const overlap = overlapMsOf(window, segment);\n if (overlap <= 0) continue;\n const duration = Date.parse(segment.endUtc) - Date.parse(segment.startUtc);\n const existing = byPlace.get(segment.place.id);\n if (existing === undefined) {\n byPlace.set(segment.place.id, {\n sourceId: source.sourceId,\n place: segment.place,\n overlapMs: overlap,\n visitMs: duration,\n confidence: segment.confidence,\n startUtc: segment.startUtc,\n endUtc: segment.endUtc,\n });\n continue;\n }\n // Same place from another source (or a revisit): accumulate.\n existing.overlapMs += overlap;\n existing.visitMs += duration;\n existing.confidence =\n existing.confidence === undefined\n ? segment.confidence\n : segment.confidence === undefined\n ? existing.confidence\n : Math.min(existing.confidence, segment.confidence);\n if (segment.startUtc < existing.startUtc) existing.startUtc = segment.startUtc;\n if (segment.endUtc > existing.endUtc) existing.endUtc = segment.endUtc;\n }\n }\n if (byPlace.size === 0) return { status: \"unmatched\", reason: \"no-overlap\" };\n\n let best: PlaceAccumulator | null = null;\n let tie = false;\n for (const candidate of byPlace.values()) {\n if (best === null || candidate.overlapMs > best.overlapMs) {\n best = candidate;\n tie = false;\n continue;\n }\n if (candidate.overlapMs === best.overlapMs) tie = true;\n }\n if (best === null || tie) return { status: \"unmatched\", reason: \"ambiguous\" };\n\n const overlapForThreshold = window.kind === \"instant\" ? best.visitMs : best.overlapMs;\n if (policy.minimumOverlapSeconds > 0 && overlapForThreshold < policy.minimumOverlapSeconds * 1000) {\n return { status: \"unmatched\", reason: \"below-minimum-overlap\" };\n }\n if (best.confidence !== undefined && best.confidence < policy.minimumConfidence) {\n return { status: \"unmatched\", reason: \"below-minimum-confidence\" };\n }\n return {\n status: \"matched\",\n match: {\n sourceId: best.sourceId,\n place: best.place,\n overlapMs: best.overlapMs,\n visitMs: best.visitMs,\n ...(best.confidence !== undefined ? { confidence: best.confidence } : {}),\n visitStartUtc: best.startUtc,\n visitEndUtc: best.endUtc,\n },\n };\n}\n\n/** Deterministic tag slug for a provider place id (`reitti:place:7` → `reitti-place-7`). */\nexport function locationTagSlug(placeId: string): string {\n const slug = placeId\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return slug.slice(0, TAG_LIMITS.maxTagLength - LOCATION_TAG_PREFIX.length);\n}\n\n/** The provider-owned tag for a matched place. */\nexport function locationTagForPlace(placeId: string): string {\n return `${LOCATION_TAG_PREFIX}${locationTagSlug(placeId)}`;\n}\n\nfunction boundedAttributeValue(value: string): string {\n return value.slice(0, STRUCTURED_ATTRIBUTE_LIMITS.maxValueLength);\n}\n\n/** Provider-owned structured attributes describing a match. */\nexport function locationAttributesForMatch(\n match: LocationMatch,\n policy: LocationTagPolicy,\n): Record<string, string> {\n const attributes: Record<string, string> = {\n locationSource: match.sourceId,\n locationPlaceId: boundedAttributeValue(match.place.id),\n locationPlace: boundedAttributeValue(match.place.label),\n ...(match.confidence !== undefined ? { locationConfidence: match.confidence.toFixed(3) } : {}),\n locationVisitStart: match.visitStartUtc,\n locationVisitEnd: match.visitEndUtc,\n };\n if (policy.retainCoordinates && match.place.latitude !== undefined && match.place.longitude !== undefined) {\n attributes.locationLatitude = String(match.place.latitude);\n attributes.locationLongitude = String(match.place.longitude);\n }\n return attributes;\n}\n\nexport interface LocationUpdatePlan {\n /** Patch for `writeMemoryFrontmatter`; absent when nothing changes. */\n patch?: { tags: string[]; structuredAttributes: Record<string, string> };\n /** Outcome for reporting. */\n outcome:\n | \"tagged\"\n | \"updated\"\n | \"removed\"\n | \"unchanged\"\n | \"manual-metadata\"\n | \"unmatched\"\n | \"untimed\"\n | \"span-too-long\";\n}\n\nfunction frontmatterHasLocationMetadata(\n frontmatter: { tags?: string[]; structuredAttributes?: Record<string, string> },\n): boolean {\n if ((frontmatter.tags ?? []).some((tag) => tag.startsWith(LOCATION_TAG_PREFIX))) return true;\n return Object.keys(frontmatter.structuredAttributes ?? {}).some((key) =>\n (LOCATION_ATTRIBUTE_KEYS as readonly string[]).includes(key),\n );\n}\n\nfunction isProviderOwned(frontmatter: { structuredAttributes?: Record<string, string> }): boolean {\n const source = frontmatter.structuredAttributes?.locationSource;\n return typeof source === \"string\" && source.length > 0;\n}\n\n/**\n * Plan the provider-owned update for one memory: a match adds/replaces ONLY\n * provider-owned fields; a lost match removes them (corrected observations);\n * manual location metadata (no `locationSource` marker) always wins and is\n * never touched. Unrelated tags and attributes survive every path.\n */\nexport function planLocationUpdate(\n frontmatter: { tags?: string[]; structuredAttributes?: Record<string, string> },\n outcome: MatchOutcome,\n policy: LocationTagPolicy,\n): LocationUpdatePlan {\n if (frontmatterHasLocationMetadata(frontmatter) && !isProviderOwned(frontmatter)) {\n return { outcome: \"manual-metadata\" };\n }\n const tags = [...(frontmatter.tags ?? [])];\n const attributes = { ...(frontmatter.structuredAttributes ?? {}) };\n const providerTagIndex = tags.findIndex((tag) => tag.startsWith(LOCATION_TAG_PREFIX));\n const stripProviderFields = (): void => {\n if (providerTagIndex >= 0) tags.splice(providerTagIndex, 1);\n for (const key of LOCATION_ATTRIBUTE_KEYS) delete attributes[key];\n };\n\n if (outcome.status !== \"matched\") {\n if (!isProviderOwned(frontmatter)) return { outcome: \"unmatched\" };\n if (providerTagIndex < 0 && !(\"locationPlaceId\" in attributes)) {\n return { outcome: \"unchanged\" };\n }\n stripProviderFields();\n return { patch: { tags, structuredAttributes: attributes }, outcome: \"removed\" };\n }\n\n const desiredTag = locationTagForPlace(outcome.match.place.id);\n const desiredAttributes = locationAttributesForMatch(outcome.match, policy);\n if (tags.length >= TAG_LIMITS.maxTags && providerTagIndex < 0) {\n // Full tag budget with no provider slot to reuse — leave the memory alone.\n return { outcome: \"unchanged\" };\n }\n const alreadyCurrent =\n providerTagIndex >= 0 &&\n tags[providerTagIndex] === desiredTag &&\n LOCATION_ATTRIBUTE_KEYS.every((key) => {\n const want = desiredAttributes[key];\n const have = attributes[key];\n return want === undefined ? have === undefined : have === want;\n });\n if (alreadyCurrent) return { outcome: \"unchanged\" };\n\n if (providerTagIndex >= 0) tags[providerTagIndex] = desiredTag;\n else tags.push(desiredTag);\n for (const key of LOCATION_ATTRIBUTE_KEYS) delete attributes[key];\n Object.assign(attributes, desiredAttributes);\n const wasProviderOwned = isProviderOwned(frontmatter);\n return {\n patch: { tags, structuredAttributes: attributes },\n outcome: wasProviderOwned ? \"updated\" : \"tagged\",\n };\n}\n","/**\n * Location day-file + sync-state store (issue #2044).\n *\n * Day documents live at `<memoryDir>/locations/<YYYY-MM-DD>.md` — OUTSIDE the\n * memory scan roots (facts/, entities/, …) so raw location history never\n * surfaces as a memory in recall or governance passes, and INSIDE the QMD\n * collection root so the markdown stays full-text searchable once an index\n * update runs. Per-source sync state lives at\n * `<memoryDir>/state/locations/sync.json` and is written only after the day\n * document is durable (see pipeline.ts).\n *\n * Both writes are atomic (temp file + rename) and containment-checked: a\n * symlinked `locations/` root or day file is rejected even when it resolves\n * inside the memory dir (AGENTS.md pattern #3), and a path that escapes the\n * memory dir is refused outright.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { lstat, mkdir, readFile, readdir, realpath } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { writeFileAtomically } from \"../maintenance/atomic-file.js\";\nimport { pathIsInside } from \"../utils/path-containment.js\";\nimport { isValidLocationDate, placeDurations } from \"./intervals.js\";\nimport type { LocationSegment } from \"./types.js\";\n\nexport const LOCATIONS_DIR_NAME = \"locations\";\nexport const LOCATION_DAY_KIND = \"location-day\";\nexport const LOCATION_DAY_FORMAT_VERSION = 1;\n\n/** Cap on remembered day payloads per source (~3 months of days). */\nconst MAX_TRACKED_LOCATION_DAYS = 90;\n\nconst DAY_FILE_NAME_PATTERN = /^\\d{4}-\\d{2}-\\d{2}\\.md$/;\n\n// Serializes state load→mutate→save sections per state file so two sources\n// syncing concurrently cannot lose-update each other's entries.\nconst stateLocks = new Map<string, Promise<unknown>>();\n\nfunction withStateLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n const prior = stateLocks.get(key) ?? Promise.resolve();\n const next = prior.then(fn, fn);\n const tail = next.then(\n () => undefined,\n () => undefined,\n );\n stateLocks.set(key, tail);\n void tail.then(() => {\n if (stateLocks.get(key) === tail) stateLocks.delete(key);\n });\n return next;\n}\n\nexport function locationDayFilePath(memoryDir: string, date: string): string {\n if (!isValidLocationDate(date)) {\n // Never interpolate an unvalidated date into a filesystem path (a `../`\n // would escape <memoryDir>/locations/). Reject loudly.\n throw new RangeError(`Invalid location date \"${date}\"; expected a real YYYY-MM-DD day.`);\n }\n return path.join(memoryDir, LOCATIONS_DIR_NAME, `${date}.md`);\n}\n\nexport function locationSyncStateFilePath(memoryDir: string): string {\n return path.join(memoryDir, \"state\", \"locations\", \"sync.json\");\n}\n\n/** A source's derived day payload, kept in sync state for multi-source merges. */\nexport interface LocationSourceDayPayload {\n observationCount: number;\n segments: LocationSegment[];\n providerDisplayName?: string;\n}\n\nexport interface LocationSourceSyncState {\n lastSyncedAtUtc?: string;\n days?: Record<string, LocationSourceDayPayload>;\n}\n\nexport interface LocationSyncStateFile {\n version: 1;\n sources: Record<string, LocationSourceSyncState>;\n}\n\nexport function emptyLocationSyncState(): LocationSyncStateFile {\n return { version: 1, sources: {} };\n}\n\nexport async function loadLocationSyncState(memoryDir: string): Promise<LocationSyncStateFile> {\n let raw: string;\n try {\n raw = await readFile(locationSyncStateFilePath(memoryDir), \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return emptyLocationSyncState();\n throw err;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // A corrupt state file must not brick syncing forever; treat it as a cold\n // start. The worst case is re-syncing days already on disk, and the day\n // file's contentHash makes that rewrite idempotent.\n return emptyLocationSyncState();\n }\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n Array.isArray(parsed) ||\n typeof (parsed as LocationSyncStateFile).sources !== \"object\" ||\n (parsed as LocationSyncStateFile).sources === null\n ) {\n return emptyLocationSyncState();\n }\n return { version: 1, sources: (parsed as LocationSyncStateFile).sources };\n}\n\nexport async function saveLocationSyncState(\n memoryDir: string,\n state: LocationSyncStateFile,\n): Promise<void> {\n await mkdir(path.dirname(locationSyncStateFilePath(memoryDir)), { recursive: true, mode: 0o700 });\n await writeFileAtomically(\n locationSyncStateFilePath(memoryDir),\n `${JSON.stringify(state, null, 2)}\\n`,\n );\n}\n\nfunction pruneSourceDays(source: LocationSourceSyncState): void {\n const days = source.days;\n if (days === undefined) return;\n const keys = Object.keys(days).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n for (const key of keys.slice(0, Math.max(0, keys.length - MAX_TRACKED_LOCATION_DAYS))) {\n delete days[key];\n }\n}\n\n/**\n * Record one source's completed day sync. Runs the whole read-modify-write\n * under the per-directory state lock so concurrent sources cannot clobber\n * each other, and prunes day payloads beyond the retention cap.\n */\nexport async function updateLocationSourceDay(\n memoryDir: string,\n sourceId: string,\n date: string,\n payload: LocationSourceDayPayload,\n completedAtUtc: string,\n): Promise<LocationSyncStateFile> {\n if (!isValidLocationDate(date)) {\n throw new RangeError(`Invalid location date \"${date}\"; expected a real YYYY-MM-DD day.`);\n }\n return withStateLock(locationSyncStateFilePath(memoryDir), async () => {\n const state = await loadLocationSyncState(memoryDir);\n const source = state.sources[sourceId] ?? {};\n const days = source.days ?? {};\n days[date] = payload;\n source.days = days;\n source.lastSyncedAtUtc = completedAtUtc;\n state.sources[sourceId] = source;\n for (const candidate of Object.values(state.sources)) pruneSourceDays(candidate);\n await saveLocationSyncState(memoryDir, state);\n return state;\n });\n}\n/** Frontmatter persisted on a rendered day document. */\nexport interface LocationDayMeta {\n kind: typeof LOCATION_DAY_KIND;\n date: string;\n timezone: string;\n formatVersion: number;\n sources: string[];\n observationCount: number;\n /** SHA-256 of the rendered body (rebuild idempotency). */\n contentHash: string;\n}\n\n/** A parsed day document (frontmatter + rendered body). */\nexport interface LocationDayDocument {\n meta: LocationDayMeta;\n body: string;\n}\n\n/** The per-source view a day document is composed from. */\nexport interface LocationDaySourceEntry extends LocationSourceDayPayload {\n /** Provider display name for the timeline heading; falls back to the id. */\n providerDisplayName?: string;\n}\n\nexport function hashLocationBody(body: string): string {\n return createHash(\"sha256\").update(body, \"utf8\").digest(\"hex\");\n}\n\nfunction clockHhMm(iso: string, timezone: string): string {\n return new Intl.DateTimeFormat(\"en-GB\", {\n timeZone: timezone,\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n }).format(new Date(iso));\n}\n\nfunction formatDuration(ms: number): string {\n const totalMinutes = Math.round(ms / 60_000);\n const hours = Math.floor(totalMinutes / 60);\n const minutes = totalMinutes % 60;\n if (hours <= 0) return `${minutes}m`;\n return `${hours}h ${String(minutes).padStart(2, \"0\")}m`;\n}\n\n/** Collapse whitespace and strip markdown emphasis/backticks from a label. */\nfunction safeLabel(label: string): string {\n return label.replace(/\\s+/g, \" \").replace(/[`*_]/g, \"\").trim();\n}\n\nfunction formatPlaceLabel(segment: LocationSegment): string {\n const label = safeLabel(segment.place.label);\n return segment.place.kind === undefined ? label : `${label} (${segment.place.kind})`;\n}\n\nfunction formatCoordinates(segment: LocationSegment): string {\n if (segment.place.latitude === undefined || segment.place.longitude === undefined) return \"\";\n return ` @ ${segment.place.latitude.toFixed(4)},${segment.place.longitude.toFixed(4)}`;\n}\n\nexport function composeLocationDayBody(\n date: string,\n timezone: string,\n sources: Record<string, LocationDaySourceEntry>,\n): string {\n const lines: string[] = [`# Location day — ${date} (${timezone})`, \"\"];\n const allSegments: LocationSegment[] = [];\n let observationCount = 0;\n for (const entry of Object.values(sources)) {\n allSegments.push(...entry.segments);\n observationCount += entry.observationCount;\n }\n\n lines.push(\"## Summary\", \"\");\n if (observationCount === 0) {\n lines.push(\"No location observations recorded.\", \"\");\n } else {\n const durations = placeDurations(allSegments).sort((a, b) => b.totalMs - a.totalMs);\n lines.push(\n `- Places visited: ${durations.length}`,\n ...durations.map(\n (duration) => `- ${safeLabel(duration.place.label)}: ${formatDuration(duration.totalMs)}`,\n ),\n \"\",\n );\n }\n\n const sourceIds = Object.keys(sources).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n for (const sourceId of sourceIds) {\n const entry = sources[sourceId]!;\n lines.push(`## Timeline — ${safeLabel(entry.providerDisplayName ?? sourceId)}`, \"\");\n if (entry.segments.length === 0) {\n lines.push(\"No observations.\", \"\");\n continue;\n }\n for (const segment of entry.segments) {\n lines.push(\n `- ${clockHhMm(segment.startUtc, timezone)}–${clockHhMm(segment.endUtc, timezone)} — ${formatPlaceLabel(segment)}${formatCoordinates(segment)}`,\n );\n }\n lines.push(\"\");\n }\n return `${lines.join(\"\\n\").trimEnd()}\\n`;\n}\n\nexport function composeLocationDayMeta(\n date: string,\n timezone: string,\n sources: Record<string, LocationDaySourceEntry>,\n body: string,\n): LocationDayMeta {\n let observationCount = 0;\n for (const entry of Object.values(sources)) observationCount += entry.observationCount;\n return {\n kind: LOCATION_DAY_KIND,\n date,\n timezone,\n formatVersion: LOCATION_DAY_FORMAT_VERSION,\n sources: Object.keys(sources).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),\n observationCount,\n contentHash: hashLocationBody(body),\n };\n}\n\nfunction frontmatterScalar(key: string, value: string | number): string {\n return `${key}: ${typeof value === \"number\" ? value : JSON.stringify(value)}`;\n}\n\nfunction frontmatterList(key: string, values: readonly string[]): string[] {\n if (values.length === 0) return [`${key}: []`];\n const lines = [`${key}:`];\n for (const value of values) lines.push(` - ${JSON.stringify(value)}`);\n return lines;\n}\n\nexport function serializeLocationDay(meta: LocationDayMeta, body: string): string {\n const lines: string[] = [\"---\"];\n lines.push(frontmatterScalar(\"kind\", meta.kind));\n lines.push(frontmatterScalar(\"date\", meta.date));\n lines.push(frontmatterScalar(\"timezone\", meta.timezone));\n lines.push(frontmatterScalar(\"formatVersion\", meta.formatVersion));\n lines.push(...frontmatterList(\"sources\", meta.sources));\n lines.push(frontmatterScalar(\"observationCount\", meta.observationCount));\n lines.push(frontmatterScalar(\"contentHash\", meta.contentHash));\n lines.push(\"---\", \"\", body);\n return lines.join(\"\\n\");\n}\n\nfunction parseScalar(raw: string): string {\n try {\n const parsed = JSON.parse(raw);\n return typeof parsed === \"string\" ? parsed : raw;\n } catch {\n return raw;\n }\n}\n\n/** A day document's frontmatter, parsed for idempotency checks and status. */\nexport interface LocationDaySummary {\n date: string;\n timezone: string;\n sources: string[];\n observationCount: number;\n contentHash: string;\n}\n\n/**\n * Parse a day document's frontmatter. Returns null when the content is not a\n * location day document (wrong kind, missing frontmatter) so callers can\n * distinguish a foreign file from a corrupt one.\n */\nexport function parseLocationDaySummary(raw: string): LocationDaySummary | null {\n if (!raw.startsWith(\"---\\n\")) return null;\n const closeIndex = raw.indexOf(\"\\n---\\n\", 4);\n if (closeIndex === -1) return null;\n const scalars: Record<string, string> = {};\n const lists: Record<string, string[]> = {};\n let currentList: string | null = null;\n for (const line of raw.slice(4, closeIndex).split(\"\\n\")) {\n const listItem = /^ {2}- (.*)$/.exec(line);\n if (listItem !== null && currentList !== null) {\n lists[currentList]!.push(parseScalar(listItem[1]!));\n continue;\n }\n currentList = null;\n const separator = line.indexOf(\":\");\n if (separator === -1) continue;\n const key = line.slice(0, separator);\n const value = line.slice(separator + 1).trim();\n if (value === \"\" || value === \"[]\") {\n lists[key] = [];\n currentList = key;\n continue;\n }\n if (value.startsWith(\"[\")) {\n lists[key] = [parseScalar(value)];\n currentList = key;\n continue;\n }\n scalars[key] = value;\n }\n if (scalars.kind === undefined || parseScalar(scalars.kind) !== LOCATION_DAY_KIND) return null;\n if (scalars.date === undefined || scalars.contentHash === undefined) return null;\n return {\n date: parseScalar(scalars.date),\n timezone: scalars.timezone === undefined ? \"UTC\" : parseScalar(scalars.timezone),\n sources: lists.sources ?? [],\n observationCount: scalars.observationCount === undefined ? 0 : Number(scalars.observationCount) || 0,\n contentHash: parseScalar(scalars.contentHash),\n };\n}\n\n// ── Containment-checked day-file IO ─────────────────────────────────────────\n\nfunction isErrnoCode(err: unknown, code: string): boolean {\n return err instanceof Error && (err as NodeJS.ErrnoException).code === code;\n}\n\nasync function assertNotSymlink(target: string, kind: \"directory\" | \"day file\"): Promise<void> {\n let stat: { isSymbolicLink(): boolean };\n try {\n stat = await lstat(target);\n } catch (err) {\n if (isErrnoCode(err, \"ENOENT\")) return;\n throw err;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(\n `locations ${kind} '${path.basename(target)}' is a symbolic link — refusing to follow it even when it resolves inside the memory dir (AGENTS.md pattern #3)`,\n );\n }\n}\n\nasync function assertNoEscape(rootReal: string, candidate: string): Promise<void> {\n let candidateReal: string;\n try {\n candidateReal = await realpath(candidate);\n } catch {\n return;\n }\n if (!pathIsInside(rootReal, candidateReal)) {\n throw new Error(\n \"location day path resolves outside the memory dir — refusing to follow a symlink/traversal (AGENTS.md pattern #3)\",\n );\n }\n}\n\nasync function assertDayPathContained(memoryDir: string, targetPath: string): Promise<void> {\n let rootReal: string;\n try {\n rootReal = await realpath(memoryDir);\n } catch {\n rootReal = path.resolve(memoryDir);\n }\n const locationsDir = path.join(memoryDir, LOCATIONS_DIR_NAME);\n await assertNoEscape(rootReal, locationsDir);\n await assertNoEscape(rootReal, targetPath);\n // Reject a symlinked locations root or day file outright — even when the\n // link resolves inside the memory dir (aliasing / tampering) — before IO.\n await assertNotSymlink(locationsDir, \"directory\");\n await assertNotSymlink(targetPath, \"day file\");\n}\n\n/**\n * Idempotently persist a day document: skip the write when the on-disk\n * contentHash already matches. The write is atomic and containment-checked;\n * the caller (pipeline) advances sync state only after this resolves.\n */\nexport async function writeLocationDay(\n memoryDir: string,\n date: string,\n serialized: string,\n): Promise<boolean> {\n const target = locationDayFilePath(memoryDir, date);\n await assertDayPathContained(memoryDir, target);\n try {\n if ((await readFile(target, \"utf-8\")) === serialized) return false;\n } catch (err) {\n if (!isErrnoCode(err, \"ENOENT\")) throw err;\n }\n await mkdir(path.dirname(target), { recursive: true, mode: 0o700 });\n await writeFileAtomically(target, serialized);\n return true;\n}\n\n/** Read a stored day document's raw markdown; null when absent. */\nexport async function readLocationDay(memoryDir: string, date: string): Promise<string | null> {\n const target = locationDayFilePath(memoryDir, date);\n await assertDayPathContained(memoryDir, target);\n try {\n return await readFile(target, \"utf-8\");\n } catch (err) {\n if (isErrnoCode(err, \"ENOENT\")) return null;\n throw err;\n }\n}\n\n/** List dates that have a stored day document, newest first. */\nexport async function listLocationDayDates(memoryDir: string): Promise<string[]> {\n const locationsDir = path.join(memoryDir, LOCATIONS_DIR_NAME);\n await assertDayPathContained(memoryDir, locationsDir);\n let entries: string[];\n try {\n entries = await readdir(locationsDir);\n } catch (err) {\n if (isErrnoCode(err, \"ENOENT\")) return [];\n throw err;\n }\n const dates = entries\n .filter((entry) => DAY_FILE_NAME_PATTERN.test(entry))\n .map((entry) => entry.slice(0, -3));\n dates.sort((a, b) => (a > b ? -1 : a < b ? 1 : 0));\n return dates;\n}\n","/**\n * Location tagging service (issue #2046) — the IO half around the pure\n * matcher in `matching.ts`. Three consumers share it so the policy can never\n * fork:\n *\n * 1. Write-time enrichment: after an extraction batch has cleared the\n * trust/importance/content-hash gates and persisted, the caller tags the\n * new memories (post-write, best-effort, never fails the extraction).\n * 2. Historical backfill (`location/backfill.ts`): the same plan/apply core\n * over a bounded date range, idempotent by construction.\n * 3. Wearable conversation fill: `WearableConversation.location` is filled\n * ONLY when the source did not provide one and the match passes policy.\n *\n * Segment data comes from the machine-global location sync state\n * (`state/locations/sync.json`, ≤ 90 tracked days — matching the bounded\n * backfill range). Reads are plain file loads; every memory mutation goes\n * through `StorageManager.writeMemoryFrontmatter`, the atomic frontmatter\n * chokepoint that preserves content (and therefore the content hash),\n * lifecycle, and versioning invariants.\n */\n\nimport type { StorageManager } from \"../index.js\";\nimport { log } from \"../logger.js\";\nimport type { MemoryFile } from \"../types.js\";\nimport type { WearableConversation } from \"../wearables/types.js\";\nimport { localDayKey, placeDurations } from \"./intervals.js\";\nimport {\n matchDominantPlace,\n memoryLocationWindow,\n planLocationUpdate,\n type LocationSourceSegments,\n type LocationTagPolicy,\n type LocationUpdatePlan,\n} from \"./matching.js\";\nimport { loadLocationSyncState } from \"./store.js\";\nimport type { LocationConfig } from \"./types.js\";\n\n/** Segments for one local day, per source id, from the sync state. */\nexport type DaySegmentIndex = Map<string, LocationSourceSegments[]>;\n\n/** Load every tracked day's per-source segments (one file read). */\nexport async function loadDaySegmentIndex(memoryDir: string): Promise<DaySegmentIndex> {\n const state = await loadLocationSyncState(memoryDir);\n const index: DaySegmentIndex = new Map();\n for (const [sourceId, source] of Object.entries(state.sources)) {\n for (const [date, payload] of Object.entries(source.days ?? {})) {\n const entry: LocationSourceSegments = { sourceId, segments: payload.segments ?? [] };\n const existing = index.get(date);\n if (existing === undefined) index.set(date, [entry]);\n else existing.push(entry);\n }\n }\n return index;\n}\n\nexport function locationTagPolicy(config: LocationConfig): LocationTagPolicy {\n return {\n minimumOverlapSeconds: config.minimumOverlapSeconds,\n minimumConfidence: config.minimumConfidence,\n retainCoordinates: config.retainCoordinates,\n };\n}\n\n/** Every tagging path is off unless BOTH gates are on. */\nexport function locationTaggingEnabled(config: LocationConfig): boolean {\n return config.enabled && config.tagging.enabled;\n}\n\n/**\n * Write-time post-persist hook (issue #2046): tag the memories an extraction\n * batch just made durable. Best-effort by contract — gated on both location\n * gates, and a location failure must never fail the extraction that already\n * persisted.\n */\nexport async function tagPersistedMemories(\n storage: StorageManager,\n memoryIds: string[],\n config: { memoryDir: string; location?: LocationConfig },\n): Promise<void> {\n if (memoryIds.length === 0 || !config.location || !locationTaggingEnabled(config.location)) return;\n try {\n await enrichMemoriesWithLocation({\n storage,\n memoryIds,\n memoryDir: config.memoryDir,\n config: config.location,\n });\n } catch (locationError) {\n log.warn(\"[location] post-write tagging failed (non-fatal)\", locationError);\n }\n}\n\nfunction segmentsForDays(index: DaySegmentIndex, days: readonly string[]): LocationSourceSegments[] {\n const merged = new Map<string, LocationSourceSegments>();\n for (const day of days) {\n for (const entry of index.get(day) ?? []) {\n const existing = merged.get(entry.sourceId);\n if (existing === undefined) {\n merged.set(entry.sourceId, { sourceId: entry.sourceId, segments: [...entry.segments] });\n continue;\n }\n existing.segments.push(...entry.segments);\n }\n }\n return [...merged.values()];\n}\n\n/** The full plan for one memory: window → segments → match → provider-owned diff. */\nfunction planForMemory(\n frontmatter: Pick<MemoryFile[\"frontmatter\"], \"valid_at\" | \"observedAt\" | \"invalid_at\" | \"tags\" | \"structuredAttributes\">,\n index: DaySegmentIndex,\n config: LocationConfig,\n): LocationUpdatePlan {\n const policy = locationTagPolicy(config);\n const result = memoryLocationWindow(frontmatter, (instant) => localDayKey(instant, config.timezone));\n if (result.rejected === \"span-too-long\") return { outcome: \"span-too-long\" };\n if (result.rejected === \"untimed\" || result.window === undefined || result.days === undefined) {\n return { outcome: \"untimed\" };\n }\n const sources = segmentsForDays(index, result.days);\n return planLocationUpdate(frontmatter, matchDominantPlace(result.window, sources, policy), policy);\n}\n\nexport interface LocationEnrichmentCounts {\n tagged: number;\n updated: number;\n removed: number;\n unchanged: number;\n unmatched: number;\n manual: number;\n untimed: number;\n spanTooLong: number;\n failed: number;\n}\n\nexport function emptyEnrichmentCounts(): LocationEnrichmentCounts {\n return {\n tagged: 0,\n updated: 0,\n removed: 0,\n unchanged: 0,\n unmatched: 0,\n manual: 0,\n untimed: 0,\n spanTooLong: 0,\n failed: 0,\n };\n}\n\nfunction countOutcome(counts: LocationEnrichmentCounts, outcome: LocationUpdatePlan[\"outcome\"]): void {\n if (outcome === \"tagged\") counts.tagged += 1;\n else if (outcome === \"updated\") counts.updated += 1;\n else if (outcome === \"removed\") counts.removed += 1;\n else if (outcome === \"unchanged\") counts.unchanged += 1;\n else if (outcome === \"manual-metadata\") counts.manual += 1;\n else if (outcome === \"unmatched\") counts.unmatched += 1;\n else if (outcome === \"untimed\") counts.untimed += 1;\n else if (outcome === \"span-too-long\") counts.spanTooLong += 1;\n}\n\nexport interface EnrichMemoriesOptions {\n storage: Pick<StorageManager, \"getMemoryById\" | \"writeMemoryFrontmatter\">;\n memoryDir: string;\n config: LocationConfig;\n /** Enrich exactly these memory ids (write-time path). */\n memoryIds?: string[];\n /** Pre-loaded memories (backfill path — one readAllMemories per run). */\n memories?: MemoryFile[];\n /** Reuse a caller-loaded segment index (backfill). */\n index?: DaySegmentIndex;\n /** Skip every mutation and only report (dry-run). */\n dryRun?: boolean;\n}\n\n/**\n * Apply the matcher to a set of memories and patch provider-owned location\n * metadata. A failure on ONE memory is counted and skipped — it never blocks\n * the others (failure isolation, issue #2046).\n */\nexport async function enrichMemoriesWithLocation(\n options: EnrichMemoriesOptions,\n): Promise<LocationEnrichmentCounts> {\n if (!locationTaggingEnabled(options.config)) return emptyEnrichmentCounts();\n const counts = emptyEnrichmentCounts();\n const index = options.index ?? (await loadDaySegmentIndex(options.memoryDir));\n\n let memories = options.memories;\n if (memories === undefined && options.memoryIds !== undefined) {\n memories = [];\n for (const id of options.memoryIds) {\n const memory = await options.storage.getMemoryById(id);\n if (memory !== null) memories.push(memory);\n }\n }\n if (memories === undefined) return counts;\n\n for (const memory of memories) {\n try {\n const plan = planForMemory(memory.frontmatter, index, options.config);\n // Count patch outcomes (tagged/updated/removed) only after the write\n // lands — a rejected write is `failed`, never also `tagged`.\n if (plan.patch === undefined || options.dryRun === true) {\n countOutcome(counts, plan.outcome);\n continue;\n }\n await options.storage.writeMemoryFrontmatter(memory, plan.patch, { actor: \"location-tagging\" });\n countOutcome(counts, plan.outcome);\n } catch {\n counts.failed += 1;\n }\n }\n return counts;\n}\n\n/**\n * Fill `WearableConversation.location` for conversations whose source did not\n * provide one. Missing-only and source-aware: a provider-supplied value is\n * never overwritten, and a failing conversation never blocks the others.\n */\nexport async function fillWearableConversationLocations(\n conversations: WearableConversation[],\n deps: { memoryDir: string; config: LocationConfig },\n): Promise<void> {\n if (!locationTaggingEnabled(deps.config) || conversations.length === 0) return;\n let index: DaySegmentIndex;\n try {\n index = await loadDaySegmentIndex(deps.memoryDir);\n } catch {\n return;\n }\n const policy = locationTagPolicy(deps.config);\n for (const conversation of conversations) {\n if (conversation.location !== undefined && conversation.location.length > 0) continue;\n try {\n const startMs = Date.parse(conversation.startIso);\n const endMs = conversation.endIso !== undefined ? Date.parse(conversation.endIso) : Number.NaN;\n const window =\n Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs\n ? { kind: \"interval\" as const, startUtc: conversation.startIso, endUtc: conversation.endIso as string }\n : { kind: \"instant\" as const, atUtc: conversation.startIso };\n const anchor = window.kind === \"interval\" ? new Date(Date.parse(window.endUtc) - 1).toISOString() : window.atUtc;\n const days = [...new Set([localDayKey(anchor, deps.config.timezone), localDayKey(conversation.startIso, deps.config.timezone)])];\n const outcome = matchDominantPlace(window, segmentsForDays(index, days), policy);\n if (outcome.status === \"matched\") {\n conversation.location = outcome.match.place.label;\n }\n } catch {\n // One bad conversation never blocks the rest of the day's fill.\n }\n }\n}\n\n/** Render an opt-in, labels-only location context section for a day summary. */\nexport async function renderDayLocationContext(\n memoryDir: string,\n date: string,\n config: LocationConfig,\n): Promise<string | null> {\n if (!locationTaggingEnabled(config)) return null;\n const state = await loadLocationSyncState(memoryDir);\n const segments = [];\n for (const source of Object.values(state.sources)) {\n for (const segment of source.days?.[date]?.segments ?? []) segments.push(segment);\n }\n if (segments.length === 0) return null;\n const durations = placeDurations(segments)\n .filter((duration) => [\"home\", \"work\", \"poi\"].includes(duration.place.kind ?? \"poi\"))\n .sort((a, b) => (a.totalMs !== b.totalMs ? b.totalMs - a.totalMs : a.place.id < b.place.id ? -1 : 1));\n if (durations.length === 0) return null;\n const lines = durations.map((duration) => {\n const minutes = Math.round(duration.totalMs / 60_000);\n const hours = Math.floor(minutes / 60);\n const rendered = hours <= 0 ? `${minutes}m` : `${hours}h ${String(minutes % 60).padStart(2, \"0\")}m`;\n return `- ${duration.place.label.replace(/\\s+/g, \" \").replace(/[`*_]/g, \"\").trim()}: ${rendered}`;\n });\n return `## Location context\\n\\n${lines.join(\"\\n\")}`;\n}\n"],"mappings":"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","names":[]}
|