@remnic/core 9.6.23 → 9.6.25

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.
Files changed (45) hide show
  1. package/dist/access-admin-ops-surface.js +1 -1
  2. package/dist/access-boundary.js +1 -1
  3. package/dist/access-cli.js +4 -4
  4. package/dist/access-http.js +3 -3
  5. package/dist/access-identity-continuity-surface.js +1 -1
  6. package/dist/access-lcm-surface.js +1 -1
  7. package/dist/access-mcp.js +2 -2
  8. package/dist/access-observe-write-surface.js +1 -1
  9. package/dist/access-operations-batch.js +1 -1
  10. package/dist/access-operations.d.ts +3 -3
  11. package/dist/access-operations.js +1 -1
  12. package/dist/access-recall-surface.js +1 -1
  13. package/dist/access-schema.d.ts +64 -64
  14. package/dist/access-service.js +1 -1
  15. package/dist/{chunk-KEY3FEMP.js → chunk-43VNWTBB.js} +3 -3
  16. package/dist/{chunk-A2OFRQ5B.js → chunk-N6VIXDT7.js} +2 -2
  17. package/dist/{chunk-ERA5RSMZ.js → chunk-P32IDSHD.js} +1 -1
  18. package/dist/{chunk-ERA5RSMZ.js.map → chunk-P32IDSHD.js.map} +1 -1
  19. package/dist/{chunk-D3D677BV.js → chunk-QLYLTKUG.js} +4 -4
  20. package/dist/{chunk-D3D677BV.js.map → chunk-QLYLTKUG.js.map} +1 -1
  21. package/dist/cli.js +4 -4
  22. package/dist/coding/coding-graph-types.d.ts +23 -1
  23. package/dist/coding/coding-graph-types.js +1 -1
  24. package/dist/coding/optional-coding-graph.js +1 -1
  25. package/dist/index.js +4 -4
  26. package/dist/orchestrator.js +4 -4
  27. package/dist/schemas.d.ts +74 -74
  28. package/dist/shared-context/manager.d.ts +8 -8
  29. package/dist/transfer/types.d.ts +66 -66
  30. package/package.json +2 -2
  31. package/src/access-http.test.ts +2 -2
  32. package/src/access-mcp.test.ts +2 -2
  33. package/src/access-mcp.ts +3 -3
  34. package/src/access-service-coding-write.test.ts +18 -18
  35. package/src/access-service-lcm-forgery.test.ts +5 -5
  36. package/src/access-service-observe-lcm-parity.test.ts +25 -25
  37. package/src/access-service-observe-scope.test.ts +16 -16
  38. package/src/access-service-project-tag.test.ts +2 -2
  39. package/src/access-service-raw-excerpt-read-gate.test.ts +1 -1
  40. package/src/coding/coding-graph-types.ts +23 -0
  41. package/src/coding/coding-namespace.test.ts +3 -3
  42. package/src/coding/wire-coding-context.test.ts +22 -22
  43. package/src/issue-1492-coding-mode-consistency.test.ts +2 -2
  44. /package/dist/{chunk-KEY3FEMP.js.map → chunk-43VNWTBB.js.map} +0 -0
  45. /package/dist/{chunk-A2OFRQ5B.js.map → chunk-N6VIXDT7.js.map} +0 -0
@@ -193,7 +193,7 @@ test("#1495 P1 FORGERY BLOCKED: a default-only caller cannot read another tenant
193
193
  const victimRes = await service.observe(
194
194
  observeRequest({
195
195
  sessionKey: "alice:s1",
196
- projectTag: "Blend/Supply",
196
+ projectTag: "Acme/Webshop",
197
197
  }),
198
198
  );
199
199
  const victimWriteKey = probe.rows[0]!.session_id;
@@ -235,7 +235,7 @@ test("#1495 P1 FORGERY BLOCKED: a default-only caller cannot read another tenant
235
235
  const service = new EngramAccessService(probe.orch);
236
236
 
237
237
  const victimRes = await service.observe(
238
- observeRequest({ sessionKey: "alice:s1", projectTag: "Blend/Supply" }),
238
+ observeRequest({ sessionKey: "alice:s1", projectTag: "Acme/Webshop" }),
239
239
  );
240
240
  const victimOverlayNs = victimRes.effectiveNamespace!;
241
241
  assert.ok(victimOverlayNs.startsWith("alice-"));
@@ -268,7 +268,7 @@ test("#1495 P1 LEGITIMATE ACCESS PRESERVED: the victim still reads its OWN overl
268
268
 
269
269
  // alice archives under her overlay AND binds the coding context to her session.
270
270
  await service.observe(
271
- observeRequest({ sessionKey: "alice:s1", projectTag: "Blend/Supply" }),
271
+ observeRequest({ sessionKey: "alice:s1", projectTag: "Acme/Webshop" }),
272
272
  );
273
273
 
274
274
  // A same-session lcmSearch by alice (no explicit namespace) must reach her rows:
@@ -330,7 +330,7 @@ test("#1505 codex P1 r2 FORGERY BLOCKED: explicit-namespace + sessionless lcmSea
330
330
 
331
331
  // VICTIM archives overlay rows.
332
332
  const victimRes = await service.observe(
333
- observeRequest({ sessionKey: "alice:s1", projectTag: "Blend/Supply" }),
333
+ observeRequest({ sessionKey: "alice:s1", projectTag: "Acme/Webshop" }),
334
334
  );
335
335
  assert.ok(victimRes.effectiveNamespace!.startsWith("alice-"));
336
336
 
@@ -361,7 +361,7 @@ test("#1505 codex P1 r2 FORGERY BLOCKED: default-readable implicit + sessionless
361
361
  const service = new EngramAccessService(probe.orch);
362
362
 
363
363
  const victimRes = await service.observe(
364
- observeRequest({ sessionKey: "alice:s1", projectTag: "Blend/Supply" }),
364
+ observeRequest({ sessionKey: "alice:s1", projectTag: "Acme/Webshop" }),
365
365
  );
366
366
  assert.ok(victimRes.effectiveNamespace!.startsWith("alice-"));
367
367
 
@@ -305,12 +305,12 @@ test("#1505 thread 2 (c) projectTag: observe LCM write key == recall reader key
305
305
  const service = new EngramAccessService(probe.orch);
306
306
 
307
307
  const res = await service.observe(
308
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
308
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
309
309
  );
310
310
 
311
311
  const expectedNs = combineNamespaces(
312
312
  "pi-geek",
313
- projectNamespaceName(projectTagProjectId("Blend/Supply")),
313
+ projectNamespaceName(projectTagProjectId("Acme/Webshop")),
314
314
  );
315
315
  const expectedKey = encodeNs(expectedNs, "pi-geek:abc123");
316
316
  assert.equal(res.effectiveNamespace, expectedNs);
@@ -442,7 +442,7 @@ test("#1505 thread 2 (d) projectScope:false ⇒ raw sessionKey everywhere (singl
442
442
  const service = new EngramAccessService(probe.orch);
443
443
 
444
444
  const res = await service.observe(
445
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
445
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
446
446
  );
447
447
  // No overlay ⇒ effective namespace is the default store ⇒ raw key.
448
448
  assert.equal(res.effectiveNamespace, "default");
@@ -470,7 +470,7 @@ test("#1505 thread 2 (d) namespacesEnabled:false ⇒ raw sessionKey everywhere",
470
470
  const service = new EngramAccessService(probe.orch);
471
471
 
472
472
  const res = await service.observe(
473
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
473
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
474
474
  );
475
475
  assert.equal(res.effectiveNamespace, "default");
476
476
  assert.equal(probe.lcmWriteKeys[0], "pi-geek:abc123", "raw LCM write key");
@@ -501,14 +501,14 @@ test("#1505 thread 1: extraction provenance principal is the resolved principal
501
501
  await service.observe(
502
502
  observeRequest({
503
503
  sessionKey: "pi-geek:abc123",
504
- projectTag: "Blend/Supply",
504
+ projectTag: "Acme/Webshop",
505
505
  skipExtraction: false, // exercise the extraction path
506
506
  }),
507
507
  );
508
508
 
509
509
  const expectedNs = combineNamespaces(
510
510
  "pi-geek",
511
- projectNamespaceName(projectTagProjectId("Blend/Supply")),
511
+ projectNamespaceName(projectTagProjectId("Acme/Webshop")),
512
512
  );
513
513
  assert.equal(probe.extractionCalls.length, 1);
514
514
  // Provenance identity: the resolved principal, never a default parsed from the
@@ -590,7 +590,7 @@ test("#1505 thread 2: LCM read namespace honors authenticatedPrincipal (write-un
590
590
  observeRequest({
591
591
  sessionKey: "sess-1",
592
592
  authenticatedPrincipal: "alice",
593
- projectTag: "Blend/Supply",
593
+ projectTag: "Acme/Webshop",
594
594
  }),
595
595
  );
596
596
  const writeKey = probe.lcmWriteKeys[0];
@@ -639,7 +639,7 @@ test("#1505 thread 2 compaction regression: flush/record overlay-derived key mat
639
639
  const service = new EngramAccessService(probe.orch);
640
640
 
641
641
  await service.observe(
642
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
642
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
643
643
  );
644
644
  await service.lcmCompactionFlush({ sessionKey: "pi-geek:abc123" });
645
645
 
@@ -748,7 +748,7 @@ test("#1505 round 3: access lcmSearch routes the session_id through the SCOPED (
748
748
  const service = new EngramAccessService(probe.orch);
749
749
 
750
750
  await service.observe(
751
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
751
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
752
752
  );
753
753
  const writeKey = probe.lcmWriteKeys[0];
754
754
  // New #1495 P1 encoding: `\x1f<overlayNs>\x1f<sessionKey>`. Parse the overlay
@@ -872,8 +872,8 @@ test("#1505 round 3 thread 1: orchestrator LCM read key falls back to default wh
872
872
 
873
873
  // Bind a coding context to sess-1 so the overlay would apply on alice's base.
874
874
  probe.contexts.set("sess-1", {
875
- projectId: "blend-supply",
876
- projectName: "Blend/Supply",
875
+ projectId: "acme-webshop",
876
+ projectName: "Acme/Webshop",
877
877
  } as unknown as CodingContext);
878
878
 
879
879
  const lcmReadNamespaceForSession = Orchestrator.prototype[
@@ -914,8 +914,8 @@ test("#1505 round 3 thread 1: orchestrator LCM read key falls back to default wh
914
914
  } as Partial<PluginConfig>);
915
915
 
916
916
  probe.contexts.set("sess-1", {
917
- projectId: "blend-supply",
918
- projectName: "Blend/Supply",
917
+ projectId: "acme-webshop",
918
+ projectName: "Acme/Webshop",
919
919
  } as unknown as CodingContext);
920
920
 
921
921
  const lcmReadNamespaceForSession = Orchestrator.prototype[
@@ -944,8 +944,8 @@ test("#1505 round 3 thread 1: orchestrator LCM read key still uses the overlay w
944
944
  } as Partial<PluginConfig>);
945
945
 
946
946
  probe.contexts.set("pi-geek:abc123", {
947
- projectId: "blend-supply",
948
- projectName: "Blend/Supply",
947
+ projectId: "acme-webshop",
948
+ projectName: "Acme/Webshop",
949
949
  } as unknown as CodingContext);
950
950
 
951
951
  const lcmReadNamespaceForSession = Orchestrator.prototype[
@@ -977,8 +977,8 @@ test("#1505 round 3 thread 2: lcmSearch returns NO overlay rows when the princip
977
977
  const service = new EngramAccessService(probe.orch);
978
978
 
979
979
  probe.contexts.set("sess-1", {
980
- projectId: "blend-supply",
981
- projectName: "Blend/Supply",
980
+ projectId: "acme-webshop",
981
+ projectName: "Acme/Webshop",
982
982
  } as unknown as CodingContext);
983
983
 
984
984
  await service.lcmSearch({
@@ -1036,7 +1036,7 @@ test("#1505 round 4 thread (codex P2): compaction flush/record target the OVERLA
1036
1036
  observeRequest({
1037
1037
  sessionKey: "sess-1",
1038
1038
  authenticatedPrincipal: "alice",
1039
- projectTag: "Blend/Supply",
1039
+ projectTag: "Acme/Webshop",
1040
1040
  }),
1041
1041
  );
1042
1042
  const writeKey = probe.lcmWriteKeys[0];
@@ -1080,8 +1080,8 @@ test("#1505 round 4 thread (codex P2): a read-only lcmSearch by the SAME write-o
1080
1080
  const service = new EngramAccessService(probe.orch);
1081
1081
 
1082
1082
  probe.contexts.set("sess-1", {
1083
- projectId: "blend-supply",
1084
- projectName: "Blend/Supply",
1083
+ projectId: "acme-webshop",
1084
+ projectName: "Acme/Webshop",
1085
1085
  } as unknown as CodingContext);
1086
1086
 
1087
1087
  await service.lcmSearch({
@@ -1115,7 +1115,7 @@ test("#1505 round 3 thread 2: lcmSearch still routes through the overlay key whe
1115
1115
  observeRequest({
1116
1116
  sessionKey: "sess-1",
1117
1117
  authenticatedPrincipal: "alice",
1118
- projectTag: "Blend/Supply",
1118
+ projectTag: "Acme/Webshop",
1119
1119
  }),
1120
1120
  );
1121
1121
  const writeKey = probe.lcmWriteKeys[0];
@@ -1172,7 +1172,7 @@ test("#1505 thread NBHWs (codex P2): restrictive `default` WRITE policy + writab
1172
1172
  observeRequest({
1173
1173
  sessionKey: "sess-1",
1174
1174
  authenticatedPrincipal: "alice",
1175
- projectTag: "Blend/Supply",
1175
+ projectTag: "Acme/Webshop",
1176
1176
  }),
1177
1177
  );
1178
1178
  const writeKey = probe.lcmWriteKeys[0];
@@ -1230,7 +1230,7 @@ test("#1505 thread NBHWz (sweep): restrictive `default` READ policy + readable s
1230
1230
 
1231
1231
  // observe archives under pi-geek's readable+writable project overlay.
1232
1232
  await service.observe(
1233
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
1233
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
1234
1234
  );
1235
1235
  const writeKey = probe.lcmWriteKeys[0];
1236
1236
  assertOverlayWriteKey(writeKey, "pi-geek-"); // observe must archive under pi-geek's overlay key, got ${writeKey}
@@ -1277,8 +1277,8 @@ test("#1505 codex P1 'Don't treat any readable namespace as default LCM access':
1277
1277
 
1278
1278
  // Bind a coding context so the overlay WOULD apply on alice's base.
1279
1279
  probe.contexts.set("sess-1", {
1280
- projectId: "blend-supply",
1281
- projectName: "Blend/Supply",
1280
+ projectId: "acme-webshop",
1281
+ projectName: "Acme/Webshop",
1282
1282
  } as unknown as CodingContext);
1283
1283
 
1284
1284
  const res = await service.lcmSearch({
@@ -162,14 +162,14 @@ test("#1495 projectTag: LCM, extraction, objective-state, and response all agree
162
162
  const service = new EngramAccessService(probe.orch);
163
163
 
164
164
  const res = await service.observe(
165
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
165
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
166
166
  );
167
167
 
168
168
  // Effective write namespace == principal self base overlaid with the project,
169
169
  // EXACTLY what a same-session project-scoped recall/store resolves.
170
170
  const expected = combineNamespaces(
171
171
  "pi-geek",
172
- projectNamespaceName(projectTagProjectId("Blend/Supply")),
172
+ projectNamespaceName(projectTagProjectId("Acme/Webshop")),
173
173
  );
174
174
 
175
175
  assert.equal(res.effectiveNamespace, expected, "response effectiveNamespace");
@@ -334,7 +334,7 @@ test("#1495 explicit namespace wins and project context does NOT silently overri
334
334
  observeRequest({
335
335
  sessionKey: "pi-geek:abc123",
336
336
  namespace: "team",
337
- projectTag: "Blend/Supply",
337
+ projectTag: "Acme/Webshop",
338
338
  }),
339
339
  );
340
340
 
@@ -364,7 +364,7 @@ test("#1495 projectScope:false ⇒ no overlay (unqualified write stays on config
364
364
  const service = new EngramAccessService(probe.orch);
365
365
 
366
366
  const res = await service.observe(
367
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
367
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
368
368
  );
369
369
 
370
370
  assert.equal(res.effectiveNamespace, "default");
@@ -385,7 +385,7 @@ test("#1495 namespacesEnabled:false ⇒ single-store behavior preserved", async
385
385
  const service = new EngramAccessService(probe.orch);
386
386
 
387
387
  const res = await service.observe(
388
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
388
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
389
389
  );
390
390
 
391
391
  assert.equal(res.effectiveNamespace, "default");
@@ -409,7 +409,7 @@ test("#1495 unauthorized explicit namespace throws BEFORE session context is att
409
409
  observeRequest({
410
410
  sessionKey: "pi-geek:abc123",
411
411
  namespace: "victim-secret",
412
- projectTag: "Blend/Supply",
412
+ projectTag: "Acme/Webshop",
413
413
  }),
414
414
  ),
415
415
  /not writable/,
@@ -445,7 +445,7 @@ test("#1505 thread 1/3: unauthorized OVERLAY self-base throws BEFORE coding cont
445
445
 
446
446
  await assert.rejects(
447
447
  service.observe(
448
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
448
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
449
449
  ),
450
450
  /not writable/,
451
451
  );
@@ -491,13 +491,13 @@ test("#1505 thread jvO: restrictive default-namespace write policy does NOT reje
491
491
 
492
492
  const expectedOverlay = combineNamespaces(
493
493
  "pi-geek",
494
- projectNamespaceName(projectTagProjectId("Blend/Supply")),
494
+ projectNamespaceName(projectTagProjectId("Acme/Webshop")),
495
495
  );
496
496
 
497
497
  // FAIL-BEFORE: this threw `namespace is not writable: default`. PASS-AFTER:
498
498
  // the observe is accepted exactly like memory_store/suggestion_submit would.
499
499
  const res = await service.observe(
500
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
500
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
501
501
  );
502
502
 
503
503
  // Every side effect uses the authorized overlay write target.
@@ -533,7 +533,7 @@ test("#1505 thread jvO: a genuine reject (unwritable self base) under a restrict
533
533
 
534
534
  await assert.rejects(
535
535
  service.observe(
536
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
536
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
537
537
  ),
538
538
  /not writable/,
539
539
  );
@@ -553,12 +553,12 @@ test("#1495 scopeDebug exposes the resolved plan for callers/tests", async () =>
553
553
  const service = new EngramAccessService(probe.orch);
554
554
 
555
555
  const res = await service.observe(
556
- observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Blend/Supply" }),
556
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Acme/Webshop" }),
557
557
  );
558
558
 
559
559
  const expected = combineNamespaces(
560
560
  "pi-geek",
561
- projectNamespaceName(projectTagProjectId("Blend/Supply")),
561
+ projectNamespaceName(projectTagProjectId("Acme/Webshop")),
562
562
  );
563
563
  assert.ok(res.scopeDebug, "scopeDebug must be present");
564
564
  assert.equal(res.scopeDebug!.principal, "pi-geek");
@@ -611,9 +611,9 @@ test("#1495 the scope plan's writeNamespace matches resolveCodingScopedWriteName
611
611
  const probe = makeObserveProbe(withSelfPolicyPrefix("pi-geek"));
612
612
  // Bind a session coding context so both resolvers see the same project.
613
613
  probe.contexts.set("pi-geek:abc123", {
614
- projectId: projectTagProjectId("Blend/Supply"),
614
+ projectId: projectTagProjectId("Acme/Webshop"),
615
615
  branch: null,
616
- rootPath: projectTagProjectId("Blend/Supply"),
616
+ rootPath: projectTagProjectId("Acme/Webshop"),
617
617
  defaultBranch: null,
618
618
  });
619
619
  const service = new EngramAccessService(probe.orch);
@@ -765,14 +765,14 @@ test("#1495 skipExtraction does not enqueue extraction but still archives LCM un
765
765
  const res = await service.observe(
766
766
  observeRequest({
767
767
  sessionKey: "pi-geek:abc123",
768
- projectTag: "Blend/Supply",
768
+ projectTag: "Acme/Webshop",
769
769
  skipExtraction: true,
770
770
  }),
771
771
  );
772
772
 
773
773
  const expected = combineNamespaces(
774
774
  "pi-geek",
775
- projectNamespaceName(projectTagProjectId("Blend/Supply")),
775
+ projectNamespaceName(projectTagProjectId("Acme/Webshop")),
776
776
  );
777
777
  assert.equal(res.extractionQueued, false);
778
778
  assert.equal(probe.extractionCalls.length, 0);
@@ -24,10 +24,10 @@ test("projectTag auto-resolution uses the shared project tag canonicalizer", asy
24
24
  } as any);
25
25
 
26
26
  await (service as any).maybeAttachCodingContext("session-a", {
27
- projectTag: "Blend/Supply",
27
+ projectTag: "Acme/Webshop",
28
28
  });
29
29
 
30
- const projectId = projectTagProjectId("Blend/Supply");
30
+ const projectId = projectTagProjectId("Acme/Webshop");
31
31
  assert.deepEqual(contexts.get("session-a"), {
32
32
  projectId,
33
33
  branch: null,
@@ -173,7 +173,7 @@ type RawExcerptInternals = {
173
173
  };
174
174
 
175
175
  const SESSION_KEY = "pi-geek:abc123";
176
- const PROJECT_TAG = "Blend/Supply";
176
+ const PROJECT_TAG = "Acme/Webshop";
177
177
 
178
178
  function overlayNamespace(): string {
179
179
  return combineNamespaces(
@@ -145,10 +145,26 @@ export interface SymbolIR {
145
145
  export interface ImportIR {
146
146
  /** Raw module specifier as written in source. */
147
147
  readonly module: string;
148
+ /** Exported names as declared by the source module (pre-alias). */
148
149
  readonly importedNames: readonly string[];
150
+ /**
151
+ * Alias-aware bindings (issue #1894 review): `local` is the identifier
152
+ * usable at call sites in THIS file; `exported` is the name in the
153
+ * source module. For non-aliased imports the two are equal. Optional so
154
+ * pre-existing IR consumers and JSON fixtures stay valid; when absent,
155
+ * consumers treat every `importedNames` entry as `local === exported`.
156
+ */
157
+ readonly bindings?: readonly ImportBindingIR[];
149
158
  readonly span: { readonly startByte: number; readonly endByte: number };
150
159
  }
151
160
 
161
+ export interface ImportBindingIR {
162
+ /** Name exported by the source module. */
163
+ readonly exported: string;
164
+ /** Local identifier bound in the importing file. */
165
+ readonly local: string;
166
+ }
167
+
152
168
  export interface ExportIR {
153
169
  readonly name: string;
154
170
  readonly span: { readonly startByte: number; readonly endByte: number };
@@ -156,6 +172,13 @@ export interface ExportIR {
156
172
 
157
173
  export interface CallSiteIR {
158
174
  readonly calleeNameCandidates: readonly string[];
175
+ /**
176
+ * True when the callee is a member/property access (`obj.save()`,
177
+ * `recv.field()`), issue #1894 review: bare-name heuristics must never
178
+ * bind these — method dispatch is Phase B (LSP) territory. Optional so
179
+ * pre-existing IR stays valid; absent = false.
180
+ */
181
+ readonly memberAccess?: boolean;
159
182
  readonly span: { readonly startByte: number; readonly endByte: number };
160
183
  }
161
184
 
@@ -58,9 +58,9 @@ test("projectNamespaceName: empty input falls back to 'unknown'", () => {
58
58
  });
59
59
 
60
60
  test("projectTagProjectId: lossy tags get hash disambiguators", () => {
61
- assert.equal(projectTagProjectId("blend-supply"), "tag:blend-supply");
62
- assert.notEqual(projectTagProjectId("blend/supply"), projectTagProjectId("blend-supply"));
63
- assert.match(projectTagProjectId("blend/supply"), /^tag:blend-supply-[0-9a-f]{8}$/);
61
+ assert.equal(projectTagProjectId("acme-webshop"), "tag:acme-webshop");
62
+ assert.notEqual(projectTagProjectId("acme/webshop"), projectTagProjectId("acme-webshop"));
63
+ assert.match(projectTagProjectId("acme/webshop"), /^tag:acme-webshop-[0-9a-f]{8}$/);
64
64
  });
65
65
 
66
66
  test("projectNamespaceName: length-capped to 64 chars, no trailing dash", () => {
@@ -39,11 +39,11 @@ test("recall schema accepts projectTag field", () => {
39
39
  const result = validateRequest<RecallRequest>("recall", {
40
40
  query: "test query",
41
41
  sessionKey: "sess-1",
42
- projectTag: "blend-supply",
42
+ projectTag: "acme-webshop",
43
43
  });
44
44
  assert.equal(result.success, true);
45
45
  if (result.success) {
46
- assert.equal(result.data.projectTag, "blend-supply");
46
+ assert.equal(result.data.projectTag, "acme-webshop");
47
47
  }
48
48
  });
49
49
 
@@ -52,7 +52,7 @@ test("recall schema accepts both cwd and projectTag", () => {
52
52
  query: "test query",
53
53
  sessionKey: "sess-1",
54
54
  cwd: "/home/user/project",
55
- projectTag: "blend-supply",
55
+ projectTag: "acme-webshop",
56
56
  });
57
57
  assert.equal(result.success, true);
58
58
  });
@@ -89,11 +89,11 @@ test("observe schema accepts projectTag field", () => {
89
89
  const result = validateRequest<ObserveRequest>("observe", {
90
90
  sessionKey: "sess-1",
91
91
  messages: [{ role: "user", content: "hello" }],
92
- projectTag: "worthington-direct",
92
+ projectTag: "globex-storefront",
93
93
  });
94
94
  assert.equal(result.success, true);
95
95
  if (result.success) {
96
- assert.equal(result.data.projectTag, "worthington-direct");
96
+ assert.equal(result.data.projectTag, "globex-storefront");
97
97
  }
98
98
  });
99
99
 
@@ -185,13 +185,13 @@ test("set_coding_context with projectTag creates tag-based context", async () =>
185
185
  const { mcp, calls } = makeMcp();
186
186
  const result = await call(mcp, "engram.set_coding_context", {
187
187
  sessionKey: "session-A",
188
- projectTag: "blend-supply",
188
+ projectTag: "acme-webshop",
189
189
  });
190
190
  assert.deepEqual(result, { ok: true });
191
191
  assert.equal(calls.length, 1);
192
- assert.equal(calls[0]!.ctx?.projectId, "tag:blend-supply");
192
+ assert.equal(calls[0]!.ctx?.projectId, "tag:acme-webshop");
193
193
  assert.equal(calls[0]!.ctx?.branch, null);
194
- assert.equal(calls[0]!.ctx?.rootPath, "tag:blend-supply");
194
+ assert.equal(calls[0]!.ctx?.rootPath, "tag:acme-webshop");
195
195
  assert.equal(calls[0]!.ctx?.defaultBranch, null);
196
196
  });
197
197
 
@@ -199,16 +199,16 @@ test("set_coding_context with projectTag disambiguates lossy tags", async () =>
199
199
  const { mcp, calls } = makeMcp();
200
200
  await call(mcp, "engram.set_coding_context", {
201
201
  sessionKey: "session-A",
202
- projectTag: "blend/supply",
202
+ projectTag: "acme/webshop",
203
203
  });
204
204
  await call(mcp, "engram.set_coding_context", {
205
205
  sessionKey: "session-B",
206
- projectTag: "blend-supply",
206
+ projectTag: "acme-webshop",
207
207
  });
208
208
 
209
209
  assert.notEqual(calls[0]!.ctx?.projectId, calls[1]!.ctx?.projectId);
210
- assert.match(calls[0]!.ctx?.projectId ?? "", /^tag:blend-supply-[0-9a-f]{8}$/);
211
- assert.equal(calls[1]!.ctx?.projectId, "tag:blend-supply");
210
+ assert.match(calls[0]!.ctx?.projectId ?? "", /^tag:acme-webshop-[0-9a-f]{8}$/);
211
+ assert.equal(calls[1]!.ctx?.projectId, "tag:acme-webshop");
212
212
  });
213
213
 
214
214
  test("set_coding_context with codingContext takes precedence over projectTag", async () => {
@@ -221,7 +221,7 @@ test("set_coding_context with codingContext takes precedence over projectTag", a
221
221
  rootPath: "/work/proj",
222
222
  defaultBranch: "main",
223
223
  },
224
- projectTag: "blend-supply",
224
+ projectTag: "acme-webshop",
225
225
  });
226
226
  assert.equal(calls.length, 1);
227
227
  assert.equal(calls[0]!.ctx?.projectId, "origin:abcd1234");
@@ -242,7 +242,7 @@ test("set_coding_context with codingContext=null clears (even with projectTag pr
242
242
  await call(mcp, "engram.set_coding_context", {
243
243
  sessionKey: "session-A",
244
244
  codingContext: null,
245
- projectTag: "blend-supply",
245
+ projectTag: "acme-webshop",
246
246
  });
247
247
  assert.equal(calls.length, 1);
248
248
  assert.equal(calls[0]!.ctx, null);
@@ -252,11 +252,11 @@ test("set_coding_context projectTag via canonical alias remnic.*", async () => {
252
252
  const { mcp, calls } = makeMcp();
253
253
  const result = await call(mcp, "remnic.set_coding_context", {
254
254
  sessionKey: "session-B",
255
- projectTag: "worthington-direct",
255
+ projectTag: "globex-storefront",
256
256
  });
257
257
  assert.deepEqual(result, { ok: true });
258
258
  assert.equal(calls.length, 1);
259
- assert.equal(calls[0]!.ctx?.projectId, "tag:worthington-direct");
259
+ assert.equal(calls[0]!.ctx?.projectId, "tag:globex-storefront");
260
260
  });
261
261
 
262
262
  // ──────────────────────────────────────────────────────────────────────────
@@ -321,10 +321,10 @@ test("maybeAttachCodingContext: projectTag attaches tag-based context", async ()
321
321
  ): Promise<void>;
322
322
  }).maybeAttachCodingContext.bind(service);
323
323
 
324
- await maybeAttach("session-X", { projectTag: "blend-supply" });
324
+ await maybeAttach("session-X", { projectTag: "acme-webshop" });
325
325
  assert.equal(calls.length, 1);
326
- assert.equal(calls[0]!.ctx?.projectId, "tag:blend-supply");
327
- assert.equal(calls[0]!.ctx?.rootPath, "tag:blend-supply");
326
+ assert.equal(calls[0]!.ctx?.projectId, "tag:acme-webshop");
327
+ assert.equal(calls[0]!.ctx?.rootPath, "tag:acme-webshop");
328
328
  });
329
329
 
330
330
  test("maybeAttachCodingContext: skips when session already has context", async () => {
@@ -351,7 +351,7 @@ test("maybeAttachCodingContext: skips when session already has context", async (
351
351
  ): Promise<void>;
352
352
  }).maybeAttachCodingContext.bind(service);
353
353
 
354
- await maybeAttach("session-X", { projectTag: "blend-supply" });
354
+ await maybeAttach("session-X", { projectTag: "acme-webshop" });
355
355
  assert.equal(calls.length, 0, "should not overwrite existing context");
356
356
  });
357
357
 
@@ -379,7 +379,7 @@ test("maybeAttachCodingContext: skips when projectScope disabled", async () => {
379
379
  ): Promise<void>;
380
380
  }).maybeAttachCodingContext.bind(service);
381
381
 
382
- await maybeAttach("session-X", { projectTag: "blend-supply" });
382
+ await maybeAttach("session-X", { projectTag: "acme-webshop" });
383
383
  assert.equal(calls.length, 0, "should not attach when projectScope is disabled");
384
384
  });
385
385
 
@@ -407,7 +407,7 @@ test("maybeAttachCodingContext: skips when no sessionKey", async () => {
407
407
  ): Promise<void>;
408
408
  }).maybeAttachCodingContext.bind(service);
409
409
 
410
- await maybeAttach(undefined, { projectTag: "blend-supply" });
410
+ await maybeAttach(undefined, { projectTag: "acme-webshop" });
411
411
  assert.equal(calls.length, 0, "should not attach without sessionKey");
412
412
  });
413
413
 
@@ -90,13 +90,13 @@ async function walkCollectDirs(root: string, out: Set<string>): Promise<void> {
90
90
  // ─── Symptom A: coding-namespace derivation is stable across write/read ────
91
91
 
92
92
  test("#1492 Symptom A: a project-scoped session derives ONE stable overlay namespace — the write/read convergence point", () => {
93
- // The reporter's scenario: user "pi-geek" in project "Blend/Supply". Both the
93
+ // The reporter's scenario: user "pi-geek" in project "Acme/Webshop". Both the
94
94
  // observe write path and the recall read path must resolve the SAME scoped
95
95
  // namespace. They do so by combining the principal base with the project
96
96
  // overlay through exactly ONE helper (combineNamespaces); a second ad-hoc
97
97
  // combination is what caused the original drift.
98
98
  const principal = "pi-geek";
99
- const projectId = projectTagProjectId("Blend/Supply");
99
+ const projectId = projectTagProjectId("Acme/Webshop");
100
100
  const codingContext = {
101
101
  projectId,
102
102
  branch: null,