@oortmem/core 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/address.d.ts +161 -0
- package/dist/address.js +225 -0
- package/dist/agents.d.ts +38 -0
- package/dist/agents.js +67 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +7 -0
- package/dist/consolidate.d.ts +4 -0
- package/dist/consolidate.js +15 -3
- package/dist/date-cue.d.ts +26 -0
- package/dist/date-cue.js +70 -0
- package/dist/export-format.d.ts +77 -0
- package/dist/export-format.js +168 -0
- package/dist/grant.d.ts +122 -15
- package/dist/grant.js +233 -26
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/isolation.d.ts +48 -2
- package/dist/isolation.js +362 -71
- package/dist/levels.d.ts +79 -0
- package/dist/levels.js +140 -0
- package/dist/memory-store.js +27 -23
- package/dist/model.d.ts +17 -0
- package/dist/observe.d.ts +9 -0
- package/dist/observe.js +27 -3
- package/dist/recall.d.ts +27 -1
- package/dist/recall.js +163 -17
- package/dist/store-conformance.d.ts +12 -0
- package/dist/store-conformance.js +366 -2
- package/dist/temporal.js +12 -7
- package/package.json +1 -1
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* more); a store with a fixed dimension must be built by `make()` with it.
|
|
12
12
|
*/
|
|
13
13
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
14
|
-
import {
|
|
14
|
+
import { addressed, addressOf, authorOf, canonicalMemory, GALAXY_ROOT, nodeOf, } from './address.js';
|
|
15
|
+
import { GrantError, localGrant, nodeGrant, principalGrant } from './grant.js';
|
|
15
16
|
import { localScope } from './scope.js';
|
|
16
17
|
import { isolationViolations, nonInterference } from './isolation.js';
|
|
17
18
|
export const CONFORMANCE_DIMENSION = 4;
|
|
@@ -387,12 +388,14 @@ export function storeConformance(name, make) {
|
|
|
387
388
|
const others = {
|
|
388
389
|
'other-org': { ...localScope(), orgId: 'another-org' },
|
|
389
390
|
'other-customer': { ...localScope(), customerId: 'someone-else' },
|
|
390
|
-
'other-agent': { ...localScope(), agentId: 'another-agent' },
|
|
391
391
|
};
|
|
392
392
|
await store.upsert(TG, makeMemory({ id: 'mine', text, embedding }));
|
|
393
393
|
for (const [id, scope] of Object.entries(others)) {
|
|
394
394
|
await store.upsert(grantFor(scope), makeMemory({ id, text, embedding, scope }));
|
|
395
395
|
}
|
|
396
|
+
// Another agent's Star is private to it (CON-62).
|
|
397
|
+
const star = { level: 'agent', orgId: 'local', agentId: 'another-agent' };
|
|
398
|
+
await store.upsert(nodesGrant(star), memoryAt('other-agent', star, { agentId: 'another-agent' }, { text, embedding }));
|
|
396
399
|
expect(ids(await store.lexical(localGrant(LOCAL), 'auth-service-8080', 10))).toEqual([
|
|
397
400
|
'mine',
|
|
398
401
|
]);
|
|
@@ -578,6 +581,367 @@ export function storeConformance(name, make) {
|
|
|
578
581
|
expect(await nonInterference(make, seed, 60)).toBeNull();
|
|
579
582
|
}
|
|
580
583
|
}, 120_000);
|
|
584
|
+
it(`isolation.non-interference-multi-node-${seeds}-seeds`, async () => {
|
|
585
|
+
for (let seed = 1; seed <= seeds; seed++) {
|
|
586
|
+
expect(await nonInterference(make, seed, 60, { multiNode: true })).toBeNull();
|
|
587
|
+
}
|
|
588
|
+
}, 120_000);
|
|
589
|
+
it(`isolation.non-interference-peer-agents-${seeds}-seeds`, async () => {
|
|
590
|
+
// CON-62: another agent of the same person, working in its own Star, never reaches mine.
|
|
591
|
+
for (let seed = 1; seed <= seeds; seed++) {
|
|
592
|
+
expect(await nonInterference(make, seed, 60, { peers: true })).toBeNull();
|
|
593
|
+
}
|
|
594
|
+
}, 120_000);
|
|
595
|
+
});
|
|
596
|
+
levelConformance(name, make);
|
|
597
|
+
}
|
|
598
|
+
/** The owner every level case belongs to or reads as: the local principal. */
|
|
599
|
+
const OWNER = { orgId: 'local', customerId: 'self', agentId: 'claude-code' };
|
|
600
|
+
/**
|
|
601
|
+
* The grant every local caller had before CON-62: the owner's Planet, restricted to memories its
|
|
602
|
+
* own agent wrote. Kept to prove data written before levels reads back unchanged.
|
|
603
|
+
*/
|
|
604
|
+
const PRE_LEVEL = nodeGrant({
|
|
605
|
+
principal: OWNER,
|
|
606
|
+
read: [
|
|
607
|
+
{
|
|
608
|
+
node: { level: 'user', orgId: 'local', customerId: 'self' },
|
|
609
|
+
projectIds: 'all',
|
|
610
|
+
authorAgentId: 'claude-code',
|
|
611
|
+
},
|
|
612
|
+
],
|
|
613
|
+
});
|
|
614
|
+
const PROVENANCE = {
|
|
615
|
+
derivedFrom: [{ vault: 'acct:1', memoryId: 'src-1' }],
|
|
616
|
+
contributors: [
|
|
617
|
+
{
|
|
618
|
+
memberRef: 'mem-1',
|
|
619
|
+
agentId: 'claude-code',
|
|
620
|
+
at: '2026-11-01T00:00:00.000Z',
|
|
621
|
+
action: 'asserted',
|
|
622
|
+
},
|
|
623
|
+
],
|
|
624
|
+
support: { members: 1, agents: 1 },
|
|
625
|
+
gate: { policyVersion: 'p1', mode: 'confirmed' },
|
|
626
|
+
};
|
|
627
|
+
/** One memory per level (and a Planet memory by another agent), each with a distinct embedding. */
|
|
628
|
+
export const LEVEL_CASES = [
|
|
629
|
+
{
|
|
630
|
+
name: 'planet-other-author',
|
|
631
|
+
address: { level: 'user', orgId: 'local', customerId: 'self', projectId: 'p1' },
|
|
632
|
+
author: { agentId: 'cursor' },
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
name: 'star',
|
|
636
|
+
address: { level: 'agent', orgId: 'local', agentId: 'cursor', projectId: 'p1' },
|
|
637
|
+
author: { agentId: 'cursor' },
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
name: 'orbit',
|
|
641
|
+
address: {
|
|
642
|
+
level: 'agent-user',
|
|
643
|
+
orgId: 'org:acme',
|
|
644
|
+
agentId: 'support-bot',
|
|
645
|
+
customerId: 'end-user-1',
|
|
646
|
+
},
|
|
647
|
+
author: { agentId: 'support-bot' },
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
name: 'flyby',
|
|
651
|
+
address: {
|
|
652
|
+
level: 'run',
|
|
653
|
+
orgId: 'local',
|
|
654
|
+
agentId: 'claude-code',
|
|
655
|
+
runId: 'run-1',
|
|
656
|
+
customerId: 'self',
|
|
657
|
+
},
|
|
658
|
+
author: { agentId: 'claude-code' },
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
name: 'flyby-no-customer',
|
|
662
|
+
address: { level: 'run', orgId: 'local', agentId: 'claude-code', runId: 'run-2' },
|
|
663
|
+
author: { agentId: 'claude-code' },
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: 'constellation',
|
|
667
|
+
address: { level: 'hive', orgId: 'org:acme', hiveId: 'checkout', projectId: 'p1' },
|
|
668
|
+
author: { agentId: 'claude-code', accountRef: 'acct:1' },
|
|
669
|
+
provenance: PROVENANCE,
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: 'galaxy',
|
|
673
|
+
address: { level: 'hive', orgId: 'org:acme', hiveId: GALAXY_ROOT },
|
|
674
|
+
author: { agentId: 'claude-code' },
|
|
675
|
+
},
|
|
676
|
+
];
|
|
677
|
+
/** A memory at `address` by `author` (scope filled to match). */
|
|
678
|
+
export function memoryAt(id, address, author, overrides = {}) {
|
|
679
|
+
return makeMemory({ id, ...addressed(address, author), ...overrides });
|
|
680
|
+
}
|
|
681
|
+
/** Read and write every project of `nodes` (no author restriction). */
|
|
682
|
+
export function nodesGrant(...nodes) {
|
|
683
|
+
return nodeGrant({
|
|
684
|
+
principal: OWNER,
|
|
685
|
+
read: nodes.map((node) => ({ node, projectIds: 'all' })),
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
function render(m) {
|
|
689
|
+
return m === null
|
|
690
|
+
? 'null'
|
|
691
|
+
: JSON.stringify({ ...m, embedding: m.embedding === undefined ? null : [...m.embedding] });
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Levels (CON-61): every address round-trips, illegal ones are refused, grants
|
|
695
|
+
* over node sets see exactly their nodes, and memories written before levels
|
|
696
|
+
* (and every pre-level grant) behave exactly as they did.
|
|
697
|
+
*/
|
|
698
|
+
function levelConformance(name, make) {
|
|
699
|
+
describe(`store levels: ${name}`, () => {
|
|
700
|
+
let store;
|
|
701
|
+
beforeEach(async () => {
|
|
702
|
+
store = await make();
|
|
703
|
+
});
|
|
704
|
+
afterEach(async () => {
|
|
705
|
+
await store.close();
|
|
706
|
+
});
|
|
707
|
+
const cases = () => LEVEL_CASES.map((c, i) => {
|
|
708
|
+
const m = memoryAt(`lv-${c.name}`, c.address, c.author, {
|
|
709
|
+
text: `levelprobe ${c.name} text`,
|
|
710
|
+
stub: `levelprobe ${c.name}`,
|
|
711
|
+
embedding: vec(i + 1, 1, -1, 2),
|
|
712
|
+
...(c.provenance !== undefined && { provenance: c.provenance }),
|
|
713
|
+
});
|
|
714
|
+
return { ...c, m, g: nodesGrant(nodeOf(c.address)) };
|
|
715
|
+
});
|
|
716
|
+
it('levels.planet-memories-keep-their-shape', async () => {
|
|
717
|
+
const m = makeMemory({ id: 'legacy', scope: localScope('p1'), embedding: vec(1, 2, 3, 4) });
|
|
718
|
+
await store.upsert(TG, m);
|
|
719
|
+
const got = await store.get(TG, 'legacy');
|
|
720
|
+
expect(got).toEqual(m);
|
|
721
|
+
expect(got !== null && ('address' in got || 'author' in got || 'provenance' in got)).toBe(false);
|
|
722
|
+
expect(addressOf(got)).toEqual({
|
|
723
|
+
level: 'user',
|
|
724
|
+
orgId: 'local',
|
|
725
|
+
customerId: 'self',
|
|
726
|
+
projectId: 'p1',
|
|
727
|
+
});
|
|
728
|
+
expect(authorOf(got)).toEqual({ agentId: 'claude-code' });
|
|
729
|
+
});
|
|
730
|
+
it('levels.every-level-roundtrips', async () => {
|
|
731
|
+
for (const c of cases()) {
|
|
732
|
+
await store.upsert(c.g, c.m);
|
|
733
|
+
const got = await store.get(c.g, c.m.id);
|
|
734
|
+
expect(got, c.name).toEqual(canonicalMemory(c.m));
|
|
735
|
+
expect(addressOf(got), c.name).toEqual(c.address);
|
|
736
|
+
expect(authorOf(got), c.name).toEqual(c.author);
|
|
737
|
+
expect(got?.provenance ?? null, c.name).toEqual(c.provenance ?? null);
|
|
738
|
+
expect(ids(await store.list(c.g)), c.name).toEqual([c.m.id]);
|
|
739
|
+
expect(ids(await store.lexical(c.g, 'levelprobe', 10)), c.name).toEqual([c.m.id]);
|
|
740
|
+
expect(ids(await store.dense(c.g, c.m.embedding, 10)), c.name).toEqual([
|
|
741
|
+
c.m.id,
|
|
742
|
+
]);
|
|
743
|
+
const snapshot = (await store.get(c.g, c.m.id));
|
|
744
|
+
expect((await store.coldPut(c.g, snapshot)).skipped, c.name).toEqual([]);
|
|
745
|
+
expect(await store.coldGet(c.g, c.m.id), c.name).toEqual(canonicalMemory(snapshot));
|
|
746
|
+
expect((await store.get(c.g, c.m.id))?.tier, c.name).toBe('cold');
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
it('levels.pre-level-grants-see-no-other-level', async () => {
|
|
750
|
+
for (const c of cases())
|
|
751
|
+
await store.upsert(c.g, c.m);
|
|
752
|
+
for (const c of cases())
|
|
753
|
+
expect(await store.get(PRE_LEVEL, c.m.id), c.name).toBeNull();
|
|
754
|
+
expect(await store.list(PRE_LEVEL, { validOnly: false })).toEqual([]);
|
|
755
|
+
expect(await store.lexical(PRE_LEVEL, 'levelprobe', 50)).toEqual([]);
|
|
756
|
+
expect(await store.dense(PRE_LEVEL, vec(1, 1, -1, 2), 50)).toEqual([]);
|
|
757
|
+
});
|
|
758
|
+
it('levels.local-grants-see-the-planet-and-their-own-star', async () => {
|
|
759
|
+
// CON-62: a person's agents share the Planet; each agent's Star is its own.
|
|
760
|
+
for (const c of cases())
|
|
761
|
+
await store.upsert(c.g, c.m);
|
|
762
|
+
const ccStar = { level: 'agent', orgId: 'local', agentId: 'claude-code' };
|
|
763
|
+
await store.upsert(nodesGrant(ccStar), memoryAt('cc-star', ccStar, { agentId: 'claude-code' }, { text: 'levelprobe cc star' }));
|
|
764
|
+
const asClaude = [TG, localGrant({ ...OWNER, projectIds: ['p1', null] })];
|
|
765
|
+
for (const g of asClaude) {
|
|
766
|
+
expect(ids(await store.list(g, { validOnly: false })).sort()).toEqual([
|
|
767
|
+
'cc-star',
|
|
768
|
+
'lv-planet-other-author',
|
|
769
|
+
]);
|
|
770
|
+
expect(ids(await store.lexical(g, 'levelprobe', 50)).sort()).toEqual([
|
|
771
|
+
'cc-star',
|
|
772
|
+
'lv-planet-other-author',
|
|
773
|
+
]);
|
|
774
|
+
expect(await store.get(g, 'lv-star')).toBeNull();
|
|
775
|
+
expect(await store.setTier(g, 'lv-star', 'hot')).toBe(false);
|
|
776
|
+
}
|
|
777
|
+
const asCursor = localGrant({ ...OWNER, agentId: 'cursor', projectIds: ['p1', null] });
|
|
778
|
+
expect(ids(await store.list(asCursor, { validOnly: false })).sort()).toEqual([
|
|
779
|
+
'lv-planet-other-author',
|
|
780
|
+
'lv-star',
|
|
781
|
+
]);
|
|
782
|
+
expect(await store.get(asCursor, 'cc-star')).toBeNull();
|
|
783
|
+
expect(ids(await store.dense(asCursor, vec(2, 1, -1, 2), 50))).not.toContain('cc-star');
|
|
784
|
+
});
|
|
785
|
+
it('levels.existing-data-unchanged', async () => {
|
|
786
|
+
const legacy = [
|
|
787
|
+
makeMemory({ id: 'old-1', text: 'levelprobe orbit one', embedding: vec(1, 1, -1, 2) }),
|
|
788
|
+
makeMemory({
|
|
789
|
+
id: 'old-2',
|
|
790
|
+
text: 'levelprobe orbit two',
|
|
791
|
+
scope: localScope('p1'),
|
|
792
|
+
embedding: vec(2, 1, -1, 2),
|
|
793
|
+
pinned: true,
|
|
794
|
+
}),
|
|
795
|
+
makeMemory({ id: 'old-3', text: 'levelprobe orbit three', scope: localScope('p2') }),
|
|
796
|
+
];
|
|
797
|
+
await store.upsertMany(TG, legacy);
|
|
798
|
+
await seedCold(store, makeMemory({ id: 'old-4', text: 'levelprobe cold', embedding: vec(3, 1, -1, 2) }));
|
|
799
|
+
const g = PRE_LEVEL;
|
|
800
|
+
const view = async () => {
|
|
801
|
+
const out = [];
|
|
802
|
+
for (const id of ['old-1', 'old-2', 'old-3', 'old-4']) {
|
|
803
|
+
out.push(render(await store.get(g, id)), render(await store.coldGet(g, id)));
|
|
804
|
+
}
|
|
805
|
+
out.push((await store.list(g, { validOnly: false })).map(render).join('|'));
|
|
806
|
+
out.push(JSON.stringify(await store.dense(g, vec(1, 1, -1, 2), 10)));
|
|
807
|
+
// bm25 scores use table-wide statistics: compare which memories match.
|
|
808
|
+
out.push(ids(await store.lexical(g, 'levelprobe orbit', 50))
|
|
809
|
+
.sort()
|
|
810
|
+
.join(','));
|
|
811
|
+
return out;
|
|
812
|
+
};
|
|
813
|
+
const before = await view();
|
|
814
|
+
for (const c of cases())
|
|
815
|
+
await store.upsert(c.g, c.m);
|
|
816
|
+
expect(await view()).toEqual(before);
|
|
817
|
+
});
|
|
818
|
+
it('levels.illegal-addresses-are-refused', async () => {
|
|
819
|
+
const bad = [
|
|
820
|
+
[
|
|
821
|
+
'planet without customer',
|
|
822
|
+
{ address: { level: 'user', orgId: 'local' } },
|
|
823
|
+
],
|
|
824
|
+
[
|
|
825
|
+
'constellation with a customer',
|
|
826
|
+
{
|
|
827
|
+
address: {
|
|
828
|
+
level: 'hive',
|
|
829
|
+
orgId: 'o',
|
|
830
|
+
hiveId: 'h',
|
|
831
|
+
customerId: 'c',
|
|
832
|
+
},
|
|
833
|
+
},
|
|
834
|
+
],
|
|
835
|
+
[
|
|
836
|
+
'star with a customer',
|
|
837
|
+
{
|
|
838
|
+
address: {
|
|
839
|
+
level: 'agent',
|
|
840
|
+
orgId: 'o',
|
|
841
|
+
agentId: 'a',
|
|
842
|
+
customerId: 'c',
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
],
|
|
846
|
+
[
|
|
847
|
+
'flyby without run',
|
|
848
|
+
{ address: { level: 'run', orgId: 'o', agentId: 'a' } },
|
|
849
|
+
],
|
|
850
|
+
['unknown level', { address: { level: 'team', orgId: 'o' } }],
|
|
851
|
+
['empty owner', { address: { level: 'hive', orgId: '', hiveId: 'h' } }],
|
|
852
|
+
[
|
|
853
|
+
'unknown field',
|
|
854
|
+
{ address: { level: 'hive', orgId: 'o', hiveId: 'h', extra: 1 } },
|
|
855
|
+
],
|
|
856
|
+
['empty author', { author: { agentId: '' } }],
|
|
857
|
+
[
|
|
858
|
+
'scope that is not the address',
|
|
859
|
+
{ address: { level: 'agent', orgId: 'local', agentId: 'cursor' }, scope: localScope() },
|
|
860
|
+
],
|
|
861
|
+
];
|
|
862
|
+
const all = nodesGrant({ level: 'user', orgId: 'local', customerId: 'self' }, { level: 'hive', orgId: 'o', hiveId: 'h' }, { level: 'agent', orgId: 'local', agentId: 'cursor' });
|
|
863
|
+
for (const [what, overrides] of bad) {
|
|
864
|
+
const m = makeMemory({ id: 'bad', ...overrides });
|
|
865
|
+
await expect(store.upsert(all, m), what).rejects.toThrow(RangeError);
|
|
866
|
+
const ok = makeMemory({ id: 'ok' });
|
|
867
|
+
await expect(store.upsertMany(all, [ok, m]), what).rejects.toThrow(RangeError);
|
|
868
|
+
expect(await store.get(all, 'ok'), what).toBeNull();
|
|
869
|
+
expect(await store.get(all, 'bad'), what).toBeNull();
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
it('levels.address-and-author-are-fixed-per-id', async () => {
|
|
873
|
+
const [star, hive] = [LEVEL_CASES[1], LEVEL_CASES[5]];
|
|
874
|
+
const g = nodesGrant(nodeOf(star.address), nodeOf(hive.address));
|
|
875
|
+
const m = memoryAt('fixed', star.address, star.author);
|
|
876
|
+
await store.upsert(g, m);
|
|
877
|
+
const moved = memoryAt('fixed', hive.address, hive.author);
|
|
878
|
+
await expect(store.upsert(g, moved)).rejects.toThrow(GrantError);
|
|
879
|
+
const reauthored = memoryAt('fixed', star.address, {
|
|
880
|
+
agentId: 'cursor',
|
|
881
|
+
accountRef: 'acct:2',
|
|
882
|
+
});
|
|
883
|
+
await expect(store.upsert(g, reauthored)).rejects.toThrow(GrantError);
|
|
884
|
+
expect(await store.get(g, 'fixed')).toEqual(canonicalMemory(m));
|
|
885
|
+
});
|
|
886
|
+
it('levels.author-restriction-is-exact', async () => {
|
|
887
|
+
const planet = { level: 'user', orgId: 'local', customerId: 'self' };
|
|
888
|
+
const mine = makeMemory({ id: 'by-cc', text: 'authorprobe one' });
|
|
889
|
+
const theirs = memoryAt('by-cursor', { level: 'user', orgId: 'local', customerId: 'self' }, { agentId: 'cursor' }, { text: 'authorprobe two' });
|
|
890
|
+
const everyone = nodesGrant(planet);
|
|
891
|
+
await store.upsertMany(everyone, [mine, theirs]);
|
|
892
|
+
expect(ids(await store.lexical(everyone, 'authorprobe', 10)).sort()).toEqual([
|
|
893
|
+
'by-cc',
|
|
894
|
+
'by-cursor',
|
|
895
|
+
]);
|
|
896
|
+
expect(ids(await store.lexical(PRE_LEVEL, 'authorprobe', 10))).toEqual(['by-cc']);
|
|
897
|
+
// The local grant shares the Planet among the person's agents (CON-62).
|
|
898
|
+
expect(ids(await store.lexical(TG, 'authorprobe', 10)).sort()).toEqual([
|
|
899
|
+
'by-cc',
|
|
900
|
+
'by-cursor',
|
|
901
|
+
]);
|
|
902
|
+
const cursor = nodeGrant({
|
|
903
|
+
principal: { ...OWNER, agentId: 'cursor' },
|
|
904
|
+
read: [{ node: planet, projectIds: 'all', authorAgentId: 'cursor' }],
|
|
905
|
+
});
|
|
906
|
+
expect(ids(await store.lexical(cursor, 'authorprobe', 10))).toEqual(['by-cursor']);
|
|
907
|
+
expect(await store.get(cursor, 'by-cc')).toBeNull();
|
|
908
|
+
});
|
|
909
|
+
it('levels.multi-node-grant-reads-across-owners', async () => {
|
|
910
|
+
const all = cases();
|
|
911
|
+
for (const c of all)
|
|
912
|
+
await store.upsert(c.g, c.m);
|
|
913
|
+
const own = makeMemory({ id: 'own', text: 'levelprobe own', embedding: vec(9, 1, -1, 2) });
|
|
914
|
+
await store.upsert(TG, own);
|
|
915
|
+
const hive = all.find((c) => c.name === 'constellation');
|
|
916
|
+
const galaxy = all.find((c) => c.name === 'galaxy');
|
|
917
|
+
const star = all.find((c) => c.name === 'star');
|
|
918
|
+
// My Planet (as before levels) plus a Constellation of another owner, read-only.
|
|
919
|
+
const g = nodeGrant({
|
|
920
|
+
principal: OWNER,
|
|
921
|
+
read: [...TG.readNodes, { node: nodeOf(hive.address), projectIds: ['p1'] }],
|
|
922
|
+
write: TG.writeNodes,
|
|
923
|
+
});
|
|
924
|
+
const other = all.find((c) => c.name === 'planet-other-author');
|
|
925
|
+
const visible = ['own', other.m.id, hive.m.id].sort();
|
|
926
|
+
expect(ids(await store.list(g, { validOnly: false })).sort()).toEqual(visible);
|
|
927
|
+
expect(ids(await store.lexical(g, 'levelprobe', 50)).sort()).toEqual(visible);
|
|
928
|
+
const dense = await store.dense(g, vec(9, 1, -1, 2), 50);
|
|
929
|
+
expect(ids(dense).sort()).toEqual(visible);
|
|
930
|
+
expect(dense.map((h) => h.score)).toEqual(dense.map((h) => h.score).sort((a, b) => b - a));
|
|
931
|
+
expect(ids(await store.dense(g, vec(9, 1, -1, 2), 1))).toEqual(['own']);
|
|
932
|
+
expect(await store.get(g, hive.m.id)).toEqual(canonicalMemory(hive.m));
|
|
933
|
+
for (const c of [galaxy, star])
|
|
934
|
+
expect(await store.get(g, c.m.id)).toBeNull();
|
|
935
|
+
// The hive side is read-only.
|
|
936
|
+
expect(await store.setTier(g, hive.m.id, 'hot')).toBe(false);
|
|
937
|
+
await store.forget(g, hive.m.id);
|
|
938
|
+
expect(await store.get(hive.g, hive.m.id)).not.toBeNull();
|
|
939
|
+
await expect(store.upsert(g, memoryAt('new-hive', hive.address, hive.author))).rejects.toThrow(GrantError);
|
|
940
|
+
// A project outside the pattern stays out.
|
|
941
|
+
const hiveP2 = memoryAt('hive-p2', { ...hive.address, projectId: 'p2' }, hive.author);
|
|
942
|
+
await store.upsert(hive.g, hiveP2);
|
|
943
|
+
expect(await store.get(g, 'hive-p2')).toBeNull();
|
|
944
|
+
});
|
|
581
945
|
});
|
|
582
946
|
}
|
|
583
947
|
//# sourceMappingURL=store-conformance.js.map
|
package/dist/temporal.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { loadChrono, mayHaveDateCue } from './date-cue.js';
|
|
2
2
|
/**
|
|
3
3
|
* Time handling in code, never in a model (CON-34): relative expressions are resolved with
|
|
4
4
|
* chrono-node against the observation's own timestamp, at day precision, in the turn's UTC
|
|
@@ -8,6 +8,13 @@ const DAY_MS = 86_400_000;
|
|
|
8
8
|
const MIN_MS = 60_000;
|
|
9
9
|
/** chrono's reference: the instant, and the offset in minutes east of UTC. */
|
|
10
10
|
const ref = (at, offset = 0) => ({ instant: at, timezone: offset });
|
|
11
|
+
/**
|
|
12
|
+
* chrono's parse, loading chrono only for a text with a date cue (CON-82): a text without one
|
|
13
|
+
* is a text chrono reads nothing the functions below act on.
|
|
14
|
+
*/
|
|
15
|
+
function parse(text, reference, option) {
|
|
16
|
+
return mayHaveDateCue(text) ? loadChrono().parse(text, reference, option) : [];
|
|
17
|
+
}
|
|
11
18
|
/** Midnight of `d`'s day in the given offset, as an instant. */
|
|
12
19
|
const startOfDay = (d, offset = 0) => {
|
|
13
20
|
const local = new Date(d.getTime() + offset * MIN_MS);
|
|
@@ -52,8 +59,8 @@ function leadIn(text, r) {
|
|
|
52
59
|
*/
|
|
53
60
|
export function factInterval(text, at, offset = 0) {
|
|
54
61
|
const out = {};
|
|
55
|
-
const forward =
|
|
56
|
-
const backward =
|
|
62
|
+
const forward = parse(text, ref(at, offset), { forwardDate: true });
|
|
63
|
+
const backward = parse(text, ref(at, offset), { forwardDate: false });
|
|
57
64
|
const day = (d) => startOfDay(d, offset);
|
|
58
65
|
for (const r of forward) {
|
|
59
66
|
const lead = leadIn(text, r);
|
|
@@ -113,9 +120,7 @@ export function checkWriterDate(turnText, factText, at, offset = 0) {
|
|
|
113
120
|
if (relatives.length !== 1 || days.length !== 1)
|
|
114
121
|
return { text: factText };
|
|
115
122
|
const isRelative = new RegExp(RELATIVE.source, 'i');
|
|
116
|
-
const parsed =
|
|
117
|
-
.parse(turnText, ref(at, offset), { forwardDate: false })
|
|
118
|
-
.filter((r) => isRelative.test(r.text));
|
|
123
|
+
const parsed = parse(turnText, ref(at, offset), { forwardDate: false }).filter((r) => isRelative.test(r.text));
|
|
119
124
|
if (parsed.length !== 1)
|
|
120
125
|
return { text: factText };
|
|
121
126
|
const resolved = ymd(parsed[0].start.date(), offset);
|
|
@@ -161,7 +166,7 @@ function without(query, part, dangling = false) {
|
|
|
161
166
|
* in November is this year's March).
|
|
162
167
|
*/
|
|
163
168
|
export function temporalIntent(query, now) {
|
|
164
|
-
const r =
|
|
169
|
+
const r = parse(query, ref(now), { forwardDate: false })[0];
|
|
165
170
|
let day;
|
|
166
171
|
if (r !== undefined) {
|
|
167
172
|
let d = r.start.date();
|