@modusensus/dsh-mneme 0.14.0-beta.1 → 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 -830
- package/assets/icon.svg +3 -3
- package/bin/cli.mjs +964 -964
- package/bin/dsh-mneme-choro.mjs +262 -262
- 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 -404
- 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 -3600
- 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 -14322
- package/lib/commands.js +95 -95
- package/lib/config-keys.js +144 -144
- package/lib/config.js +1361 -1344
- 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 -945
- package/lib/dream/narratives.js +44 -44
- package/lib/dream/sleep.js +2027 -2027
- package/lib/dream/triage.js +139 -139
- package/lib/dream.js +1879 -1879
- 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 -119
- package/lib/image-describe.js +158 -0
- package/lib/index.js +852 -852
- package/lib/inject.js +656 -651
- 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 -595
- 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 -3268
- package/lib/session-gate.js +91 -91
- package/lib/session-usage.js +350 -350
- package/lib/settings.js +827 -817
- 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 -5895
- 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 -267
- 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 -640
- package/lib/wiki/render.js +261 -261
- package/lib/wiki/scan.js +371 -371
- package/lib/wiki/t0.js +500 -500
- package/lib/wiki/t1-jobs.js +232 -232
- package/lib/wiki/t1.js +609 -609
- package/lib/write-admission.js +404 -404
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +1 -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 -210
- package/scripts/release-notes.mjs +164 -164
- 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 -3600
- 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 -1344
- 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 -945
- package/src/dream/narratives.js +44 -44
- package/src/dream/sleep.js +2027 -2027
- package/src/dream/triage.js +139 -139
- package/src/dream.js +1879 -1879
- 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 -119
- package/src/image-describe.js +158 -0
- package/src/index.js +852 -852
- package/src/inject.js +656 -651
- 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 -595
- 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 -3268
- package/src/session-gate.js +91 -91
- package/src/session-usage.js +350 -350
- package/src/settings.js +827 -817
- 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 -5895
- 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 -267
- 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 -640
- package/src/wiki/render.js +261 -261
- package/src/wiki/scan.js +371 -371
- package/src/wiki/t0.js +500 -500
- package/src/wiki/t1-jobs.js +232 -232
- package/src/wiki/t1.js +609 -609
- 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 -2998
- 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 -4425
- 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 -452
- package/test/conflict-ledger.test.js +242 -242
- 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 -432
- 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 -174
- 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 -333
- 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 -259
- 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 -109
- 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 -553
- 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 -90
- package/test/panel-tokens.test.js +245 -245
- 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 -156
- package/test/release-notes.test.js +265 -265
- 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 -188
- 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 -1455
- 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 -734
- package/test/wiki-claims.test.js +194 -194
- 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 -202
- package/test/wiki-queries.test.js +398 -398
- package/test/wiki-scan.test.js +208 -208
- package/test/wiki-store.test.js +190 -190
- package/test/wiki-t0.test.js +456 -456
- package/test/wiki-t1-jobs.test.js +125 -125
- package/test/wiki-t1.test.js +460 -460
- package/test/wiki-tools.test.js +176 -176
- package/test/workflow-guards.test.js +155 -155
- package/test/write-admission.test.js +668 -668
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
// 外来区的服务端读数锁:面板要能「只筛外来行」并且「知道每行属于哪个包」。
|
|
2
|
-
// 盯三件事:
|
|
3
|
-
// a) /list 的 source=imported 是**精确匹配**(src/store.js:1658),且 total 同口径——
|
|
4
|
-
// 面板的「外来」chip 直接吃这个参数,分页与计数不许各算一套;
|
|
5
|
-
// b) /list 的面板行在**有包号时**才带 share_pack_id,普通行不带这个键(不是 null)。
|
|
6
|
-
// 这是抽屉「收起这个包」按钮的自门控依据,缺了它按钮就不该出现;
|
|
7
|
-
// c) /search 的 DTO **既不带** share_pack_id,它的 `source` 也**不是**行来源——
|
|
8
|
-
// service.searchMemories 给每条候选盖上召回渠道(`source: "keyword"|"vector"|"bm25"`,
|
|
9
|
-
// src/service.js:939/966/974),把行自己的 provenance 覆盖掉了,所以搜索态压根读不出
|
|
10
|
-
// 「这条是不是外来的」。这一条锁住的是那个事实,不是待实现的功能,它同时是 D4 那条
|
|
11
|
-
// 「外来 chip 只管浏览态」裁定的凭据。哪天有人把召回渠道改名让开 `source`(那是另一刀,
|
|
12
|
-
// 牵动 recall_runs 的回执形状),这条会红,D4 的裁定届时要一起重看。
|
|
13
|
-
// 扩展点住在 HTTP 层不是 toApiList:模型工具严格复用后者(src/service.js:2094),
|
|
14
|
-
// archived/quality_score/heat 都在这同一处补,包号跟着走同一条路。
|
|
15
|
-
import test from "node:test";
|
|
16
|
-
import assert from "node:assert/strict";
|
|
17
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
18
|
-
import { tmpdir } from "node:os";
|
|
19
|
-
import { join } from "node:path";
|
|
20
|
-
import { createStore } from "../src/store.js";
|
|
21
|
-
import { createService } from "../src/service.js";
|
|
22
|
-
import { createSettings } from "../src/settings.js";
|
|
23
|
-
import { createApi } from "../src/api.js";
|
|
24
|
-
import { IMPORTED_SOURCE } from "../src/memory-source.js";
|
|
25
|
-
import { deriveExternalId } from "../src/share.js";
|
|
26
|
-
|
|
27
|
-
const PACK_ID = "0123456789abcdef";
|
|
28
|
-
|
|
29
|
-
class FakeRes {
|
|
30
|
-
constructor() {
|
|
31
|
-
this.statusCode = 0;
|
|
32
|
-
this.body = "";
|
|
33
|
-
this.headers = {};
|
|
34
|
-
}
|
|
35
|
-
writeHead(code, headers) {
|
|
36
|
-
this.statusCode = code;
|
|
37
|
-
this.headers = headers || {};
|
|
38
|
-
return this;
|
|
39
|
-
}
|
|
40
|
-
end(text) {
|
|
41
|
-
this.body = text ?? "";
|
|
42
|
-
return this;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function foreignSetup() {
|
|
47
|
-
const dir = mkdtempSync(join(tmpdir(), "dsh-mneme-foreign-rows-"));
|
|
48
|
-
const store = createStore(join(dir, "memory.db"));
|
|
49
|
-
const service = createService({ store, mirror: null, config: {} });
|
|
50
|
-
const settings = createSettings(store.db);
|
|
51
|
-
const commands = { add: () => {}, remove: () => {}, list: () => [] };
|
|
52
|
-
const routes = [];
|
|
53
|
-
const ctx = { logger: null, webServer: { register(route) { routes.push(route); return () => {}; } } };
|
|
54
|
-
createApi(ctx, service, settings, commands, null, undefined, "secret-token", null, null);
|
|
55
|
-
return {
|
|
56
|
-
store,
|
|
57
|
-
service,
|
|
58
|
-
routes,
|
|
59
|
-
route(path) { return routes.find((r) => r.path === path); },
|
|
60
|
-
close() {
|
|
61
|
-
// 闭包拿 store,不用 this:裸调用下 this 是 undefined,异常被吞就变成
|
|
62
|
-
// 「库没关就删目录」的 EPERM(同 share-routes.test.js 的理由)。
|
|
63
|
-
try { store.close(); } catch { /* already closed */ }
|
|
64
|
-
rmSync(dir, { recursive: true, force: true });
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** GET 用的最小假 req:/list 与 /search 都是 policy open,不读 body。 */
|
|
70
|
-
function getReq(url) {
|
|
71
|
-
return { method: "GET", url, headers: {} };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function call(route, url) {
|
|
75
|
-
const res = new FakeRes();
|
|
76
|
-
await route.handler(getReq(url), res);
|
|
77
|
-
return { status: res.statusCode, data: res.body ? JSON.parse(res.body) : null };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** 造一条外来行,字段与真实入库路径同一套(src/share-import.js 的落值)。
|
|
81
|
-
* 注意 store.save 直接返回**那一行**(`insertMemoryRow` 的结果,见 src/store.js:1180-1185),
|
|
82
|
-
* 而 service.saveWithDedupe 返回 `{ action, memory }`——两个形状不一样,别混。 */
|
|
83
|
-
function foreignRow(store, title) {
|
|
84
|
-
return store.save({
|
|
85
|
-
type: "decision",
|
|
86
|
-
title,
|
|
87
|
-
content: "对方库里的原文,本机不巩固它。",
|
|
88
|
-
tags: ["from-桉桉"],
|
|
89
|
-
source: IMPORTED_SOURCE,
|
|
90
|
-
share_pack_id: PACK_ID,
|
|
91
|
-
share_external_id: deriveExternalId(PACK_ID, "decision", title, "deadbeef"),
|
|
92
|
-
epistemic_status: "confirmed"
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
test("list?source=imported returns only foreign rows and counts the same set", async () => {
|
|
97
|
-
const h = foreignSetup();
|
|
98
|
-
try {
|
|
99
|
-
h.service.saveWithDedupe({ type: "decision", title: "我自己的决定", content: "本机原文", tags: [] });
|
|
100
|
-
foreignRow(h.store, "外来决定一");
|
|
101
|
-
foreignRow(h.store, "外来决定二");
|
|
102
|
-
const out = await call(h.route("/api/dsh-mneme/list"), "/api/dsh-mneme/list?source=imported");
|
|
103
|
-
assert.equal(out.status, 200);
|
|
104
|
-
assert.equal(out.data.items.length, 2, "只回外来行");
|
|
105
|
-
assert.equal(out.data.total, 2, "total 与行同口径,否则分页的 has-more 会算错");
|
|
106
|
-
assert.ok(out.data.items.every((m) => m.source === IMPORTED_SOURCE));
|
|
107
|
-
const all = await call(h.route("/api/dsh-mneme/list"), "/api/dsh-mneme/list");
|
|
108
|
-
assert.equal(all.data.items.length, 3, "不带 source 时三条都在");
|
|
109
|
-
} finally { h.close(); }
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test("list puts share_pack_id on foreign panel rows and omits the key on own rows", async () => {
|
|
113
|
-
const h = foreignSetup();
|
|
114
|
-
try {
|
|
115
|
-
const own = h.service.saveWithDedupe({ type: "preference", title: "语言", content: "中文", tags: [] });
|
|
116
|
-
foreignRow(h.store, "外来决定");
|
|
117
|
-
const out = await call(h.route("/api/dsh-mneme/list"), "/api/dsh-mneme/list");
|
|
118
|
-
assert.equal(out.status, 200);
|
|
119
|
-
const foreign = out.data.items.find((m) => m.source === IMPORTED_SOURCE);
|
|
120
|
-
const ownRow = out.data.items.find((m) => m.id === own.memory.id);
|
|
121
|
-
assert.ok(foreign, "筛选结果里要有那条外来行");
|
|
122
|
-
assert.ok(ownRow, "筛选结果里要有那条本机行");
|
|
123
|
-
assert.equal(foreign.share_pack_id, PACK_ID);
|
|
124
|
-
// 键**不存在**而不是 null:面板的按钮按 `memory.share_pack_id` 真值门控,
|
|
125
|
-
// 一个 null 与一个 undefined 在 JSON 里是两种形状,锁死成一种。
|
|
126
|
-
assert.equal("share_pack_id" in ownRow, false, "自己的行不许带包号键");
|
|
127
|
-
} finally { h.close(); }
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("search DTO carries neither the pack id nor the row's own source", async () => {
|
|
131
|
-
const h = foreignSetup();
|
|
132
|
-
try {
|
|
133
|
-
foreignRow(h.store, "外来检索命中");
|
|
134
|
-
const out = await call(h.route("/api/dsh-mneme/search"), "/api/dsh-mneme/search?q=%E5%A4%96%E6%9D%A5%E6%A3%80%E7%B4%A2%E5%91%BD%E4%B8%AD");
|
|
135
|
-
assert.equal(out.status, 200);
|
|
136
|
-
assert.ok(out.data.items.length > 0, "关键词路径要能命中这条");
|
|
137
|
-
assert.equal("share_pack_id" in out.data.items[0], false, "search 的 DTO 不带包号");
|
|
138
|
-
// 召回渠道把行的 provenance 盖掉了(src/service.js:939/966/974):这一格在搜索态**不是**
|
|
139
|
-
// 行来源,所以「这条是不是外来的」在这张 DTO 上读不出来。D4 的外来 chip 因此只管浏览态。
|
|
140
|
-
// 别把这条断言当 bug 去「修」——它是那条裁定的凭据,真要修的是召回侧的字段命名。
|
|
141
|
-
assert.match(String(out.data.items[0].source), /^(keyword|vector|bm25)$/,
|
|
142
|
-
"search 的 source 是召回渠道,不是行来源——正因为它有值且是渠道名,provenance 才读不出来");
|
|
143
|
-
} finally { h.close(); }
|
|
144
|
-
});
|
|
1
|
+
// 外来区的服务端读数锁:面板要能「只筛外来行」并且「知道每行属于哪个包」。
|
|
2
|
+
// 盯三件事:
|
|
3
|
+
// a) /list 的 source=imported 是**精确匹配**(src/store.js:1658),且 total 同口径——
|
|
4
|
+
// 面板的「外来」chip 直接吃这个参数,分页与计数不许各算一套;
|
|
5
|
+
// b) /list 的面板行在**有包号时**才带 share_pack_id,普通行不带这个键(不是 null)。
|
|
6
|
+
// 这是抽屉「收起这个包」按钮的自门控依据,缺了它按钮就不该出现;
|
|
7
|
+
// c) /search 的 DTO **既不带** share_pack_id,它的 `source` 也**不是**行来源——
|
|
8
|
+
// service.searchMemories 给每条候选盖上召回渠道(`source: "keyword"|"vector"|"bm25"`,
|
|
9
|
+
// src/service.js:939/966/974),把行自己的 provenance 覆盖掉了,所以搜索态压根读不出
|
|
10
|
+
// 「这条是不是外来的」。这一条锁住的是那个事实,不是待实现的功能,它同时是 D4 那条
|
|
11
|
+
// 「外来 chip 只管浏览态」裁定的凭据。哪天有人把召回渠道改名让开 `source`(那是另一刀,
|
|
12
|
+
// 牵动 recall_runs 的回执形状),这条会红,D4 的裁定届时要一起重看。
|
|
13
|
+
// 扩展点住在 HTTP 层不是 toApiList:模型工具严格复用后者(src/service.js:2094),
|
|
14
|
+
// archived/quality_score/heat 都在这同一处补,包号跟着走同一条路。
|
|
15
|
+
import test from "node:test";
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { createStore } from "../src/store.js";
|
|
21
|
+
import { createService } from "../src/service.js";
|
|
22
|
+
import { createSettings } from "../src/settings.js";
|
|
23
|
+
import { createApi } from "../src/api.js";
|
|
24
|
+
import { IMPORTED_SOURCE } from "../src/memory-source.js";
|
|
25
|
+
import { deriveExternalId } from "../src/share.js";
|
|
26
|
+
|
|
27
|
+
const PACK_ID = "0123456789abcdef";
|
|
28
|
+
|
|
29
|
+
class FakeRes {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.statusCode = 0;
|
|
32
|
+
this.body = "";
|
|
33
|
+
this.headers = {};
|
|
34
|
+
}
|
|
35
|
+
writeHead(code, headers) {
|
|
36
|
+
this.statusCode = code;
|
|
37
|
+
this.headers = headers || {};
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
end(text) {
|
|
41
|
+
this.body = text ?? "";
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function foreignSetup() {
|
|
47
|
+
const dir = mkdtempSync(join(tmpdir(), "dsh-mneme-foreign-rows-"));
|
|
48
|
+
const store = createStore(join(dir, "memory.db"));
|
|
49
|
+
const service = createService({ store, mirror: null, config: {} });
|
|
50
|
+
const settings = createSettings(store.db);
|
|
51
|
+
const commands = { add: () => {}, remove: () => {}, list: () => [] };
|
|
52
|
+
const routes = [];
|
|
53
|
+
const ctx = { logger: null, webServer: { register(route) { routes.push(route); return () => {}; } } };
|
|
54
|
+
createApi(ctx, service, settings, commands, null, undefined, "secret-token", null, null);
|
|
55
|
+
return {
|
|
56
|
+
store,
|
|
57
|
+
service,
|
|
58
|
+
routes,
|
|
59
|
+
route(path) { return routes.find((r) => r.path === path); },
|
|
60
|
+
close() {
|
|
61
|
+
// 闭包拿 store,不用 this:裸调用下 this 是 undefined,异常被吞就变成
|
|
62
|
+
// 「库没关就删目录」的 EPERM(同 share-routes.test.js 的理由)。
|
|
63
|
+
try { store.close(); } catch { /* already closed */ }
|
|
64
|
+
rmSync(dir, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** GET 用的最小假 req:/list 与 /search 都是 policy open,不读 body。 */
|
|
70
|
+
function getReq(url) {
|
|
71
|
+
return { method: "GET", url, headers: {} };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function call(route, url) {
|
|
75
|
+
const res = new FakeRes();
|
|
76
|
+
await route.handler(getReq(url), res);
|
|
77
|
+
return { status: res.statusCode, data: res.body ? JSON.parse(res.body) : null };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 造一条外来行,字段与真实入库路径同一套(src/share-import.js 的落值)。
|
|
81
|
+
* 注意 store.save 直接返回**那一行**(`insertMemoryRow` 的结果,见 src/store.js:1180-1185),
|
|
82
|
+
* 而 service.saveWithDedupe 返回 `{ action, memory }`——两个形状不一样,别混。 */
|
|
83
|
+
function foreignRow(store, title) {
|
|
84
|
+
return store.save({
|
|
85
|
+
type: "decision",
|
|
86
|
+
title,
|
|
87
|
+
content: "对方库里的原文,本机不巩固它。",
|
|
88
|
+
tags: ["from-桉桉"],
|
|
89
|
+
source: IMPORTED_SOURCE,
|
|
90
|
+
share_pack_id: PACK_ID,
|
|
91
|
+
share_external_id: deriveExternalId(PACK_ID, "decision", title, "deadbeef"),
|
|
92
|
+
epistemic_status: "confirmed"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
test("list?source=imported returns only foreign rows and counts the same set", async () => {
|
|
97
|
+
const h = foreignSetup();
|
|
98
|
+
try {
|
|
99
|
+
h.service.saveWithDedupe({ type: "decision", title: "我自己的决定", content: "本机原文", tags: [] });
|
|
100
|
+
foreignRow(h.store, "外来决定一");
|
|
101
|
+
foreignRow(h.store, "外来决定二");
|
|
102
|
+
const out = await call(h.route("/api/dsh-mneme/list"), "/api/dsh-mneme/list?source=imported");
|
|
103
|
+
assert.equal(out.status, 200);
|
|
104
|
+
assert.equal(out.data.items.length, 2, "只回外来行");
|
|
105
|
+
assert.equal(out.data.total, 2, "total 与行同口径,否则分页的 has-more 会算错");
|
|
106
|
+
assert.ok(out.data.items.every((m) => m.source === IMPORTED_SOURCE));
|
|
107
|
+
const all = await call(h.route("/api/dsh-mneme/list"), "/api/dsh-mneme/list");
|
|
108
|
+
assert.equal(all.data.items.length, 3, "不带 source 时三条都在");
|
|
109
|
+
} finally { h.close(); }
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("list puts share_pack_id on foreign panel rows and omits the key on own rows", async () => {
|
|
113
|
+
const h = foreignSetup();
|
|
114
|
+
try {
|
|
115
|
+
const own = h.service.saveWithDedupe({ type: "preference", title: "语言", content: "中文", tags: [] });
|
|
116
|
+
foreignRow(h.store, "外来决定");
|
|
117
|
+
const out = await call(h.route("/api/dsh-mneme/list"), "/api/dsh-mneme/list");
|
|
118
|
+
assert.equal(out.status, 200);
|
|
119
|
+
const foreign = out.data.items.find((m) => m.source === IMPORTED_SOURCE);
|
|
120
|
+
const ownRow = out.data.items.find((m) => m.id === own.memory.id);
|
|
121
|
+
assert.ok(foreign, "筛选结果里要有那条外来行");
|
|
122
|
+
assert.ok(ownRow, "筛选结果里要有那条本机行");
|
|
123
|
+
assert.equal(foreign.share_pack_id, PACK_ID);
|
|
124
|
+
// 键**不存在**而不是 null:面板的按钮按 `memory.share_pack_id` 真值门控,
|
|
125
|
+
// 一个 null 与一个 undefined 在 JSON 里是两种形状,锁死成一种。
|
|
126
|
+
assert.equal("share_pack_id" in ownRow, false, "自己的行不许带包号键");
|
|
127
|
+
} finally { h.close(); }
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("search DTO carries neither the pack id nor the row's own source", async () => {
|
|
131
|
+
const h = foreignSetup();
|
|
132
|
+
try {
|
|
133
|
+
foreignRow(h.store, "外来检索命中");
|
|
134
|
+
const out = await call(h.route("/api/dsh-mneme/search"), "/api/dsh-mneme/search?q=%E5%A4%96%E6%9D%A5%E6%A3%80%E7%B4%A2%E5%91%BD%E4%B8%AD");
|
|
135
|
+
assert.equal(out.status, 200);
|
|
136
|
+
assert.ok(out.data.items.length > 0, "关键词路径要能命中这条");
|
|
137
|
+
assert.equal("share_pack_id" in out.data.items[0], false, "search 的 DTO 不带包号");
|
|
138
|
+
// 召回渠道把行的 provenance 盖掉了(src/service.js:939/966/974):这一格在搜索态**不是**
|
|
139
|
+
// 行来源,所以「这条是不是外来的」在这张 DTO 上读不出来。D4 的外来 chip 因此只管浏览态。
|
|
140
|
+
// 别把这条断言当 bug 去「修」——它是那条裁定的凭据,真要修的是召回侧的字段命名。
|
|
141
|
+
assert.match(String(out.data.items[0].source), /^(keyword|vector|bm25)$/,
|
|
142
|
+
"search 的 source 是召回渠道,不是行来源——正因为它有值且是渠道名,provenance 才读不出来");
|
|
143
|
+
} finally { h.close(); }
|
|
144
|
+
});
|
package/test/share-http.test.js
CHANGED
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
// 分享包的 HTTP 口径。两类回归:
|
|
2
|
-
// a) 口径本身 —— 这张脸有两个入口(宿主内面板路由、standalone Bearer 数据面),
|
|
3
|
-
// 任何一处松掉都会同时污染两侧;
|
|
4
|
-
// b) **不许再长出第二份实现**(#410 修好面板侧、数据面停在旧形态的那次教训)。
|
|
5
|
-
// 下面的静态锁把「校验字面量只许出现在本模块」写成断言。
|
|
6
|
-
import test from "node:test";
|
|
7
|
-
import assert from "node:assert/strict";
|
|
8
|
-
import { readFileSync } from "node:fs";
|
|
9
|
-
import { dirname, join, resolve } from "node:path";
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
11
|
-
import { parseShareBody, shareReply, shareFilename } from "../src/share-http.js";
|
|
12
|
-
|
|
13
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
const src = (f) => readFileSync(resolve(here, "../src", f), "utf8");
|
|
15
|
-
const rows = [
|
|
16
|
-
{ id: "a", type: "decision", title: "选 SQLite", content: "正文", tags: [], importance: 4,
|
|
17
|
-
archived: 0, forgotten: 0, created_at: "2026-10-01T00:00:00.000Z", updated_at: "2026-10-01T00:00:00.000Z" },
|
|
18
|
-
{ id: "b", type: "preference", archived: 1, forgotten: 0, title: "归档项", content: "x", tags: [] }
|
|
19
|
-
];
|
|
20
|
-
const fakeService = { all: () => rows };
|
|
21
|
-
const baseArgs = { service: fakeService, packageVersion: "0.9.4", homeDir: "/fake/home/alex", now: () => new Date("2026-10-22T00:00:00.000Z") };
|
|
22
|
-
|
|
23
|
-
test("bad json and non-object bodies are refused with a code, not a throw", () => {
|
|
24
|
-
assert.deepEqual(parseShareBody(""), { ok: true, options: {} });
|
|
25
|
-
// 没有 body 与空 body 同口径:分享的主用法就是「一个字节都不发,按整库口径打」。
|
|
26
|
-
assert.deepEqual(parseShareBody(undefined), { ok: true, options: {} });
|
|
27
|
-
assert.equal(parseShareBody("{oops").ok, false);
|
|
28
|
-
assert.equal(parseShareBody("[1,2]").ok, false);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("unknown option keys are dropped, not silently accepted", () => {
|
|
32
|
-
const parsed = parseShareBody(JSON.stringify({ alias: "桉", type: ["decision"], color: "red" }));
|
|
33
|
-
assert.equal(parsed.ok, true);
|
|
34
|
-
assert.ok(!("color" in parsed.options));
|
|
35
|
-
// dryRun 单列一条:它曾经是「登记但不参与答复构造」的键(照 reclaim 形状抄来的),
|
|
36
|
-
// 后来删掉——share 纯读、不改库,干跑归 CLI 侧的 --dry-run,服务端没人消费它。留这
|
|
37
|
-
// 一条是挡住「为了兼容把它加回来」的那次提交:发它的客户端拿到的是真包,不是干跑报告。
|
|
38
|
-
assert.ok(!("dryRun" in parseShareBody(JSON.stringify({ dryRun: true })).options));
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("reply returns the pack body with kind and disposition-ready filename", () => {
|
|
42
|
-
const { status, body } = shareReply({ ...baseArgs, options: {} });
|
|
43
|
-
assert.equal(status, 200);
|
|
44
|
-
assert.equal(body.kind, "mneme.share/v1");
|
|
45
|
-
assert.equal(body.report.included, 1);
|
|
46
|
-
assert.equal(body.report.skipped.archived, 1);
|
|
47
|
-
assert.match(shareFilename("abcdef0123456789", () => new Date("2026-10-22T00:00:00.000Z")), /^dsh-mneme-share-20261022-abcdef01\.json$/);
|
|
48
|
-
// 不给时钟就走本机当日:文件名是给人存的,日期缺一位就成了「未知日期的包」。
|
|
49
|
-
assert.match(shareFilename("abcdef0123456789"), /^dsh-mneme-share-\d{8}-abcdef01\.json$/);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("oversized packs answer 413 with the measured size and the batching hint", () => {
|
|
53
|
-
const secret = "P@ssw0rd-for-prod-db";
|
|
54
|
-
const fat = [
|
|
55
|
-
{ id: "f", type: "decision", title: "t", content: "字".repeat(2_000_000), tags: [], archived: 0, forgotten: 0 },
|
|
56
|
-
// 这条是给「拒发也带 report」那格用的:整库被水位挡下时,出口照样看得见跳过清单,
|
|
57
|
-
// 而清单随答复体外发——含凭据那行的标题必须在源头就留空,不然这一路是第二次泄露。
|
|
58
|
-
{ id: "s", type: "decision", title: `生产库密码 ${secret} 别丢`, content: `password: ${secret}`, tags: [], archived: 1, forgotten: 0 }
|
|
59
|
-
];
|
|
60
|
-
const out = shareReply({ ...baseArgs, service: { all: () => fat }, options: {} });
|
|
61
|
-
assert.equal(out.status, 413);
|
|
62
|
-
assert.equal(out.body.error, "too-large");
|
|
63
|
-
assert.ok(out.body.bytes > out.body.limitBytes);
|
|
64
|
-
assert.match(out.body.hint, /--type/);
|
|
65
|
-
// 拒答里绝不带 memories:这条路径存在的原因恰恰是包太大,把正文再序列化一遍回去
|
|
66
|
-
// 等于把刚拒绝掉的几 MB 原样发出去(buildSharePack 超限时也把完整 pack 交回,
|
|
67
|
-
// 本层只许取它的 report)。
|
|
68
|
-
assert.ok(!("memories" in out.body), "413 body must not carry the pack body");
|
|
69
|
-
assert.equal(out.body.report.included, 1, "但报告要给全:分批的依据就是这份数");
|
|
70
|
-
assert.ok(!JSON.stringify(out.body).includes(secret), "the refused reply must not ship the credential in a title");
|
|
71
|
-
assert.deepEqual(out.body.report.skip_list, [{ title: "", reason: "archived" }]);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("empty library answers 200 with included 0 — not an error", () => {
|
|
75
|
-
const out = shareReply({ ...baseArgs, service: { all: () => [] }, options: {} });
|
|
76
|
-
assert.equal(out.status, 200);
|
|
77
|
-
assert.equal(out.body.report.included, 0);
|
|
78
|
-
// 出口只给 service/packageVersion/homeDir 也答得出来:now 与 options 都有默认值,
|
|
79
|
-
// 缺省是「整库口径 + 本机此刻」,不是错误。
|
|
80
|
-
const bare = shareReply({ service: { all: () => [] }, packageVersion: "0.9.4", homeDir: "/fake/home/alex" });
|
|
81
|
-
assert.equal(bare.status, 200);
|
|
82
|
-
assert.ok(Number.isFinite(Date.parse(bare.body.generated_at)), "generated_at 必须是可解析的时刻");
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("a filter this layer cannot read narrows the pack, never opens the library", () => {
|
|
86
|
-
// share.js 把「读不懂的形状只许收窄、不许放行」钉死在它那一侧(share-pack.test.js)。
|
|
87
|
-
// 本层是它上游第一个出口:在这里把裸字符串、混合数组当「没填」丢掉,等于把那条洞在
|
|
88
|
-
// HTTP 面上重新挖开——她以为只发了一类,实际整库进了包。所以这一层不做形状归一也不做
|
|
89
|
-
// 形状校验,原样交给 share.js(判据只许有一份)。
|
|
90
|
-
const pair = [
|
|
91
|
-
{ id: "1", type: "decision", title: "选 SQLite", content: "正文", tags: ["keep"], archived: 0, forgotten: 0 },
|
|
92
|
-
{ id: "2", type: "preference", title: "浅色主题", content: "正文", tags: ["later"], archived: 0, forgotten: 0 }
|
|
93
|
-
];
|
|
94
|
-
const args = { ...baseArgs, service: { all: () => pair } };
|
|
95
|
-
const optionsOf = (body) => parseShareBody(JSON.stringify(body)).options;
|
|
96
|
-
const includedOf = (body) => shareReply({ ...args, options: optionsOf(body) }).body.report.included;
|
|
97
|
-
assert.equal(includedOf({ type: "decision" }), 1, "type 给成串同样是收窄");
|
|
98
|
-
assert.equal(includedOf({ tag: ["keep"] }), 1, "tag 给成数组同样是收窄");
|
|
99
|
-
assert.equal(includedOf({ type: ["decision", 7] }), 1, "条目里混进非字符串不撤销整条收窄");
|
|
100
|
-
assert.equal(includedOf({ type: [7] }), 0, "全是认不出的条目 = 谁都不匹配,不是没筛");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("a throwing row read answers 500 with a log line instead of leaking out as a rejection", () => {
|
|
104
|
-
// 面板侧 handler 的形状是 `try { return readBody(...).then(...) } catch`:外层 try 在
|
|
105
|
-
// .then 的回调里已经退栈,从共用层抛出去就是一个没人接的 rejection,客户端拿到的不是
|
|
106
|
-
// 500 而是「一个字节都没回」(#410)。兜在共用层里,两张皮才不会因为各自接线不同而
|
|
107
|
-
// 有一种会漏。
|
|
108
|
-
const boom = { all: () => { throw new Error("database is locked"); } };
|
|
109
|
-
const warned = [];
|
|
110
|
-
const out = shareReply({ ...baseArgs, service: boom, options: {}, warn: (m) => warned.push(m) });
|
|
111
|
-
assert.deepEqual(out, { status: 500, body: { error: "internal" } }, "对外只有一个码,不带原始错误文本");
|
|
112
|
-
assert.match(warned.join(" "), /share pack failed/);
|
|
113
|
-
assert.match(warned.join(" "), /database is locked/, "原始错误文本要留在日志里,否则现场无从下手");
|
|
114
|
-
assert.equal(shareReply({ ...baseArgs, service: boom, options: {} }).status, 500, "没有 logger 也不许把异常漏出去");
|
|
115
|
-
// 日志那一环自己是坏的那一环时也一样:这一层的契约是「从不抛」,不是「看调用面有没有
|
|
116
|
-
// .catch」。C6 之前这里是裸调 warn,出包侧的面板路由(车道 A)恰好就少了那一声 .catch,
|
|
117
|
-
// 于是宿主 logger 一抛就成了没人接的 rejection(#410 的形态)。守卫还回层里之后,
|
|
118
|
-
// 三个构造器同一脾气——对称是要点,见 test/share-receive-http.test.js 的那条静态锁。
|
|
119
|
-
const screaming = () => { throw new Error("log sink on fire"); };
|
|
120
|
-
assert.deepEqual(shareReply({ ...baseArgs, service: boom, options: {}, warn: screaming }),
|
|
121
|
-
{ status: 500, body: { error: "internal" } }, "warn 自己抛时仍须把已经算好的 500 交出去");
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("the share literals live in the share modules only", () => {
|
|
125
|
-
const FORKED = ["mneme.share/v1", "too-large", "parseShareBody", "shareReply"];
|
|
126
|
-
for (const lit of FORKED) {
|
|
127
|
-
assert.ok(src("share-http.js").includes(lit) || src("share.js").includes(lit), `${lit} must live in the share modules`);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// 两张皮都接上之后才补得了这条(A4 时数据面还没有 /share,锁无可锁)。
|
|
132
|
-
// 上一条只管「字面量别在别处冒出来」,这条管反方向:两面必须真的**调**共用构造器。
|
|
133
|
-
// #411 那次是面板侧修好了、数据面停在复制的旧形态——复制出去的代码不会留下任何
|
|
134
|
-
// 痕迹让静态检查发现,只有「这一侧不许出现口径、又必须出现 shareReply(」这对双向
|
|
135
|
-
// 断言能抓住。
|
|
136
|
-
test("both surfaces call the shared builder instead of forking the口径", () => {
|
|
137
|
-
for (const file of ["api.js", "api-standalone.js"]) {
|
|
138
|
-
const text = src(file);
|
|
139
|
-
assert.ok(text.includes("shareReply("), `${file} must call the shared reply builder`);
|
|
140
|
-
for (const lit of ["mneme.share/v1", '"too-large"']) {
|
|
141
|
-
assert.ok(!text.includes(lit), `${file} must not re-implement ${lit}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
});
|
|
1
|
+
// 分享包的 HTTP 口径。两类回归:
|
|
2
|
+
// a) 口径本身 —— 这张脸有两个入口(宿主内面板路由、standalone Bearer 数据面),
|
|
3
|
+
// 任何一处松掉都会同时污染两侧;
|
|
4
|
+
// b) **不许再长出第二份实现**(#410 修好面板侧、数据面停在旧形态的那次教训)。
|
|
5
|
+
// 下面的静态锁把「校验字面量只许出现在本模块」写成断言。
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { dirname, join, resolve } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { parseShareBody, shareReply, shareFilename } from "../src/share-http.js";
|
|
12
|
+
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const src = (f) => readFileSync(resolve(here, "../src", f), "utf8");
|
|
15
|
+
const rows = [
|
|
16
|
+
{ id: "a", type: "decision", title: "选 SQLite", content: "正文", tags: [], importance: 4,
|
|
17
|
+
archived: 0, forgotten: 0, created_at: "2026-10-01T00:00:00.000Z", updated_at: "2026-10-01T00:00:00.000Z" },
|
|
18
|
+
{ id: "b", type: "preference", archived: 1, forgotten: 0, title: "归档项", content: "x", tags: [] }
|
|
19
|
+
];
|
|
20
|
+
const fakeService = { all: () => rows };
|
|
21
|
+
const baseArgs = { service: fakeService, packageVersion: "0.9.4", homeDir: "/fake/home/alex", now: () => new Date("2026-10-22T00:00:00.000Z") };
|
|
22
|
+
|
|
23
|
+
test("bad json and non-object bodies are refused with a code, not a throw", () => {
|
|
24
|
+
assert.deepEqual(parseShareBody(""), { ok: true, options: {} });
|
|
25
|
+
// 没有 body 与空 body 同口径:分享的主用法就是「一个字节都不发,按整库口径打」。
|
|
26
|
+
assert.deepEqual(parseShareBody(undefined), { ok: true, options: {} });
|
|
27
|
+
assert.equal(parseShareBody("{oops").ok, false);
|
|
28
|
+
assert.equal(parseShareBody("[1,2]").ok, false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("unknown option keys are dropped, not silently accepted", () => {
|
|
32
|
+
const parsed = parseShareBody(JSON.stringify({ alias: "桉", type: ["decision"], color: "red" }));
|
|
33
|
+
assert.equal(parsed.ok, true);
|
|
34
|
+
assert.ok(!("color" in parsed.options));
|
|
35
|
+
// dryRun 单列一条:它曾经是「登记但不参与答复构造」的键(照 reclaim 形状抄来的),
|
|
36
|
+
// 后来删掉——share 纯读、不改库,干跑归 CLI 侧的 --dry-run,服务端没人消费它。留这
|
|
37
|
+
// 一条是挡住「为了兼容把它加回来」的那次提交:发它的客户端拿到的是真包,不是干跑报告。
|
|
38
|
+
assert.ok(!("dryRun" in parseShareBody(JSON.stringify({ dryRun: true })).options));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("reply returns the pack body with kind and disposition-ready filename", () => {
|
|
42
|
+
const { status, body } = shareReply({ ...baseArgs, options: {} });
|
|
43
|
+
assert.equal(status, 200);
|
|
44
|
+
assert.equal(body.kind, "mneme.share/v1");
|
|
45
|
+
assert.equal(body.report.included, 1);
|
|
46
|
+
assert.equal(body.report.skipped.archived, 1);
|
|
47
|
+
assert.match(shareFilename("abcdef0123456789", () => new Date("2026-10-22T00:00:00.000Z")), /^dsh-mneme-share-20261022-abcdef01\.json$/);
|
|
48
|
+
// 不给时钟就走本机当日:文件名是给人存的,日期缺一位就成了「未知日期的包」。
|
|
49
|
+
assert.match(shareFilename("abcdef0123456789"), /^dsh-mneme-share-\d{8}-abcdef01\.json$/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("oversized packs answer 413 with the measured size and the batching hint", () => {
|
|
53
|
+
const secret = "P@ssw0rd-for-prod-db";
|
|
54
|
+
const fat = [
|
|
55
|
+
{ id: "f", type: "decision", title: "t", content: "字".repeat(2_000_000), tags: [], archived: 0, forgotten: 0 },
|
|
56
|
+
// 这条是给「拒发也带 report」那格用的:整库被水位挡下时,出口照样看得见跳过清单,
|
|
57
|
+
// 而清单随答复体外发——含凭据那行的标题必须在源头就留空,不然这一路是第二次泄露。
|
|
58
|
+
{ id: "s", type: "decision", title: `生产库密码 ${secret} 别丢`, content: `password: ${secret}`, tags: [], archived: 1, forgotten: 0 }
|
|
59
|
+
];
|
|
60
|
+
const out = shareReply({ ...baseArgs, service: { all: () => fat }, options: {} });
|
|
61
|
+
assert.equal(out.status, 413);
|
|
62
|
+
assert.equal(out.body.error, "too-large");
|
|
63
|
+
assert.ok(out.body.bytes > out.body.limitBytes);
|
|
64
|
+
assert.match(out.body.hint, /--type/);
|
|
65
|
+
// 拒答里绝不带 memories:这条路径存在的原因恰恰是包太大,把正文再序列化一遍回去
|
|
66
|
+
// 等于把刚拒绝掉的几 MB 原样发出去(buildSharePack 超限时也把完整 pack 交回,
|
|
67
|
+
// 本层只许取它的 report)。
|
|
68
|
+
assert.ok(!("memories" in out.body), "413 body must not carry the pack body");
|
|
69
|
+
assert.equal(out.body.report.included, 1, "但报告要给全:分批的依据就是这份数");
|
|
70
|
+
assert.ok(!JSON.stringify(out.body).includes(secret), "the refused reply must not ship the credential in a title");
|
|
71
|
+
assert.deepEqual(out.body.report.skip_list, [{ title: "", reason: "archived" }]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("empty library answers 200 with included 0 — not an error", () => {
|
|
75
|
+
const out = shareReply({ ...baseArgs, service: { all: () => [] }, options: {} });
|
|
76
|
+
assert.equal(out.status, 200);
|
|
77
|
+
assert.equal(out.body.report.included, 0);
|
|
78
|
+
// 出口只给 service/packageVersion/homeDir 也答得出来:now 与 options 都有默认值,
|
|
79
|
+
// 缺省是「整库口径 + 本机此刻」,不是错误。
|
|
80
|
+
const bare = shareReply({ service: { all: () => [] }, packageVersion: "0.9.4", homeDir: "/fake/home/alex" });
|
|
81
|
+
assert.equal(bare.status, 200);
|
|
82
|
+
assert.ok(Number.isFinite(Date.parse(bare.body.generated_at)), "generated_at 必须是可解析的时刻");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("a filter this layer cannot read narrows the pack, never opens the library", () => {
|
|
86
|
+
// share.js 把「读不懂的形状只许收窄、不许放行」钉死在它那一侧(share-pack.test.js)。
|
|
87
|
+
// 本层是它上游第一个出口:在这里把裸字符串、混合数组当「没填」丢掉,等于把那条洞在
|
|
88
|
+
// HTTP 面上重新挖开——她以为只发了一类,实际整库进了包。所以这一层不做形状归一也不做
|
|
89
|
+
// 形状校验,原样交给 share.js(判据只许有一份)。
|
|
90
|
+
const pair = [
|
|
91
|
+
{ id: "1", type: "decision", title: "选 SQLite", content: "正文", tags: ["keep"], archived: 0, forgotten: 0 },
|
|
92
|
+
{ id: "2", type: "preference", title: "浅色主题", content: "正文", tags: ["later"], archived: 0, forgotten: 0 }
|
|
93
|
+
];
|
|
94
|
+
const args = { ...baseArgs, service: { all: () => pair } };
|
|
95
|
+
const optionsOf = (body) => parseShareBody(JSON.stringify(body)).options;
|
|
96
|
+
const includedOf = (body) => shareReply({ ...args, options: optionsOf(body) }).body.report.included;
|
|
97
|
+
assert.equal(includedOf({ type: "decision" }), 1, "type 给成串同样是收窄");
|
|
98
|
+
assert.equal(includedOf({ tag: ["keep"] }), 1, "tag 给成数组同样是收窄");
|
|
99
|
+
assert.equal(includedOf({ type: ["decision", 7] }), 1, "条目里混进非字符串不撤销整条收窄");
|
|
100
|
+
assert.equal(includedOf({ type: [7] }), 0, "全是认不出的条目 = 谁都不匹配,不是没筛");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("a throwing row read answers 500 with a log line instead of leaking out as a rejection", () => {
|
|
104
|
+
// 面板侧 handler 的形状是 `try { return readBody(...).then(...) } catch`:外层 try 在
|
|
105
|
+
// .then 的回调里已经退栈,从共用层抛出去就是一个没人接的 rejection,客户端拿到的不是
|
|
106
|
+
// 500 而是「一个字节都没回」(#410)。兜在共用层里,两张皮才不会因为各自接线不同而
|
|
107
|
+
// 有一种会漏。
|
|
108
|
+
const boom = { all: () => { throw new Error("database is locked"); } };
|
|
109
|
+
const warned = [];
|
|
110
|
+
const out = shareReply({ ...baseArgs, service: boom, options: {}, warn: (m) => warned.push(m) });
|
|
111
|
+
assert.deepEqual(out, { status: 500, body: { error: "internal" } }, "对外只有一个码,不带原始错误文本");
|
|
112
|
+
assert.match(warned.join(" "), /share pack failed/);
|
|
113
|
+
assert.match(warned.join(" "), /database is locked/, "原始错误文本要留在日志里,否则现场无从下手");
|
|
114
|
+
assert.equal(shareReply({ ...baseArgs, service: boom, options: {} }).status, 500, "没有 logger 也不许把异常漏出去");
|
|
115
|
+
// 日志那一环自己是坏的那一环时也一样:这一层的契约是「从不抛」,不是「看调用面有没有
|
|
116
|
+
// .catch」。C6 之前这里是裸调 warn,出包侧的面板路由(车道 A)恰好就少了那一声 .catch,
|
|
117
|
+
// 于是宿主 logger 一抛就成了没人接的 rejection(#410 的形态)。守卫还回层里之后,
|
|
118
|
+
// 三个构造器同一脾气——对称是要点,见 test/share-receive-http.test.js 的那条静态锁。
|
|
119
|
+
const screaming = () => { throw new Error("log sink on fire"); };
|
|
120
|
+
assert.deepEqual(shareReply({ ...baseArgs, service: boom, options: {}, warn: screaming }),
|
|
121
|
+
{ status: 500, body: { error: "internal" } }, "warn 自己抛时仍须把已经算好的 500 交出去");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("the share literals live in the share modules only", () => {
|
|
125
|
+
const FORKED = ["mneme.share/v1", "too-large", "parseShareBody", "shareReply"];
|
|
126
|
+
for (const lit of FORKED) {
|
|
127
|
+
assert.ok(src("share-http.js").includes(lit) || src("share.js").includes(lit), `${lit} must live in the share modules`);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// 两张皮都接上之后才补得了这条(A4 时数据面还没有 /share,锁无可锁)。
|
|
132
|
+
// 上一条只管「字面量别在别处冒出来」,这条管反方向:两面必须真的**调**共用构造器。
|
|
133
|
+
// #411 那次是面板侧修好了、数据面停在复制的旧形态——复制出去的代码不会留下任何
|
|
134
|
+
// 痕迹让静态检查发现,只有「这一侧不许出现口径、又必须出现 shareReply(」这对双向
|
|
135
|
+
// 断言能抓住。
|
|
136
|
+
test("both surfaces call the shared builder instead of forking the口径", () => {
|
|
137
|
+
for (const file of ["api.js", "api-standalone.js"]) {
|
|
138
|
+
const text = src(file);
|
|
139
|
+
assert.ok(text.includes("shareReply("), `${file} must call the shared reply builder`);
|
|
140
|
+
for (const lit of ["mneme.share/v1", '"too-large"']) {
|
|
141
|
+
assert.ok(!text.includes(lit), `${file} must not re-implement ${lit}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|