solopup-memory 0.21.0-beta.2 → 0.22.0-beta.3

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 (99) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/COMPATIBILITY.md +8 -5
  3. package/DETECTION.md +139 -0
  4. package/DREAMING-DESIGN.md +167 -0
  5. package/README.en.md +35 -6
  6. package/README.md +44 -7
  7. package/RELEASE.md +36 -0
  8. package/SECURITY.md +11 -0
  9. package/context-history.mjs +20 -3
  10. package/detect.mjs +441 -0
  11. package/eval/bench-detect.mjs +107 -0
  12. package/eval/semantic/cases.json +387 -0
  13. package/eval/semantic-suite.mjs +156 -0
  14. package/index.mjs +117 -27
  15. package/managed-policy.mjs +3 -1
  16. package/package.json +91 -68
  17. package/privacy.mjs +25 -0
  18. package/scripts/demo-source-recall.mjs +123 -0
  19. package/scripts/detect.mjs +69 -0
  20. package/scripts/dream.mjs +205 -0
  21. package/scripts/probe-history.mjs +92 -0
  22. package/scripts/probe-load.mjs +1 -1
  23. package/scripts/probe-package.mjs +6 -2
  24. package/scripts/security-check.mjs +173 -0
  25. package/scripts/soak.mjs +219 -0
  26. package/scripts/telemetry.mjs +187 -0
  27. package/scripts/verify.mjs +3 -1
  28. package/{test-adversarial.mjs → test/test-adversarial.mjs} +5 -5
  29. package/{test-belief-revision.mjs → test/test-belief-revision.mjs} +7 -7
  30. package/{test-candidates.mjs → test/test-candidates.mjs} +4 -4
  31. package/{test-capture-diagnostics.mjs → test/test-capture-diagnostics.mjs} +6 -6
  32. package/{test-capture-field-lineage.mjs → test/test-capture-field-lineage.mjs} +4 -4
  33. package/{test-capture-inputs.mjs → test/test-capture-inputs.mjs} +8 -8
  34. package/{test-capture-jobs.mjs → test/test-capture-jobs.mjs} +5 -5
  35. package/{test-capture-projections.mjs → test/test-capture-projections.mjs} +12 -12
  36. package/{test-capture-revision.mjs → test/test-capture-revision.mjs} +6 -6
  37. package/{test-capture-submission.mjs → test/test-capture-submission.mjs} +3 -3
  38. package/{test-capture-validation-flow.mjs → test/test-capture-validation-flow.mjs} +5 -5
  39. package/{test-capture-validation-job.mjs → test/test-capture-validation-job.mjs} +5 -5
  40. package/{test-capture-validation-lineage.mjs → test/test-capture-validation-lineage.mjs} +2 -2
  41. package/{test-capture-validation.mjs → test/test-capture-validation.mjs} +2 -2
  42. package/{test-capture.mjs → test/test-capture.mjs} +20 -20
  43. package/{test-compaction-compat.mjs → test/test-compaction-compat.mjs} +5 -5
  44. package/{test-compat.mjs → test/test-compat.mjs} +3 -3
  45. package/{test-concurrency.mjs → test/test-concurrency.mjs} +3 -3
  46. package/{test-conflict.mjs → test/test-conflict.mjs} +2 -2
  47. package/{test-context-history.mjs → test/test-context-history.mjs} +1 -1
  48. package/{test-control-schema.mjs → test/test-control-schema.mjs} +1 -1
  49. package/test/test-detect.mjs +125 -0
  50. package/test/test-dream-plan.mjs +72 -0
  51. package/test/test-dsh-registration.mjs +111 -0
  52. package/{test-episodes.mjs → test/test-episodes.mjs} +5 -5
  53. package/{test-explicit-memory.mjs → test/test-explicit-memory.mjs} +4 -4
  54. package/{test-field-lineage.mjs → test/test-field-lineage.mjs} +4 -4
  55. package/{test-history.mjs → test/test-history.mjs} +4 -4
  56. package/{test-inline-summary.mjs → test/test-inline-summary.mjs} +5 -5
  57. package/{test-legacy-source-identity.mjs → test/test-legacy-source-identity.mjs} +2 -2
  58. package/{test-links.mjs → test/test-links.mjs} +4 -4
  59. package/{test-managed-access.mjs → test/test-managed-access.mjs} +29 -5
  60. package/{test-managed-activation.mjs → test/test-managed-activation.mjs} +5 -5
  61. package/{test-meaning.mjs → test/test-meaning.mjs} +6 -6
  62. package/{test-native-service-lifecycle.mjs → test/test-native-service-lifecycle.mjs} +1 -1
  63. package/{test-native-write-validation.mjs → test/test-native-write-validation.mjs} +10 -4
  64. package/{test-packaging.mjs → test/test-packaging.mjs} +32 -5
  65. package/{test-paired-analysis.mjs → test/test-paired-analysis.mjs} +1 -1
  66. package/{test-patch.mjs → test/test-patch.mjs} +11 -11
  67. package/{test-plugin-contract.mjs → test/test-plugin-contract.mjs} +4 -4
  68. package/test/test-privacy.mjs +65 -0
  69. package/{test-project-stores.mjs → test/test-project-stores.mjs} +5 -5
  70. package/{test-publication.mjs → test/test-publication.mjs} +4 -4
  71. package/{test-restore.mjs → test/test-restore.mjs} +11 -11
  72. package/{test-retrieval.mjs → test/test-retrieval.mjs} +2 -2
  73. package/{test-revision-admission.mjs → test/test-revision-admission.mjs} +3 -3
  74. package/{test-rule-admission.mjs → test/test-rule-admission.mjs} +8 -8
  75. package/{test-runtime-startup.mjs → test/test-runtime-startup.mjs} +2 -2
  76. package/{test-schema.mjs → test/test-schema.mjs} +2 -2
  77. package/{test-security.mjs → test/test-security.mjs} +6 -6
  78. package/test/test-semantic-suite.mjs +20 -0
  79. package/{test-service-readiness.mjs → test/test-service-readiness.mjs} +1 -1
  80. package/{test-smell.mjs → test/test-smell.mjs} +4 -4
  81. package/{test-snapshots.mjs → test/test-snapshots.mjs} +3 -3
  82. package/{test-source-completeness.mjs → test/test-source-completeness.mjs} +6 -6
  83. package/{test-source-key.mjs → test/test-source-key.mjs} +2 -2
  84. package/{test-source-retirement.mjs → test/test-source-retirement.mjs} +4 -4
  85. package/{test-status-both.mjs → test/test-status-both.mjs} +12 -4
  86. package/{test-supersede.mjs → test/test-supersede.mjs} +3 -3
  87. package/test/test-telemetry.mjs +60 -0
  88. package/{test-text-admission.mjs → test/test-text-admission.mjs} +6 -6
  89. package/{test-tool-inputs.mjs → test/test-tool-inputs.mjs} +1 -1
  90. package/{test-tool-observations.mjs → test/test-tool-observations.mjs} +6 -6
  91. package/{test-tool-write-guard.mjs → test/test-tool-write-guard.mjs} +3 -3
  92. package/{test-tool-write-lifecycle.mjs → test/test-tool-write-lifecycle.mjs} +3 -3
  93. package/{test-usability.mjs → test/test-usability.mjs} +7 -7
  94. package/{test-validation-source-refs.mjs → test/test-validation-source-refs.mjs} +2 -2
  95. package/{test-validation-transition-context.mjs → test/test-validation-transition-context.mjs} +1 -1
  96. package/{test-withdrawal-archives.mjs → test/test-withdrawal-archives.mjs} +6 -6
  97. package/{test-withdrawal.mjs → test/test-withdrawal.mjs} +2 -2
  98. package/{test.mjs → test/test.mjs} +4 -4
  99. package/version.mjs +1 -1
@@ -1,14 +1,14 @@
1
- import {applyWithValidationFixture as apply} from './fixture-host.mjs'
1
+ import {applyWithValidationFixture as apply} from '../fixture-host.mjs'
2
2
  import {test} from 'node:test'
3
3
  import assert from 'node:assert/strict'
4
4
  import {mkdtempSync,realpathSync,mkdirSync,readFileSync,writeFileSync,existsSync,rmSync} from 'node:fs'
5
5
  import {join} from 'node:path'
6
6
  import {tmpdir} from 'node:os'
7
- import {parseCaptureBatch,buildCaptureDiagnostic,normalizeCaptureDiagnostic,captureDiagnosticOutcome,CAPTURE_RESPONSE_BYTES} from './capture-diagnostics.mjs'
8
- import {extractWithLlm} from './capture-llm.mjs'
9
- import {searchStore, indexLine} from './index.mjs'
10
- import {listCaptureJobs,captureJobStatus,claimCaptureJob,finishCaptureJob} from './capture-jobs.mjs'
11
- import {writeFixtureMemory} from './fixture-host.mjs'
7
+ import {parseCaptureBatch,buildCaptureDiagnostic,normalizeCaptureDiagnostic,captureDiagnosticOutcome,CAPTURE_RESPONSE_BYTES} from '../capture-diagnostics.mjs'
8
+ import {extractWithLlm} from '../capture-llm.mjs'
9
+ import {searchStore, indexLine} from '../index.mjs'
10
+ import {listCaptureJobs,captureJobStatus,claimCaptureJob,finishCaptureJob} from '../capture-jobs.mjs'
11
+ import {writeFixtureMemory} from '../fixture-host.mjs'
12
12
  const item=(n=1)=>({type:'user',summary:'持久有效的测试条目'+n})
13
13
  test('an explicit empty array is a model-reported no-op, never proof that no durable information exists',()=>{
14
14
  const batch=parseCaptureBatch('[]',3),d=buildCaptureDiagnostic(batch)
@@ -5,10 +5,10 @@ import assert from 'node:assert/strict'
5
5
  import {mkdtempSync,realpathSync,mkdirSync,readFileSync,rmSync} from 'node:fs'
6
6
  import {join} from 'node:path'
7
7
  import {tmpdir} from 'node:os'
8
- import {apply,searchStore,withdrawManagedSource} from './index.mjs'
9
- import {candidateSnapshot} from './candidates.mjs'
10
- import {VALIDATION_FIELDS} from './capture-validation-contract.mjs'
11
- import {writeFixtureMemory} from './fixture-host.mjs'
8
+ import {apply,searchStore,withdrawManagedSource} from '../index.mjs'
9
+ import {candidateSnapshot} from '../candidates.mjs'
10
+ import {VALIDATION_FIELDS} from '../capture-validation-contract.mjs'
11
+ import {writeFixtureMemory} from '../fixture-host.mjs'
12
12
  const modes=['value','expiring-value','complete','changed-condition','inferred','wrong-target','unsupported','uncertain','withdraw-during','root-overflow','self-excluding'];
13
13
  for(const mode of modes)test('automatic code-owned field lineage: '+mode,async()=>{
14
14
  const base=realpathSync(mkdtempSync(join(tmpdir(),'auto-field-lineage-'))),home=join(base,'home'),cwd=join(base,'project'),root=join(home,'memory'),previous=process.env.DSH_HOME;
@@ -1,5 +1,5 @@
1
- import {applyWithValidationFixture as apply} from './fixture-host.mjs'
2
- import {prepareMountedFixture} from './fixture-host.mjs'
1
+ import {applyWithValidationFixture as apply} from '../fixture-host.mjs'
2
+ import {prepareMountedFixture} from '../fixture-host.mjs'
3
3
  import {realpathSync} from 'node:fs'
4
4
  import {test} from 'node:test'
5
5
  import assert from 'node:assert/strict'
@@ -8,10 +8,10 @@ import {join} from 'node:path'
8
8
  import {tmpdir} from 'node:os'
9
9
  import {fileURLToPath} from 'node:url'
10
10
  import {spawnSync} from 'node:child_process'
11
- import {captureMessage} from './capture-evidence.mjs'
12
- import {captureSessionEligible,captureInputDescriptor,enqueueCaptureInput,listCaptureInputs,pendingCaptureInputs,bindCaptureInput,blockCaptureInput,reconstructCaptureInput,pruneCaptureInputs,observeCaptureSource} from './capture-inputs.mjs'
13
- import {claimCaptureJob,listCaptureJobs,finishCaptureJob} from './capture-jobs.mjs'
14
- import {storeDirFor, withdrawManagedSource} from './index.mjs'
11
+ import {captureMessage} from '../capture-evidence.mjs'
12
+ import {captureSessionEligible,captureInputDescriptor,enqueueCaptureInput,listCaptureInputs,pendingCaptureInputs,bindCaptureInput,blockCaptureInput,reconstructCaptureInput,pruneCaptureInputs,observeCaptureSource} from '../capture-inputs.mjs'
13
+ import {claimCaptureJob,listCaptureJobs,finishCaptureJob} from '../capture-jobs.mjs'
14
+ import {storeDirFor, withdrawManagedSource} from '../index.mjs'
15
15
 
16
16
  const options={everyTurns:1,maxEntries:3,maxTokens:800,maxInputBytes:24*1024,timeoutMs:25000}
17
17
  const route={provider:'fixture',model:'fixture-model'}
@@ -83,7 +83,7 @@ test('损坏、多余正文字段、链接及无效引用拒绝并保留现场',
83
83
  }))
84
84
 
85
85
  test('来源入队后真实进程SIGKILL,重开保留尚未发出的输入',()=>box(dir=>{
86
- const modulePath=fileURLToPath(new URL('./capture-inputs.mjs',import.meta.url))
86
+ const modulePath=fileURLToPath(new URL('../capture-inputs.mjs',import.meta.url))
87
87
  const code=`import{enqueueCaptureInput}from${JSON.stringify(modulePath)};enqueueCaptureInput(process.env.INPUT_DIR,${JSON.stringify(descriptor())});process.kill(process.pid,'SIGKILL')`
88
88
  const child=spawnSync(process.execPath,['--input-type=module','-e',code],{env:{...process.env,INPUT_DIR:dir},timeout:5000})
89
89
  assert.equal(child.signal,'SIGKILL');assert.equal(pendingCaptureInputs(dir,session()).length,1)
@@ -105,7 +105,7 @@ test('清理只选已知终态,双版本过期拒绝;待处理与未决保
105
105
  }))
106
106
 
107
107
  test('输入CLI默认只读,明确清理须同时带两份版本',()=>box(dir=>{
108
- const script=fileURLToPath(new URL('./scripts/capture-jobs.mjs',import.meta.url)),env={...process.env,DSH_HOME:dir}
108
+ const script=fileURLToPath(new URL('../scripts/capture-jobs.mjs',import.meta.url)),env={...process.env,DSH_HOME:dir}
109
109
  const run=(...args)=>spawnSync(process.execPath,[script,...args],{env,encoding:'utf8',timeout:5000})
110
110
  assert.equal(run('inputs').status,0);assert.equal(existsSync(join(dir,'memory')),false)
111
111
  const store=join(dir,'memory'),row=enqueueCaptureInput(store,descriptor());blockCaptureInput(store,row.id,'source-changed')
@@ -6,10 +6,10 @@ import { tmpdir } from 'node:os'
6
6
  import { spawn, spawnSync } from 'node:child_process'
7
7
  import { createHash } from 'node:crypto'
8
8
  import { fileURLToPath } from 'node:url'
9
- import { claimCaptureJob, assertCaptureClaim, finishCaptureJob, listCaptureJobs, recoverCaptureJobs, abandonCaptureJob, retryBusyWrite } from './capture-jobs.mjs'
10
- import { withLock } from './security.mjs'
9
+ import { claimCaptureJob, assertCaptureClaim, finishCaptureJob, listCaptureJobs, recoverCaptureJobs, abandonCaptureJob, retryBusyWrite } from '../capture-jobs.mjs'
10
+ import { withLock } from '../security.mjs'
11
11
 
12
- const modulePath = fileURLToPath(new URL('./capture-jobs.mjs', import.meta.url))
12
+ const modulePath = fileURLToPath(new URL('../capture-jobs.mjs', import.meta.url))
13
13
  const hash = (v) => createHash('sha256').update(String(v)).digest('hex')
14
14
  const options = { now: 1000, leaseMs: 1000 }
15
15
  const file = (dir) => join(dir, '.capture-jobs.json')
@@ -121,7 +121,7 @@ test('两个真实进程并发领取同一快照,只有一个领取成功', as
121
121
  })
122
122
 
123
123
  test('诊断 CLI 默认只读;recover --apply 只标未决,不执行模型请求', () => box((dir) => {
124
- const script = fileURLToPath(new URL('./scripts/capture-jobs.mjs', import.meta.url))
124
+ const script = fileURLToPath(new URL('../scripts/capture-jobs.mjs', import.meta.url))
125
125
  const env = { ...process.env, DSH_HOME: dir }
126
126
  const run = (...args) => spawnSync(process.execPath, [script, ...args], { env, encoding: 'utf8', timeout: 5000 })
127
127
  assert.equal(run().status, 0); assert.equal(existsSync(join(dir, 'memory')), false)
@@ -286,7 +286,7 @@ test('两个真实进程对同一预览并发处置,仅一次生效,另一
286
286
  })
287
287
 
288
288
  test('处置 CLI 默认预览,缺版本/未知参数/过期版本不修改记录,明确应用后仍为未知结果', () => box((dir) => {
289
- const script = fileURLToPath(new URL('./scripts/capture-jobs.mjs', import.meta.url))
289
+ const script = fileURLToPath(new URL('../scripts/capture-jobs.mjs', import.meta.url))
290
290
  const run = (...args) => spawnSync(process.execPath, [script, ...args], { env: { ...process.env, DSH_HOME: dir }, encoding: 'utf8', timeout: 5000 })
291
291
  const job = claimCaptureJob(join(dir, 'memory'), hash('cli-close'), options)
292
292
  const before = readFileSync(file(join(dir,'memory')),'utf8')
@@ -1,17 +1,17 @@
1
- import {applyWithValidationFixture as apply} from './fixture-host.mjs'
1
+ import {applyWithValidationFixture as apply} from '../fixture-host.mjs'
2
2
  import {test}from'node:test'
3
3
  import assert from'node:assert/strict'
4
4
  import{readFileSync,mkdtempSync,realpathSync,mkdirSync,rmSync,existsSync}from'node:fs'
5
5
  import{join}from'node:path'
6
6
  import{tmpdir}from'node:os'
7
- import{captureProjection}from'./capture-projections.mjs'
8
- import{candidateDetail}from'./withdrawal.mjs'
9
- import{buildUserPrompt,extractWithLlm}from'./capture-llm.mjs'
10
- import{captureInputDescriptor,reconstructCaptureInput,enqueueCaptureInput,bindCaptureInput,listCaptureInputs,blockCaptureInput}from'./capture-inputs.mjs'
11
- import{claimCaptureJob,recoverCaptureJobs,listCaptureJobs}from'./capture-jobs.mjs'
12
- import {indexLine, searchStore, storeRevision, withdrawManagedSource} from './index.mjs'
13
- import{writeFixtureMemory,prepareMountedFixture}from'./fixture-host.mjs'
14
- import{candidateSnapshot}from'./candidates.mjs'
7
+ import{captureProjection}from'../capture-projections.mjs'
8
+ import{candidateDetail}from'../withdrawal.mjs'
9
+ import{buildUserPrompt,extractWithLlm}from'../capture-llm.mjs'
10
+ import{captureInputDescriptor,reconstructCaptureInput,enqueueCaptureInput,bindCaptureInput,listCaptureInputs,blockCaptureInput}from'../capture-inputs.mjs'
11
+ import{claimCaptureJob,recoverCaptureJobs,listCaptureJobs}from'../capture-jobs.mjs'
12
+ import {indexLine, searchStore, storeRevision, withdrawManagedSource} from '../index.mjs'
13
+ import{writeFixtureMemory,prepareMountedFixture}from'../fixture-host.mjs'
14
+ import{candidateSnapshot}from'../candidates.mjs'
15
15
  const record={id:'a'.repeat(64),type:'user',scope:'global',summary:'只对初稿用标记 jade26',meaning:{kind:'preference',conditions:[]},detail:'Synthetic detail restricted to drafts.',origin:'tool-context',sessionId:'synthetic',evidence:[{messageId:'synthetic:1',quote:'Synthetic draft-only source'}]}
16
16
  const line='[global] - [user] 只对初稿用标记 jade26 — user.md'
17
17
  test('projection exposes complete existing fields and source references as data, not a new message',()=>{
@@ -28,7 +28,7 @@ test('structured projections fit the input byte budget atomically and report omi
28
28
  for(const limit of [1024,2048,24576,65536]){const prompt=buildUserPrompt([{id:'source',role:'user',text:'原话只对初稿适用'}],[line],3,limit,[],projected);assert.ok(Buffer.byteLength(prompt)<=limit,`budget ${limit}`);assert.match(prompt,/省略/);for(const row of prompt.split('\n').filter(x=>x.startsWith('{')))assert.doesNotThrow(()=>JSON.parse(row))}
29
29
  })
30
30
  test('the actual v25 descriptor cannot resume under a changed extraction contract',()=>{
31
- const f=JSON.parse(readFileSync(new URL('./corpus/capture-contract-v25.json',import.meta.url))),session={...f.session,surface:{nodes:[1]},isOwnSeq:n=>n===1},events=[];events[1]=f.event
31
+ const f=JSON.parse(readFileSync(new URL('../corpus/capture-contract-v25.json',import.meta.url))),session={...f.session,surface:{nodes:[1]},isOwnSeq:n=>n===1},events=[];events[1]=f.event
32
32
  const next=captureInputDescriptor(session,[f.message],f.cwd,f.options,f.route)
33
33
  assert.notEqual(next.configurationHash,f.descriptor.configurationHash);assert.notEqual(next.id,f.descriptor.id)
34
34
  assert.throws(()=>reconstructCaptureInput(f.descriptor,session,events,f.cwd,f.options,f.route),/configuration-changed/)
@@ -62,7 +62,7 @@ for(const mode of ['repair','stale','withdrawn'])test('live capture projection a
62
62
  })
63
63
 
64
64
  for(const status of ['pending','bound','blocked'])test('changed contract cannot create another durable invocation for the same owner sources: '+status,()=>{
65
- const f=JSON.parse(readFileSync(new URL('./corpus/capture-contract-v25.json',import.meta.url))),root=mkdtempSync(join(tmpdir(),'dsh-contract-alias-'))
65
+ const f=JSON.parse(readFileSync(new URL('../corpus/capture-contract-v25.json',import.meta.url))),root=mkdtempSync(join(tmpdir(),'dsh-contract-alias-'))
66
66
  try{
67
67
  enqueueCaptureInput(root,f.descriptor,{now:100})
68
68
  if(status==='bound'){const job=claimCaptureJob(root,f.descriptor.id,{now:100,leaseMs:1000});bindCaptureInput(root,f.descriptor.id,job.id,{now:101});recoverCaptureJobs(root,{now:2000})}
@@ -73,6 +73,6 @@ for(const status of ['pending','bound','blocked'])test('changed contract cannot
73
73
  }finally{rmSync(root,{recursive:true,force:true})}
74
74
  })
75
75
  test('genuine new owner source can create a new durable input under the new contract',()=>{
76
- const f=JSON.parse(readFileSync(new URL('./corpus/capture-contract-v25.json',import.meta.url))),root=mkdtempSync(join(tmpdir(),'dsh-contract-new-'))
76
+ const f=JSON.parse(readFileSync(new URL('../corpus/capture-contract-v25.json',import.meta.url))),root=mkdtempSync(join(tmpdir(),'dsh-contract-new-'))
77
77
  try{enqueueCaptureInput(root,f.descriptor,{now:100});const next=captureInputDescriptor(f.session,[{...f.message,seq:2,id:f.session.id+':2'}],f.cwd,f.options,f.route);assert.equal(enqueueCaptureInput(root,next,{now:101}).id,next.id);assert.equal(listCaptureInputs(root).inputs.length,2)}finally{rmSync(root,{recursive:true,force:true})}
78
78
  })
@@ -1,14 +1,14 @@
1
- import {applyWithValidationFixture as apply} from './fixture-host.mjs'
1
+ import {applyWithValidationFixture as apply} from '../fixture-host.mjs'
2
2
  import {test} from 'node:test'
3
3
  import assert from 'node:assert/strict'
4
4
  import {mkdtempSync,realpathSync,mkdirSync,readFileSync,existsSync,rmSync} from 'node:fs'
5
5
  import {join} from 'node:path'
6
6
  import {tmpdir} from 'node:os'
7
- import {captureRevision} from './capture-revision.mjs'
8
- import {parseCandidates} from './capture-llm.mjs'
9
- import {indexLine, lookupLinks, storeDirFor} from './index.mjs'
10
- import {prepareMountedFixture,writeFixtureMemory} from './fixture-host.mjs'
11
- import {listCandidates} from './candidates.mjs'
7
+ import {captureRevision} from '../capture-revision.mjs'
8
+ import {parseCandidates} from '../capture-llm.mjs'
9
+ import {indexLine, lookupLinks, storeDirFor} from '../index.mjs'
10
+ import {prepareMountedFixture,writeFixtureMemory} from '../fixture-host.mjs'
11
+ import {listCandidates} from '../candidates.mjs'
12
12
 
13
13
  const oldSummary='个人长期默认配色使用 violet-4826',newSummary='个人长期默认配色使用 silver-5937',otherSummary='日常答复保持简洁且先给结论'
14
14
  const oldLine=indexLine('user',oldSummary),target='[global] '+oldLine
@@ -1,8 +1,8 @@
1
1
  import {test} from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
- import {CaptureSubmission,CAPTURE_SUBMISSION_NAME,CAPTURE_SUBMISSION_TOOL} from './capture-submission.mjs'
4
- import {extractWithLlm,CAPTURE_REQUEST_CONTRACT} from './capture-llm.mjs'
5
- import {TEXT_MEMORY_POLICY} from './memory-policy.mjs'
3
+ import {CaptureSubmission,CAPTURE_SUBMISSION_NAME,CAPTURE_SUBMISSION_TOOL} from '../capture-submission.mjs'
4
+ import {extractWithLlm,CAPTURE_REQUEST_CONTRACT} from '../capture-llm.mjs'
5
+ import {TEXT_MEMORY_POLICY} from '../memory-policy.mjs'
6
6
  const candidate={type:'user',summary:'长期固定搭档负责声音,本人负责剪辑',scope:'global',basis:'explicit',meaning:{kind:'claim'},evidence:[{messageId:'synthetic-user',quote:'我的固定搭档负责声音,我本人负责剪辑'}]}
7
7
  function chunks(entries=[candidate],options={}){
8
8
  const raw=options.raw??JSON.stringify({entries}),name=options.name??CAPTURE_SUBMISSION_NAME,id='synthetic-submit'
@@ -3,11 +3,11 @@ import assert from 'node:assert/strict'
3
3
  import {mkdtempSync,realpathSync,mkdirSync,readFileSync,rmSync,existsSync} from 'node:fs'
4
4
  import {tmpdir} from 'node:os'
5
5
  import {join} from 'node:path'
6
- import {apply,indexLine} from './index.mjs'
7
- import {writeFixtureMemory,prepareMountedFixture} from './fixture-host.mjs'
8
- import {candidateSnapshot,listCandidates} from './candidates.mjs'
9
- import {listCaptureJobs} from './capture-jobs.mjs'
10
- import {VALIDATION_FIELDS} from './capture-validation-contract.mjs'
6
+ import {apply,indexLine} from '../index.mjs'
7
+ import {writeFixtureMemory,prepareMountedFixture} from '../fixture-host.mjs'
8
+ import {candidateSnapshot,listCandidates} from '../candidates.mjs'
9
+ import {listCaptureJobs} from '../capture-jobs.mjs'
10
+ import {VALIDATION_FIELDS} from '../capture-validation-contract.mjs'
11
11
  for(const mode of ['approved','rejected','uncertain','validation-error','correction-rejected','transition-rejected','stale'])test('actual automatic acquisition → durable validation → source-bound admission: '+mode,async()=>{
12
12
  const base=realpathSync(mkdtempSync(join(tmpdir(),'dsh-validated-flow-'))),home=join(base,'home'),cwd=join(base,'project'),root=join(home,'memory'),previous=process.env.DSH_HOME
13
13
  mkdirSync(home);mkdirSync(cwd);process.env.DSH_HOME=home
@@ -3,10 +3,10 @@ import assert from 'node:assert/strict'
3
3
  import {mkdtempSync,readFileSync,rmSync} from 'node:fs'
4
4
  import {tmpdir} from 'node:os'
5
5
  import {join} from 'node:path'
6
- import {runCandidateValidation} from './capture-validation-job.mjs'
7
- import {VALIDATION_FIELDS} from './capture-validation-contract.mjs'
8
- import {listCaptureJobs,claimCaptureJob,finishCaptureJob,captureJobStatus} from './capture-jobs.mjs'
9
- import {buildCaptureDiagnostic} from './capture-diagnostics.mjs'
6
+ import {runCandidateValidation} from '../capture-validation-job.mjs'
7
+ import {VALIDATION_FIELDS} from '../capture-validation-contract.mjs'
8
+ import {listCaptureJobs,claimCaptureJob,finishCaptureJob,captureJobStatus} from '../capture-jobs.mjs'
9
+ import {buildCaptureDiagnostic} from '../capture-diagnostics.mjs'
10
10
  import {writeFileSync} from 'node:fs'
11
11
  import {spawn,spawnSync} from 'node:child_process'
12
12
  import {existsSync} from 'node:fs'
@@ -41,7 +41,7 @@ test('a source/revision change during validation propagates and prevents a publi
41
41
  assert.equal(calls,1);assert.equal(listCaptureJobs(dir).jobs[0].status,'failed')
42
42
  }))
43
43
  test('SIGKILL after a validation invocation is claimed cannot resend that child from a new process',()=>box(async dir=>{
44
- const marker=join(dir,'invocation-started'),moduleUrl=new URL('./capture-validation-job.mjs',import.meta.url).href
44
+ const marker=join(dir,'invocation-started'),moduleUrl=new URL('../capture-validation-job.mjs',import.meta.url).href
45
45
  const opts={jobDir:dir,parentId:'e'.repeat(64),messages:[message],candidates:[candidate],sessionId:'synthetic-crash',timeoutMs:5000}
46
46
  const script=`import{runCandidateValidation}from${JSON.stringify(moduleUrl)};import{writeFileSync}from'node:fs';let calls=0;let ticker;const opts=${JSON.stringify(opts)};opts.llm={async prepareCall(config){return{config,async*stream(){calls++;writeFileSync(${JSON.stringify(marker)},'started');if(process.argv[1]==='issue'){ticker=setInterval(()=>{},1000);await new Promise(()=>{})}yield{type:'text-delta',text:'{"checks":[]}'};yield{type:'finish',reason:{kind:'stop'}}}}}};const r=await runCandidateValidation(opts);clearInterval(ticker);console.log(JSON.stringify({calls,reason:r.reason}));`
47
47
  const child=spawn(process.execPath,['--input-type=module','-e',script,'issue'],{stdio:['ignore','pipe','pipe']})
@@ -1,4 +1,4 @@
1
- import {VALIDATION_FIELDS} from './capture-validation-contract.mjs'
1
+ import {VALIDATION_FIELDS} from '../capture-validation-contract.mjs'
2
2
  /** Scripted pipeline regression; optional source override reproduces the frozen dev28 bug. */
3
3
  import {test} from 'node:test'
4
4
  import assert from 'node:assert/strict'
@@ -6,7 +6,7 @@ import {mkdtempSync,realpathSync,mkdirSync,readFileSync,rmSync} from 'node:fs'
6
6
  import {join,resolve} from 'node:path'
7
7
  import {tmpdir} from 'node:os'
8
8
  import {pathToFileURL} from 'node:url'
9
- const baseUrl=process.env.DSH_VALIDATION_LINEAGE_SOURCE?pathToFileURL(resolve(process.env.DSH_VALIDATION_LINEAGE_SOURCE)+'/'):new URL('./',import.meta.url)
9
+ const baseUrl=process.env.DSH_VALIDATION_LINEAGE_SOURCE?pathToFileURL(resolve(process.env.DSH_VALIDATION_LINEAGE_SOURCE)+'/'):new URL('../',import.meta.url)
10
10
  const {apply,withdrawManagedSource,searchStore}=await import(new URL('index.mjs',baseUrl))
11
11
  const {candidateSnapshot}=await import(new URL('candidates.mjs',baseUrl))
12
12
  const {writeFixtureMemory}=await import(new URL('fixture-host.mjs',baseUrl))
@@ -1,7 +1,7 @@
1
1
  import {test} from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
- import {prepareValidationPrompt,ValidationSubmission,validateCandidatesWithLlm,validationCandidateId} from './capture-validation.mjs'
4
- import {VALIDATION_FIELDS,VALIDATION_NAME} from './capture-validation-contract.mjs'
3
+ import {prepareValidationPrompt,ValidationSubmission,validateCandidatesWithLlm,validationCandidateId} from '../capture-validation.mjs'
4
+ import {VALIDATION_FIELDS,VALIDATION_NAME} from '../capture-validation-contract.mjs'
5
5
  const message={id:'owner-1',role:'user',text:'我在所有项目处理反馈时都先看最重要的两处问题,然后再看怎么改。'}
6
6
  const candidate={type:'user',scope:'global',basis:'explicit',summary:'处理反馈先看两处问题再给修改办法,跨项目通用',meaning:{kind:'preference',conditions:[{dimension:'task',value:'review'}]},evidence:[{messageId:message.id,quote:message.text}]}
7
7
  const prepared=()=>prepareValidationPrompt([message],[candidate])
@@ -1,8 +1,8 @@
1
- import {applyWithValidationFixture as apply} from './fixture-host.mjs'
2
- import {prepareMountedFixture,writeFixtureMemory} from './fixture-host.mjs'
1
+ import {applyWithValidationFixture as apply} from '../fixture-host.mjs'
2
+ import {prepareMountedFixture,writeFixtureMemory} from '../fixture-host.mjs'
3
3
  import {realpathSync} from 'node:fs'
4
4
  /**
5
- * 自动捕获的测试。跑法:node --test ~/.dsh/plugins/memory/test-capture.mjs
5
+ * 自动捕获的测试。跑法:node --test ~/.dsh/plugins/memory/test/test-capture.mjs
6
6
  *
7
7
  * 这里用假 LLM 验证「提示词 → 解析 → 过闸 → 落库 → 审计」整条链路,
8
8
  * 不产生真实模型调用。
@@ -15,13 +15,13 @@ import { join, dirname } from 'node:path'
15
15
  import { tmpdir } from 'node:os'
16
16
  import { fileURLToPath } from 'node:url'
17
17
 
18
- import { chunkText, parseCandidates, buildUserPrompt, extractWithLlm, selectCaptureControls } from './capture-llm.mjs'
19
- import { captureMessage, assessCandidate } from './capture-evidence.mjs'
20
- import { listCaptureJobs, recoverCaptureJobs, abandonCaptureJob } from './capture-jobs.mjs'
21
- import { listCandidates, stageCandidate, candidateSnapshot } from './candidates.mjs'
22
- import {writeMemory, indexLine, storeDirFor, storeRevision, forgetMemory, adoptCandidate, withdrawManagedSource, searchStore} from './index.mjs'
18
+ import { chunkText, parseCandidates, buildUserPrompt, extractWithLlm, selectCaptureControls } from '../capture-llm.mjs'
19
+ import { captureMessage, assessCandidate } from '../capture-evidence.mjs'
20
+ import { listCaptureJobs, recoverCaptureJobs, abandonCaptureJob } from '../capture-jobs.mjs'
21
+ import { listCandidates, stageCandidate, candidateSnapshot } from '../candidates.mjs'
22
+ import {writeMemory, indexLine, storeDirFor, storeRevision, forgetMemory, adoptCandidate, withdrawManagedSource, searchStore} from '../index.mjs'
23
23
 
24
- const HERE = dirname(fileURLToPath(import.meta.url))
24
+ const HERE = dirname(dirname(fileURLToPath(import.meta.url)))
25
25
 
26
26
  function sandbox() {
27
27
  const root = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-mem-capture-')))
@@ -261,7 +261,7 @@ test('每 N 轮触发一次:候选经写入闸落库并留下审计', async ()
261
261
  { type: 'feedback', summary: '改稿必须附「改了什么、为什么」', detail: '来自用户纠正', scope: 'global', meaning:{kind:'claim'},basis: 'explicit', evidence: [{ messageId: 's1:event-2', quote: '改稿必须附「改了什么、为什么」' }] },
262
262
  { type: 'user', summary: '验收要量化到字数与标点', scope: 'global', meaning:{kind:'claim'},basis: 'explicit', evidence: [{ messageId: 's1:event-2', quote: '验收要量化到字数与标点' }] },
263
263
  ])
264
- const { applyWithValidationFixture: apply } = await import('./fixture-host.mjs')
264
+ const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
265
265
  const { ctx, handlers } = mount(box, { llm })
266
266
  prepareMountedFixture()
267
267
  apply(ctx, { autoCapture: { everyTurns: 3 } })
@@ -308,7 +308,7 @@ test('每 N 轮触发一次:候选经写入闸落库并留下审计', async ()
308
308
  test('没有 llm 时不触发自动捕获,仍保留显式工具来源监听', async () => {
309
309
  const box = sandbox()
310
310
  try {
311
- const { applyWithValidationFixture: apply } = await import('./fixture-host.mjs')
311
+ const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
312
312
  const { ctx, handlers } = mount(box, {})
313
313
  prepareMountedFixture()
314
314
  apply(ctx, {})
@@ -321,7 +321,7 @@ test('没有 llm 时不触发自动捕获,仍保留显式工具来源监听',
321
321
  test('配置 enabled:false 时关闭自动捕获', async () => {
322
322
  const box = sandbox()
323
323
  try {
324
- const { applyWithValidationFixture: apply } = await import('./fixture-host.mjs')
324
+ const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
325
325
  const { ctx, handlers } = mount(box, { llm: fakeLlm([]) })
326
326
  prepareMountedFixture()
327
327
  apply(ctx, { autoCapture: { enabled: false } })
@@ -334,7 +334,7 @@ test('配置 enabled:false 时关闭自动捕获', async () => {
334
334
  test('模型输出坏 JSON 时只记审计,不写入、不抛错', async () => {
335
335
  const box = sandbox()
336
336
  try {
337
- const { applyWithValidationFixture: apply } = await import('./fixture-host.mjs')
337
+ const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
338
338
  const { ctx, handlers } = mount(box, { llm: fakeLlm('这不是 JSON') })
339
339
  prepareMountedFixture()
340
340
  apply(ctx, { autoCapture: { everyTurns: 1 } })
@@ -358,7 +358,7 @@ test('重复候选不会写第二次(走既有幂等闸)', async () => {
358
358
  const message = promptMessages(request).find((m) => m.role === 'user')
359
359
  return [{ type: 'user', summary: '回答要结论先行', scope: 'global', meaning:{kind:'claim'},basis: 'explicit', evidence: [{ messageId: message.id, quote: '回答一直都要结论先行' }] }]
360
360
  })
361
- const { applyWithValidationFixture: apply } = await import('./fixture-host.mjs')
361
+ const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
362
362
  const { ctx, handlers } = mount(box, { llm })
363
363
  prepareMountedFixture()
364
364
  apply(ctx, { autoCapture: { everyTurns: 1 } })
@@ -440,7 +440,7 @@ test('信念:推断只成为候选,同一消息重复引用不增加独立
440
440
  })
441
441
 
442
442
  async function runCapture(box, payload, cwd = box.cwd, sessionId = 'evidence-session') {
443
- const { applyWithValidationFixture: apply } = await import('./fixture-host.mjs')
443
+ const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
444
444
  const { ctx, handlers } = mount(box, { llm: fakeLlm(payload) })
445
445
  ctx.workingDirectory.get = () => cwd
446
446
  prepareMountedFixture()
@@ -551,7 +551,7 @@ test('接线:模型推断持久化但不进入长期索引或注入上下文',
551
551
  assert.doesNotMatch(readFileSync(join(project, '.audit.jsonl'), 'utf8'), /暖色/)
552
552
  writeFixtureMemory({ type: 'user', dir: box.store, line: indexLine('user', '已确认的有效偏好使用中文回答'), scope: 'global' })
553
553
  const { ctx, captured } = mount(box)
554
- const { applyWithValidationFixture: apply } = await import('./fixture-host.mjs')
554
+ const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
555
555
  prepareMountedFixture()
556
556
  apply(ctx, { autoCapture: { enabled: false } })
557
557
  const injected = captured.context.text({ agent: { session: {} } })
@@ -660,7 +660,7 @@ test('能力失败仅保存固定错误码,同一快照重开不把失败当
660
660
  let prepares = 0
661
661
  const llm = fakeLlm([])
662
662
  llm.prepareCall = async () => { prepares++; throw Object.assign(new Error('synthetic private capability diagnostic'), { code: 'INVALID_MODEL_REASONING' }) }
663
- const { applyWithValidationFixture: apply } = await import('./fixture-host.mjs')
663
+ const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
664
664
  const run = async () => {
665
665
  const { ctx, handlers } = mount(box, { llm })
666
666
  prepareMountedFixture()
@@ -691,7 +691,7 @@ test('显式捕获路由优先于主会话模型,预算不被主请求头覆
691
691
  return prepare(config, ...rest)
692
692
  }
693
693
  const { ctx, handlers } = mount(box, { llm })
694
- const { applyWithValidationFixture: apply } = await import('./fixture-host.mjs')
694
+ const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
695
695
  prepareMountedFixture()
696
696
  apply(ctx, { autoCapture: { everyTurns: 1, provider: 'configured-capture', model: 'configured-model', maxTokens: 777 } })
697
697
  const emit = ((session,event)=>{for(const [name,handler] of handlers)if(name==='session/event')handler(session,event)}), session = { id: 'explicit-route-session' }
@@ -706,7 +706,7 @@ test('显式捕获路由优先于主会话模型,预算不被主请求头覆
706
706
  test('不完整或空白显式路由在注册能力前明确拒绝配置', async () => {
707
707
  const box = sandbox()
708
708
  try {
709
- const { applyWithValidationFixture: apply } = await import('./fixture-host.mjs')
709
+ const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
710
710
  for (const route of [{ provider: 'one' }, { model: 'one' }, { provider: ' ', model: 'one' }, { provider: 'one', model: 3 }]) {
711
711
  const llm = fakeLlm([]), { ctx, captured, handlers } = mount(box, { llm })
712
712
  assert.throws(() => apply(ctx, { autoCapture: { ...route } }), /memory-invalid-config/)
@@ -754,7 +754,7 @@ test('作业接线:损坏账本暂停自动捕获,六工具与主注入保
754
754
  mkdirSync(box.store, { recursive: true }); writeFileSync(join(box.store, '.capture-jobs.json'), '{bad')
755
755
  const llm = fakeLlm([])
756
756
  const { ctx, handlers, captured } = mount(box, { llm })
757
- const { applyWithValidationFixture: apply } = await import('./fixture-host.mjs'); prepareMountedFixture(); apply(ctx)
757
+ const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs'); prepareMountedFixture(); apply(ctx)
758
758
  assert.equal(handlers.filter(([name]) => name === 'session/event').length, 1); assert.equal(llm.calls, 0)
759
759
  assert.equal(captured.tools.length, 6); assert.equal(typeof captured.context.text, 'function')
760
760
  const marker = JSON.parse(readFileSync(join(box.store, '.loaded.json')))
@@ -1,9 +1,9 @@
1
- import {writeFixtureMemory} from './fixture-host.mjs'
1
+ import {writeFixtureMemory} from '../fixture-host.mjs'
2
2
  import {realpathSync} from 'node:fs'
3
- import {applyWithOwnerFixture as apply} from './fixture-host.mjs'
3
+ import {applyWithOwnerFixture as apply} from '../fixture-host.mjs'
4
4
  /**
5
5
  * 记忆系统 × 宿主对话压缩(compaction)的兼容性测试。
6
- * 跑法:node --test ~/.dsh/plugins/memory/test-compaction-compat.mjs
6
+ * 跑法:node --test ~/.dsh/plugins/memory/test/test-compaction-compat.mjs
7
7
  *
8
8
  * 背景:DSH 自带对话压缩(dsh-compaction-basic + /compact + 工具输出修剪),
9
9
  * 会在 token 压力上升时把较早历史换成摘要。记忆系统必须和它互不干扰。
@@ -30,8 +30,8 @@ import {
30
30
  statusOf,
31
31
  storeDirFor,
32
32
  writeMemory,
33
- } from './index.mjs'
34
- import { entryId } from './security.mjs'
33
+ } from '../index.mjs'
34
+ import { entryId } from '../security.mjs'
35
35
 
36
36
  /** 宿主 tool-result-pruner 的默认阈值,见 dsh-base/cordis.patch.yml。 */
37
37
  const HOST_TOOL_RESULT_LIMIT = 8192
@@ -1,7 +1,7 @@
1
1
  import {realpathSync} from 'node:fs'
2
- import {applyWithOwnerFixture as apply} from './fixture-host.mjs'
2
+ import {applyWithOwnerFixture as apply} from '../fixture-host.mjs'
3
3
  /**
4
- * 宿主 API 兼容与降级。跑法:node --test ~/.dsh/plugins/memory/test-compat.mjs
4
+ * 宿主 API 兼容与降级。跑法:node --test ~/.dsh/plugins/memory/test/test-compat.mjs
5
5
  *
6
6
  * 目的:宿主升级后能区分「插件坏了」和「宿主变了」。
7
7
  * 没有 systemPrompt.context 就没法注入 → 立刻抛错(响);
@@ -14,7 +14,7 @@ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
14
14
  import { join } from 'node:path'
15
15
  import { tmpdir } from 'node:os'
16
16
 
17
- import { selfCheck, storeDirFor } from './index.mjs'
17
+ import { selfCheck, storeDirFor } from '../index.mjs'
18
18
 
19
19
  function sandbox() {
20
20
  const root = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-mem-compat-')))
@@ -1,6 +1,6 @@
1
1
  import {realpathSync} from 'node:fs'
2
2
  /**
3
- * 并发与故障注入。跑法:node --test ~/.dsh/plugins/memory/test-concurrency.mjs
3
+ * 并发与故障注入。跑法:node --test ~/.dsh/plugins/memory/test/test-concurrency.mjs
4
4
  *
5
5
  * 这两类最容易被本地测试放过:
6
6
  * · 进程内锁测过了,跨进程仍可能丢更新
@@ -24,9 +24,9 @@ import { join, dirname } from 'node:path'
24
24
  import { fileURLToPath } from 'node:url'
25
25
  import { tmpdir } from 'node:os'
26
26
 
27
- import { apply, storeDirFor, writeMemory } from './index.mjs'
27
+ import { apply, storeDirFor, writeMemory } from '../index.mjs'
28
28
 
29
- const HERE = dirname(fileURLToPath(import.meta.url))
29
+ const HERE = dirname(dirname(fileURLToPath(import.meta.url)))
30
30
  const WORKER = join(HERE, 'worker-write.mjs')
31
31
 
32
32
  function sandbox() {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 冲突检测(同一事实、不同取值)。跑法:node --test ~/.dsh/plugins/memory/test-conflict.mjs
2
+ * 冲突检测(同一事实、不同取值)。跑法:node --test ~/.dsh/plugins/memory/test/test-conflict.mjs
3
3
  *
4
4
  * 规则只有一条:**非取值部分完全相同,且恰好一个取值不同** → 疑似冲突。
5
5
  * 它产生「警告」而不是「拒绝」,因为文本结构无法区分「改口」和「枚举」:
@@ -14,7 +14,7 @@ import { mkdtempSync, mkdirSync, readFileSync, rmSync } from 'node:fs'
14
14
  import { join } from 'node:path'
15
15
  import { tmpdir } from 'node:os'
16
16
 
17
- import { conflictSignature, findConflict, storeDirFor, writeMemory } from './index.mjs'
17
+ import { conflictSignature, findConflict, storeDirFor, writeMemory } from '../index.mjs'
18
18
 
19
19
  function sandbox() {
20
20
  const root = mkdtempSync(join(tmpdir(), 'dsh-mem-conflict-'))
@@ -1,6 +1,6 @@
1
1
  import {test} from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
- import {memoryContextProjection,reconcileMemoryContext,assertCurrentMemoryRequest,setupMemoryHistory,HISTORY_PROJECTION_KEY} from './context-history.mjs'
3
+ import {memoryContextProjection,reconcileMemoryContext,assertCurrentMemoryRequest,setupMemoryHistory,HISTORY_PROJECTION_KEY} from '../context-history.mjs'
4
4
  const header='Current runtime context. This snapshot supersedes earlier runtime-context snapshots.'
5
5
  const context=(id,text,extra=[])=>{const sections=[{name:'memory:index',text},...extra];return{id,role:'user',source:{kind:'runtime-context',form:'snapshot',sections},content:[{type:'text',text:header+'\n\n'+sections.map(s=>s.text).join('\n\n')}]}}
6
6
  const freeze=value=>{if(value&&typeof value==='object'){Object.values(value).forEach(freeze);Object.freeze(value)}return value}
@@ -1,7 +1,7 @@
1
1
  import test from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
3
  import {DatabaseSync} from 'node:sqlite'
4
- import {CONTROL_APPLICATION_ID,CONTROL_SCHEMA_SQL,assertControlSchema,readControlStateRows} from './control-schema.mjs'
4
+ import {CONTROL_APPLICATION_ID,CONTROL_SCHEMA_SQL,assertControlSchema,readControlStateRows} from '../control-schema.mjs'
5
5
  const setup=db=>db.exec(CONTROL_SCHEMA_SQL+'; PRAGMA application_id='+CONTROL_APPLICATION_ID+'; PRAGMA user_version=1;')
6
6
  const box=run=>{const db=new DatabaseSync(':memory:');try{setup(db);run(db)}finally{db.close()}}
7
7
  test('one live connection reads newly added and removed schema rows rather than a cached approval',()=>box(db=>{