@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
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
// Issue #239(第 5 项):注入条数的查询自适应。
|
|
2
|
-
// 判据只看查询本身(零成本,不做探针检索——先探针检索等于白付一次 fuseRecall),
|
|
3
|
-
// 且只做**单向收缩**:模糊维持上限、确定收缩,绝不越过 maxInjectedItems——注入
|
|
4
|
-
// 相关却带偏生成的内容可能比不注入更糟。
|
|
5
|
-
import test from "node:test";
|
|
6
|
-
import assert from "node:assert/strict";
|
|
7
|
-
import { needsBroadRecall, adaptiveInjectBudget } from "../src/search/adaptive.js";
|
|
8
|
-
import { createStore } from "../src/store.js";
|
|
9
|
-
import { createService } from "../src/service.js";
|
|
10
|
-
import { createInjector } from "../src/inject.js";
|
|
11
|
-
import { createSettings } from "../src/settings.js";
|
|
12
|
-
|
|
13
|
-
function injectorSetup(over = {}) {
|
|
14
|
-
const store = createStore(":memory:");
|
|
15
|
-
const service = createService({ store, mirror: null, config: {} });
|
|
16
|
-
const settings = createSettings(store.db);
|
|
17
|
-
const contexts = [];
|
|
18
|
-
const ctx = { systemPrompt: { context(def) { contexts.push(def); return () => {}; } } };
|
|
19
|
-
createInjector(ctx, service, settings, { maxInjectedItems: 4, importanceThreshold: 3, ...over });
|
|
20
|
-
return { store, service, contexts };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// lastUserQuery 读的是 materialized 会话日志(DSH ≥0.1.2-rc 走 snapshotEvents)。
|
|
24
|
-
function ctxWithQuery(text) {
|
|
25
|
-
return {
|
|
26
|
-
agent: {
|
|
27
|
-
session: {
|
|
28
|
-
events: [{ type: "user/message", data: { source: { kind: "user" }, content: [{ type: "text", text }] } }]
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function seed(service, n = 6) {
|
|
35
|
-
for (let i = 0; i < n; i++) {
|
|
36
|
-
service.saveWithDedupe({ type: "project", title: `项目${i}`, content: `内容${i}`, importance: 5 });
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const injectedCount = (text) => [0, 1, 2, 3, 4, 5].filter((i) => text.includes(`内容${i}`)).length;
|
|
41
|
-
|
|
42
|
-
test("#239-5 宽召回判据:回指/时间线索与极短查询算模糊,其余算确定", () => {
|
|
43
|
-
// 模糊:回指或时间线索(中英各有)
|
|
44
|
-
for (const q of ["上次那个方案怎么样了", "我们之前讨论过什么", "还记得我提过的偏好吗",
|
|
45
|
-
"what did we discuss last time", "do you remember my preference", "remind me"]) {
|
|
46
|
-
assert.equal(needsBroadRecall(q), true, `${q} 应判为模糊`);
|
|
47
|
-
}
|
|
48
|
-
// 模糊:极短(与 adaptiveThreshold 对 <5 字符的判断同源:几乎匹配任何东西)
|
|
49
|
-
assert.equal(needsBroadRecall("你好"), true);
|
|
50
|
-
assert.equal(needsBroadRecall(""), true, "拿不到查询时不猜,维持现状");
|
|
51
|
-
// 确定:具体技术问句
|
|
52
|
-
for (const q of ["pnpm MODULE_NOT_FOUND 怎么修", "把 settings.js 里的白名单登记补上", "node:sqlite 的 WAL 配置项在哪"]) {
|
|
53
|
-
assert.equal(needsBroadRecall(q), false, `${q} 应判为确定`);
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("#239-5 注入预算:确定收缩到一半(下限 1),模糊维持上限,绝不越界", () => {
|
|
58
|
-
assert.equal(adaptiveInjectBudget("pnpm MODULE_NOT_FOUND 怎么修", 5), 3, "确定 → 收缩到一半");
|
|
59
|
-
assert.equal(adaptiveInjectBudget("上次讨论的方案", 5), 5, "模糊 → 维持上限");
|
|
60
|
-
assert.equal(adaptiveInjectBudget("pnpm MODULE_NOT_FOUND 怎么修", 1), 1, "下限 1");
|
|
61
|
-
assert.equal(adaptiveInjectBudget("pnpm MODULE_NOT_FOUND 怎么修", 0), 0, "0 = 不注入,flag 打开也不该变 1");
|
|
62
|
-
assert.equal(adaptiveInjectBudget("any", undefined), undefined, "非法上限原样返回,不猜");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test("#239-5 默认关:开关未开时注入条数与查询无关(行为逐字节不变)", () => {
|
|
66
|
-
const { contexts, service } = injectorSetup();
|
|
67
|
-
seed(service);
|
|
68
|
-
assert.equal(injectedCount(contexts[0].text(ctxWithQuery("pnpm MODULE_NOT_FOUND 怎么修"))), 4);
|
|
69
|
-
assert.equal(injectedCount(contexts[0].text(ctxWithQuery("上次讨论的方案"))), 4);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("#239-5 开关打开:确定查询少注入,模糊查询保持上限", () => {
|
|
73
|
-
const { contexts, service } = injectorSetup({ injectUncertaintyAdaptive: true });
|
|
74
|
-
seed(service);
|
|
75
|
-
assert.equal(injectedCount(contexts[0].text(ctxWithQuery("pnpm MODULE_NOT_FOUND 怎么修"))), 2,
|
|
76
|
-
"确定 → 注入条数减半");
|
|
77
|
-
assert.equal(injectedCount(contexts[0].text(ctxWithQuery("上次讨论的方案"))), 4,
|
|
78
|
-
"模糊 → 维持 maxInjectedItems");
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("#239-5 拿不到查询(无会话日志)时维持上限,不因开关而减少", () => {
|
|
82
|
-
const { contexts, service } = injectorSetup({ injectUncertaintyAdaptive: true });
|
|
83
|
-
seed(service);
|
|
84
|
-
assert.equal(injectedCount(contexts[0].text({})), 4, "退化路径不猜、不缩");
|
|
85
|
-
});
|
|
1
|
+
// Issue #239(第 5 项):注入条数的查询自适应。
|
|
2
|
+
// 判据只看查询本身(零成本,不做探针检索——先探针检索等于白付一次 fuseRecall),
|
|
3
|
+
// 且只做**单向收缩**:模糊维持上限、确定收缩,绝不越过 maxInjectedItems——注入
|
|
4
|
+
// 相关却带偏生成的内容可能比不注入更糟。
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { needsBroadRecall, adaptiveInjectBudget } from "../src/search/adaptive.js";
|
|
8
|
+
import { createStore } from "../src/store.js";
|
|
9
|
+
import { createService } from "../src/service.js";
|
|
10
|
+
import { createInjector } from "../src/inject.js";
|
|
11
|
+
import { createSettings } from "../src/settings.js";
|
|
12
|
+
|
|
13
|
+
function injectorSetup(over = {}) {
|
|
14
|
+
const store = createStore(":memory:");
|
|
15
|
+
const service = createService({ store, mirror: null, config: {} });
|
|
16
|
+
const settings = createSettings(store.db);
|
|
17
|
+
const contexts = [];
|
|
18
|
+
const ctx = { systemPrompt: { context(def) { contexts.push(def); return () => {}; } } };
|
|
19
|
+
createInjector(ctx, service, settings, { maxInjectedItems: 4, importanceThreshold: 3, ...over });
|
|
20
|
+
return { store, service, contexts };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// lastUserQuery 读的是 materialized 会话日志(DSH ≥0.1.2-rc 走 snapshotEvents)。
|
|
24
|
+
function ctxWithQuery(text) {
|
|
25
|
+
return {
|
|
26
|
+
agent: {
|
|
27
|
+
session: {
|
|
28
|
+
events: [{ type: "user/message", data: { source: { kind: "user" }, content: [{ type: "text", text }] } }]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function seed(service, n = 6) {
|
|
35
|
+
for (let i = 0; i < n; i++) {
|
|
36
|
+
service.saveWithDedupe({ type: "project", title: `项目${i}`, content: `内容${i}`, importance: 5 });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const injectedCount = (text) => [0, 1, 2, 3, 4, 5].filter((i) => text.includes(`内容${i}`)).length;
|
|
41
|
+
|
|
42
|
+
test("#239-5 宽召回判据:回指/时间线索与极短查询算模糊,其余算确定", () => {
|
|
43
|
+
// 模糊:回指或时间线索(中英各有)
|
|
44
|
+
for (const q of ["上次那个方案怎么样了", "我们之前讨论过什么", "还记得我提过的偏好吗",
|
|
45
|
+
"what did we discuss last time", "do you remember my preference", "remind me"]) {
|
|
46
|
+
assert.equal(needsBroadRecall(q), true, `${q} 应判为模糊`);
|
|
47
|
+
}
|
|
48
|
+
// 模糊:极短(与 adaptiveThreshold 对 <5 字符的判断同源:几乎匹配任何东西)
|
|
49
|
+
assert.equal(needsBroadRecall("你好"), true);
|
|
50
|
+
assert.equal(needsBroadRecall(""), true, "拿不到查询时不猜,维持现状");
|
|
51
|
+
// 确定:具体技术问句
|
|
52
|
+
for (const q of ["pnpm MODULE_NOT_FOUND 怎么修", "把 settings.js 里的白名单登记补上", "node:sqlite 的 WAL 配置项在哪"]) {
|
|
53
|
+
assert.equal(needsBroadRecall(q), false, `${q} 应判为确定`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("#239-5 注入预算:确定收缩到一半(下限 1),模糊维持上限,绝不越界", () => {
|
|
58
|
+
assert.equal(adaptiveInjectBudget("pnpm MODULE_NOT_FOUND 怎么修", 5), 3, "确定 → 收缩到一半");
|
|
59
|
+
assert.equal(adaptiveInjectBudget("上次讨论的方案", 5), 5, "模糊 → 维持上限");
|
|
60
|
+
assert.equal(adaptiveInjectBudget("pnpm MODULE_NOT_FOUND 怎么修", 1), 1, "下限 1");
|
|
61
|
+
assert.equal(adaptiveInjectBudget("pnpm MODULE_NOT_FOUND 怎么修", 0), 0, "0 = 不注入,flag 打开也不该变 1");
|
|
62
|
+
assert.equal(adaptiveInjectBudget("any", undefined), undefined, "非法上限原样返回,不猜");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("#239-5 默认关:开关未开时注入条数与查询无关(行为逐字节不变)", () => {
|
|
66
|
+
const { contexts, service } = injectorSetup();
|
|
67
|
+
seed(service);
|
|
68
|
+
assert.equal(injectedCount(contexts[0].text(ctxWithQuery("pnpm MODULE_NOT_FOUND 怎么修"))), 4);
|
|
69
|
+
assert.equal(injectedCount(contexts[0].text(ctxWithQuery("上次讨论的方案"))), 4);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("#239-5 开关打开:确定查询少注入,模糊查询保持上限", () => {
|
|
73
|
+
const { contexts, service } = injectorSetup({ injectUncertaintyAdaptive: true });
|
|
74
|
+
seed(service);
|
|
75
|
+
assert.equal(injectedCount(contexts[0].text(ctxWithQuery("pnpm MODULE_NOT_FOUND 怎么修"))), 2,
|
|
76
|
+
"确定 → 注入条数减半");
|
|
77
|
+
assert.equal(injectedCount(contexts[0].text(ctxWithQuery("上次讨论的方案"))), 4,
|
|
78
|
+
"模糊 → 维持 maxInjectedItems");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("#239-5 拿不到查询(无会话日志)时维持上限,不因开关而减少", () => {
|
|
82
|
+
const { contexts, service } = injectorSetup({ injectUncertaintyAdaptive: true });
|
|
83
|
+
seed(service);
|
|
84
|
+
assert.equal(injectedCount(contexts[0].text({})), 4, "退化路径不猜、不缩");
|
|
85
|
+
});
|
|
@@ -1,173 +1,173 @@
|
|
|
1
|
-
// 2026-10 供应链与代码安全审计的回归锁(宿主内 API 面)。
|
|
2
|
-
// 锁的是四件事,防的回归各写在各用例注释里:
|
|
3
|
-
// 1. /export 不再对任何调用方开放(审计 P0:配了 apiToken 也照旧全库导出)
|
|
4
|
-
// 2. /external-api 的 GET 与 PUT 同门(审计 P0:未鉴权 GET 会明文回吐 8790 的
|
|
5
|
-
// Bearer,而且顺手把 token 铸造进库——一条不需要凭证的写路径)
|
|
6
|
-
// 3. list/search 的分页参数有上界(免鉴权读面 + 浏览器简单请求 = 廉价 DoS)
|
|
7
|
-
// 4. readBody 不再无界累积(4 MiB 上限,超限走 400 而不是把内存交给调用方)
|
|
8
|
-
import test from "node:test";
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
|
-
import { EventEmitter } from "node:events";
|
|
11
|
-
import { createStore } from "../src/store.js";
|
|
12
|
-
import { createService } from "../src/service.js";
|
|
13
|
-
import { createApi } from "../src/api.js";
|
|
14
|
-
import { createSettings } from "../src/settings.js";
|
|
15
|
-
|
|
16
|
-
class FakeRes extends EventEmitter {
|
|
17
|
-
constructor() { super(); this.statusCode = 200; this.body = ""; }
|
|
18
|
-
writeHead(code, headers) { this.statusCode = code; this.headers = headers; return this; }
|
|
19
|
-
end(text) { this.body = text ?? ""; this.emit("end"); return this; }
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function req(path, method = "GET", body = null) {
|
|
23
|
-
const r = new EventEmitter();
|
|
24
|
-
r.url = path;
|
|
25
|
-
r.method = method;
|
|
26
|
-
r.headers = {};
|
|
27
|
-
if (body !== null) {
|
|
28
|
-
process.nextTick(() => {
|
|
29
|
-
r.emit("data", Buffer.from(JSON.stringify(body)));
|
|
30
|
-
r.emit("end");
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
return r;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function setup(apiToken = "") {
|
|
37
|
-
const store = createStore(":memory:");
|
|
38
|
-
const service = createService({ store, mirror: null, config: {} });
|
|
39
|
-
const settings = createSettings(store.db);
|
|
40
|
-
const commands = { add: () => {}, remove: () => {}, list: () => [] };
|
|
41
|
-
const routes = [];
|
|
42
|
-
const ctx = { webServer: { register(route) { routes.push(route); return () => {}; } } };
|
|
43
|
-
createApi(ctx, service, settings, commands, null, undefined, apiToken);
|
|
44
|
-
return { store, service, routes, settings };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function handler(routes, path) {
|
|
48
|
-
return routes.find((r) => r.path === path);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
test("/export 在配置 apiToken 后必须带 Bearer", async () => {
|
|
52
|
-
// 审计 P0:这条路由长期没有鉴权分支,即使配了 token 也把全库(含
|
|
53
|
-
// archived/forgotten 全字段)交给任何能打到端口的人。
|
|
54
|
-
const { routes, service } = setup("secret-token");
|
|
55
|
-
service.saveWithDedupe({ type: "preference", title: "甲", content: "内容甲" });
|
|
56
|
-
const route = handler(routes, "/api/dsh-mneme/export");
|
|
57
|
-
|
|
58
|
-
let res = new FakeRes();
|
|
59
|
-
await route.handler(req("/api/dsh-mneme/export"), res);
|
|
60
|
-
assert.equal(res.statusCode, 401, "无 token → 401");
|
|
61
|
-
assert.equal(JSON.parse(res.body).error, "unauthorized");
|
|
62
|
-
|
|
63
|
-
res = new FakeRes();
|
|
64
|
-
const ok = req("/api/dsh-mneme/export?format=json");
|
|
65
|
-
ok.headers = { authorization: "Bearer secret-token" };
|
|
66
|
-
await route.handler(ok, res);
|
|
67
|
-
assert.equal(res.statusCode, 200, "带对 token → 200");
|
|
68
|
-
assert.equal(JSON.parse(res.body).count, 1);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("/external-api 的 GET 与 PUT 同门,未鉴权不铸造 token", async () => {
|
|
72
|
-
// 两个回归一起锁:①未鉴权 GET 能把 8790 的 Bearer 明文读走;②那次 GET 还会
|
|
73
|
-
// 顺手 randomBytes 铸造并持久化一个 token——没有凭证的写路径。
|
|
74
|
-
const { routes, settings } = setup("secret-token");
|
|
75
|
-
const route = handler(routes, "/api/dsh-mneme/external-api");
|
|
76
|
-
|
|
77
|
-
const res = new FakeRes();
|
|
78
|
-
await route.handler(req("/api/dsh-mneme/external-api"), res);
|
|
79
|
-
assert.equal(res.statusCode, 401);
|
|
80
|
-
assert.equal(settings.getExternalApi()?.token, undefined, "未鉴权请求不得铸造 token");
|
|
81
|
-
|
|
82
|
-
const ok = req("/api/dsh-mneme/external-api");
|
|
83
|
-
ok.headers = { authorization: "Bearer secret-token" };
|
|
84
|
-
const res2 = new FakeRes();
|
|
85
|
-
await route.handler(ok, res2);
|
|
86
|
-
assert.equal(res2.statusCode, 200);
|
|
87
|
-
assert.ok(JSON.parse(res2.body).config.token.length >= 16, "鉴权后照旧发 token");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("list 的 limit 被上界钳制", async () => {
|
|
91
|
-
// 上界是防御:limit 直接进 SQL LIMIT,?limit=100000 会让一次免鉴权 GET 把
|
|
92
|
-
// 全库序列化出来(浏览器 <img> 就能触发)。
|
|
93
|
-
const { routes, service } = setup();
|
|
94
|
-
let seen = null;
|
|
95
|
-
const original = service.list;
|
|
96
|
-
service.list = (args) => { seen = args; return []; };
|
|
97
|
-
const res = new FakeRes();
|
|
98
|
-
await handler(routes, "/api/dsh-mneme/list").handler(req("/api/dsh-mneme/list?limit=100000"), res);
|
|
99
|
-
assert.equal(res.statusCode, 200);
|
|
100
|
-
assert.equal(seen.limit, 1000);
|
|
101
|
-
service.list = original;
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("search 的 topK 被上界钳制", async () => {
|
|
105
|
-
const { routes, service } = setup();
|
|
106
|
-
let seen = null;
|
|
107
|
-
const original = service.searchMemories;
|
|
108
|
-
service.searchMemories = (q, opts) => { seen = opts; return []; };
|
|
109
|
-
const res = new FakeRes();
|
|
110
|
-
await handler(routes, "/api/dsh-mneme/search").handler(req("/api/dsh-mneme/search?q=abc&topK=100000"), res);
|
|
111
|
-
assert.equal(res.statusCode, 200);
|
|
112
|
-
assert.equal(seen.topK, 200);
|
|
113
|
-
service.searchMemories = original;
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
function oversizedPost(path, method = "POST") {
|
|
117
|
-
const huge = Buffer.from("{\"content\":\"" + "x".repeat(5 * 1024 * 1024) + "\"}");
|
|
118
|
-
const r = new EventEmitter();
|
|
119
|
-
r.url = path;
|
|
120
|
-
r.method = method;
|
|
121
|
-
r.headers = {};
|
|
122
|
-
process.nextTick(() => { r.emit("data", huge); r.emit("end"); });
|
|
123
|
-
return r;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
test("超大请求体回 413,且不落库", async () => {
|
|
127
|
-
// 4 MiB 上限。断言必须是**明确的 413**:旧写法只断言 notEqual(200),而 /create
|
|
128
|
-
// 成功时回 201——一个真被写进去的超限请求也能让那条断言通过(评审指出的空断言)。
|
|
129
|
-
const { routes, service } = setup();
|
|
130
|
-
const before = service.all().length;
|
|
131
|
-
const res = new FakeRes();
|
|
132
|
-
await handler(routes, "/api/dsh-mneme/create").handler(oversizedPost("/api/dsh-mneme/create"), res);
|
|
133
|
-
assert.equal(res.statusCode, 413);
|
|
134
|
-
assert.equal(JSON.parse(res.body).error, "payload-too-large");
|
|
135
|
-
assert.equal(service.all().length, before, "超限请求不得留下任何记忆");
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("超限后又来 transport error,仍回 413 且不写库", async () => {
|
|
139
|
-
// readBody 的超限状态必须活过 error 分支:旧实现 error 时 resolve(""),
|
|
140
|
-
// 调用点只认 null,于是拿着空串继续走 → PUT /persona 把 "" 解析成 {},
|
|
141
|
-
// 覆盖既有设置。这条锁的就是「413 发出去了,但后面的写入还在跑」。
|
|
142
|
-
const { routes, settings } = setup("secret-token");
|
|
143
|
-
settings.setRules(["别丢"]);
|
|
144
|
-
const huge = Buffer.from("x".repeat(5 * 1024 * 1024));
|
|
145
|
-
const r = new EventEmitter();
|
|
146
|
-
r.url = "/api/dsh-mneme/rules";
|
|
147
|
-
r.method = "PUT";
|
|
148
|
-
r.headers = { authorization: "Bearer secret-token" };
|
|
149
|
-
const res = new FakeRes();
|
|
150
|
-
const done = handler(routes, "/api/dsh-mneme/rules").handler(r, res);
|
|
151
|
-
process.nextTick(() => { r.emit("data", huge); r.emit("error", new Error("boom")); });
|
|
152
|
-
await done;
|
|
153
|
-
assert.equal(res.statusCode, 413);
|
|
154
|
-
assert.deepEqual(settings.getRules(), ["别丢"], "error 分支不得让空体落库");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("超大请求体不会清空画像/规则", async () => {
|
|
158
|
-
// 这是最要命的一条:readBody 旧实现超限回空串 → parseBody 得到 {} →
|
|
159
|
-
// setProfile("") / setRules([]) 把既有内容静默清掉。413 必须在碰 store 之前。
|
|
160
|
-
const { routes, settings } = setup("secret-token");
|
|
161
|
-
settings.setProfile("原有画像");
|
|
162
|
-
settings.setRules(["原有规则"]);
|
|
163
|
-
|
|
164
|
-
for (const [path, method] of [["/api/dsh-mneme/profile", "PUT"], ["/api/dsh-mneme/rules", "PUT"]]) {
|
|
165
|
-
const res = new FakeRes();
|
|
166
|
-
const req = oversizedPost(path, method);
|
|
167
|
-
req.headers = { authorization: "Bearer secret-token" };
|
|
168
|
-
await handler(routes, path).handler(req, res);
|
|
169
|
-
assert.equal(res.statusCode, 413, path + " 超限应回 413");
|
|
170
|
-
}
|
|
171
|
-
assert.equal(settings.getProfile(), "原有画像", "超限请求不得清空画像");
|
|
172
|
-
assert.deepEqual(settings.getRules(), ["原有规则"], "超限请求不得清空规则");
|
|
173
|
-
});
|
|
1
|
+
// 2026-10 供应链与代码安全审计的回归锁(宿主内 API 面)。
|
|
2
|
+
// 锁的是四件事,防的回归各写在各用例注释里:
|
|
3
|
+
// 1. /export 不再对任何调用方开放(审计 P0:配了 apiToken 也照旧全库导出)
|
|
4
|
+
// 2. /external-api 的 GET 与 PUT 同门(审计 P0:未鉴权 GET 会明文回吐 8790 的
|
|
5
|
+
// Bearer,而且顺手把 token 铸造进库——一条不需要凭证的写路径)
|
|
6
|
+
// 3. list/search 的分页参数有上界(免鉴权读面 + 浏览器简单请求 = 廉价 DoS)
|
|
7
|
+
// 4. readBody 不再无界累积(4 MiB 上限,超限走 400 而不是把内存交给调用方)
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { EventEmitter } from "node:events";
|
|
11
|
+
import { createStore } from "../src/store.js";
|
|
12
|
+
import { createService } from "../src/service.js";
|
|
13
|
+
import { createApi } from "../src/api.js";
|
|
14
|
+
import { createSettings } from "../src/settings.js";
|
|
15
|
+
|
|
16
|
+
class FakeRes extends EventEmitter {
|
|
17
|
+
constructor() { super(); this.statusCode = 200; this.body = ""; }
|
|
18
|
+
writeHead(code, headers) { this.statusCode = code; this.headers = headers; return this; }
|
|
19
|
+
end(text) { this.body = text ?? ""; this.emit("end"); return this; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function req(path, method = "GET", body = null) {
|
|
23
|
+
const r = new EventEmitter();
|
|
24
|
+
r.url = path;
|
|
25
|
+
r.method = method;
|
|
26
|
+
r.headers = {};
|
|
27
|
+
if (body !== null) {
|
|
28
|
+
process.nextTick(() => {
|
|
29
|
+
r.emit("data", Buffer.from(JSON.stringify(body)));
|
|
30
|
+
r.emit("end");
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return r;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function setup(apiToken = "") {
|
|
37
|
+
const store = createStore(":memory:");
|
|
38
|
+
const service = createService({ store, mirror: null, config: {} });
|
|
39
|
+
const settings = createSettings(store.db);
|
|
40
|
+
const commands = { add: () => {}, remove: () => {}, list: () => [] };
|
|
41
|
+
const routes = [];
|
|
42
|
+
const ctx = { webServer: { register(route) { routes.push(route); return () => {}; } } };
|
|
43
|
+
createApi(ctx, service, settings, commands, null, undefined, apiToken);
|
|
44
|
+
return { store, service, routes, settings };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function handler(routes, path) {
|
|
48
|
+
return routes.find((r) => r.path === path);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test("/export 在配置 apiToken 后必须带 Bearer", async () => {
|
|
52
|
+
// 审计 P0:这条路由长期没有鉴权分支,即使配了 token 也把全库(含
|
|
53
|
+
// archived/forgotten 全字段)交给任何能打到端口的人。
|
|
54
|
+
const { routes, service } = setup("secret-token");
|
|
55
|
+
service.saveWithDedupe({ type: "preference", title: "甲", content: "内容甲" });
|
|
56
|
+
const route = handler(routes, "/api/dsh-mneme/export");
|
|
57
|
+
|
|
58
|
+
let res = new FakeRes();
|
|
59
|
+
await route.handler(req("/api/dsh-mneme/export"), res);
|
|
60
|
+
assert.equal(res.statusCode, 401, "无 token → 401");
|
|
61
|
+
assert.equal(JSON.parse(res.body).error, "unauthorized");
|
|
62
|
+
|
|
63
|
+
res = new FakeRes();
|
|
64
|
+
const ok = req("/api/dsh-mneme/export?format=json");
|
|
65
|
+
ok.headers = { authorization: "Bearer secret-token" };
|
|
66
|
+
await route.handler(ok, res);
|
|
67
|
+
assert.equal(res.statusCode, 200, "带对 token → 200");
|
|
68
|
+
assert.equal(JSON.parse(res.body).count, 1);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("/external-api 的 GET 与 PUT 同门,未鉴权不铸造 token", async () => {
|
|
72
|
+
// 两个回归一起锁:①未鉴权 GET 能把 8790 的 Bearer 明文读走;②那次 GET 还会
|
|
73
|
+
// 顺手 randomBytes 铸造并持久化一个 token——没有凭证的写路径。
|
|
74
|
+
const { routes, settings } = setup("secret-token");
|
|
75
|
+
const route = handler(routes, "/api/dsh-mneme/external-api");
|
|
76
|
+
|
|
77
|
+
const res = new FakeRes();
|
|
78
|
+
await route.handler(req("/api/dsh-mneme/external-api"), res);
|
|
79
|
+
assert.equal(res.statusCode, 401);
|
|
80
|
+
assert.equal(settings.getExternalApi()?.token, undefined, "未鉴权请求不得铸造 token");
|
|
81
|
+
|
|
82
|
+
const ok = req("/api/dsh-mneme/external-api");
|
|
83
|
+
ok.headers = { authorization: "Bearer secret-token" };
|
|
84
|
+
const res2 = new FakeRes();
|
|
85
|
+
await route.handler(ok, res2);
|
|
86
|
+
assert.equal(res2.statusCode, 200);
|
|
87
|
+
assert.ok(JSON.parse(res2.body).config.token.length >= 16, "鉴权后照旧发 token");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("list 的 limit 被上界钳制", async () => {
|
|
91
|
+
// 上界是防御:limit 直接进 SQL LIMIT,?limit=100000 会让一次免鉴权 GET 把
|
|
92
|
+
// 全库序列化出来(浏览器 <img> 就能触发)。
|
|
93
|
+
const { routes, service } = setup();
|
|
94
|
+
let seen = null;
|
|
95
|
+
const original = service.list;
|
|
96
|
+
service.list = (args) => { seen = args; return []; };
|
|
97
|
+
const res = new FakeRes();
|
|
98
|
+
await handler(routes, "/api/dsh-mneme/list").handler(req("/api/dsh-mneme/list?limit=100000"), res);
|
|
99
|
+
assert.equal(res.statusCode, 200);
|
|
100
|
+
assert.equal(seen.limit, 1000);
|
|
101
|
+
service.list = original;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("search 的 topK 被上界钳制", async () => {
|
|
105
|
+
const { routes, service } = setup();
|
|
106
|
+
let seen = null;
|
|
107
|
+
const original = service.searchMemories;
|
|
108
|
+
service.searchMemories = (q, opts) => { seen = opts; return []; };
|
|
109
|
+
const res = new FakeRes();
|
|
110
|
+
await handler(routes, "/api/dsh-mneme/search").handler(req("/api/dsh-mneme/search?q=abc&topK=100000"), res);
|
|
111
|
+
assert.equal(res.statusCode, 200);
|
|
112
|
+
assert.equal(seen.topK, 200);
|
|
113
|
+
service.searchMemories = original;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
function oversizedPost(path, method = "POST") {
|
|
117
|
+
const huge = Buffer.from("{\"content\":\"" + "x".repeat(5 * 1024 * 1024) + "\"}");
|
|
118
|
+
const r = new EventEmitter();
|
|
119
|
+
r.url = path;
|
|
120
|
+
r.method = method;
|
|
121
|
+
r.headers = {};
|
|
122
|
+
process.nextTick(() => { r.emit("data", huge); r.emit("end"); });
|
|
123
|
+
return r;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
test("超大请求体回 413,且不落库", async () => {
|
|
127
|
+
// 4 MiB 上限。断言必须是**明确的 413**:旧写法只断言 notEqual(200),而 /create
|
|
128
|
+
// 成功时回 201——一个真被写进去的超限请求也能让那条断言通过(评审指出的空断言)。
|
|
129
|
+
const { routes, service } = setup();
|
|
130
|
+
const before = service.all().length;
|
|
131
|
+
const res = new FakeRes();
|
|
132
|
+
await handler(routes, "/api/dsh-mneme/create").handler(oversizedPost("/api/dsh-mneme/create"), res);
|
|
133
|
+
assert.equal(res.statusCode, 413);
|
|
134
|
+
assert.equal(JSON.parse(res.body).error, "payload-too-large");
|
|
135
|
+
assert.equal(service.all().length, before, "超限请求不得留下任何记忆");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("超限后又来 transport error,仍回 413 且不写库", async () => {
|
|
139
|
+
// readBody 的超限状态必须活过 error 分支:旧实现 error 时 resolve(""),
|
|
140
|
+
// 调用点只认 null,于是拿着空串继续走 → PUT /persona 把 "" 解析成 {},
|
|
141
|
+
// 覆盖既有设置。这条锁的就是「413 发出去了,但后面的写入还在跑」。
|
|
142
|
+
const { routes, settings } = setup("secret-token");
|
|
143
|
+
settings.setRules(["别丢"]);
|
|
144
|
+
const huge = Buffer.from("x".repeat(5 * 1024 * 1024));
|
|
145
|
+
const r = new EventEmitter();
|
|
146
|
+
r.url = "/api/dsh-mneme/rules";
|
|
147
|
+
r.method = "PUT";
|
|
148
|
+
r.headers = { authorization: "Bearer secret-token" };
|
|
149
|
+
const res = new FakeRes();
|
|
150
|
+
const done = handler(routes, "/api/dsh-mneme/rules").handler(r, res);
|
|
151
|
+
process.nextTick(() => { r.emit("data", huge); r.emit("error", new Error("boom")); });
|
|
152
|
+
await done;
|
|
153
|
+
assert.equal(res.statusCode, 413);
|
|
154
|
+
assert.deepEqual(settings.getRules(), ["别丢"], "error 分支不得让空体落库");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("超大请求体不会清空画像/规则", async () => {
|
|
158
|
+
// 这是最要命的一条:readBody 旧实现超限回空串 → parseBody 得到 {} →
|
|
159
|
+
// setProfile("") / setRules([]) 把既有内容静默清掉。413 必须在碰 store 之前。
|
|
160
|
+
const { routes, settings } = setup("secret-token");
|
|
161
|
+
settings.setProfile("原有画像");
|
|
162
|
+
settings.setRules(["原有规则"]);
|
|
163
|
+
|
|
164
|
+
for (const [path, method] of [["/api/dsh-mneme/profile", "PUT"], ["/api/dsh-mneme/rules", "PUT"]]) {
|
|
165
|
+
const res = new FakeRes();
|
|
166
|
+
const req = oversizedPost(path, method);
|
|
167
|
+
req.headers = { authorization: "Bearer secret-token" };
|
|
168
|
+
await handler(routes, path).handler(req, res);
|
|
169
|
+
assert.equal(res.statusCode, 413, path + " 超限应回 413");
|
|
170
|
+
}
|
|
171
|
+
assert.equal(settings.getProfile(), "原有画像", "超限请求不得清空画像");
|
|
172
|
+
assert.deepEqual(settings.getRules(), ["原有规则"], "超限请求不得清空规则");
|
|
173
|
+
});
|