@modusensus/dsh-mneme 0.13.0 → 0.14.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +830 -828
- package/assets/icon.svg +3 -3
- package/bin/cli.mjs +964 -964
- package/bin/dsh-mneme-choro.mjs +262 -254
- package/bin/dsh-mneme-mcp.mjs +510 -510
- package/bin/dsh-mneme-serve.mjs +151 -151
- package/cordis.patch.yml +21 -21
- package/docs/CONFIGURATION.md +407 -402
- package/docs/CONTINUITY.md +139 -139
- package/docs/DAEMON.md +57 -57
- package/docs/DIRECTORIES.md +114 -114
- package/docs/DISTILL.md +397 -371
- package/docs/DSH-GO.md +97 -97
- package/docs/ENTITIES.md +247 -247
- package/docs/FORGET.md +77 -77
- package/docs/KB.md +110 -110
- package/docs/LOCAL_MODEL.md +222 -222
- package/docs/SEMANTIC.md +256 -256
- package/docs/SHARE.md +552 -552
- package/docs/SLEEP.md +202 -202
- package/docs/STORAGE.md +122 -122
- package/docs/WORKFLOWS.md +366 -366
- package/lib/api-standalone.js +1007 -1007
- package/lib/api.js +3600 -3394
- package/lib/attachment-text.js +122 -122
- package/lib/bootstrap-http.js +105 -105
- package/lib/bootstrap.js +215 -215
- package/lib/candidates.js +454 -454
- package/lib/capabilities.js +819 -819
- package/lib/choro-command.js +111 -111
- package/lib/client.js +14420 -13436
- package/lib/commands.js +95 -95
- package/lib/config-keys.js +144 -144
- package/lib/config.js +1361 -1317
- package/lib/content-hash.js +54 -54
- package/lib/continuity.js +258 -258
- package/lib/date-hint.js +32 -0
- package/lib/diary/auto.js +190 -190
- package/lib/diary/compose.js +440 -440
- package/lib/diary/dir.js +59 -59
- package/lib/diary/export.js +97 -97
- package/lib/diary/index.js +26 -26
- package/lib/diary/library.js +189 -189
- package/lib/diary/qq-inbox.js +142 -142
- package/lib/diary/rollover.js +61 -61
- package/lib/diary/session-provider.js +122 -122
- package/lib/document-index.js +94 -94
- package/lib/document.js +322 -322
- package/lib/dream/clustering.js +123 -123
- package/lib/dream/decisions.js +945 -907
- package/lib/dream/narratives.js +44 -44
- package/lib/dream/sleep.js +2027 -1887
- package/lib/dream/triage.js +139 -0
- package/lib/dream.js +1879 -1764
- package/lib/dsh-go/host.js +162 -162
- package/lib/dsh-go/panel.js +2091 -2091
- package/lib/dsh-go/text.js +342 -342
- package/lib/dsh-go/views/common.js +68 -68
- package/lib/dsh-go/views/entity.js +48 -48
- package/lib/dsh-go/views/memory.js +198 -198
- package/lib/dsh-go/views/root.js +30 -30
- package/lib/dsh-go/views/settings.js +398 -398
- package/lib/dsh-go/views/status.js +51 -51
- package/lib/embedding.js +342 -342
- package/lib/entities/extractor.js +285 -285
- package/lib/fence.js +24 -24
- package/lib/graph/anchoring.js +184 -184
- package/lib/guide.js +74 -74
- package/lib/heat.js +144 -144
- package/lib/hot-memory.js +119 -107
- package/lib/image-describe.js +158 -0
- package/lib/index.js +852 -822
- package/lib/inject.js +656 -639
- package/lib/kb/index.js +309 -309
- package/lib/kb/routes.js +219 -219
- package/lib/kb/tools.js +148 -148
- package/lib/lang.js +607 -584
- package/lib/local-embedder.js +358 -358
- package/lib/maintenance.js +162 -162
- package/lib/memory-source.js +9 -9
- package/lib/mirror.js +330 -330
- package/lib/organize.js +456 -456
- package/lib/peak-hours.js +172 -172
- package/lib/pipeline-activities.js +178 -178
- package/lib/pipeline-attribution.js +165 -165
- package/lib/pipeline-command.js +279 -279
- package/lib/pipeline-confirm.js +142 -142
- package/lib/pipeline-exec.js +174 -174
- package/lib/pipeline-nudge-auto.js +129 -129
- package/lib/pipeline-nudge.js +1623 -1623
- package/lib/pipeline-observations.js +181 -181
- package/lib/pipeline-observe.js +133 -133
- package/lib/pipeline-runner.js +361 -361
- package/lib/pipeline-runs.js +442 -442
- package/lib/pipeline.js +716 -716
- package/lib/placement.js +13 -13
- package/lib/pre-inject-gate.js +354 -354
- package/lib/profile-extract.js +164 -164
- package/lib/profile-facts.js +356 -356
- package/lib/provenance-http.js +251 -251
- package/lib/quality-filter.js +162 -162
- package/lib/recall-stats.js +190 -190
- package/lib/reclaim-http.js +150 -150
- package/lib/reranker.js +269 -269
- package/lib/runtime/adopt.js +176 -176
- package/lib/runtime/closure.js +172 -172
- package/lib/runtime/download.js +247 -247
- package/lib/runtime/layout.js +265 -265
- package/lib/runtime/loader.js +252 -252
- package/lib/runtime/model-fetch.js +361 -361
- package/lib/runtime/provision.js +237 -237
- package/lib/runtime/tarball.js +130 -130
- package/lib/runtime/verify.js +223 -223
- package/lib/safe-slice.js +18 -18
- package/lib/scope-state.js +277 -277
- package/lib/scope.js +187 -187
- package/lib/search/adaptive.js +60 -60
- package/lib/search/bm25.js +96 -96
- package/lib/semantic.js +572 -572
- package/lib/sensitive-scan.js +209 -209
- package/lib/serve.js +228 -228
- package/lib/service.js +3268 -3196
- package/lib/session-gate.js +91 -91
- package/lib/session-usage.js +350 -350
- package/lib/settings.js +827 -802
- package/lib/share-http.js +281 -281
- package/lib/share-import.js +234 -234
- package/lib/share.js +335 -335
- package/lib/store.js +5895 -5662
- package/lib/summarize.js +1388 -1343
- package/lib/tools.js +1370 -1356
- package/lib/user-settings.js +230 -230
- package/lib/vector-index.js +180 -180
- package/lib/version-check.js +122 -122
- package/lib/wiki/claims.js +267 -0
- package/lib/wiki/cli-hint.js +90 -90
- package/lib/wiki/inject.js +164 -164
- package/lib/wiki/partition.js +200 -200
- package/lib/wiki/plan.js +214 -214
- package/lib/wiki/queries.js +640 -474
- package/lib/wiki/render.js +261 -187
- package/lib/wiki/scan.js +371 -371
- package/lib/wiki/t0.js +500 -463
- package/lib/wiki/t1-jobs.js +232 -232
- package/lib/wiki/t1.js +609 -502
- package/lib/write-admission.js +404 -404
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +15 -1
- package/runtime-manifest.json +526 -526
- package/scripts/benchmark-embed.js +201 -201
- package/scripts/benchmark-recall.js +249 -249
- package/scripts/benchmark-rerank.js +166 -166
- package/scripts/build-runtime-manifest.mjs +224 -224
- package/scripts/changelog-prep.mjs +91 -91
- package/scripts/check-sync.js +44 -44
- package/scripts/e2e-dsh.js +220 -220
- package/scripts/mneme-runtime.mjs +277 -277
- package/scripts/publish-gate.mjs +197 -197
- package/scripts/release-docs.mjs +210 -204
- package/scripts/release-notes.mjs +164 -161
- package/scripts/stress-dsh.js +255 -255
- package/scripts/sync-lib.js +47 -47
- package/scripts/sync-test-badge.mjs +69 -69
- package/scripts/test-count-sync.mjs +48 -48
- package/src/api-standalone.js +1007 -1007
- package/src/api.js +3600 -3394
- package/src/attachment-text.js +122 -122
- package/src/bootstrap-http.js +105 -105
- package/src/bootstrap.js +215 -215
- package/src/candidates.js +454 -454
- package/src/capabilities.js +819 -819
- package/src/choro-command.js +111 -111
- package/src/commands.js +95 -95
- package/src/config-keys.js +144 -144
- package/src/config.js +1361 -1317
- package/src/content-hash.js +54 -54
- package/src/continuity.js +258 -258
- package/src/date-hint.js +32 -0
- package/src/diary/auto.js +190 -190
- package/src/diary/compose.js +440 -440
- package/src/diary/dir.js +59 -59
- package/src/diary/export.js +97 -97
- package/src/diary/index.js +26 -26
- package/src/diary/library.js +189 -189
- package/src/diary/qq-inbox.js +142 -142
- package/src/diary/rollover.js +61 -61
- package/src/diary/session-provider.js +122 -122
- package/src/document-index.js +94 -94
- package/src/document.js +322 -322
- package/src/dream/clustering.js +123 -123
- package/src/dream/decisions.js +945 -907
- package/src/dream/narratives.js +44 -44
- package/src/dream/sleep.js +2027 -1887
- package/src/dream/triage.js +139 -0
- package/src/dream.js +1879 -1764
- package/src/dsh-go/host.js +162 -162
- package/src/dsh-go/panel.js +2091 -2091
- package/src/dsh-go/text.js +342 -342
- package/src/dsh-go/views/common.js +68 -68
- package/src/dsh-go/views/entity.js +48 -48
- package/src/dsh-go/views/memory.js +198 -198
- package/src/dsh-go/views/root.js +30 -30
- package/src/dsh-go/views/settings.js +398 -398
- package/src/dsh-go/views/status.js +51 -51
- package/src/embedding.js +342 -342
- package/src/entities/extractor.js +285 -285
- package/src/fence.js +24 -24
- package/src/graph/anchoring.js +184 -184
- package/src/guide.js +74 -74
- package/src/heat.js +144 -144
- package/src/hot-memory.js +119 -107
- package/src/image-describe.js +158 -0
- package/src/index.js +852 -822
- package/src/inject.js +656 -639
- package/src/kb/index.js +309 -309
- package/src/kb/routes.js +219 -219
- package/src/kb/tools.js +148 -148
- package/src/lang.js +607 -584
- package/src/local-embedder.js +358 -358
- package/src/maintenance.js +162 -162
- package/src/memory-source.js +9 -9
- package/src/mirror.js +330 -330
- package/src/organize.js +456 -456
- package/src/peak-hours.js +172 -172
- package/src/pipeline-activities.js +178 -178
- package/src/pipeline-attribution.js +165 -165
- package/src/pipeline-command.js +279 -279
- package/src/pipeline-confirm.js +142 -142
- package/src/pipeline-exec.js +174 -174
- package/src/pipeline-nudge-auto.js +129 -129
- package/src/pipeline-nudge.js +1623 -1623
- package/src/pipeline-observations.js +181 -181
- package/src/pipeline-observe.js +133 -133
- package/src/pipeline-runner.js +361 -361
- package/src/pipeline-runs.js +442 -442
- package/src/pipeline.js +716 -716
- package/src/placement.js +13 -13
- package/src/pre-inject-gate.js +354 -354
- package/src/profile-extract.js +164 -164
- package/src/profile-facts.js +356 -356
- package/src/provenance-http.js +251 -251
- package/src/quality-filter.js +162 -162
- package/src/recall-stats.js +190 -190
- package/src/reclaim-http.js +150 -150
- package/src/reranker.js +269 -269
- package/src/runtime/adopt.js +176 -176
- package/src/runtime/closure.js +172 -172
- package/src/runtime/download.js +247 -247
- package/src/runtime/layout.js +265 -265
- package/src/runtime/loader.js +252 -252
- package/src/runtime/model-fetch.js +361 -361
- package/src/runtime/provision.js +237 -237
- package/src/runtime/tarball.js +130 -130
- package/src/runtime/verify.js +223 -223
- package/src/safe-slice.js +18 -18
- package/src/scope-state.js +277 -277
- package/src/scope.js +187 -187
- package/src/search/adaptive.js +60 -60
- package/src/search/bm25.js +96 -96
- package/src/semantic.js +572 -572
- package/src/sensitive-scan.js +209 -209
- package/src/serve.js +228 -228
- package/src/service.js +3268 -3196
- package/src/session-gate.js +91 -91
- package/src/session-usage.js +350 -350
- package/src/settings.js +827 -802
- package/src/share-http.js +281 -281
- package/src/share-import.js +234 -234
- package/src/share.js +335 -335
- package/src/store.js +5895 -5662
- package/src/summarize.js +1388 -1343
- package/src/tools.js +1370 -1356
- package/src/user-settings.js +230 -230
- package/src/vector-index.js +180 -180
- package/src/version-check.js +122 -122
- package/src/wiki/claims.js +267 -0
- package/src/wiki/cli-hint.js +90 -90
- package/src/wiki/inject.js +164 -164
- package/src/wiki/partition.js +200 -200
- package/src/wiki/plan.js +214 -214
- package/src/wiki/queries.js +640 -474
- package/src/wiki/render.js +261 -187
- package/src/wiki/scan.js +371 -371
- package/src/wiki/t0.js +500 -463
- package/src/wiki/t1-jobs.js +232 -232
- package/src/wiki/t1.js +609 -502
- package/src/write-admission.js +404 -404
- package/test/adaptive-inject-budget.test.js +85 -85
- package/test/api-hardening.test.js +173 -173
- package/test/api-nudge-route.test.js +259 -259
- package/test/api-routes-gaps.test.js +1092 -1092
- package/test/api.test.js +3007 -2988
- package/test/attachment-text.test.js +139 -139
- package/test/audit.test.js +448 -448
- package/test/baseline-persist.test.js +248 -248
- package/test/benchmark.test.js +94 -94
- package/test/bm25-corpus-cache.test.js +145 -145
- package/test/bootstrap.test.js +228 -228
- package/test/candidates.test.js +345 -345
- package/test/capabilities.test.js +577 -577
- package/test/capacity-warning.test.js +262 -262
- package/test/changelog-prep.test.js +125 -125
- package/test/choro-command.test.js +218 -218
- package/test/cli-enums.test.js +37 -37
- package/test/cli-flag-value.test.js +86 -86
- package/test/cli-receive.test.js +390 -390
- package/test/cli-share.test.js +437 -437
- package/test/client-share.test.js +225 -225
- package/test/client.test.js +4595 -4184
- package/test/clustering.test.js +100 -100
- package/test/commands.test.js +106 -106
- package/test/config-keys-routes.test.js +233 -233
- package/test/config.test.js +153 -153
- package/test/conflict-freeze.test.js +344 -344
- package/test/conflict-ledger-enforce.test.js +452 -0
- package/test/conflict-ledger.test.js +242 -0
- package/test/conflict-reviewed.test.js +79 -79
- package/test/content-hash.test.js +204 -204
- package/test/continuity.test.js +452 -452
- package/test/contrast.test.js +432 -320
- package/test/date-hint.test.js +156 -0
- package/test/dedupe-candidate-window.test.js +180 -180
- package/test/dependency-overrides.test.js +78 -78
- package/test/diary-library.test.js +187 -187
- package/test/diary.test.js +576 -576
- package/test/differentiate-feedback.test.js +248 -248
- package/test/distill-tx-embed-gap.test.js +110 -110
- package/test/docs-config-keys.test.js +65 -65
- package/test/document-dir.test.js +233 -233
- package/test/document.test.js +703 -703
- package/test/dream-archive-guardrail.test.js +116 -116
- package/test/dream-baseline-clamp.test.js +174 -0
- package/test/dream-candidate-hybrid.test.js +281 -281
- package/test/dream-coverage-degrade.test.js +86 -86
- package/test/dream-decision-evidence.test.js +314 -314
- package/test/dream-failure-backoff.test.js +173 -173
- package/test/dream-fence.test.js +207 -207
- package/test/dream-maxtokens-fallback.test.js +132 -132
- package/test/dream-merge-guardrail.test.js +219 -219
- package/test/dream-peak-hours.test.js +194 -194
- package/test/dream.test.js +1528 -1528
- package/test/dsh-go-host.test.js +134 -134
- package/test/dsh-go-panel-actions.test.js +854 -854
- package/test/dsh-go-panel.test.js +251 -251
- package/test/embed-ready-poll.test.js +94 -94
- package/test/embed-text-reasons.test.js +112 -112
- package/test/embedding-cache-f32.test.js +276 -276
- package/test/embedding-ready.test.js +226 -226
- package/test/entities.test.js +711 -711
- package/test/entity-recall.test.js +179 -179
- package/test/epistemic.test.js +325 -325
- package/test/failure-query.test.js +147 -147
- package/test/failure-retention.test.js +105 -105
- package/test/fnew-0112.test.js +311 -311
- package/test/fnew-03.test.js +422 -422
- package/test/forget-corrections.test.js +70 -70
- package/test/graph-anchoring.test.js +184 -184
- package/test/graph-api.test.js +333 -209
- package/test/graph-boosted-relations.test.js +189 -189
- package/test/graph-inject-budget.test.js +103 -103
- package/test/graph-multipath.test.js +269 -269
- package/test/graph-passive-confirm.test.js +91 -91
- package/test/graph-weight-evolution.test.js +104 -104
- package/test/heat.test.js +175 -175
- package/test/helpers/dream-mock.js +86 -86
- package/test/helpers/peer-worker.mjs +25 -25
- package/test/helpers/runtime-source.js +60 -60
- package/test/helpers/tar-builder.js +80 -80
- package/test/helpers/write-admission-samples.js +312 -312
- package/test/hot-memory.test.js +259 -211
- package/test/image-description.test.js +271 -0
- package/test/image-memory.test.js +170 -170
- package/test/inject-content-cap.test.js +91 -91
- package/test/inject-first-round-bm25.test.js +89 -89
- package/test/inject-heat.test.js +77 -77
- package/test/inject-parent-gate.test.js +134 -106
- package/test/inject-pin-explicit.test.js +300 -300
- package/test/inject-pin-vector-crowd.test.js +225 -225
- package/test/inject-pools.test.js +195 -195
- package/test/inject-recall.test.js +172 -172
- package/test/inject-rotation.test.js +161 -161
- package/test/inject.test.js +553 -494
- package/test/issue-617-step1-query-and-fusion.test.js +361 -361
- package/test/kb-api-wiring.test.js +128 -128
- package/test/kb-collections.test.js +272 -272
- package/test/kb-routes.test.js +379 -379
- package/test/lang.test.js +98 -98
- package/test/lastrunat-persist.test.js +101 -101
- package/test/lib-smoke.test.js +116 -116
- package/test/llm-audit.test.js +815 -815
- package/test/llm-message-source.test.js +207 -207
- package/test/local-embedder.test.js +292 -292
- package/test/maintenance.test.js +501 -501
- package/test/mcp-stdio.test.js +385 -385
- package/test/mint-source.test.js +311 -311
- package/test/mirror-dirty.test.js +424 -424
- package/test/mirror-edit-digest.test.js +187 -187
- package/test/mirror-generation.test.js +499 -499
- package/test/mirror-types.test.js +208 -208
- package/test/mirror.test.js +249 -249
- package/test/narrative-bars.test.js +162 -162
- package/test/organize-tool.test.js +232 -232
- package/test/organize.test.js +418 -418
- package/test/panel-chart-scale.test.js +90 -0
- package/test/panel-tokens.test.js +245 -0
- package/test/peak-hours-observability.test.js +70 -70
- package/test/peer-blockers.test.js +193 -193
- package/test/persona-addressing.test.js +83 -83
- package/test/persona-api.test.js +172 -172
- package/test/persona-by-session.test.js +568 -568
- package/test/persona-flag.test.js +36 -36
- package/test/persona-inject.test.js +145 -145
- package/test/persona-settings.test.js +198 -198
- package/test/pipeline-activities.test.js +155 -155
- package/test/pipeline-attribution.test.js +175 -175
- package/test/pipeline-command.test.js +243 -243
- package/test/pipeline-confirm.test.js +148 -148
- package/test/pipeline-exec.test.js +172 -172
- package/test/pipeline-nudge-auto.test.js +248 -248
- package/test/pipeline-nudge.test.js +1278 -1278
- package/test/pipeline-observations.test.js +157 -157
- package/test/pipeline-observe.test.js +127 -127
- package/test/pipeline-r1r2.test.js +278 -278
- package/test/pipeline-runner.test.js +468 -468
- package/test/pipeline-runs.test.js +767 -767
- package/test/pipeline.test.js +636 -636
- package/test/placement.test.js +36 -36
- package/test/policy-epoch.test.js +259 -259
- package/test/pre-inject-gate.test.js +444 -444
- package/test/profile-extract.test.js +269 -269
- package/test/profile-facts.test.js +492 -492
- package/test/prompt-stance.test.js +128 -128
- package/test/provenance-http.test.js +367 -367
- package/test/publish-gate.test.js +272 -272
- package/test/quality-backfill.test.js +290 -290
- package/test/quality-filter.test.js +295 -295
- package/test/readme-claims.test.js +113 -113
- package/test/reasoning-effort.test.js +752 -752
- package/test/recall-anti-update.test.js +82 -82
- package/test/recall-evals.test.js +253 -253
- package/test/recall-layer.test.js +326 -326
- package/test/recall-retention.test.js +128 -128
- package/test/recall-stats.test.js +274 -274
- package/test/receipt-chain.test.js +451 -451
- package/test/reclaim-http.test.js +209 -209
- package/test/reflection-update-gate.test.js +129 -129
- package/test/reflection.test.js +226 -226
- package/test/reindex-backfill.test.js +475 -475
- package/test/reindex-scheduler.test.js +369 -369
- package/test/release-docs.test.js +156 -136
- package/test/release-notes.test.js +265 -240
- package/test/release-workflow.test.js +93 -93
- package/test/reranker-single-logit.test.js +73 -73
- package/test/reranker.test.js +197 -197
- package/test/resident-status.test.js +83 -83
- package/test/route-policy.test.js +113 -113
- package/test/runtime-adopt.test.js +206 -206
- package/test/runtime-cli.test.js +89 -89
- package/test/runtime-closure.test.js +193 -193
- package/test/runtime-download.test.js +302 -302
- package/test/runtime-layout.test.js +163 -163
- package/test/runtime-loader.test.js +255 -255
- package/test/runtime-manifest.test.js +159 -159
- package/test/runtime-model-fetch.test.js +348 -348
- package/test/runtime-provision.test.js +101 -101
- package/test/runtime-tarball.test.js +81 -81
- package/test/runtime-verify.test.js +319 -319
- package/test/scope-candidates.test.js +491 -491
- package/test/scope-distill-stamp.test.js +134 -134
- package/test/scope-explicit.test.js +452 -452
- package/test/scope-inject-soft.test.js +106 -106
- package/test/scope-optional-service.test.js +110 -110
- package/test/scope-retrieval.test.js +197 -197
- package/test/scope-state.test.js +610 -610
- package/test/scope-storage.test.js +255 -255
- package/test/scope-strict.test.js +367 -367
- package/test/search-fusion.test.js +90 -90
- package/test/security-doc.test.js +188 -178
- package/test/semantic.test.js +124 -124
- package/test/sensitive-scan.test.js +251 -251
- package/test/serve-bin.test.js +121 -121
- package/test/serve.test.js +258 -258
- package/test/service-search.test.js +174 -174
- package/test/service.test.js +410 -410
- package/test/session-gate.test.js +264 -264
- package/test/session-usage.test.js +317 -317
- package/test/settings.test.js +273 -273
- package/test/share-foreign-rows.test.js +144 -144
- package/test/share-http.test.js +144 -144
- package/test/share-import.test.js +577 -577
- package/test/share-imported-guard.test.js +311 -311
- package/test/share-imported-identity.test.js +111 -111
- package/test/share-pack.test.js +511 -511
- package/test/share-receive-http.test.js +756 -756
- package/test/share-routes.test.js +337 -337
- package/test/share-store.test.js +133 -133
- package/test/share-validate.test.js +101 -101
- package/test/skip-invalid-restore.test.js +262 -262
- package/test/sleep-action-set.test.js +441 -441
- package/test/sleep-catchup.test.js +315 -315
- package/test/sleep-failure-backoff.test.js +156 -156
- package/test/sleep-heat.test.js +174 -174
- package/test/sleep-scheduler-rearm.test.js +89 -89
- package/test/sleep.test.js +1455 -1418
- package/test/standalone-api.test.js +1344 -1344
- package/test/store-vector-cache.test.js +98 -98
- package/test/store-write-path-columns.test.js +191 -191
- package/test/store.test.js +744 -744
- package/test/stress.test.js +209 -209
- package/test/summarize-occurred-at.test.js +127 -127
- package/test/summarize-parse-diagnostics.test.js +75 -75
- package/test/summarize-reasoning-effort.test.js +189 -189
- package/test/summarize.test.js +2251 -2251
- package/test/supersede-state-tracking.test.js +394 -394
- package/test/test-count-sync.test.js +71 -71
- package/test/tool-state-ignore.test.js +65 -65
- package/test/tools-dto-schema-matrix.test.js +398 -398
- package/test/tools-memory-get-schema.test.js +65 -65
- package/test/tools.test.js +585 -585
- package/test/updated-at-semantics.test.js +113 -113
- package/test/user-settings-preview.test.js +127 -127
- package/test/user-settings-segments.test.js +527 -527
- package/test/vector-index.test.js +334 -334
- package/test/version-check.test.js +152 -152
- package/test/webServer-optional.test.js +82 -82
- package/test/wiki-api.test.js +734 -627
- package/test/wiki-claims.test.js +194 -0
- package/test/wiki-cli-hint.test.js +118 -118
- package/test/wiki-inject.test.js +265 -265
- package/test/wiki-partition.test.js +112 -112
- package/test/wiki-plan.test.js +80 -80
- package/test/wiki-provenance.test.js +202 -0
- package/test/wiki-queries.test.js +398 -277
- package/test/wiki-scan.test.js +208 -208
- package/test/wiki-store.test.js +190 -190
- package/test/wiki-t0.test.js +456 -380
- package/test/wiki-t1-jobs.test.js +125 -125
- package/test/wiki-t1.test.js +460 -235
- package/test/wiki-tools.test.js +176 -176
- package/test/workflow-guards.test.js +155 -155
- package/test/write-admission.test.js +668 -668
package/test/share-store.test.js
CHANGED
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
// 车道 C 的存储层。这里盯的不是「列在不在」,而是三件容易写错的事:
|
|
2
|
-
// a) 存量库重开即建列,且存量行的新列是 NULL 而不是被填成某个假值;
|
|
3
|
-
// b) UNIQUE 索引只对外来行生效——普通本地行必须能无限多条共享 NULL;
|
|
4
|
-
// c) 批删清 side table 且 generation 前移,否则镜像恢复会把删掉的行当「没跟上」。
|
|
5
|
-
import test from "node:test";
|
|
6
|
-
import assert from "node:assert/strict";
|
|
7
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
8
|
-
import { tmpdir } from "node:os";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import { DatabaseSync } from "node:sqlite";
|
|
11
|
-
import { createStore } from "../src/store.js";
|
|
12
|
-
import { IMPORTED_SOURCE } from "../src/memory-source.js";
|
|
13
|
-
|
|
14
|
-
const temp = () => mkdtempSync(join(tmpdir(), "dsh-mneme-share-store-"));
|
|
15
|
-
|
|
16
|
-
test("an existing library gains both columns on reopen, legacy rows stay NULL", () => {
|
|
17
|
-
const dir = temp(), path = join(dir, "memory.db");
|
|
18
|
-
const raw = new DatabaseSync(path);
|
|
19
|
-
raw.exec(`CREATE TABLE memories (
|
|
20
|
-
id TEXT PRIMARY KEY, type TEXT NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL,
|
|
21
|
-
tags TEXT NOT NULL DEFAULT '[]', importance INTEGER NOT NULL DEFAULT 3,
|
|
22
|
-
forgotten INTEGER NOT NULL DEFAULT 0, archived INTEGER NOT NULL DEFAULT 0,
|
|
23
|
-
archived_at TEXT, source TEXT, content_history TEXT, content_hash TEXT, embedding TEXT,
|
|
24
|
-
epistemic_status TEXT NOT NULL DEFAULT 'subjective', last_accessed_at TEXT,
|
|
25
|
-
_full_content TEXT, evidence TEXT, doc_path TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);`);
|
|
26
|
-
raw.prepare(`INSERT INTO memories (id, type, title, content, created_at, updated_at)
|
|
27
|
-
VALUES ('legacy-1','decision','旧条目','正文','2026-01-01T00:00:00.000Z','2026-01-01T00:00:00.000Z')`).run();
|
|
28
|
-
raw.close();
|
|
29
|
-
|
|
30
|
-
const store = createStore(path);
|
|
31
|
-
try {
|
|
32
|
-
const row = store.getById("legacy-1");
|
|
33
|
-
assert.equal(row.share_pack_id, undefined, "legacy row must not be stamped as imported");
|
|
34
|
-
assert.equal(row.share_external_id, undefined);
|
|
35
|
-
// toRow 读不到列时也返回 undefined,所以「列真的建了」「建的是 NULL 而不是 ''」
|
|
36
|
-
// 这两件事只能在库里看——否则本用例对迁移毫无判别力。
|
|
37
|
-
const cols = store.db.prepare("PRAGMA table_info(memories)").all().map((c) => c.name);
|
|
38
|
-
assert.ok(cols.includes("share_pack_id") && cols.includes("share_external_id"),
|
|
39
|
-
"reopen must add both columns to a legacy library");
|
|
40
|
-
const stored = store.db.prepare("SELECT share_pack_id, share_external_id AS e FROM memories WHERE id = 'legacy-1'").get();
|
|
41
|
-
assert.equal(stored.share_pack_id, null, "legacy cells are NULL, not '' (empty string would collide in the UNIQUE index)");
|
|
42
|
-
assert.equal(stored.e, null);
|
|
43
|
-
} finally { store.close(); }
|
|
44
|
-
// 幂等重开:第二个连接开在同一条路径上,列已在,迁移不许再动它一根手指。
|
|
45
|
-
const again = createStore(path);
|
|
46
|
-
try {
|
|
47
|
-
assert.ok(again.getById("legacy-1"));
|
|
48
|
-
assert.equal(again.getById("legacy-1").share_pack_id, undefined);
|
|
49
|
-
} finally { again.close(); rmSync(dir, { recursive: true, force: true }); }
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("foreign rows are unique per external_id while local rows share NULL freely", () => {
|
|
53
|
-
const dir = temp(), store = createStore(join(dir, "memory.db"));
|
|
54
|
-
try {
|
|
55
|
-
store.save({ type: "decision", title: "a", content: "x", source: IMPORTED_SOURCE, share_external_id: "deadbeef00000001" });
|
|
56
|
-
assert.throws(() => store.save({ type: "decision", title: "b", content: "y", source: IMPORTED_SOURCE, share_external_id: "deadbeef00000001" }),
|
|
57
|
-
/UNIQUE constraint failed/);
|
|
58
|
-
for (const t of ["p1", "p2", "p3"]) store.save({ type: "decision", title: t, content: "x" });
|
|
59
|
-
assert.equal(store.list({ type: "decision", limit: null }).length, 4, "NULL must not collide");
|
|
60
|
-
} finally { store.close(); rmSync(dir, { recursive: true, force: true }); }
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("share columns round-trip through toRow and are queryable", () => {
|
|
64
|
-
const dir = temp(), store = createStore(join(dir, "memory.db"));
|
|
65
|
-
try {
|
|
66
|
-
const row = store.save({ type: "preference", title: "t", content: "c", source: IMPORTED_SOURCE,
|
|
67
|
-
share_pack_id: "packA", share_external_id: "deadbeef00000002" });
|
|
68
|
-
assert.equal(row.share_pack_id, "packA");
|
|
69
|
-
assert.equal(row.share_external_id, "deadbeef00000002");
|
|
70
|
-
assert.equal(store.getByShareExternalId("deadbeef00000002").id, row.id);
|
|
71
|
-
assert.equal(store.getByShareExternalId("nope"), undefined);
|
|
72
|
-
store.save({ type: "preference", title: "t2", content: "c2", source: IMPORTED_SOURCE, share_pack_id: "packA", share_external_id: "deadbeef00000003" });
|
|
73
|
-
assert.deepEqual(store.listBySharePackId("packA").map((m) => m.share_external_id).sort(),
|
|
74
|
-
["deadbeef00000002", "deadbeef00000003"]);
|
|
75
|
-
} finally { store.close(); rmSync(dir, { recursive: true, force: true }); }
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("deleteBySharePackId removes the whole pack, cleans provenance, and bumps generation", () => {
|
|
79
|
-
const dir = temp(), store = createStore(join(dir, "memory.db"));
|
|
80
|
-
try {
|
|
81
|
-
const a = store.save({ type: "preference", title: "t1", content: "c", source: IMPORTED_SOURCE, share_pack_id: "packA", share_external_id: "deadbeef00000011" });
|
|
82
|
-
const b = store.save({ type: "preference", title: "t2", content: "c", source: IMPORTED_SOURCE, share_pack_id: "packA", share_external_id: "deadbeef00000012" });
|
|
83
|
-
store.save({ type: "preference", title: "keep", content: "c", source: IMPORTED_SOURCE, share_pack_id: "packB", share_external_id: "deadbeef00000013" });
|
|
84
|
-
// 归档面也要一起收走:整包退出之后不许留下「来自一个已经收起的包」的行。
|
|
85
|
-
// 行数用库级 count 而不是 store.count()——后者按活跃面过滤,归档行不在账上,
|
|
86
|
-
// 拿它当「删了几行」的锚会把断言变成过滤器口径的探针(那不是本用例要锁的东西)。
|
|
87
|
-
store.setArchived(b.id, true);
|
|
88
|
-
const totalRows = () => store.db.prepare("SELECT count(*) AS c FROM memories").get().c;
|
|
89
|
-
// 真实 schema 的必填列是 (memory_id, session_id, seq_start, seq_end, created_at) 全部
|
|
90
|
-
// NOT NULL,走 store 自己的写入口 saveMemoryProvenance,不走手搓 INSERT。
|
|
91
|
-
//
|
|
92
|
-
// 这里先把 foreign_keys 关掉,不是随手配置:memory_provenance 挂着
|
|
93
|
-
// ON DELETE CASCADE,而 node:sqlite 的 DatabaseSync 默认就把 PRAGMA foreign_keys 开成
|
|
94
|
-
// 1(实测:新库上 `PRAGMA foreign_keys` 返回 1)。FK 开着时删记忆由级联代劳,本用例
|
|
95
|
-
// 断言的「批删自己清链接」就永远测不到那行代码——变异检验证实:摘掉 deleteBySharePackId
|
|
96
|
-
// 里那条 DELETE FROM memory_provenance,五条用例照绿。关掉之后级联不再代劳,
|
|
97
|
-
// 断言测的才是显式清理本身。(`src/store.js` 的 `remove()` 注释在 C1 修复轮已经改成
|
|
98
|
-
// 真口径:node:sqlite 默认就把外键开着、级联是活的,显式清理是为了不把清理押在一份没人
|
|
99
|
-
// 钉过的驱动默认上——本用例反过来**临时关掉**它,恰恰就是为了让那句「不押注」可测。)
|
|
100
|
-
store.db.exec("PRAGMA foreign_keys = OFF");
|
|
101
|
-
store.saveMemoryProvenance({ memoryId: a.id, sessionId: "s-1", seqStart: 1, seqEnd: 3, createdAt: "2026-01-01T00:00:00.000Z" });
|
|
102
|
-
assert.equal(store.provenanceFor(a.id).length, 1, "the link must exist before the delete or the assertion below is empty");
|
|
103
|
-
const before = totalRows();
|
|
104
|
-
const genBefore = store.getMirrorState().generation;
|
|
105
|
-
assert.equal(store.listBySharePackId("packA").length, 2, "the withdrawal inventory must see archived rows too");
|
|
106
|
-
const deleted = store.deleteBySharePackId("packA");
|
|
107
|
-
assert.equal(deleted, 2);
|
|
108
|
-
assert.ok(store.getMirrorState().generation > genBefore,
|
|
109
|
-
"batch delete must register mirror debt like remove() does, else recoverMirror reads the pack as 「not yet synced」");
|
|
110
|
-
assert.equal(totalRows(), before - 2);
|
|
111
|
-
assert.equal(store.provenanceFor(a.id).length, 0, "provenance links must not dangle after a batch delete");
|
|
112
|
-
assert.equal(store.getByShareExternalId("deadbeef00000011"), undefined, "UNIQUE slot must be freed for a clean re-import");
|
|
113
|
-
assert.equal(store.listBySharePackId("packA").length, 0);
|
|
114
|
-
assert.equal(store.listBySharePackId("packB").length, 1);
|
|
115
|
-
const genAfterDelete = store.getMirrorState().generation;
|
|
116
|
-
assert.equal(store.deleteBySharePackId("packA"), 0, "idempotent");
|
|
117
|
-
assert.equal(store.getMirrorState().generation, genAfterDelete,
|
|
118
|
-
"a no-op withdrawal deletes nothing, so it must not register mirror debt either");
|
|
119
|
-
} finally { store.close(); rmSync(dir, { recursive: true, force: true }); }
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("a UNIQUE collision aborts the whole transaction — nothing lands partially", () => {
|
|
123
|
-
const dir = temp(), store = createStore(join(dir, "memory.db"));
|
|
124
|
-
try {
|
|
125
|
-
store.save({ type: "decision", title: "seed", content: "x", source: IMPORTED_SOURCE, share_external_id: "cafe000000000001" });
|
|
126
|
-
assert.throws(() => store.runAtomically(() => {
|
|
127
|
-
store.save({ type: "decision", title: "dup", content: "y", source: IMPORTED_SOURCE, share_external_id: "cafe000000000001" });
|
|
128
|
-
store.save({ type: "decision", title: "later", content: "z", source: IMPORTED_SOURCE, share_external_id: "cafe000000000002" });
|
|
129
|
-
}), /UNIQUE constraint failed/);
|
|
130
|
-
assert.equal(store.getByShareExternalId("cafe000000000002"), undefined, "the row after the collision must not have landed");
|
|
131
|
-
assert.equal(store.list({ limit: null }).length, 1, "the batch left nothing behind");
|
|
132
|
-
} finally { store.close(); rmSync(dir, { recursive: true, force: true }); }
|
|
133
|
-
});
|
|
1
|
+
// 车道 C 的存储层。这里盯的不是「列在不在」,而是三件容易写错的事:
|
|
2
|
+
// a) 存量库重开即建列,且存量行的新列是 NULL 而不是被填成某个假值;
|
|
3
|
+
// b) UNIQUE 索引只对外来行生效——普通本地行必须能无限多条共享 NULL;
|
|
4
|
+
// c) 批删清 side table 且 generation 前移,否则镜像恢复会把删掉的行当「没跟上」。
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { DatabaseSync } from "node:sqlite";
|
|
11
|
+
import { createStore } from "../src/store.js";
|
|
12
|
+
import { IMPORTED_SOURCE } from "../src/memory-source.js";
|
|
13
|
+
|
|
14
|
+
const temp = () => mkdtempSync(join(tmpdir(), "dsh-mneme-share-store-"));
|
|
15
|
+
|
|
16
|
+
test("an existing library gains both columns on reopen, legacy rows stay NULL", () => {
|
|
17
|
+
const dir = temp(), path = join(dir, "memory.db");
|
|
18
|
+
const raw = new DatabaseSync(path);
|
|
19
|
+
raw.exec(`CREATE TABLE memories (
|
|
20
|
+
id TEXT PRIMARY KEY, type TEXT NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL,
|
|
21
|
+
tags TEXT NOT NULL DEFAULT '[]', importance INTEGER NOT NULL DEFAULT 3,
|
|
22
|
+
forgotten INTEGER NOT NULL DEFAULT 0, archived INTEGER NOT NULL DEFAULT 0,
|
|
23
|
+
archived_at TEXT, source TEXT, content_history TEXT, content_hash TEXT, embedding TEXT,
|
|
24
|
+
epistemic_status TEXT NOT NULL DEFAULT 'subjective', last_accessed_at TEXT,
|
|
25
|
+
_full_content TEXT, evidence TEXT, doc_path TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);`);
|
|
26
|
+
raw.prepare(`INSERT INTO memories (id, type, title, content, created_at, updated_at)
|
|
27
|
+
VALUES ('legacy-1','decision','旧条目','正文','2026-01-01T00:00:00.000Z','2026-01-01T00:00:00.000Z')`).run();
|
|
28
|
+
raw.close();
|
|
29
|
+
|
|
30
|
+
const store = createStore(path);
|
|
31
|
+
try {
|
|
32
|
+
const row = store.getById("legacy-1");
|
|
33
|
+
assert.equal(row.share_pack_id, undefined, "legacy row must not be stamped as imported");
|
|
34
|
+
assert.equal(row.share_external_id, undefined);
|
|
35
|
+
// toRow 读不到列时也返回 undefined,所以「列真的建了」「建的是 NULL 而不是 ''」
|
|
36
|
+
// 这两件事只能在库里看——否则本用例对迁移毫无判别力。
|
|
37
|
+
const cols = store.db.prepare("PRAGMA table_info(memories)").all().map((c) => c.name);
|
|
38
|
+
assert.ok(cols.includes("share_pack_id") && cols.includes("share_external_id"),
|
|
39
|
+
"reopen must add both columns to a legacy library");
|
|
40
|
+
const stored = store.db.prepare("SELECT share_pack_id, share_external_id AS e FROM memories WHERE id = 'legacy-1'").get();
|
|
41
|
+
assert.equal(stored.share_pack_id, null, "legacy cells are NULL, not '' (empty string would collide in the UNIQUE index)");
|
|
42
|
+
assert.equal(stored.e, null);
|
|
43
|
+
} finally { store.close(); }
|
|
44
|
+
// 幂等重开:第二个连接开在同一条路径上,列已在,迁移不许再动它一根手指。
|
|
45
|
+
const again = createStore(path);
|
|
46
|
+
try {
|
|
47
|
+
assert.ok(again.getById("legacy-1"));
|
|
48
|
+
assert.equal(again.getById("legacy-1").share_pack_id, undefined);
|
|
49
|
+
} finally { again.close(); rmSync(dir, { recursive: true, force: true }); }
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("foreign rows are unique per external_id while local rows share NULL freely", () => {
|
|
53
|
+
const dir = temp(), store = createStore(join(dir, "memory.db"));
|
|
54
|
+
try {
|
|
55
|
+
store.save({ type: "decision", title: "a", content: "x", source: IMPORTED_SOURCE, share_external_id: "deadbeef00000001" });
|
|
56
|
+
assert.throws(() => store.save({ type: "decision", title: "b", content: "y", source: IMPORTED_SOURCE, share_external_id: "deadbeef00000001" }),
|
|
57
|
+
/UNIQUE constraint failed/);
|
|
58
|
+
for (const t of ["p1", "p2", "p3"]) store.save({ type: "decision", title: t, content: "x" });
|
|
59
|
+
assert.equal(store.list({ type: "decision", limit: null }).length, 4, "NULL must not collide");
|
|
60
|
+
} finally { store.close(); rmSync(dir, { recursive: true, force: true }); }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("share columns round-trip through toRow and are queryable", () => {
|
|
64
|
+
const dir = temp(), store = createStore(join(dir, "memory.db"));
|
|
65
|
+
try {
|
|
66
|
+
const row = store.save({ type: "preference", title: "t", content: "c", source: IMPORTED_SOURCE,
|
|
67
|
+
share_pack_id: "packA", share_external_id: "deadbeef00000002" });
|
|
68
|
+
assert.equal(row.share_pack_id, "packA");
|
|
69
|
+
assert.equal(row.share_external_id, "deadbeef00000002");
|
|
70
|
+
assert.equal(store.getByShareExternalId("deadbeef00000002").id, row.id);
|
|
71
|
+
assert.equal(store.getByShareExternalId("nope"), undefined);
|
|
72
|
+
store.save({ type: "preference", title: "t2", content: "c2", source: IMPORTED_SOURCE, share_pack_id: "packA", share_external_id: "deadbeef00000003" });
|
|
73
|
+
assert.deepEqual(store.listBySharePackId("packA").map((m) => m.share_external_id).sort(),
|
|
74
|
+
["deadbeef00000002", "deadbeef00000003"]);
|
|
75
|
+
} finally { store.close(); rmSync(dir, { recursive: true, force: true }); }
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("deleteBySharePackId removes the whole pack, cleans provenance, and bumps generation", () => {
|
|
79
|
+
const dir = temp(), store = createStore(join(dir, "memory.db"));
|
|
80
|
+
try {
|
|
81
|
+
const a = store.save({ type: "preference", title: "t1", content: "c", source: IMPORTED_SOURCE, share_pack_id: "packA", share_external_id: "deadbeef00000011" });
|
|
82
|
+
const b = store.save({ type: "preference", title: "t2", content: "c", source: IMPORTED_SOURCE, share_pack_id: "packA", share_external_id: "deadbeef00000012" });
|
|
83
|
+
store.save({ type: "preference", title: "keep", content: "c", source: IMPORTED_SOURCE, share_pack_id: "packB", share_external_id: "deadbeef00000013" });
|
|
84
|
+
// 归档面也要一起收走:整包退出之后不许留下「来自一个已经收起的包」的行。
|
|
85
|
+
// 行数用库级 count 而不是 store.count()——后者按活跃面过滤,归档行不在账上,
|
|
86
|
+
// 拿它当「删了几行」的锚会把断言变成过滤器口径的探针(那不是本用例要锁的东西)。
|
|
87
|
+
store.setArchived(b.id, true);
|
|
88
|
+
const totalRows = () => store.db.prepare("SELECT count(*) AS c FROM memories").get().c;
|
|
89
|
+
// 真实 schema 的必填列是 (memory_id, session_id, seq_start, seq_end, created_at) 全部
|
|
90
|
+
// NOT NULL,走 store 自己的写入口 saveMemoryProvenance,不走手搓 INSERT。
|
|
91
|
+
//
|
|
92
|
+
// 这里先把 foreign_keys 关掉,不是随手配置:memory_provenance 挂着
|
|
93
|
+
// ON DELETE CASCADE,而 node:sqlite 的 DatabaseSync 默认就把 PRAGMA foreign_keys 开成
|
|
94
|
+
// 1(实测:新库上 `PRAGMA foreign_keys` 返回 1)。FK 开着时删记忆由级联代劳,本用例
|
|
95
|
+
// 断言的「批删自己清链接」就永远测不到那行代码——变异检验证实:摘掉 deleteBySharePackId
|
|
96
|
+
// 里那条 DELETE FROM memory_provenance,五条用例照绿。关掉之后级联不再代劳,
|
|
97
|
+
// 断言测的才是显式清理本身。(`src/store.js` 的 `remove()` 注释在 C1 修复轮已经改成
|
|
98
|
+
// 真口径:node:sqlite 默认就把外键开着、级联是活的,显式清理是为了不把清理押在一份没人
|
|
99
|
+
// 钉过的驱动默认上——本用例反过来**临时关掉**它,恰恰就是为了让那句「不押注」可测。)
|
|
100
|
+
store.db.exec("PRAGMA foreign_keys = OFF");
|
|
101
|
+
store.saveMemoryProvenance({ memoryId: a.id, sessionId: "s-1", seqStart: 1, seqEnd: 3, createdAt: "2026-01-01T00:00:00.000Z" });
|
|
102
|
+
assert.equal(store.provenanceFor(a.id).length, 1, "the link must exist before the delete or the assertion below is empty");
|
|
103
|
+
const before = totalRows();
|
|
104
|
+
const genBefore = store.getMirrorState().generation;
|
|
105
|
+
assert.equal(store.listBySharePackId("packA").length, 2, "the withdrawal inventory must see archived rows too");
|
|
106
|
+
const deleted = store.deleteBySharePackId("packA");
|
|
107
|
+
assert.equal(deleted, 2);
|
|
108
|
+
assert.ok(store.getMirrorState().generation > genBefore,
|
|
109
|
+
"batch delete must register mirror debt like remove() does, else recoverMirror reads the pack as 「not yet synced」");
|
|
110
|
+
assert.equal(totalRows(), before - 2);
|
|
111
|
+
assert.equal(store.provenanceFor(a.id).length, 0, "provenance links must not dangle after a batch delete");
|
|
112
|
+
assert.equal(store.getByShareExternalId("deadbeef00000011"), undefined, "UNIQUE slot must be freed for a clean re-import");
|
|
113
|
+
assert.equal(store.listBySharePackId("packA").length, 0);
|
|
114
|
+
assert.equal(store.listBySharePackId("packB").length, 1);
|
|
115
|
+
const genAfterDelete = store.getMirrorState().generation;
|
|
116
|
+
assert.equal(store.deleteBySharePackId("packA"), 0, "idempotent");
|
|
117
|
+
assert.equal(store.getMirrorState().generation, genAfterDelete,
|
|
118
|
+
"a no-op withdrawal deletes nothing, so it must not register mirror debt either");
|
|
119
|
+
} finally { store.close(); rmSync(dir, { recursive: true, force: true }); }
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("a UNIQUE collision aborts the whole transaction — nothing lands partially", () => {
|
|
123
|
+
const dir = temp(), store = createStore(join(dir, "memory.db"));
|
|
124
|
+
try {
|
|
125
|
+
store.save({ type: "decision", title: "seed", content: "x", source: IMPORTED_SOURCE, share_external_id: "cafe000000000001" });
|
|
126
|
+
assert.throws(() => store.runAtomically(() => {
|
|
127
|
+
store.save({ type: "decision", title: "dup", content: "y", source: IMPORTED_SOURCE, share_external_id: "cafe000000000001" });
|
|
128
|
+
store.save({ type: "decision", title: "later", content: "z", source: IMPORTED_SOURCE, share_external_id: "cafe000000000002" });
|
|
129
|
+
}), /UNIQUE constraint failed/);
|
|
130
|
+
assert.equal(store.getByShareExternalId("cafe000000000002"), undefined, "the row after the collision must not have landed");
|
|
131
|
+
assert.equal(store.list({ limit: null }).length, 1, "the batch left nothing behind");
|
|
132
|
+
} finally { store.close(); rmSync(dir, { recursive: true, force: true }); }
|
|
133
|
+
});
|
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
// 接收侧的格式闸门。锁三件事:
|
|
2
|
-
// a) 一个不合规的包**整包**不合格(车道 C 不许半途入库);
|
|
3
|
-
// b) 键必须可重算——包是别人发来的,重算对不上就是被人改过,幂等键失效;
|
|
4
|
-
// c) 允许的类型集合不许偷偷跟着 mirror 扩:这里刻意重述一遍,并由静态锁比对
|
|
5
|
-
// TYPE_FILE 的键集减去两个排除项,将来加第 9 类时它会转红提醒人来决定。
|
|
6
|
-
import test from "node:test";
|
|
7
|
-
import assert from "node:assert/strict";
|
|
8
|
-
// 静态锁用真仪器而不是源码正则:test/mirror-types.test.js 已经是 import TYPE_FILE +
|
|
9
|
-
// Object.keys 的先例。在**测试**里 import 它不花钱——src/share.js 的重述字面集照旧,
|
|
10
|
-
// 那是接收侧的兼容契约,不是漂移。
|
|
11
|
-
import { TYPE_FILE } from "../src/mirror.js";
|
|
12
|
-
import {
|
|
13
|
-
PACK_ID_RE, SHARE_ALLOWED_TYPES, SHARE_READONLY_TYPES, SHARE_EXCLUDED_TYPES,
|
|
14
|
-
importedTagFor, validateSharePack, deriveExternalId, SHARE_KIND
|
|
15
|
-
} from "../src/share.js";
|
|
16
|
-
|
|
17
|
-
const entry = (over = {}) => {
|
|
18
|
-
const base = { type: "decision", title: "选 SQLite", content: "正文正文", tags: ["x"], importance: 4,
|
|
19
|
-
epistemic_status: "subjective", created_at: "2026-10-01T00:00:00.000Z", updated_at: "2026-10-01T00:00:00.000Z", content_hash: "ab" };
|
|
20
|
-
return { ...base, external_id: deriveExternalId("f0f0f0f0f0f0f0f0", base.type, base.title, base.content_hash), ...over };
|
|
21
|
-
};
|
|
22
|
-
const packOf = (memories, over = {}) => ({
|
|
23
|
-
kind: SHARE_KIND, pack_id: "f0f0f0f0f0f0f0f0", alias: "桉", generated_at: "2026-10-22T00:00:00.000Z",
|
|
24
|
-
source_version: "0.9.4", report: { included: memories.length, skipped: {}, skip_list: [], deduped: 0 },
|
|
25
|
-
memories, ...over
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("pack_id is exactly 16 lowercase hex", () => {
|
|
29
|
-
assert.match("f0f0f0f0f0f0f0f0", PACK_ID_RE);
|
|
30
|
-
assert.doesNotMatch("f0f0f0f0f0f0f0", PACK_ID_RE); // 车道 A 铸的是 16 位,不是 12
|
|
31
|
-
assert.doesNotMatch("F0F0F0F0F0F0F0F0", PACK_ID_RE);
|
|
32
|
-
// 后两条只验 {16} 那个量词,验不到两个锚:锚一掉,「16 位以内」就成了「正文里含 16 位」,
|
|
33
|
-
// 于是 18 位的串和带空白的串都能过——而这两种恰好是手改包与编码包的形状。
|
|
34
|
-
assert.doesNotMatch("f0f0f0f0f0f0f0f0ff", PACK_ID_RE);
|
|
35
|
-
assert.doesNotMatch(" f0f0f0f0f0f0f0f0", PACK_ID_RE);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("a well-formed pack validates and reports its count", () => {
|
|
39
|
-
const out = validateSharePack(packOf([entry()]));
|
|
40
|
-
assert.equal(out.ok, true);
|
|
41
|
-
assert.equal(out.count, 1);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("every rejection names a code and never a partial success", () => {
|
|
45
|
-
assert.equal(validateSharePack({}).error, "invalid-kind");
|
|
46
|
-
assert.equal(validateSharePack(packOf([entry()], { kind: "other" })).error, "invalid-kind");
|
|
47
|
-
assert.equal(validateSharePack(packOf([entry()], { pack_id: "nope" })).error, "invalid-pack-id");
|
|
48
|
-
assert.equal(validateSharePack(packOf([])).error, "empty-pack");
|
|
49
|
-
assert.equal(validateSharePack({ ...packOf([entry()]), memories: "x" }).error, "invalid-memories");
|
|
50
|
-
const badType = validateSharePack(packOf([entry({ type: "document" })]));
|
|
51
|
-
assert.equal(badType.error, "invalid-type");
|
|
52
|
-
assert.equal(badType.index, 0);
|
|
53
|
-
assert.equal(validateSharePack(packOf([entry({ title: " " })])).error, "invalid-entry");
|
|
54
|
-
assert.equal(validateSharePack(packOf([entry({ content: "" })])).error, "invalid-entry");
|
|
55
|
-
assert.equal(validateSharePack(packOf([entry({ importance: "high" })])).error, "invalid-entry");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("a recomputed key that disagrees means the pack was edited by hand", () => {
|
|
59
|
-
const out = validateSharePack(packOf([entry({ external_id: "0000000000000000" })]));
|
|
60
|
-
assert.equal(out.ok, false);
|
|
61
|
-
assert.equal(out.error, "external-id-mismatch");
|
|
62
|
-
assert.equal(out.index, 0);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test("duplicate external_id inside one pack is rejected, not folded here", () => {
|
|
66
|
-
// 折叠是出包侧的义务(§3 的 deduped);收到重复键说明包被人改过,按不合格处置。
|
|
67
|
-
const dup = packOf([entry(), entry({ content: "另一条" })]);
|
|
68
|
-
assert.equal(validateSharePack(dup).error, "duplicate-external-id");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("the tag is derived in one place and never from the sharer's identity", () => {
|
|
72
|
-
assert.equal(importedTagFor("桉"), "from-桉");
|
|
73
|
-
assert.equal(importedTagFor(""), "from-shared");
|
|
74
|
-
assert.equal(importedTagFor(undefined), "from-shared");
|
|
75
|
-
assert.equal(importedTagFor(" "), "from-shared"); // 全空白别名走中性值,不许剩 `from--`
|
|
76
|
-
assert.equal(importedTagFor("a\nb c"), "from-a-b-c"); // 空白游程一律折成一个 -,标签不许带换行
|
|
77
|
-
assert.equal(importedTagFor("x".repeat(80)).length, 32); // 上限:别人的字符串不许无限长
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("folding keeps the word boundary: a newline alias is not the squashed spelling", () => {
|
|
81
|
-
// 上一版对同一类字符开两条规则:换行/制表符**删除**、其余空白折成 -。删除那一侧
|
|
82
|
-
// 恰恰制造了它自己测试要防的撞名——"a\nb" 与 "ab" 读出同一个标签,而对面看到的
|
|
83
|
-
// 来源读数还丢了词缝。裁定:整类空白(含 \r\n\t)折成一个 -(mirror.js 的
|
|
84
|
-
// oneLine() 同是先例:只折不删)。单行的要求折叠同样满足。
|
|
85
|
-
assert.equal(importedTagFor("a\nb"), "from-a-b");
|
|
86
|
-
assert.equal(importedTagFor("ab"), "from-ab");
|
|
87
|
-
assert.notEqual(importedTagFor("a\nb"), importedTagFor("ab"));
|
|
88
|
-
assert.equal(importedTagFor("a\tb"), "from-a-b"); // 制表符与换行同一条规则
|
|
89
|
-
assert.equal(importedTagFor("a\r\nb"), "from-a-b"); // \r\n 游程折成**一个** -
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("the allowed type set is the mirror's minus the two exclusions", () => {
|
|
93
|
-
// 真仪器读真数据:TYPE_FILE import 进来直接 Object.keys()。源码正则有两类静默失明——
|
|
94
|
-
// 不匹配 `[a-z_]+: "….md"` 形状的键(大写、连字符、带引号的键名)会被无声丢掉,
|
|
95
|
-
// 而缩进/换行这类纯排版改动同样能让它一条都不解析。断言本体不变:新键进 TYPE_FILE
|
|
96
|
-
// 而没在这里被决定过,这条必须转红。
|
|
97
|
-
const keys = Object.keys(TYPE_FILE);
|
|
98
|
-
assert.ok(keys.length >= 8, `TYPE_FILE should expose its keys to the lock, got ${keys.length}`);
|
|
99
|
-
const minus = new Set(keys.filter((k) => !SHARE_READONLY_TYPES.has(k) && !SHARE_EXCLUDED_TYPES.has(k)));
|
|
100
|
-
assert.deepEqual([...SHARE_ALLOWED_TYPES].sort(), [...minus].sort());
|
|
101
|
-
});
|
|
1
|
+
// 接收侧的格式闸门。锁三件事:
|
|
2
|
+
// a) 一个不合规的包**整包**不合格(车道 C 不许半途入库);
|
|
3
|
+
// b) 键必须可重算——包是别人发来的,重算对不上就是被人改过,幂等键失效;
|
|
4
|
+
// c) 允许的类型集合不许偷偷跟着 mirror 扩:这里刻意重述一遍,并由静态锁比对
|
|
5
|
+
// TYPE_FILE 的键集减去两个排除项,将来加第 9 类时它会转红提醒人来决定。
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
// 静态锁用真仪器而不是源码正则:test/mirror-types.test.js 已经是 import TYPE_FILE +
|
|
9
|
+
// Object.keys 的先例。在**测试**里 import 它不花钱——src/share.js 的重述字面集照旧,
|
|
10
|
+
// 那是接收侧的兼容契约,不是漂移。
|
|
11
|
+
import { TYPE_FILE } from "../src/mirror.js";
|
|
12
|
+
import {
|
|
13
|
+
PACK_ID_RE, SHARE_ALLOWED_TYPES, SHARE_READONLY_TYPES, SHARE_EXCLUDED_TYPES,
|
|
14
|
+
importedTagFor, validateSharePack, deriveExternalId, SHARE_KIND
|
|
15
|
+
} from "../src/share.js";
|
|
16
|
+
|
|
17
|
+
const entry = (over = {}) => {
|
|
18
|
+
const base = { type: "decision", title: "选 SQLite", content: "正文正文", tags: ["x"], importance: 4,
|
|
19
|
+
epistemic_status: "subjective", created_at: "2026-10-01T00:00:00.000Z", updated_at: "2026-10-01T00:00:00.000Z", content_hash: "ab" };
|
|
20
|
+
return { ...base, external_id: deriveExternalId("f0f0f0f0f0f0f0f0", base.type, base.title, base.content_hash), ...over };
|
|
21
|
+
};
|
|
22
|
+
const packOf = (memories, over = {}) => ({
|
|
23
|
+
kind: SHARE_KIND, pack_id: "f0f0f0f0f0f0f0f0", alias: "桉", generated_at: "2026-10-22T00:00:00.000Z",
|
|
24
|
+
source_version: "0.9.4", report: { included: memories.length, skipped: {}, skip_list: [], deduped: 0 },
|
|
25
|
+
memories, ...over
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("pack_id is exactly 16 lowercase hex", () => {
|
|
29
|
+
assert.match("f0f0f0f0f0f0f0f0", PACK_ID_RE);
|
|
30
|
+
assert.doesNotMatch("f0f0f0f0f0f0f0", PACK_ID_RE); // 车道 A 铸的是 16 位,不是 12
|
|
31
|
+
assert.doesNotMatch("F0F0F0F0F0F0F0F0", PACK_ID_RE);
|
|
32
|
+
// 后两条只验 {16} 那个量词,验不到两个锚:锚一掉,「16 位以内」就成了「正文里含 16 位」,
|
|
33
|
+
// 于是 18 位的串和带空白的串都能过——而这两种恰好是手改包与编码包的形状。
|
|
34
|
+
assert.doesNotMatch("f0f0f0f0f0f0f0f0ff", PACK_ID_RE);
|
|
35
|
+
assert.doesNotMatch(" f0f0f0f0f0f0f0f0", PACK_ID_RE);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("a well-formed pack validates and reports its count", () => {
|
|
39
|
+
const out = validateSharePack(packOf([entry()]));
|
|
40
|
+
assert.equal(out.ok, true);
|
|
41
|
+
assert.equal(out.count, 1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("every rejection names a code and never a partial success", () => {
|
|
45
|
+
assert.equal(validateSharePack({}).error, "invalid-kind");
|
|
46
|
+
assert.equal(validateSharePack(packOf([entry()], { kind: "other" })).error, "invalid-kind");
|
|
47
|
+
assert.equal(validateSharePack(packOf([entry()], { pack_id: "nope" })).error, "invalid-pack-id");
|
|
48
|
+
assert.equal(validateSharePack(packOf([])).error, "empty-pack");
|
|
49
|
+
assert.equal(validateSharePack({ ...packOf([entry()]), memories: "x" }).error, "invalid-memories");
|
|
50
|
+
const badType = validateSharePack(packOf([entry({ type: "document" })]));
|
|
51
|
+
assert.equal(badType.error, "invalid-type");
|
|
52
|
+
assert.equal(badType.index, 0);
|
|
53
|
+
assert.equal(validateSharePack(packOf([entry({ title: " " })])).error, "invalid-entry");
|
|
54
|
+
assert.equal(validateSharePack(packOf([entry({ content: "" })])).error, "invalid-entry");
|
|
55
|
+
assert.equal(validateSharePack(packOf([entry({ importance: "high" })])).error, "invalid-entry");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("a recomputed key that disagrees means the pack was edited by hand", () => {
|
|
59
|
+
const out = validateSharePack(packOf([entry({ external_id: "0000000000000000" })]));
|
|
60
|
+
assert.equal(out.ok, false);
|
|
61
|
+
assert.equal(out.error, "external-id-mismatch");
|
|
62
|
+
assert.equal(out.index, 0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("duplicate external_id inside one pack is rejected, not folded here", () => {
|
|
66
|
+
// 折叠是出包侧的义务(§3 的 deduped);收到重复键说明包被人改过,按不合格处置。
|
|
67
|
+
const dup = packOf([entry(), entry({ content: "另一条" })]);
|
|
68
|
+
assert.equal(validateSharePack(dup).error, "duplicate-external-id");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("the tag is derived in one place and never from the sharer's identity", () => {
|
|
72
|
+
assert.equal(importedTagFor("桉"), "from-桉");
|
|
73
|
+
assert.equal(importedTagFor(""), "from-shared");
|
|
74
|
+
assert.equal(importedTagFor(undefined), "from-shared");
|
|
75
|
+
assert.equal(importedTagFor(" "), "from-shared"); // 全空白别名走中性值,不许剩 `from--`
|
|
76
|
+
assert.equal(importedTagFor("a\nb c"), "from-a-b-c"); // 空白游程一律折成一个 -,标签不许带换行
|
|
77
|
+
assert.equal(importedTagFor("x".repeat(80)).length, 32); // 上限:别人的字符串不许无限长
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("folding keeps the word boundary: a newline alias is not the squashed spelling", () => {
|
|
81
|
+
// 上一版对同一类字符开两条规则:换行/制表符**删除**、其余空白折成 -。删除那一侧
|
|
82
|
+
// 恰恰制造了它自己测试要防的撞名——"a\nb" 与 "ab" 读出同一个标签,而对面看到的
|
|
83
|
+
// 来源读数还丢了词缝。裁定:整类空白(含 \r\n\t)折成一个 -(mirror.js 的
|
|
84
|
+
// oneLine() 同是先例:只折不删)。单行的要求折叠同样满足。
|
|
85
|
+
assert.equal(importedTagFor("a\nb"), "from-a-b");
|
|
86
|
+
assert.equal(importedTagFor("ab"), "from-ab");
|
|
87
|
+
assert.notEqual(importedTagFor("a\nb"), importedTagFor("ab"));
|
|
88
|
+
assert.equal(importedTagFor("a\tb"), "from-a-b"); // 制表符与换行同一条规则
|
|
89
|
+
assert.equal(importedTagFor("a\r\nb"), "from-a-b"); // \r\n 游程折成**一个** -
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("the allowed type set is the mirror's minus the two exclusions", () => {
|
|
93
|
+
// 真仪器读真数据:TYPE_FILE import 进来直接 Object.keys()。源码正则有两类静默失明——
|
|
94
|
+
// 不匹配 `[a-z_]+: "….md"` 形状的键(大写、连字符、带引号的键名)会被无声丢掉,
|
|
95
|
+
// 而缩进/换行这类纯排版改动同样能让它一条都不解析。断言本体不变:新键进 TYPE_FILE
|
|
96
|
+
// 而没在这里被决定过,这条必须转红。
|
|
97
|
+
const keys = Object.keys(TYPE_FILE);
|
|
98
|
+
assert.ok(keys.length >= 8, `TYPE_FILE should expose its keys to the lock, got ${keys.length}`);
|
|
99
|
+
const minus = new Set(keys.filter((k) => !SHARE_READONLY_TYPES.has(k) && !SHARE_EXCLUDED_TYPES.has(k)));
|
|
100
|
+
assert.deepEqual([...SHARE_ALLOWED_TYPES].sort(), [...minus].sort());
|
|
101
|
+
});
|