@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,233 +1,233 @@
|
|
|
1
|
-
// issue #386 第 6 条(面板 bootstrap 入口)与第 7 条(配置键覆盖面)的服务端契约。
|
|
2
|
-
//
|
|
3
|
-
// 这一层要钉住的不是「路由存在」,而是三件容易在改动里悄悄走样的事:
|
|
4
|
-
// 1. /bootstrap 的 policy 是 token(写库),且先判 body 形状、再判能力接线——
|
|
5
|
-
// 顺序反了会把「你没给目录」报成「能力没接线」,把人引去查错的排查方向。
|
|
6
|
-
// 2. /config-keys 只回键名与计数,一个值都不回。apiToken 的名字必须出现(那正是
|
|
7
|
-
// 用户想知道它存在的原因),值必须不出现(那会是一处新泄漏面)。
|
|
8
|
-
// 3. 三份清单是真算出来的:panelEditable = 有默认值的白名单键,
|
|
9
|
-
// fileOnly = schema 声明 − 白名单,undefaulted = 白名单 − 有默认值。
|
|
10
|
-
import test from "node:test";
|
|
11
|
-
import assert from "node:assert/strict";
|
|
12
|
-
import { EventEmitter } from "node:events";
|
|
13
|
-
import { createStore } from "../src/store.js";
|
|
14
|
-
import { createService } from "../src/service.js";
|
|
15
|
-
import { createApi } from "../src/api.js";
|
|
16
|
-
import { createSettings } from "../src/settings.js";
|
|
17
|
-
import { Config } from "../src/config.js";
|
|
18
|
-
import { describeConfigKeys, schemaLeafKeys, defaultedLeafKeys, flagKeysFromSpec } from "../src/config-keys.js";
|
|
19
|
-
import { bootstrapReply } from "../src/bootstrap-http.js";
|
|
20
|
-
|
|
21
|
-
class FakeRes extends EventEmitter {
|
|
22
|
-
constructor() { super(); this.statusCode = 200; this.body = ""; }
|
|
23
|
-
writeHead(code, headers) { this.statusCode = code; this.headers = headers; return this; }
|
|
24
|
-
end(text) { this.body = text ?? ""; this.emit("end"); return this; }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function req(path, { method = "GET", body = null } = {}) {
|
|
28
|
-
const r = new EventEmitter();
|
|
29
|
-
r.url = path;
|
|
30
|
-
r.method = method;
|
|
31
|
-
r.headers = {};
|
|
32
|
-
// readBody 是事件式的(data/end),不是 async iterable:必须真的把事件发出去,
|
|
33
|
-
// 否则 handler 会永远挂在那个 Promise 上(第一版测试就是这么挂死的)。
|
|
34
|
-
setImmediate(() => {
|
|
35
|
-
if (body !== null) r.emit("data", Buffer.from(body, "utf8"));
|
|
36
|
-
r.emit("end");
|
|
37
|
-
});
|
|
38
|
-
return r;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function setup({ config = {}, apiToken = "" } = {}) {
|
|
42
|
-
const store = createStore(":memory:");
|
|
43
|
-
const service = createService({ store, mirror: null, config });
|
|
44
|
-
const settings = createSettings(store.db);
|
|
45
|
-
const commands = { add: () => {}, remove: () => {}, list: () => [] };
|
|
46
|
-
const routes = [];
|
|
47
|
-
const ctx = { webServer: { register(route) { routes.push(route); return () => {}; } } };
|
|
48
|
-
createApi(ctx, service, settings, commands, null, undefined, apiToken, config);
|
|
49
|
-
return { store, service, settings, routes };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const BOOTSTRAP = "/api/dsh-mneme/bootstrap";
|
|
53
|
-
const CONFIG_KEYS = "/api/dsh-mneme/config-keys";
|
|
54
|
-
|
|
55
|
-
async function call(routes, path, opts) {
|
|
56
|
-
const route = routes.find((r) => r.path === path);
|
|
57
|
-
assert.ok(route, `route ${path} must be registered`);
|
|
58
|
-
const res = new FakeRes();
|
|
59
|
-
await route.handler(req(path, opts), res);
|
|
60
|
-
return { route, res, json: res.body ? JSON.parse(res.body) : null };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// --- /bootstrap -------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
test("bootstrap route is registered with the token policy", () => {
|
|
66
|
-
const { routes } = setup({ apiToken: "secret-token" });
|
|
67
|
-
const route = routes.find((r) => r.path === BOOTSTRAP);
|
|
68
|
-
assert.ok(route, "route must be registered");
|
|
69
|
-
// 它写库(虽然幂等),所以不能是 open;也不该借 token-write 的读写双义——这条
|
|
70
|
-
// 路由没有 GET 分支,声明 token-write 只会让 SECURITY.md 的开放读清单多一条假的。
|
|
71
|
-
assert.equal(route.policy, "token");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("bootstrap demands a token when one is configured", async () => {
|
|
75
|
-
const { routes } = setup({ apiToken: "secret-token" });
|
|
76
|
-
const { res } = await call(routes, BOOTSTRAP, { method: "POST", body: JSON.stringify({ dir: "/tmp" }) });
|
|
77
|
-
assert.equal(res.statusCode, 401);
|
|
78
|
-
assert.equal(JSON.parse(res.body).error, "unauthorized");
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("bootstrap rejects GET with 404 (no read branch exists)", async () => {
|
|
82
|
-
const { routes } = setup();
|
|
83
|
-
const { res } = await call(routes, BOOTSTRAP, { method: "GET" });
|
|
84
|
-
assert.equal(res.statusCode, 404);
|
|
85
|
-
assert.equal(JSON.parse(res.body).error, "not-found");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("bootstrap separates malformed JSON from a non-object body", async () => {
|
|
89
|
-
const { routes } = setup();
|
|
90
|
-
const bad = await call(routes, BOOTSTRAP, { method: "POST", body: "{not json" });
|
|
91
|
-
assert.equal(bad.res.statusCode, 400);
|
|
92
|
-
assert.equal(bad.json.error, "invalid-json");
|
|
93
|
-
// 数组是合法 JSON 但不是「对象」:两个码分开,调用方才知道该改哪里。
|
|
94
|
-
const arr = await call(routes, BOOTSTRAP, { method: "POST", body: "[]" });
|
|
95
|
-
assert.equal(arr.res.statusCode, 400);
|
|
96
|
-
assert.equal(arr.json.error, "invalid-body");
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("bootstrap validates dir before it decides whether the capability is wired", async () => {
|
|
100
|
-
// 这条是顺序锁:给一个没有 saveWithDedupe 的装配 + 缺 dir 的 body。若先判能力,
|
|
101
|
-
// 用户会看到「能力没接线」——而真正的问题是他没填目录。
|
|
102
|
-
const store = createStore(":memory:");
|
|
103
|
-
const settings = createSettings(store.db);
|
|
104
|
-
const routes = [];
|
|
105
|
-
const ctx = { webServer: { register(route) { routes.push(route); return () => {}; } } };
|
|
106
|
-
createApi(ctx, {}, settings, { add: () => {}, remove: () => {}, list: () => [] }, null, undefined, "", {});
|
|
107
|
-
const { res, json } = await call(routes, BOOTSTRAP, { method: "POST", body: JSON.stringify({}) });
|
|
108
|
-
assert.equal(res.statusCode, 400);
|
|
109
|
-
assert.equal(json.error, "missing-dir", "缺 dir 必须报 missing-dir,不能报 bootstrap-unavailable");
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test("bootstrap reports the coded error when the directory does not exist", async () => {
|
|
113
|
-
const { routes, service } = setup();
|
|
114
|
-
const { res, json } = await call(routes, BOOTSTRAP, {
|
|
115
|
-
method: "POST",
|
|
116
|
-
body: JSON.stringify({ dir: "/definitely/not/here/386-bootstrap" })
|
|
117
|
-
});
|
|
118
|
-
assert.equal(res.statusCode, 400);
|
|
119
|
-
assert.equal(json.error, "dir-not-found");
|
|
120
|
-
// 失败路径不能留下半截记忆:目录都没读到,一条都不该落库。
|
|
121
|
-
assert.equal(service.count({}), 0);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("bootstrapReply survives a logger that throws (the layer never throws)", async () => {
|
|
125
|
-
// 这一层的契约是「从不抛」:warn 裸调时,宿主 logger 自己坏掉会从 catch 块里再抛出去,
|
|
126
|
-
// 而两侧 handler 的外层 try 在 .then 回调里早已退栈——现场就是「一个字节都没回」
|
|
127
|
-
// (#410,share-http.js 的 warnSafely 注释记着同一件事)。守卫必须住在层里。
|
|
128
|
-
const reply = await bootstrapReply({
|
|
129
|
-
bootstrapper: () => Promise.reject(new Error("disk on fire")),
|
|
130
|
-
dir: "/tmp",
|
|
131
|
-
warn: () => { throw new Error("logger is broken too"); }
|
|
132
|
-
});
|
|
133
|
-
assert.equal(reply.status, 500);
|
|
134
|
-
assert.deepEqual(reply.body, { error: "internal" });
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("bootstrap imports deterministic artifacts from a real directory", async () => {
|
|
138
|
-
const { routes, service } = setup();
|
|
139
|
-
const os = await import("node:os");
|
|
140
|
-
const fs = await import("node:fs");
|
|
141
|
-
const path = await import("node:path");
|
|
142
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "mneme-386-"));
|
|
143
|
-
try {
|
|
144
|
-
fs.writeFileSync(path.join(dir, "README.md"), "# sample project\n\nA tiny fixture.\n", "utf8");
|
|
145
|
-
fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ name: "sample", scripts: { build: "tsc -p ." } }), "utf8");
|
|
146
|
-
const { res, json } = await call(routes, BOOTSTRAP, { method: "POST", body: JSON.stringify({ dir }) });
|
|
147
|
-
assert.equal(res.statusCode, 200);
|
|
148
|
-
assert.ok(json.created >= 2, `expected at least 2 imported rows, got ${JSON.stringify(json)}`);
|
|
149
|
-
assert.equal(json.dir, fs.realpathSync(dir));
|
|
150
|
-
// 「返回成功不等于生效」:去看库里到底有没有那几行。
|
|
151
|
-
const rows = service.list({ limit: 20, includeArchived: false });
|
|
152
|
-
assert.ok(rows.length >= 2, "the response must correspond to rows that actually exist");
|
|
153
|
-
assert.ok(rows.every((m) => (m.tags || []).includes("bootstrap")), "every imported row carries the bootstrap tag");
|
|
154
|
-
} finally {
|
|
155
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// --- /config-keys -----------------------------------------------------------
|
|
160
|
-
|
|
161
|
-
test("config-keys route is open and read-only", async () => {
|
|
162
|
-
const { routes } = setup({ apiToken: "secret-token" });
|
|
163
|
-
const { route, res } = await call(routes, CONFIG_KEYS, { method: "GET" });
|
|
164
|
-
assert.equal(route.policy, "open");
|
|
165
|
-
assert.equal(res.statusCode, 200, "read-only route must not demand a token");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("config-keys rejects non-GET", async () => {
|
|
169
|
-
const { routes } = setup();
|
|
170
|
-
const { res } = await call(routes, CONFIG_KEYS, { method: "POST", body: "{}" });
|
|
171
|
-
assert.equal(res.statusCode, 404);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test("config-keys returns key names but never the apiToken value", async () => {
|
|
175
|
-
const { routes } = setup({ config: { apiToken: "super-secret-value" }, apiToken: "super-secret-value" });
|
|
176
|
-
const { res } = await call(routes, CONFIG_KEYS, { method: "GET" });
|
|
177
|
-
assert.equal(res.statusCode, 200);
|
|
178
|
-
// 名字必须在:用户正是要靠这份清单知道「apiToken 只能改配置文件」。
|
|
179
|
-
assert.ok(res.body.includes("apiToken"), "the key name must be listed");
|
|
180
|
-
// 值必须不在:这条断言是这整条路由存在的安全边界。
|
|
181
|
-
assert.ok(!res.body.includes("super-secret-value"), "the value must never leave the server");
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test("config-keys only reads the schema — it never echoes a configured value", async () => {
|
|
185
|
-
// 上一条锁的是「没漏出去」,这条锁的是「不是靠脱敏才没漏」:即便装了一个
|
|
186
|
-
// 非敏感键的值,响应里也不该出现它——这份清单只谈键名与计数。
|
|
187
|
-
const { routes } = setup({ config: { memoryDir: "/unique/marker/path/386" } });
|
|
188
|
-
const { res } = await call(routes, CONFIG_KEYS, { method: "GET" });
|
|
189
|
-
assert.ok(!res.body.includes("/unique/marker/path/386"), "no configured value may appear in this response");
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test("config-keys partitions the two source lists without losing anything", async () => {
|
|
193
|
-
const d = describeConfigKeys();
|
|
194
|
-
const declared = schemaLeafKeys();
|
|
195
|
-
const defaulted = defaultedLeafKeys();
|
|
196
|
-
const flags = flagKeysFromSpec();
|
|
197
|
-
assert.equal(d.counts.schemaLeaf, declared.length);
|
|
198
|
-
assert.equal(d.counts.flag, flags.length);
|
|
199
|
-
// 并集必须正好铺满「schema 声明 ∪ 白名单」,一个键都不许漏在三个桶之外。
|
|
200
|
-
const union = new Set([...d.panelEditable, ...d.fileOnly, ...d.undefaulted]);
|
|
201
|
-
const expected = new Set([...declared, ...flags]);
|
|
202
|
-
assert.deepEqual([...union].sort(), [...expected].sort());
|
|
203
|
-
// 两个等式同时成立,才说明三个桶互不相交且覆盖完整。注意第一个等式要把
|
|
204
|
-
// undefaulted 也加进去:dreamProvider / dreamModel 既是 schema 声明的键、又挂在
|
|
205
|
-
// 白名单上,它既不属于 panelEditable(没默认值)也不属于 fileOnly(白名单有它),
|
|
206
|
-
// 只落在 undefaulted 这一个桶里。
|
|
207
|
-
assert.equal(d.panelEditable.length + d.fileOnly.length + d.undefaulted.length, declared.length);
|
|
208
|
-
assert.equal(d.panelEditable.length + d.undefaulted.length, flags.length);
|
|
209
|
-
// 白名单里每个键都必须在 schema 里声明过——否则那是登记了一个不存在的键。
|
|
210
|
-
for (const key of flags) assert.ok(expected.has(key), `flag ${key} is not declared in the schema`);
|
|
211
|
-
// 有默认值的键必须都被 schema 声明覆盖(反向包含,防 schema 漏声明)。
|
|
212
|
-
for (const key of defaulted) assert.ok(declared.includes(key), `defaulted key ${key} is missing from the schema declaration`);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("config-keys flags keys that the whitelist registers without a schema default", async () => {
|
|
216
|
-
const d = describeConfigKeys();
|
|
217
|
-
// dreamProvider / dreamModel 在 settings.js 白名单里,但 config.js 只有
|
|
218
|
-
// z.string().description(...) 没有 .default()。面板因此有个永远不进 effective 的
|
|
219
|
-
// 输入框——这正是 #386 第 7 条说的「不对称」,必须被告知而不是被悄悄抹平。
|
|
220
|
-
assert.deepEqual(d.undefaulted.sort(), ["dreamModel", "dreamProvider"]);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test("config-keys lists file-only keys that users actually hit (apiToken, memoryDir, maxInjectedItems)", async () => {
|
|
224
|
-
const d = describeConfigKeys();
|
|
225
|
-
// apiToken 是最要紧的一个:它没有默认值,所以「拿解析结果当 schema」的写法会把它
|
|
226
|
-
// 整个漏掉——第一版正是这么错的,这条用例是那个错的回归锁。
|
|
227
|
-
assert.ok(d.fileOnly.includes("apiToken"), "apiToken must be visible as file-only");
|
|
228
|
-
// 另外两个是 issue 原文点名的键:用户能理解、却在面板上找不到。
|
|
229
|
-
assert.ok(d.fileOnly.includes("memoryDir"));
|
|
230
|
-
assert.ok(d.fileOnly.includes("maxInjectedItems"));
|
|
231
|
-
// 反向:一个面板确实能改的键,绝不能被列进 fileOnly。
|
|
232
|
-
assert.ok(!d.fileOnly.includes("autoInject"));
|
|
233
|
-
});
|
|
1
|
+
// issue #386 第 6 条(面板 bootstrap 入口)与第 7 条(配置键覆盖面)的服务端契约。
|
|
2
|
+
//
|
|
3
|
+
// 这一层要钉住的不是「路由存在」,而是三件容易在改动里悄悄走样的事:
|
|
4
|
+
// 1. /bootstrap 的 policy 是 token(写库),且先判 body 形状、再判能力接线——
|
|
5
|
+
// 顺序反了会把「你没给目录」报成「能力没接线」,把人引去查错的排查方向。
|
|
6
|
+
// 2. /config-keys 只回键名与计数,一个值都不回。apiToken 的名字必须出现(那正是
|
|
7
|
+
// 用户想知道它存在的原因),值必须不出现(那会是一处新泄漏面)。
|
|
8
|
+
// 3. 三份清单是真算出来的:panelEditable = 有默认值的白名单键,
|
|
9
|
+
// fileOnly = schema 声明 − 白名单,undefaulted = 白名单 − 有默认值。
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { EventEmitter } from "node:events";
|
|
13
|
+
import { createStore } from "../src/store.js";
|
|
14
|
+
import { createService } from "../src/service.js";
|
|
15
|
+
import { createApi } from "../src/api.js";
|
|
16
|
+
import { createSettings } from "../src/settings.js";
|
|
17
|
+
import { Config } from "../src/config.js";
|
|
18
|
+
import { describeConfigKeys, schemaLeafKeys, defaultedLeafKeys, flagKeysFromSpec } from "../src/config-keys.js";
|
|
19
|
+
import { bootstrapReply } from "../src/bootstrap-http.js";
|
|
20
|
+
|
|
21
|
+
class FakeRes extends EventEmitter {
|
|
22
|
+
constructor() { super(); this.statusCode = 200; this.body = ""; }
|
|
23
|
+
writeHead(code, headers) { this.statusCode = code; this.headers = headers; return this; }
|
|
24
|
+
end(text) { this.body = text ?? ""; this.emit("end"); return this; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function req(path, { method = "GET", body = null } = {}) {
|
|
28
|
+
const r = new EventEmitter();
|
|
29
|
+
r.url = path;
|
|
30
|
+
r.method = method;
|
|
31
|
+
r.headers = {};
|
|
32
|
+
// readBody 是事件式的(data/end),不是 async iterable:必须真的把事件发出去,
|
|
33
|
+
// 否则 handler 会永远挂在那个 Promise 上(第一版测试就是这么挂死的)。
|
|
34
|
+
setImmediate(() => {
|
|
35
|
+
if (body !== null) r.emit("data", Buffer.from(body, "utf8"));
|
|
36
|
+
r.emit("end");
|
|
37
|
+
});
|
|
38
|
+
return r;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function setup({ config = {}, apiToken = "" } = {}) {
|
|
42
|
+
const store = createStore(":memory:");
|
|
43
|
+
const service = createService({ store, mirror: null, config });
|
|
44
|
+
const settings = createSettings(store.db);
|
|
45
|
+
const commands = { add: () => {}, remove: () => {}, list: () => [] };
|
|
46
|
+
const routes = [];
|
|
47
|
+
const ctx = { webServer: { register(route) { routes.push(route); return () => {}; } } };
|
|
48
|
+
createApi(ctx, service, settings, commands, null, undefined, apiToken, config);
|
|
49
|
+
return { store, service, settings, routes };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const BOOTSTRAP = "/api/dsh-mneme/bootstrap";
|
|
53
|
+
const CONFIG_KEYS = "/api/dsh-mneme/config-keys";
|
|
54
|
+
|
|
55
|
+
async function call(routes, path, opts) {
|
|
56
|
+
const route = routes.find((r) => r.path === path);
|
|
57
|
+
assert.ok(route, `route ${path} must be registered`);
|
|
58
|
+
const res = new FakeRes();
|
|
59
|
+
await route.handler(req(path, opts), res);
|
|
60
|
+
return { route, res, json: res.body ? JSON.parse(res.body) : null };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// --- /bootstrap -------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
test("bootstrap route is registered with the token policy", () => {
|
|
66
|
+
const { routes } = setup({ apiToken: "secret-token" });
|
|
67
|
+
const route = routes.find((r) => r.path === BOOTSTRAP);
|
|
68
|
+
assert.ok(route, "route must be registered");
|
|
69
|
+
// 它写库(虽然幂等),所以不能是 open;也不该借 token-write 的读写双义——这条
|
|
70
|
+
// 路由没有 GET 分支,声明 token-write 只会让 SECURITY.md 的开放读清单多一条假的。
|
|
71
|
+
assert.equal(route.policy, "token");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("bootstrap demands a token when one is configured", async () => {
|
|
75
|
+
const { routes } = setup({ apiToken: "secret-token" });
|
|
76
|
+
const { res } = await call(routes, BOOTSTRAP, { method: "POST", body: JSON.stringify({ dir: "/tmp" }) });
|
|
77
|
+
assert.equal(res.statusCode, 401);
|
|
78
|
+
assert.equal(JSON.parse(res.body).error, "unauthorized");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("bootstrap rejects GET with 404 (no read branch exists)", async () => {
|
|
82
|
+
const { routes } = setup();
|
|
83
|
+
const { res } = await call(routes, BOOTSTRAP, { method: "GET" });
|
|
84
|
+
assert.equal(res.statusCode, 404);
|
|
85
|
+
assert.equal(JSON.parse(res.body).error, "not-found");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("bootstrap separates malformed JSON from a non-object body", async () => {
|
|
89
|
+
const { routes } = setup();
|
|
90
|
+
const bad = await call(routes, BOOTSTRAP, { method: "POST", body: "{not json" });
|
|
91
|
+
assert.equal(bad.res.statusCode, 400);
|
|
92
|
+
assert.equal(bad.json.error, "invalid-json");
|
|
93
|
+
// 数组是合法 JSON 但不是「对象」:两个码分开,调用方才知道该改哪里。
|
|
94
|
+
const arr = await call(routes, BOOTSTRAP, { method: "POST", body: "[]" });
|
|
95
|
+
assert.equal(arr.res.statusCode, 400);
|
|
96
|
+
assert.equal(arr.json.error, "invalid-body");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("bootstrap validates dir before it decides whether the capability is wired", async () => {
|
|
100
|
+
// 这条是顺序锁:给一个没有 saveWithDedupe 的装配 + 缺 dir 的 body。若先判能力,
|
|
101
|
+
// 用户会看到「能力没接线」——而真正的问题是他没填目录。
|
|
102
|
+
const store = createStore(":memory:");
|
|
103
|
+
const settings = createSettings(store.db);
|
|
104
|
+
const routes = [];
|
|
105
|
+
const ctx = { webServer: { register(route) { routes.push(route); return () => {}; } } };
|
|
106
|
+
createApi(ctx, {}, settings, { add: () => {}, remove: () => {}, list: () => [] }, null, undefined, "", {});
|
|
107
|
+
const { res, json } = await call(routes, BOOTSTRAP, { method: "POST", body: JSON.stringify({}) });
|
|
108
|
+
assert.equal(res.statusCode, 400);
|
|
109
|
+
assert.equal(json.error, "missing-dir", "缺 dir 必须报 missing-dir,不能报 bootstrap-unavailable");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("bootstrap reports the coded error when the directory does not exist", async () => {
|
|
113
|
+
const { routes, service } = setup();
|
|
114
|
+
const { res, json } = await call(routes, BOOTSTRAP, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
body: JSON.stringify({ dir: "/definitely/not/here/386-bootstrap" })
|
|
117
|
+
});
|
|
118
|
+
assert.equal(res.statusCode, 400);
|
|
119
|
+
assert.equal(json.error, "dir-not-found");
|
|
120
|
+
// 失败路径不能留下半截记忆:目录都没读到,一条都不该落库。
|
|
121
|
+
assert.equal(service.count({}), 0);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("bootstrapReply survives a logger that throws (the layer never throws)", async () => {
|
|
125
|
+
// 这一层的契约是「从不抛」:warn 裸调时,宿主 logger 自己坏掉会从 catch 块里再抛出去,
|
|
126
|
+
// 而两侧 handler 的外层 try 在 .then 回调里早已退栈——现场就是「一个字节都没回」
|
|
127
|
+
// (#410,share-http.js 的 warnSafely 注释记着同一件事)。守卫必须住在层里。
|
|
128
|
+
const reply = await bootstrapReply({
|
|
129
|
+
bootstrapper: () => Promise.reject(new Error("disk on fire")),
|
|
130
|
+
dir: "/tmp",
|
|
131
|
+
warn: () => { throw new Error("logger is broken too"); }
|
|
132
|
+
});
|
|
133
|
+
assert.equal(reply.status, 500);
|
|
134
|
+
assert.deepEqual(reply.body, { error: "internal" });
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("bootstrap imports deterministic artifacts from a real directory", async () => {
|
|
138
|
+
const { routes, service } = setup();
|
|
139
|
+
const os = await import("node:os");
|
|
140
|
+
const fs = await import("node:fs");
|
|
141
|
+
const path = await import("node:path");
|
|
142
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "mneme-386-"));
|
|
143
|
+
try {
|
|
144
|
+
fs.writeFileSync(path.join(dir, "README.md"), "# sample project\n\nA tiny fixture.\n", "utf8");
|
|
145
|
+
fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ name: "sample", scripts: { build: "tsc -p ." } }), "utf8");
|
|
146
|
+
const { res, json } = await call(routes, BOOTSTRAP, { method: "POST", body: JSON.stringify({ dir }) });
|
|
147
|
+
assert.equal(res.statusCode, 200);
|
|
148
|
+
assert.ok(json.created >= 2, `expected at least 2 imported rows, got ${JSON.stringify(json)}`);
|
|
149
|
+
assert.equal(json.dir, fs.realpathSync(dir));
|
|
150
|
+
// 「返回成功不等于生效」:去看库里到底有没有那几行。
|
|
151
|
+
const rows = service.list({ limit: 20, includeArchived: false });
|
|
152
|
+
assert.ok(rows.length >= 2, "the response must correspond to rows that actually exist");
|
|
153
|
+
assert.ok(rows.every((m) => (m.tags || []).includes("bootstrap")), "every imported row carries the bootstrap tag");
|
|
154
|
+
} finally {
|
|
155
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// --- /config-keys -----------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
test("config-keys route is open and read-only", async () => {
|
|
162
|
+
const { routes } = setup({ apiToken: "secret-token" });
|
|
163
|
+
const { route, res } = await call(routes, CONFIG_KEYS, { method: "GET" });
|
|
164
|
+
assert.equal(route.policy, "open");
|
|
165
|
+
assert.equal(res.statusCode, 200, "read-only route must not demand a token");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("config-keys rejects non-GET", async () => {
|
|
169
|
+
const { routes } = setup();
|
|
170
|
+
const { res } = await call(routes, CONFIG_KEYS, { method: "POST", body: "{}" });
|
|
171
|
+
assert.equal(res.statusCode, 404);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("config-keys returns key names but never the apiToken value", async () => {
|
|
175
|
+
const { routes } = setup({ config: { apiToken: "super-secret-value" }, apiToken: "super-secret-value" });
|
|
176
|
+
const { res } = await call(routes, CONFIG_KEYS, { method: "GET" });
|
|
177
|
+
assert.equal(res.statusCode, 200);
|
|
178
|
+
// 名字必须在:用户正是要靠这份清单知道「apiToken 只能改配置文件」。
|
|
179
|
+
assert.ok(res.body.includes("apiToken"), "the key name must be listed");
|
|
180
|
+
// 值必须不在:这条断言是这整条路由存在的安全边界。
|
|
181
|
+
assert.ok(!res.body.includes("super-secret-value"), "the value must never leave the server");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("config-keys only reads the schema — it never echoes a configured value", async () => {
|
|
185
|
+
// 上一条锁的是「没漏出去」,这条锁的是「不是靠脱敏才没漏」:即便装了一个
|
|
186
|
+
// 非敏感键的值,响应里也不该出现它——这份清单只谈键名与计数。
|
|
187
|
+
const { routes } = setup({ config: { memoryDir: "/unique/marker/path/386" } });
|
|
188
|
+
const { res } = await call(routes, CONFIG_KEYS, { method: "GET" });
|
|
189
|
+
assert.ok(!res.body.includes("/unique/marker/path/386"), "no configured value may appear in this response");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("config-keys partitions the two source lists without losing anything", async () => {
|
|
193
|
+
const d = describeConfigKeys();
|
|
194
|
+
const declared = schemaLeafKeys();
|
|
195
|
+
const defaulted = defaultedLeafKeys();
|
|
196
|
+
const flags = flagKeysFromSpec();
|
|
197
|
+
assert.equal(d.counts.schemaLeaf, declared.length);
|
|
198
|
+
assert.equal(d.counts.flag, flags.length);
|
|
199
|
+
// 并集必须正好铺满「schema 声明 ∪ 白名单」,一个键都不许漏在三个桶之外。
|
|
200
|
+
const union = new Set([...d.panelEditable, ...d.fileOnly, ...d.undefaulted]);
|
|
201
|
+
const expected = new Set([...declared, ...flags]);
|
|
202
|
+
assert.deepEqual([...union].sort(), [...expected].sort());
|
|
203
|
+
// 两个等式同时成立,才说明三个桶互不相交且覆盖完整。注意第一个等式要把
|
|
204
|
+
// undefaulted 也加进去:dreamProvider / dreamModel 既是 schema 声明的键、又挂在
|
|
205
|
+
// 白名单上,它既不属于 panelEditable(没默认值)也不属于 fileOnly(白名单有它),
|
|
206
|
+
// 只落在 undefaulted 这一个桶里。
|
|
207
|
+
assert.equal(d.panelEditable.length + d.fileOnly.length + d.undefaulted.length, declared.length);
|
|
208
|
+
assert.equal(d.panelEditable.length + d.undefaulted.length, flags.length);
|
|
209
|
+
// 白名单里每个键都必须在 schema 里声明过——否则那是登记了一个不存在的键。
|
|
210
|
+
for (const key of flags) assert.ok(expected.has(key), `flag ${key} is not declared in the schema`);
|
|
211
|
+
// 有默认值的键必须都被 schema 声明覆盖(反向包含,防 schema 漏声明)。
|
|
212
|
+
for (const key of defaulted) assert.ok(declared.includes(key), `defaulted key ${key} is missing from the schema declaration`);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("config-keys flags keys that the whitelist registers without a schema default", async () => {
|
|
216
|
+
const d = describeConfigKeys();
|
|
217
|
+
// dreamProvider / dreamModel 在 settings.js 白名单里,但 config.js 只有
|
|
218
|
+
// z.string().description(...) 没有 .default()。面板因此有个永远不进 effective 的
|
|
219
|
+
// 输入框——这正是 #386 第 7 条说的「不对称」,必须被告知而不是被悄悄抹平。
|
|
220
|
+
assert.deepEqual(d.undefaulted.sort(), ["dreamModel", "dreamProvider"]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("config-keys lists file-only keys that users actually hit (apiToken, memoryDir, maxInjectedItems)", async () => {
|
|
224
|
+
const d = describeConfigKeys();
|
|
225
|
+
// apiToken 是最要紧的一个:它没有默认值,所以「拿解析结果当 schema」的写法会把它
|
|
226
|
+
// 整个漏掉——第一版正是这么错的,这条用例是那个错的回归锁。
|
|
227
|
+
assert.ok(d.fileOnly.includes("apiToken"), "apiToken must be visible as file-only");
|
|
228
|
+
// 另外两个是 issue 原文点名的键:用户能理解、却在面板上找不到。
|
|
229
|
+
assert.ok(d.fileOnly.includes("memoryDir"));
|
|
230
|
+
assert.ok(d.fileOnly.includes("maxInjectedItems"));
|
|
231
|
+
// 反向:一个面板确实能改的键,绝不能被列进 fileOnly。
|
|
232
|
+
assert.ok(!d.fileOnly.includes("autoInject"));
|
|
233
|
+
});
|