@sanqianx/project-knowledge 5.0.24 → 5.0.26
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/.agents/plugins/marketplace.json +20 -20
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +1361 -1353
- package/LICENSE +201 -201
- package/NOTICE +5 -5
- package/README.md +349 -349
- package/_modules/knowledge-engine/package.json +1 -1
- package/_modules/knowledge-engine/src/coordinator.js +48 -3
- package/_modules/knowledge-engine/src/runtime/automation-config.js +140 -140
- package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +238 -238
- package/_modules/knowledge-engine/src/runtime/commit-processing-ledger.js +26 -26
- package/_modules/knowledge-engine/src/runtime/commit-prompt.js +193 -193
- package/_modules/knowledge-engine/src/runtime/commit-reconciler.js +394 -394
- package/_modules/knowledge-engine/src/runtime/contracts.js +282 -282
- package/_modules/knowledge-engine/src/runtime/conversation-store.js +389 -389
- package/_modules/knowledge-engine/src/runtime/evidence-bundle.js +228 -228
- package/_modules/knowledge-engine/src/runtime/git-runner.js +62 -62
- package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +505 -505
- package/_modules/knowledge-engine/src/runtime/knowledge-retrieval-service.js +400 -400
- package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +171 -171
- package/_modules/knowledge-engine/src/runtime/scanner.js +298 -298
- package/_modules/knowledge-engine/ui/app.js +23 -2
- package/_site/README.md +30 -30
- package/_site/_test/agent-capture-mcp-ui-test.js +68 -68
- package/_site/_test/agent-capture-ui-test.js +101 -101
- package/_site/_test/ai-profile-resolver-test.js +137 -137
- package/_site/_test/ai-profile-test.js +75 -75
- package/_site/_test/ai-vendor-presets-test.js +91 -91
- package/_site/_test/automatic-context-test.js +70 -70
- package/_site/_test/automation-queue-test.js +14 -14
- package/_site/_test/automation-ui-test.js +40 -40
- package/_site/_test/background-task-registry-test.js +43 -43
- package/_site/_test/baseline-schema-test.js +91 -91
- package/_site/_test/bin-cli-test.js +230 -230
- package/_site/_test/bridge-adapter-test.js +95 -95
- package/_site/_test/bridge-consumer-service-test.js +310 -310
- package/_site/_test/candidate-bin-content-test.js +35 -35
- package/_site/_test/candidate-product-assets-test.js +13 -13
- package/_site/_test/candidate-release-contract-test.js +14 -14
- package/_site/_test/capture-hook-dedup-test.js +158 -158
- package/_site/_test/cdp-browser-failure-test.js +103 -103
- package/_site/_test/chat-claudecodeui-match-test.js +92 -92
- package/_site/_test/claude-executable-discovery-test.js +90 -90
- package/_site/_test/claude-session-lifecycle-sweep-test.js +252 -252
- package/_site/_test/claude-workbench-test.js +174 -174
- package/_site/_test/cli-instance-management-test.js +62 -62
- package/_site/_test/client-capture-restart-e2e-test.js +189 -189
- package/_site/_test/codex-conversation-projection-test.js +60 -60
- package/_site/_test/codex-marketplace-scoped-path-test.js +186 -186
- package/_site/_test/codex-notify-capture-e2e-test.js +101 -101
- package/_site/_test/codex-notify-fanout-test.js +141 -134
- package/_site/_test/codex-notify-upgrade-test.js +253 -253
- package/_site/_test/commit-boundary-freeze-test.js +147 -147
- package/_site/_test/commit-conversation-binding-test.js +99 -99
- package/_site/_test/commit-evidence-test.js +110 -110
- package/_site/_test/configuration-generation-test.js +22 -22
- package/_site/_test/context-pack-test.js +200 -200
- package/_site/_test/conversation-api-test.js +119 -119
- package/_site/_test/conversation-origin-test.js +15 -15
- package/_site/_test/conversation-store-test.js +138 -138
- package/_site/_test/cross-repo-isolation-e2e-test.js +187 -187
- package/_site/_test/desktop-browser-compat-test.js +28 -28
- package/_site/_test/desktop-hook-runtime-regression-test.js +204 -204
- package/_site/_test/embedding-config-test.js +32 -32
- package/_site/_test/explicit-commit-processor-test.js +37 -37
- package/_site/_test/fixtures/fake-browser-exit.js +10 -10
- package/_site/_test/fixtures/fake-browser-hang.js +10 -10
- package/_site/_test/fixtures/integration-busy-worker.cjs +6 -6
- package/_site/_test/fixtures/make-git-repos.js +92 -92
- package/_site/_test/fixtures/retrieval-gold-queries.json +32 -32
- package/_site/_test/folder-picker-output-test.js +19 -19
- package/_site/_test/fresh-hook-reimport-test.js +53 -53
- package/_site/_test/fresh-product-data-test.js +56 -56
- package/_site/_test/full-integration-e2e-test.js +167 -167
- package/_site/_test/git-path-compatibility-test.js +80 -80
- package/_site/_test/git-validation-test.js +81 -81
- package/_site/_test/glm-ui-refresh-review-test.js +313 -404
- package/_site/_test/harness-isolation-test.js +138 -0
- package/_site/_test/helpers/cdp-browser.js +383 -0
- package/_site/_test/helpers/cdp-network-capture.js +66 -0
- package/_site/_test/helpers/find-chrome.js +34 -0
- package/_site/_test/helpers/free-port.js +8 -0
- package/_site/_test/helpers/logging-ui-fixture.js +131 -0
- package/_site/_test/helpers/module-fixture.js +282 -0
- package/_site/_test/helpers/parallel-engine-advanced-engine-child.js +45 -0
- package/_site/_test/helpers/parallel-engine-advanced-fault-writer.js +171 -0
- package/_site/_test/helpers/parallel-engine-advanced-harness.js +245 -0
- package/_site/_test/helpers/parallel-engine-advanced-standin-analyzer.js +80 -0
- package/_site/_test/helpers/parallel-engine-advanced-ui-host.js +177 -0
- package/_site/_test/helpers/parallel-engine-final-harness.js +314 -0
- package/_site/_test/helpers/parallel-engine-final-standin-analyzer.js +159 -0
- package/_site/_test/helpers/parallel-knowledge-fixture.js +255 -0
- package/_site/_test/helpers/parallel-logging-acceptance-fixture.js +111 -0
- package/_site/_test/helpers/parallel-logging-acceptance-proxy.js +106 -0
- package/_site/_test/helpers/parallel-logging-fix-r2-proxy.js +205 -0
- package/_site/_test/helpers/parallel-logging-integration-5025-env.js +432 -0
- package/_site/_test/helpers/parallel-memory-analyzer.js +81 -0
- package/_site/_test/helpers/parallel-memory-event-bridge.js +128 -0
- package/_site/_test/helpers/parallel-memory-shell.js +470 -0
- package/_site/_test/helpers/parallel-nav-isolation-env.js +389 -0
- package/_site/_test/helpers/parallel-nav-module-ui.js +186 -0
- package/_site/_test/helpers/parallel-nav-proxy.js +148 -0
- package/_site/_test/helpers/parallel-nav-shell.js +361 -0
- package/_site/_test/helpers/parallel-qa-engine-endpoint-guard.js +206 -0
- package/_site/_test/helpers/parallel-qa-engine-endpoint-isolate.js +257 -0
- package/_site/_test/helpers/parallel-retrieval-followup-corpus.js +293 -0
- package/_site/_test/helpers/parallel-retrieval-followup-next-session.js +70 -0
- package/_site/_test/helpers/parallel-retrieval-followup-r2-shell.js +737 -0
- package/_site/_test/helpers/parallel-retrieval-followup-r2-wb-host.js +82 -0
- package/_site/_test/helpers/parallel-retrieval-followup-shell.js +279 -0
- package/_site/_test/helpers/parallel-workbench-fixture.js +371 -0
- package/_site/_test/helpers/parallel-workbench-host.js +80 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-fixture.js +408 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-harness.js +593 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-host.js +70 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-launcher.js +240 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-runtime-harness.js +630 -0
- package/_site/_test/helpers/parallel-workbench-source-map.js +196 -0
- package/_site/_test/helpers/spawn-server.js +106 -0
- package/_site/_test/helpers/version-visibility-fixture.js +251 -0
- package/_site/_test/hook-runtime-endpoint-test.js +88 -88
- package/_site/_test/hook-status-repair-api-test.js +152 -152
- package/_site/_test/hook-trigger-test.js +85 -85
- package/_site/_test/hook-worktree-test.js +42 -42
- package/_site/_test/import-preflight-api-test.js +234 -234
- package/_site/_test/index-builder-test.js +41 -41
- package/_site/_test/index-error-display-contract-test.js +10 -10
- package/_site/_test/index-writer-concurrency-test.js +135 -135
- package/_site/_test/integration-adapters-test.js +262 -262
- package/_site/_test/integration-auto-repair-test.js +113 -113
- package/_site/_test/integration-capture-setup-test.js +118 -118
- package/_site/_test/integration-intent-test.js +81 -81
- package/_site/_test/integration-runtime-binding-a2-test.js +566 -566
- package/_site/_test/integration-runtime-binding-a3-test.js +227 -227
- package/_site/_test/integration-runtime-binding-test.js +473 -473
- package/_site/_test/integration-status-scan-test.js +29 -29
- package/_site/_test/integration-surface-coverage-test.js +164 -164
- package/_site/_test/kb-framework-test.js +57 -57
- package/_site/_test/kbpath-follow-test.js +63 -63
- package/_site/_test/knowledge-db-test.js +144 -144
- package/_site/_test/knowledge-engine-client-test.js +120 -109
- package/_site/_test/knowledge-language-control-test.js +170 -170
- package/_site/_test/knowledge-maintenance-test.js +40 -40
- package/_site/_test/knowledge-promotion-recovery-test.js +260 -260
- package/_site/_test/knowledge-query-test.js +54 -54
- package/_site/_test/knowledge-retrieval-service-test.js +77 -77
- package/_site/_test/knowledge-scopes-test.js +37 -37
- package/_site/_test/knowledge-storage-location-test.js +28 -28
- package/_site/_test/knowledge-storage-startup-test.js +52 -52
- package/_site/_test/knowledge-store-logs-supervision-test.js +89 -89
- package/_site/_test/launcher-readiness-test.js +80 -80
- package/_site/_test/legacy-claude-session-alias-test.js +106 -106
- package/_site/_test/legacy-routes-removed-test.js +47 -47
- package/_site/_test/legacy-workbench-exclusion-test.js +92 -92
- package/_site/_test/live-gateway-test.js +48 -48
- package/_site/_test/local-marketplace-test.js +16 -16
- package/_site/_test/log-redaction-test.js +58 -58
- package/_site/_test/logging-api-test.js +89 -89
- package/_site/_test/logging-sse-no-gap-test.js +88 -88
- package/_site/_test/logging-ui-test.js +107 -107
- package/_site/_test/markdown-delta-overlay-test.js +85 -85
- package/_site/_test/markdown-maintenance-api-test.js +75 -75
- package/_site/_test/markdown-maintenance-test.js +11 -11
- package/_site/_test/mcp-server-test.js +149 -149
- package/_site/_test/model-context-windows-test.js +89 -89
- package/_site/_test/module-agent-start-proxy-test.js +65 -65
- package/_site/_test/module-artifact-integrity-test.js +27 -27
- package/_site/_test/module-boundary-test.js +30 -30
- package/_site/_test/module-bridge-eventbridge-test.js +202 -202
- package/_site/_test/module-dynamic-port-test.js +118 -118
- package/_site/_test/module-index-proxy-test.js +33 -33
- package/_site/_test/module-model-configuration-test.js +44 -44
- package/_site/_test/module-process-lifecycle-test.js +35 -35
- package/_site/_test/module-stream-proxy-test.js +34 -34
- package/_site/_test/module-terminal-restart-test.js +63 -63
- package/_site/_test/module-wiring-unbind-race-test.js +59 -59
- package/_site/_test/native-context-integrations-test.js +86 -86
- package/_site/_test/non-release-ci-test.js +34 -34
- package/_site/_test/offline-boundary-isolation-test.js +32 -32
- package/_site/_test/orphaned-claim-recovery-test.js +80 -80
- package/_site/_test/package-boundary-test.js +43 -43
- package/_site/_test/package-startup-test.js +56 -56
- package/_site/_test/packaged-ui-smoke-test.js +88 -88
- package/_site/_test/parallel-ci-r9-posix-socket-budget-test.js +156 -0
- package/_site/_test/parallel-engine-advanced-eng005-test.js +136 -0
- package/_site/_test/parallel-engine-advanced-eng006-test.js +171 -0
- package/_site/_test/parallel-engine-advanced-eng007-reconcile-test.js +231 -0
- package/_site/_test/parallel-engine-advanced-eng009-test.js +208 -0
- package/_site/_test/parallel-engine-advanced-eng010-test.js +213 -0
- package/_site/_test/parallel-engine-advanced-eng011-ui-probe.js +459 -0
- package/_site/_test/parallel-engine-final-eng002-test.js +255 -0
- package/_site/_test/parallel-engine-final-eng007-test.js +269 -0
- package/_site/_test/parallel-engine-final-eng011-test.js +197 -0
- package/_site/_test/parallel-engine-final-eng012-test.js +354 -0
- package/_site/_test/parallel-knowledge-client-error-code-test.js +184 -0
- package/_site/_test/parallel-knowledge-engine-test.js +353 -0
- package/_site/_test/parallel-knowledge-retrieval-test.js +292 -0
- package/_site/_test/parallel-knowledge-vector-test.js +168 -0
- package/_site/_test/parallel-linux-ci-r7-log007-day-test.js +110 -0
- package/_site/_test/parallel-logging-acceptance-log003-test.js +224 -0
- package/_site/_test/parallel-logging-acceptance-log004-test.js +231 -0
- package/_site/_test/parallel-logging-acceptance-log005-test.js +208 -0
- package/_site/_test/parallel-logging-acceptance-log006-test.js +214 -0
- package/_site/_test/parallel-logging-acceptance-log007-test.js +223 -0
- package/_site/_test/parallel-logging-acceptance-log008-test.js +180 -0
- package/_site/_test/parallel-logging-fix-r2-crash-probe.js +271 -0
- package/_site/_test/parallel-logging-fix-r2-gap-test.js +294 -0
- package/_site/_test/parallel-logging-fix-r2-normalhook-test.js +330 -0
- package/_site/_test/parallel-logging-integration-5025-portability-test.js +554 -0
- package/_site/_test/parallel-memory-candidate-test.js +301 -0
- package/_site/_test/parallel-memory-manual-test.js +197 -0
- package/_site/_test/parallel-memory-scope-test.js +228 -0
- package/_site/_test/parallel-memory-source-test.js +227 -0
- package/_site/_test/parallel-memory-sync-test.js +238 -0
- package/_site/_test/parallel-memory-view-r8-reload-gate-test.js +123 -0
- package/_site/_test/parallel-nav-failure-test.js +119 -0
- package/_site/_test/parallel-nav-isolation-test.js +308 -0
- package/_site/_test/parallel-nav-isolation-vector-path-test.js +90 -0
- package/_site/_test/parallel-nav-offline-test.js +272 -0
- package/_site/_test/parallel-nav-operability-test.js +257 -0
- package/_site/_test/parallel-nav-switch-test.js +247 -0
- package/_site/_test/parallel-nav-theme-test.js +130 -0
- package/_site/_test/parallel-nav-viewport-test.js +310 -0
- package/_site/_test/parallel-qa-engine-endpoint-guard-test.js +273 -0
- package/_site/_test/parallel-retrieval-followup-citation-test.js +219 -0
- package/_site/_test/parallel-retrieval-followup-corpus-test.js +287 -0
- package/_site/_test/parallel-workbench-independent-host.js +262 -0
- package/_site/_test/parallel-workbench-integration-5025-abort-probe.js +340 -0
- package/_site/_test/parallel-workbench-integration-5025-drafts-probe.js +588 -0
- package/_site/_test/parallel-workbench-integration-5025-env-check.js +317 -0
- package/_site/_test/parallel-workbench-integration-5025-model-probe.js +342 -0
- package/_site/_test/parallel-workbench-integration-5025-ordering-probe.js +326 -0
- package/_site/_test/parallel-workbench-model-probe.js +336 -0
- package/_site/_test/parallel-workbench-offline-test.js +169 -0
- package/_site/_test/parallel-workbench-provenance-trace.js +134 -0
- package/_site/_test/parallel-workbench-source-match.js +206 -0
- package/_site/_test/parallel-workbench-standalone-turn.js +349 -0
- package/_site/_test/path-consistency-test.js +151 -151
- package/_site/_test/pending-sweep-test.js +9 -9
- package/_site/_test/post-commit-automation-test.js +87 -87
- package/_site/_test/pr-consumer-contract-test.js +55 -55
- package/_site/_test/product-brand-contract-test.js +25 -25
- package/_site/_test/product-diagnostics-ui-test.js +93 -93
- package/_site/_test/product-empty-state-test.js +148 -0
- package/_site/_test/product-import-ui-test.js +219 -104
- package/_site/_test/product-navigation-test.js +152 -0
- package/_site/_test/product-progress-status-test.js +84 -84
- package/_site/_test/project-delete-recovery-test.js +64 -64
- package/_site/_test/project-domain-create-test.js +91 -0
- package/_site/_test/project-domain-scope-retrieval-test.js +214 -0
- package/_site/_test/project-git-init-confirm-test.js +115 -0
- package/_site/_test/project-goal-editor-test.js +144 -144
- package/_site/_test/project-import-duplicate-alias-test.js +134 -0
- package/_site/_test/project-import-unicode-restart-test.js +140 -0
- package/_site/_test/project-lifecycle-transaction-test.js +106 -106
- package/_site/_test/project-module-partial-recovery-test.js +222 -0
- package/_site/_test/project-remove-running-guard-test.js +79 -79
- package/_site/_test/project-store-test.js +90 -90
- package/_site/_test/project-unbind-late-read-ui-test.js +250 -0
- package/_site/_test/project-unbind-writer-guard-test.js +231 -0
- package/_site/_test/prompt-settings-test.js +115 -115
- package/_site/_test/protected-architecture-gate-test.js +122 -122
- package/_site/_test/refactor-characterization-test.js +36 -36
- package/_site/_test/registry-events-test.js +38 -38
- package/_site/_test/release-version-sync-test.js +115 -115
- package/_site/_test/requirement-binding-test.js +113 -113
- package/_site/_test/requirement-recorder-test.js +173 -173
- package/_site/_test/run-all-tests.js +173 -161
- package/_site/_test/runtime-endpoint-test.js +64 -64
- package/_site/_test/scanner-test.js +83 -83
- package/_site/_test/server-security-test.js +96 -96
- package/_site/_test/sessions-stream-test.js +93 -93
- package/_site/_test/shared-contracts-test.js +58 -58
- package/_site/_test/shared-scope-root-binding-test.js +74 -67
- package/_site/_test/simple-import-test.js +98 -98
- package/_site/_test/startup-analysis-disabled-test.js +16 -16
- package/_site/_test/storage-foundation-test.js +78 -78
- package/_site/_test/structured-logger-test.js +101 -101
- package/_site/_test/t07b-cln-005-coverage-test.js +212 -0
- package/_site/_test/team-knowledge-store-sparse-checkout-test.js +196 -196
- package/_site/_test/team-knowledge-store-test.js +132 -132
- package/_site/_test/test-server-cleanup-test.js +35 -35
- package/_site/_test/test-server-module-isolation-test.js +37 -37
- package/_site/_test/tracking-start-test.js +144 -144
- package/_site/_test/vector-storage-root-test.js +75 -75
- package/_site/_test/version-visibility-browser-probe.js +249 -0
- package/_site/_test/version-visibility-test.js +92 -0
- package/_site/_test/workbench-context-receipts-test.js +22 -22
- package/_site/_test/workbench-permission-test.js +117 -117
- package/_site/_test/zcode-capture-integration-test.js +112 -112
- package/_site/lib/ai-profile-resolver.js +78 -78
- package/_site/lib/ai-vendor-presets.js +83 -83
- package/_site/lib/ai-workspace.js +104 -104
- package/_site/lib/atomic-file.js +204 -204
- package/_site/lib/automation-config.js +140 -140
- package/_site/lib/bridge-adapter.js +260 -260
- package/_site/lib/bridge-consumer-service.js +402 -402
- package/_site/lib/capture-hook-config.js +108 -108
- package/_site/lib/claude-cli-runner.js +1599 -1599
- package/_site/lib/cli-instances.js +43 -43
- package/_site/lib/codex-context-status.js +17 -17
- package/_site/lib/codex-notify-config.js +193 -193
- package/_site/lib/commit-conversation-binder.js +247 -247
- package/_site/lib/commit-processing-ledger.js +27 -27
- package/_site/lib/commit-prompt.js +202 -202
- package/_site/lib/commit-reconciler.js +444 -444
- package/_site/lib/context-pack-builder.js +299 -299
- package/_site/lib/contracts.js +291 -291
- package/_site/lib/conversation-exclusions.js +204 -204
- package/_site/lib/conversation-query-service.js +161 -161
- package/_site/lib/conversation-store.js +387 -387
- package/_site/lib/data-dir.js +13 -13
- package/_site/lib/embedding-service.js +85 -85
- package/_site/lib/engine-connection.js +93 -93
- package/_site/lib/engine-knowledge-client.js +76 -72
- package/_site/lib/evidence-bundle.js +228 -228
- package/_site/lib/folder-picker-output.js +24 -24
- package/_site/lib/git-path.js +14 -14
- package/_site/lib/git-runner.js +62 -62
- package/_site/lib/github-team-store.js +1175 -1175
- package/_site/lib/hook-manager.js +203 -203
- package/_site/lib/index-builder.js +157 -157
- package/_site/lib/index-service.js +173 -173
- package/_site/lib/integration-auto-repair.js +77 -77
- package/_site/lib/integration-installer.js +1240 -1240
- package/_site/lib/integration-intent.js +88 -88
- package/_site/lib/integration-status-scan.js +99 -99
- package/_site/lib/integration-status-worker.js +14 -14
- package/_site/lib/kb-framework.js +191 -191
- package/_site/lib/kb-validator.js +124 -124
- package/_site/lib/knowledge-db.js +363 -363
- package/_site/lib/knowledge-promotion.js +435 -435
- package/_site/lib/knowledge-retrieval-service.js +401 -401
- package/_site/lib/knowledge-schema.js +121 -121
- package/_site/lib/knowledge-tool-runtime.js +366 -366
- package/_site/lib/live-gateway.js +78 -78
- package/_site/lib/llm-client.js +173 -173
- package/_site/lib/markdown-knowledge-indexer.js +230 -230
- package/_site/lib/model-context-windows.js +82 -82
- package/_site/lib/module-bridge.js +874 -874
- package/_site/lib/module-port-resolver.js +57 -57
- package/_site/lib/post-commit-automation.js +141 -141
- package/_site/lib/product-data.js +35 -35
- package/_site/lib/project-lifecycle-service.js +554 -554
- package/_site/lib/project-registry-store.js +129 -129
- package/_site/lib/project-store.js +268 -268
- package/_site/lib/prompt-registry.js +148 -148
- package/_site/lib/public-version-info.js +37 -0
- package/_site/lib/registry-events.js +16 -16
- package/_site/lib/requirement-adapters.js +10 -10
- package/_site/lib/requirement-binder.js +112 -112
- package/_site/lib/requirement-recorder.js +278 -278
- package/_site/lib/runtime-control.js +89 -89
- package/_site/lib/runtime-endpoint.js +160 -160
- package/_site/lib/scanner.js +298 -298
- package/_site/lib/server-app.js +1672 -1664
- package/_site/lib/settings-store.js +153 -153
- package/_site/lib/storage-layout.js +166 -166
- package/_site/lib/structured-logger.js +574 -574
- package/_site/lib/zcode-context-integration.js +60 -60
- package/_site/scripts/codex-notify-fanout.cjs +95 -95
- package/_site/scripts/folder-picker.ps1 +156 -156
- package/_site/scripts/hook-trigger.js +158 -158
- package/_site/scripts/install-module-candidates.js +69 -69
- package/_site/scripts/pack-module-candidates.js +89 -89
- package/_site/scripts/sync-product-content.js +28 -28
- package/_site/scripts/sync-release-version.js +164 -164
- package/_site/scripts/vendor-modules.js +49 -49
- package/_site/scripts/verify-module-runtime.js +50 -50
- package/_site/scripts/verify-product-runtime.js +36 -36
- package/_site/scripts/verify-retrieval-gold.js +83 -83
- package/_site/server.js +15 -15
- package/_site/start.bat +26 -26
- package/_site/stop.bat +11 -11
- package/bin/project-knowledge-integrations.js +254 -254
- package/bin/project-knowledge-kb.js +67 -67
- package/bin/project-knowledge-mcp.js +201 -201
- package/bin/project-knowledge.js +269 -170
- package/claude-prompts.json +21 -21
- package/docs/README.zh-CN.md +207 -205
- package/docs/assets/architecture.svg +181 -181
- package/docs/assets/logo.svg +49 -49
- package/docs/fresh-v5-start.md +44 -44
- package/docs/leader-glm-workbench-playbook.md +122 -122
- package/docs/project-registry-schema.md +33 -33
- package/docs/release-5.0.16-acceptance.md +176 -176
- package/docs/release-5.0.17-acceptance.md +117 -117
- package/docs/release-5.0.19-acceptance.md +39 -39
- package/docs/team-knowledge-mode-a-plan.md +73 -73
- package/docs/testing-strategy.md +33 -33
- package/module-runtime-manifest.json +68 -65
- package/package.json +113 -110
- package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.mcp.json +1 -1
- package/templates/change.md +30 -30
- package/templates/project-readme.md +52 -52
- package/ui/index.html +3 -2
- package/ui/version-info.css +44 -0
- package/ui/version-info.js +24 -0
- package/vendor-manifest.json +55 -55
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Parallel knowledge lane — retrieval cases RET-001, RET-003, RET-004, RET-005.
|
|
3
|
+
// Every assertion runs against real Markdown on disk and the real vendored
|
|
4
|
+
// knowledge-engine over real loopback HTTP. The vector endpoint is an
|
|
5
|
+
// explicitly declared stub that returns no results, so nothing in this file
|
|
6
|
+
// may be counted as vector-channel evidence.
|
|
7
|
+
const assert = require('node:assert/strict');
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const http = require('node:http');
|
|
11
|
+
const crypto = require('node:crypto');
|
|
12
|
+
const fixture = require('./helpers/parallel-knowledge-fixture');
|
|
13
|
+
// The entry point isolates itself: ambient module addresses, live data
|
|
14
|
+
// directories and the real HOME are cleared here, before any product module
|
|
15
|
+
// is loaded, so the test never depends on the caller running `env -u`.
|
|
16
|
+
fixture.isolateProcess();
|
|
17
|
+
const { StorageLayout } = require('../lib/storage-layout');
|
|
18
|
+
const { ProjectStore } = require('../lib/project-store');
|
|
19
|
+
const { ProjectRegistryStore } = require('../lib/project-registry-store');
|
|
20
|
+
const { KnowledgeRetrievalService } = require('../lib/knowledge-retrieval-service');
|
|
21
|
+
const { EngineKnowledgeClient } = require('../lib/engine-knowledge-client');
|
|
22
|
+
const { CHUNKER_VERSION } = require('../lib/markdown-knowledge-indexer');
|
|
23
|
+
const { sha256 } = require('../lib/knowledge-schema');
|
|
24
|
+
const { createEngineServer } = require('../../_modules/knowledge-engine/src/server');
|
|
25
|
+
const AtomicFile = require('../../_modules/knowledge-engine/src/runtime/atomic-file');
|
|
26
|
+
|
|
27
|
+
const results = [];
|
|
28
|
+
function record(caseId, status, detail) {
|
|
29
|
+
results.push({ caseId, status, detail });
|
|
30
|
+
console.log(`CASE ${caseId} ${status} ${JSON.stringify(detail)}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function stubVectorServer() {
|
|
34
|
+
const requests = [];
|
|
35
|
+
const server = http.createServer((req, res) => {
|
|
36
|
+
let body = '';
|
|
37
|
+
req.on('data', chunk => { body += chunk; });
|
|
38
|
+
req.on('end', () => {
|
|
39
|
+
requests.push({ url: req.url, method: req.method, body: body || null });
|
|
40
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
41
|
+
res.end(JSON.stringify({ ok: true, results: [], errors: [], projects: [] }));
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
45
|
+
return { requests, url: `http://127.0.0.1:${server.address().port}`, close: () => { server.closeAllConnections(); return new Promise(resolve => server.close(resolve)); } };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The engine must never reach the user's event bridge (8790) from this lane.
|
|
49
|
+
function forbiddenBridge() {
|
|
50
|
+
const guard = name => async () => { throw new Error(`event bridge must not be contacted from the test lane (${name})`); };
|
|
51
|
+
return { registerConsumer: guard('registerConsumer'), listJournals: guard('listJournals'), readEvents: guard('readEvents'), ackConsumerCursor: guard('ackConsumerCursor'), close() {} };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// A declared stub analyzer: it never calls a model, it only stages Markdown
|
|
55
|
+
// so the promotion path can be exercised end to end.
|
|
56
|
+
function stubAnalyzer(events = []) {
|
|
57
|
+
return {
|
|
58
|
+
sourceReadMode: 'isolated-frozen-evidence',
|
|
59
|
+
async runClaim(input) {
|
|
60
|
+
events.push({ kind: 'start', projectId: input.projectId, sha: input.claim.commitSha });
|
|
61
|
+
const relative = `changes/${input.claim.commitSha.slice(0, 12)}.md`;
|
|
62
|
+
const content = `# 真实晋升条目 ${input.claim.commitSha.slice(0, 12)}\n\nRET001TOKEN 事实:本次提交把配额采样改为串行执行。\n\ncommit: ${input.claim.commitSha}\n`;
|
|
63
|
+
AtomicFile.writeFileAtomic(path.join(input.stagingPath, 'files', relative), content);
|
|
64
|
+
AtomicFile.writeJsonAtomic(input.manifestPath, {
|
|
65
|
+
schema: 'knowledge-staging-manifest/v1',
|
|
66
|
+
projectId: input.projectId,
|
|
67
|
+
runId: input.claim.runId,
|
|
68
|
+
commitSha: input.claim.commitSha,
|
|
69
|
+
operations: [{
|
|
70
|
+
path: relative,
|
|
71
|
+
operation: 'create',
|
|
72
|
+
sha256: `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`,
|
|
73
|
+
reason: 'Frozen Git evidence',
|
|
74
|
+
evidenceReferences: [`commit:${input.claim.commitSha}`, `patch:${input.claim.patchHash}`, `conversation:${input.claim.conversationSnapshotHash}`, `retrieval:${input.claim.retrievalManifestHash}`],
|
|
75
|
+
}],
|
|
76
|
+
});
|
|
77
|
+
events.push({ kind: 'finish', projectId: input.projectId, sha: input.claim.commitSha });
|
|
78
|
+
return { ok: true };
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
(async () => {
|
|
84
|
+
const root = fixture.makeTempDir('parallel-knowledge-retrieval');
|
|
85
|
+
const dataDir = path.join(root, 'data');
|
|
86
|
+
const layout = new StorageLayout({ dataDir });
|
|
87
|
+
const projects = new ProjectStore({ layout });
|
|
88
|
+
const registry = new ProjectRegistryStore({ layout });
|
|
89
|
+
const vector = await stubVectorServer();
|
|
90
|
+
const analyzerEvents = [];
|
|
91
|
+
const engineDataDir = path.join(root, 'engine-data');
|
|
92
|
+
fs.mkdirSync(engineDataDir, { recursive: true });
|
|
93
|
+
const engine = createEngineServer({ dataDir: engineDataDir, vectorUrl: vector.url, bridge: forbiddenBridge(), analyzer: stubAnalyzer(analyzerEvents) });
|
|
94
|
+
await engine.ready;
|
|
95
|
+
await new Promise(resolve => engine.server.listen(0, '127.0.0.1', resolve));
|
|
96
|
+
const engineUrl = `http://127.0.0.1:${engine.server.address().port}`;
|
|
97
|
+
const call = async (route, body) => {
|
|
98
|
+
const response = await fetch(`${engineUrl}${route}`, {
|
|
99
|
+
method: body === undefined ? 'GET' : 'POST',
|
|
100
|
+
headers: { 'Content-Type': 'application/json' },
|
|
101
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
102
|
+
});
|
|
103
|
+
return { status: response.status, body: await response.json() };
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
// ---------------------------------------------------------------- fixtures
|
|
108
|
+
const repoA = fixture.makeRepo(root, 'repo-a', [
|
|
109
|
+
{ message: 'initial', files: { 'README.md': '# A\n\n初始内容\n' } },
|
|
110
|
+
{ message: 'feature', files: { 'feature.txt': 'feature A\n' } },
|
|
111
|
+
]);
|
|
112
|
+
const repoB = fixture.makeRepo(root, 'repo-b', [{ message: 'initial', files: { 'README.md': '# B\n' } }]);
|
|
113
|
+
const repoC = fixture.makeRepo(root, 'repo-c', [{ message: 'initial', files: { 'README.md': '# C\n' } }]);
|
|
114
|
+
const knowledgeA = fixture.makeKnowledgeDir(root, 'knowledge-a');
|
|
115
|
+
const knowledgeB = fixture.makeKnowledgeDir(root, 'knowledge-b');
|
|
116
|
+
const knowledgeC = fixture.makeKnowledgeDir(root, 'knowledge-c');
|
|
117
|
+
const promotedEntry = `changes/planted-${repoA.head.slice(0, 12)}.md`;
|
|
118
|
+
const promotedBody = `# 真实晋升条目\n\nRET001TOKEN 事实:本次提交把配额采样改为串行执行。\n\ncommit: ${repoA.head}\n`;
|
|
119
|
+
fs.mkdirSync(path.join(knowledgeA, 'changes'), { recursive: true });
|
|
120
|
+
fs.writeFileSync(path.join(knowledgeA, promotedEntry), promotedBody, 'utf8');
|
|
121
|
+
fs.writeFileSync(path.join(knowledgeA, 'changes', 'template.md'), '# 模板\n\nTODO: 待补充\n', 'utf8');
|
|
122
|
+
fs.writeFileSync(path.join(knowledgeB, 'README.md'), '# B\n\nDOMAINONLYTOKEN 属于领域知识\n', 'utf8');
|
|
123
|
+
fs.writeFileSync(path.join(knowledgeC, 'README.md'), '# C\n\nOTHERDOMAINTOKEN 属于其它领域\n', 'utf8');
|
|
124
|
+
|
|
125
|
+
await projects.create('project-a', { displayName: 'A', repoPath: repoA.repoPath, knowledgePath: knowledgeA, relatedProjectIds: ['project-b'], brainId: 'brain-default', domainId: 'domain-tools' });
|
|
126
|
+
await projects.create('project-b', { displayName: 'B', repoPath: repoB.repoPath, knowledgePath: knowledgeB, brainId: 'brain-default', domainId: 'domain-tools' });
|
|
127
|
+
await projects.create('project-c', { displayName: 'C', repoPath: repoC.repoPath, knowledgePath: knowledgeC, brainId: 'brain-default', domainId: 'domain-other' });
|
|
128
|
+
for (const id of ['project-a', 'project-b', 'project-c']) await registry.add(id, { displayNameSnapshot: id });
|
|
129
|
+
|
|
130
|
+
// The root product service is the authoritative-Markdown reader used by
|
|
131
|
+
// the shell. No embedder and no index exist yet.
|
|
132
|
+
const service = new KnowledgeRetrievalService({ layout, registryStore: registry, projectStore: projects });
|
|
133
|
+
|
|
134
|
+
// --------------------------------------------------------------- RET-001
|
|
135
|
+
{
|
|
136
|
+
const before = fixture.hashTree(knowledgeA);
|
|
137
|
+
const response = await service.search({ projectId: 'project-a', query: 'RET001TOKEN' });
|
|
138
|
+
assert.equal(response.ok, true);
|
|
139
|
+
assert.ok(response.results.length >= 1, 'the promoted fact must be retrievable');
|
|
140
|
+
const top = response.results[0];
|
|
141
|
+
assert.equal(top.entry_id, promotedEntry, 'citation must point at the promoted relative entry');
|
|
142
|
+
assert.ok(top.chunk_text.includes('RET001TOKEN'), 'the result must carry the real body text');
|
|
143
|
+
assert.ok(top.chunk_text.includes('配额采样改为串行执行'), 'the returned body must contain the promoted fact sentence');
|
|
144
|
+
const expectedDocumentHash = sha256(`chunker:${CHUNKER_VERSION}\n${promotedBody}`);
|
|
145
|
+
assert.equal(top.document_hash, expectedDocumentHash, 'document hash must be recomputable from the real file');
|
|
146
|
+
assert.equal(top.content_hash, crypto.createHash('sha256').update(top.chunk_text, 'utf8').digest('hex'), 'content hash must match the returned body');
|
|
147
|
+
assert.ok(!response.results.some(row => /TODO|待补充/.test(row.chunk_text)), 'template placeholders must not be presented as facts');
|
|
148
|
+
assert.equal(response.backend, 'markdown-hybrid-fallback', 'no vector channel exists in this lane, so the backend must not claim a hybrid hit');
|
|
149
|
+
assert.notEqual(response.health.index, 'ok');
|
|
150
|
+
assert.deepEqual(fixture.hashTree(knowledgeA), before, 'retrieval must not mutate knowledge files');
|
|
151
|
+
record('RET-001', 'PASS', { entryId: top.entry_id, documentHash: top.document_hash, backend: response.backend, healthIndex: response.health.index, resultCount: response.results.length });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// --------------------------------------------------------------- RET-003
|
|
155
|
+
{
|
|
156
|
+
const projectScope = await service.search({ projectId: 'project-a', query: 'RET001TOKEN' });
|
|
157
|
+
assert.ok(projectScope.results.every(row => row.scope_project_id === 'project-a' || row.scope_project_id === 'project-b'), 'project scope may only expose the project and its declared related projects');
|
|
158
|
+
assert.ok(!projectScope.results.some(row => row.chunk_text.includes('OTHERDOMAINTOKEN')), 'an unrelated project must never leak into the project scope');
|
|
159
|
+
const unrelated = await service.search({ projectId: 'project-c', query: 'OTHERDOMAINTOKEN' });
|
|
160
|
+
assert.ok(unrelated.results.every(row => row.scope_project_id === 'project-c'));
|
|
161
|
+
assert.ok(!unrelated.results.some(row => row.chunk_text.includes('RET001TOKEN')), 'another project must not borrow this project context');
|
|
162
|
+
await assert.rejects(service.search({ projectId: 'project-unknown', query: 'RET001TOKEN' }), 'an unknown project must fail instead of borrowing a registered context');
|
|
163
|
+
|
|
164
|
+
// Real engine scope semantics over real loopback HTTP.
|
|
165
|
+
for (const project of [
|
|
166
|
+
{ projectId: 'project-a', name: 'A', repoPath: repoA.repoPath, knowledgePath: knowledgeA, brainId: 'brain-default', domainId: 'domain-tools' },
|
|
167
|
+
{ projectId: 'project-b', name: 'B', repoPath: repoB.repoPath, knowledgePath: knowledgeB, brainId: 'brain-default', domainId: 'domain-tools' },
|
|
168
|
+
{ projectId: 'project-c', name: 'C', repoPath: repoC.repoPath, knowledgePath: knowledgeC, brainId: 'brain-default', domainId: 'domain-other' },
|
|
169
|
+
]) await call('/api/projects', project);
|
|
170
|
+
fs.writeFileSync(path.join(knowledgeA, 'shared.md'), '# A\n\nSHAREDTOKEN 共享事实 A\n', 'utf8');
|
|
171
|
+
fs.writeFileSync(path.join(knowledgeB, 'shared.md'), '# B\n\nSHAREDTOKEN 共享事实 B\n', 'utf8');
|
|
172
|
+
fs.writeFileSync(path.join(knowledgeC, 'shared.md'), '# C\n\nSHAREDTOKEN 共享事实 C\n', 'utf8');
|
|
173
|
+
|
|
174
|
+
const projectScopeEngine = await call('/api/search', { projectId: 'project-a', query: 'SHAREDTOKEN', scope: 'project' });
|
|
175
|
+
assert.equal(projectScopeEngine.status, 200);
|
|
176
|
+
assert.deepEqual([...new Set(projectScopeEngine.body.results.map(row => row.scope_project_id))], ['project-a'], 'engine project scope must stay inside the project');
|
|
177
|
+
const domainScopeEngine = await call('/api/search', { projectId: 'project-a', query: 'SHAREDTOKEN', scope: 'domain' });
|
|
178
|
+
assert.deepEqual([...new Set(domainScopeEngine.body.results.map(row => row.scope_project_id))].sort(), ['project-a', 'project-b'], 'engine domain scope must expose the same-domain project only');
|
|
179
|
+
const brainScopeEngine = await call('/api/search', { projectId: 'project-a', query: 'SHAREDTOKEN', scope: 'brain' });
|
|
180
|
+
assert.deepEqual([...new Set(brainScopeEngine.body.results.map(row => row.scope_project_id))].sort(), ['project-a', 'project-b', 'project-c'], 'engine brain scope must expose every project of the brain');
|
|
181
|
+
const unknownEngine = await call('/api/search', { projectId: 'project-unknown', query: 'SHAREDTOKEN' });
|
|
182
|
+
assert.equal(unknownEngine.status, 404, 'an unknown project must be refused by the engine');
|
|
183
|
+
assert.equal(unknownEngine.body.error, 'PROJECT_NOT_FOUND', 'the real engine reports the failure code as a string');
|
|
184
|
+
const invalidScope = await call('/api/search', { projectId: 'project-a', query: 'SHAREDTOKEN', scope: 'global' });
|
|
185
|
+
assert.equal(invalidScope.body.error, 'INVALID_ARGUMENT', 'an unknown scope must be refused');
|
|
186
|
+
record('RET-003', 'PASS', {
|
|
187
|
+
rootServiceScopes: [...new Set(projectScope.results.map(row => row.scope_project_id))].sort(),
|
|
188
|
+
engineProjectScope: [...new Set(projectScopeEngine.body.results.map(row => row.scope_project_id))],
|
|
189
|
+
engineDomainScope: [...new Set(domainScopeEngine.body.results.map(row => row.scope_project_id))].sort(),
|
|
190
|
+
engineBrainScope: [...new Set(brainScopeEngine.body.results.map(row => row.scope_project_id))].sort(),
|
|
191
|
+
unknownProjectStatus: unknownEngine.status,
|
|
192
|
+
vectorStubRequests: vector.requests.length,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// --------------------------------------------------------------- RET-004
|
|
197
|
+
{
|
|
198
|
+
// A real promoted run: the engine analyses the real commit SHA and the
|
|
199
|
+
// promoted file keeps that SHA as its source.
|
|
200
|
+
const run = engine.coordinator.enqueue({ projectId: 'project-a', commitSha: repoA.head });
|
|
201
|
+
const promoted = await fixture.eventually(() => engine.store.getRun(run.runId) && ['succeeded', 'failed', 'index-pending'].includes(engine.store.getRun(run.runId).state));
|
|
202
|
+
assert.equal(promoted, true, 'the run must reach a terminal state');
|
|
203
|
+
const finished = engine.store.getRun(run.runId);
|
|
204
|
+
assert.equal(finished.state, 'succeeded', `run failed: ${JSON.stringify(finished.error)}`);
|
|
205
|
+
const promotedFile = path.join(knowledgeA, `changes/${repoA.head.slice(0, 12)}.md`);
|
|
206
|
+
assert.ok(fs.existsSync(promotedFile), 'the promoted Markdown must exist on disk');
|
|
207
|
+
const promotedText = fs.readFileSync(promotedFile, 'utf8');
|
|
208
|
+
assert.ok(promotedText.includes(repoA.head), 'the promoted body must carry the real commit SHA');
|
|
209
|
+
|
|
210
|
+
const search = await call('/api/search', { projectId: 'project-a', query: repoA.head.slice(0, 12) });
|
|
211
|
+
assert.equal(search.status, 200);
|
|
212
|
+
const hit = search.body.results.find(row => row.entry_id === `changes/${repoA.head.slice(0, 12)}.md`);
|
|
213
|
+
assert.ok(hit, 'the promoted entry must be retrievable');
|
|
214
|
+
assert.equal(hit.commitSha, repoA.head, 'the citation must carry the full commit source of the current result');
|
|
215
|
+
assert.equal(hit.path, `changes/${repoA.head.slice(0, 12)}.md`, 'the citation path must be relative to the project knowledge root');
|
|
216
|
+
|
|
217
|
+
const read = await call('/api/get', { projectId: 'project-a', entryId: hit.entry_id });
|
|
218
|
+
assert.equal(read.status, 200);
|
|
219
|
+
assert.ok(read.body.chunks[0].chunk_text.includes(repoA.head), 'reading the citation must return the real body');
|
|
220
|
+
const outside = await call('/api/get', { projectId: 'project-a', entryId: '../outside.md' });
|
|
221
|
+
assert.equal(outside.status, 403, 'path traversal must be refused');
|
|
222
|
+
assert.equal(outside.body.error, 'PATH_OUTSIDE_ROOT', 'the real engine reports the failure code as a string');
|
|
223
|
+
|
|
224
|
+
// The product's own client must reach the same real engine.
|
|
225
|
+
const client = new EngineKnowledgeClient({ layout, engineUrl });
|
|
226
|
+
const viaClient = await client.request('get', { entry: hit.entry_id }, { projectId: 'project-a', repoPath: repoA.repoPath, knowledgePath: knowledgeA });
|
|
227
|
+
assert.ok(viaClient.chunks[0].chunk_text.includes(repoA.head));
|
|
228
|
+
await assert.rejects(client.request('get', { entry: '../outside.md' }, { projectId: 'project-a', repoPath: repoA.repoPath, knowledgePath: knowledgeA }), error => error.code === 'PATH_OUTSIDE_ROOT' && !error.message.includes(repoA.head));
|
|
229
|
+
await assert.rejects(client.request('search', { query: 'x', scope: 'global' }, { projectId: 'project-a', repoPath: repoA.repoPath, knowledgePath: knowledgeA }), error => error.code === 'INVALID_ARGUMENT');
|
|
230
|
+
record('RET-004', 'PASS', {
|
|
231
|
+
promotedEntry: hit.entry_id,
|
|
232
|
+
commitSha: hit.commitSha,
|
|
233
|
+
documentHash: hit.document_hash,
|
|
234
|
+
traversalStatus: outside.status,
|
|
235
|
+
traversalCode: outside.body.error.code,
|
|
236
|
+
analyzerStubRuns: analyzerEvents.length,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// --------------------------------------------------------------- RET-005
|
|
241
|
+
{
|
|
242
|
+
// The embedding channel is switched off: no embedder, then a failing
|
|
243
|
+
// embedder. The Markdown truth must still answer, and nothing may claim
|
|
244
|
+
// a vector success or delete knowledge.
|
|
245
|
+
const before = fixture.hashTree(knowledgeA);
|
|
246
|
+
fs.mkdirSync(layout.getIndexPath(), { recursive: true });
|
|
247
|
+
const disabled = new KnowledgeRetrievalService({ layout, registryStore: registry, projectStore: projects, embedderProvider: () => null });
|
|
248
|
+
const disabledResponse = await disabled.search({ projectId: 'project-a', query: 'RET001TOKEN' });
|
|
249
|
+
assert.equal(disabledResponse.backend, 'markdown-hybrid-fallback');
|
|
250
|
+
assert.equal(disabledResponse.health.index, 'unavailable');
|
|
251
|
+
assert.ok(disabledResponse.results.length >= 1, 'Markdown truth must stay visible when the embedding channel is off');
|
|
252
|
+
assert.ok(disabledResponse.results.every(row => !row.match_channels.includes('index:vector-hub')), 'no result may claim a vector channel');
|
|
253
|
+
|
|
254
|
+
const failing = new KnowledgeRetrievalService({
|
|
255
|
+
layout,
|
|
256
|
+
registryStore: registry,
|
|
257
|
+
projectStore: projects,
|
|
258
|
+
// Row-less index: the database refuses every query, so no vector row
|
|
259
|
+
// can ever reach a result. The only observable effect is the
|
|
260
|
+
// degradation path of the Markdown truth reader.
|
|
261
|
+
databaseProvider: () => ({ async hybridSearch() { throw new Error('index offline'); } }),
|
|
262
|
+
embedderProvider: () => ({ async embedQuery() { throw new Error('embedding disabled'); } }),
|
|
263
|
+
});
|
|
264
|
+
const failingResponse = await failing.search({ projectId: 'project-a', query: 'RET001TOKEN' });
|
|
265
|
+
assert.equal(failingResponse.health.index, 'degraded');
|
|
266
|
+
assert.equal(failingResponse.health.reason, 'index-query-failed');
|
|
267
|
+
assert.equal(failingResponse.backend, 'markdown-hybrid-fallback');
|
|
268
|
+
assert.ok(failingResponse.results.length >= 1, 'a degraded index must still expose the Markdown truth');
|
|
269
|
+
assert.ok(failingResponse.results.every(row => !row.match_channels.includes('index:vector-hub')));
|
|
270
|
+
assert.deepEqual(fixture.hashTree(knowledgeA), before, 'a disabled embedding channel must not delete or rewrite knowledge');
|
|
271
|
+
record('RET-005', 'PASS', {
|
|
272
|
+
disabledBackend: disabledResponse.backend,
|
|
273
|
+
disabledHealth: disabledResponse.health.index,
|
|
274
|
+
degradedHealth: failingResponse.health.index,
|
|
275
|
+
degradedReason: failingResponse.health.reason,
|
|
276
|
+
knowledgeFilesUnchanged: true,
|
|
277
|
+
resultCount: failingResponse.results.length,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const summary = { lane: 'test-knowledge', file: 'parallel-knowledge-retrieval-test.js', cases: results, vectorChannel: 'stubbed-empty-results (not vector evidence)' };
|
|
282
|
+
const target = process.env.PARALLEL_KNOWLEDGE_EVIDENCE;
|
|
283
|
+
if (target) fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
284
|
+
if (target) fs.writeFileSync(target, JSON.stringify(summary, null, 2), 'utf8');
|
|
285
|
+
console.log('parallel-knowledge-retrieval-test PASS');
|
|
286
|
+
} finally {
|
|
287
|
+
await engine.close();
|
|
288
|
+
await vector.close();
|
|
289
|
+
fixture.removeTree(root);
|
|
290
|
+
fixture.cleanupIsolatedProcess();
|
|
291
|
+
}
|
|
292
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Parallel knowledge lane — canonical vector-side contracts (RET-006 sub-items).
|
|
3
|
+
//
|
|
4
|
+
// Scope: only the deterministic, environment-independent contracts are proven
|
|
5
|
+
// here, because the file is collected by the offline regression runner:
|
|
6
|
+
// RET-006.a-root-switch the hub accepts and reports a new index root
|
|
7
|
+
// RET-006.b-writer-drain an in-flight writer is drained before the switch resolves
|
|
8
|
+
// RET-006.c-old-root-retained the old index root keeps its data byte-for-byte
|
|
9
|
+
// RET-006.d-project-knowledge-kept a project knowledge directory is never moved by the switch
|
|
10
|
+
//
|
|
11
|
+
// RET-006 as a whole is NOT claimed to pass here: this file proves the
|
|
12
|
+
// deterministic sub-items a-d only. The remaining sub-item ("新根重建":
|
|
13
|
+
// writing an actual index into the new root) needs a real embedding model,
|
|
14
|
+
// because the hub refuses any index write without one. That
|
|
15
|
+
// environment-dependent acceptance lives in the explicitly invoked probe
|
|
16
|
+
// qa-autopilot/tools/product-probes/parallel-knowledge-vector.js, which now
|
|
17
|
+
// drives a real hub upsert/search, a real root switch, a real rebuild in the
|
|
18
|
+
// new root and a real old-root retention check, and reports RET-002/RET-006
|
|
19
|
+
// as BLOCKED instead of faking a green result.
|
|
20
|
+
//
|
|
21
|
+
// This file performs no model load, no download and no paid API call, and it
|
|
22
|
+
// never touches the live 5757/5790/8787/8790 endpoints.
|
|
23
|
+
const assert = require('node:assert/strict');
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
const fixture = require('./helpers/parallel-knowledge-fixture');
|
|
27
|
+
// The entry point isolates itself: ambient module addresses, live data
|
|
28
|
+
// directories and the real HOME are cleared here, before anything else loads.
|
|
29
|
+
fixture.isolateProcess();
|
|
30
|
+
|
|
31
|
+
const results = [];
|
|
32
|
+
function record(caseId, status, detail) {
|
|
33
|
+
results.push({ caseId, status, detail });
|
|
34
|
+
console.log(`CASE ${caseId} ${status} ${JSON.stringify(detail)}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const HUB_DIST = path.resolve(__dirname, '..', '..', 'node_modules', '@sanqianx', 'vector-hub', 'dist', 'cjs');
|
|
38
|
+
|
|
39
|
+
(async () => {
|
|
40
|
+
const root = fixture.makeTempDir('parallel-knowledge-vector');
|
|
41
|
+
try {
|
|
42
|
+
const oldRoot = path.join(root, 'indexes-old');
|
|
43
|
+
const newRoot = path.join(root, 'indexes-new');
|
|
44
|
+
const configPath = path.join(root, 'vector-hub', 'config.json');
|
|
45
|
+
fs.mkdirSync(path.join(oldRoot, 'demo'), { recursive: true });
|
|
46
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
47
|
+
fs.writeFileSync(path.join(oldRoot, 'demo', 'note.md'), '旧索引根下的真实数据\n', 'utf8');
|
|
48
|
+
const oldRootBefore = fixture.hashTree(oldRoot);
|
|
49
|
+
const knowledgePath = fixture.makeKnowledgeDir(root, 'knowledge-index');
|
|
50
|
+
fs.writeFileSync(path.join(knowledgePath, 'README.md'), '# 索引根切换\n\nRET006TOKEN 项目知识\n', 'utf8');
|
|
51
|
+
const knowledgeBefore = fixture.hashTree(knowledgePath);
|
|
52
|
+
|
|
53
|
+
const port = await fixture.freePort();
|
|
54
|
+
const token = 'parallel-knowledge-runtime-token';
|
|
55
|
+
const process_ = fixture.spawnNode([
|
|
56
|
+
path.join(HUB_DIST, 'bin.js'), 'serve', '--port', String(port), '--root', oldRoot, '--config', configPath,
|
|
57
|
+
], {
|
|
58
|
+
KB_VECTOR_MANAGED: '1',
|
|
59
|
+
KB_VECTOR_RUNTIME_TOKEN: token,
|
|
60
|
+
VECTOR_HUB_DISABLE_PICKER: '1',
|
|
61
|
+
VECTOR_HUB_ROOT: oldRoot,
|
|
62
|
+
VECTOR_HUB_CONFIG: configPath,
|
|
63
|
+
MINIMAX_API_KEY: '',
|
|
64
|
+
});
|
|
65
|
+
try {
|
|
66
|
+
const base = `http://127.0.0.1:${port}`;
|
|
67
|
+
fixture.assertAllowedEndpoint(base, 'vector hub');
|
|
68
|
+
await fixture.waitForHttp(`${base}/api/projects`, 20000);
|
|
69
|
+
const before = await (await fetch(`${base}/api/projects`)).json();
|
|
70
|
+
assert.ok(before.projects.length >= 0);
|
|
71
|
+
|
|
72
|
+
// The real switch: the shell posts the new absolute index root.
|
|
73
|
+
const switched = await fetch(`${base}/api/internal/runtime-config`, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
|
76
|
+
body: JSON.stringify({ settings: null, storageRoot: newRoot }),
|
|
77
|
+
});
|
|
78
|
+
const switchedText = await switched.text();
|
|
79
|
+
assert.equal(switched.status, 200, `runtime-config switch failed: ${switched.status} ${switchedText}`);
|
|
80
|
+
const switchedBody = JSON.parse(switchedText);
|
|
81
|
+
|
|
82
|
+
// ---- RET-006.a-root-switch
|
|
83
|
+
assert.equal(switchedBody.rootPath, path.resolve(newRoot), 'the hub must report the new index root');
|
|
84
|
+
assert.ok(fs.existsSync(newRoot), 'the new index root must be created');
|
|
85
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
86
|
+
assert.equal(path.resolve(config.settings.rootPath), path.resolve(newRoot), 'the persisted configuration must point at the new root');
|
|
87
|
+
const after = await (await fetch(`${base}/api/projects`)).json();
|
|
88
|
+
assert.ok(Array.isArray(after.projects));
|
|
89
|
+
record('RET-006.a-root-switch', 'PASS', {
|
|
90
|
+
port,
|
|
91
|
+
reportedRootPath: switchedBody.rootPath,
|
|
92
|
+
persistedRoot: config.settings.rootPath,
|
|
93
|
+
newRootCreated: fs.existsSync(newRoot),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// ---- RET-006.c-old-root-retained
|
|
97
|
+
assert.deepEqual(fixture.hashTree(oldRoot), oldRootBefore, 'the old index root data must be retained byte-for-byte');
|
|
98
|
+
record('RET-006.c-old-root-retained', 'PASS', {
|
|
99
|
+
oldRoot: path.resolve(oldRoot),
|
|
100
|
+
oldRootRetained: JSON.stringify(fixture.hashTree(oldRoot)) === JSON.stringify(oldRootBefore),
|
|
101
|
+
oldRootStillExists: fs.existsSync(oldRoot),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ---- RET-006.d-project-knowledge-kept
|
|
105
|
+
assert.deepEqual(fixture.hashTree(knowledgePath), knowledgeBefore, 'the project knowledge must not be moved by an index root switch');
|
|
106
|
+
record('RET-006.d-project-knowledge-kept', 'PASS', {
|
|
107
|
+
knowledgePath: path.resolve(knowledgePath),
|
|
108
|
+
projectKnowledgeUnchanged: JSON.stringify(fixture.hashTree(knowledgePath)) === JSON.stringify(knowledgeBefore),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// ---- RET-006.b-writer-drain
|
|
112
|
+
// The drain barrier, measured on the real HubManager: a real project
|
|
113
|
+
// write that is still running must finish before the root switch
|
|
114
|
+
// resolves.
|
|
115
|
+
const { HubManager } = require(path.join(HUB_DIST, 'server', 'manager.js'));
|
|
116
|
+
const drainOldRoot = path.join(root, 'drain-old');
|
|
117
|
+
const drainNewRoot = path.join(root, 'drain-new');
|
|
118
|
+
const drainConfig = path.join(root, 'drain', 'config.json');
|
|
119
|
+
fs.mkdirSync(drainOldRoot, { recursive: true });
|
|
120
|
+
fs.mkdirSync(path.dirname(drainConfig), { recursive: true });
|
|
121
|
+
const manager = await HubManager.load({ rootPath: drainOldRoot, configPath: drainConfig, managed: true });
|
|
122
|
+
const order = {};
|
|
123
|
+
const write = manager.withProjectWrite('demo', async () => {
|
|
124
|
+
await new Promise(resolve => setTimeout(resolve, 600));
|
|
125
|
+
order.writeFinished = Date.now();
|
|
126
|
+
return 'written';
|
|
127
|
+
});
|
|
128
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
129
|
+
const switchStart = Date.now();
|
|
130
|
+
await manager.applyRuntimeSettings(null, drainNewRoot);
|
|
131
|
+
order.switchFinished = Date.now();
|
|
132
|
+
order.switchStart = switchStart;
|
|
133
|
+
await write;
|
|
134
|
+
assert.ok(order.writeFinished, 'the in-flight write must have finished');
|
|
135
|
+
assert.ok(order.writeFinished <= order.switchFinished, 'the root switch must wait for the old writer to drain');
|
|
136
|
+
assert.ok(order.switchFinished - switchStart >= 400, 'the root switch must not resolve before the pending write completes');
|
|
137
|
+
assert.equal(path.resolve(manager.hub.rootPath), path.resolve(drainNewRoot), 'the manager must point at the new root after the switch');
|
|
138
|
+
assert.ok(fs.existsSync(drainOldRoot), 'the drained root must still exist');
|
|
139
|
+
record('RET-006.b-writer-drain', 'PASS', {
|
|
140
|
+
switchWaitedForWriter: order.switchFinished - order.switchStart,
|
|
141
|
+
writerFinishedBeforeSwitch: order.writeFinished <= order.switchFinished,
|
|
142
|
+
managerRootAfterSwitch: path.resolve(manager.hub.rootPath),
|
|
143
|
+
drainedRootStillExists: fs.existsSync(drainOldRoot),
|
|
144
|
+
});
|
|
145
|
+
} finally {
|
|
146
|
+
process_.child.kill();
|
|
147
|
+
await process_.exited;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const summary = {
|
|
151
|
+
lane: 'test-knowledge',
|
|
152
|
+
file: 'parallel-knowledge-vector-test.js',
|
|
153
|
+
cases: results,
|
|
154
|
+
ret006Overall: 'PARTIAL',
|
|
155
|
+
ret006ScopeHere: 'a-d only: this file proves the deterministic, environment-independent sub-items of RET-006 and nothing more.',
|
|
156
|
+
ret006NotExercisedHere: '新根重建(向新根写入真实索引)需要真实 embedding 模型,hub 在未配置模型时拒绝任何索引写入;该环境依赖验收在显式调用的 probe qa-autopilot/tools/product-probes/parallel-knowledge-vector.js 中判定——该 probe 已实现真实 hub upsert/search、根切换、新根重建检索与旧根保留,未取得模型时记 BLOCKED。',
|
|
157
|
+
vectorChannel: 'no index content was written: the environment has no authorised embedding model, so only the deterministic storage-root contracts are proven here',
|
|
158
|
+
probe: 'qa-autopilot/tools/product-probes/parallel-knowledge-vector.js',
|
|
159
|
+
};
|
|
160
|
+
const target = process.env.PARALLEL_KNOWLEDGE_EVIDENCE;
|
|
161
|
+
if (target) fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
162
|
+
if (target) fs.writeFileSync(target, JSON.stringify(summary, null, 2), 'utf8');
|
|
163
|
+
console.log('parallel-knowledge-vector-test PASS (RET-006 sub-items a-d only; the new-root rebuild is covered by the tracked probe, not here)');
|
|
164
|
+
} finally {
|
|
165
|
+
fixture.removeTree(root);
|
|
166
|
+
fixture.cleanupIsolatedProcess();
|
|
167
|
+
}
|
|
168
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// P-LINUX-CI-CLOSEOUT-R7 focused case: the log007 day-filter contract, pinned
|
|
3
|
+
// deterministically OFFLINE (real Logger + real LogRepository; no server, no
|
|
4
|
+
// browser, no clock race).
|
|
5
|
+
//
|
|
6
|
+
// CI shape reproduced (run 40062048045, linux22+24, log007
|
|
7
|
+
// timeDayGranularity=false while every other filter stayed green): the live
|
|
8
|
+
// server writes a project-scoped bridge_consumer.identity_backfilled record
|
|
9
|
+
// into the same local-day file ~0.2-1s after health, so the old
|
|
10
|
+
// count === 4 day-window expectation raced the server's own async startup
|
|
11
|
+
// write and lost on the Linux boot timing (Windows CI landed it after the
|
|
12
|
+
// queries). Here the same five-record shape is built with the real logger and
|
|
13
|
+
// the record-anchored contract is asserted: all four fixture records appear in
|
|
14
|
+
// their day's window, and EVERY entry of a from=X&to=X window carries
|
|
15
|
+
// localDay(ts) === X.
|
|
16
|
+
//
|
|
17
|
+
// Day-boundary contract: records with explicit timestamps one millisecond
|
|
18
|
+
// either side of a UTC midnight keep whatever local days the machine's zone
|
|
19
|
+
// assigns; the windows derived from the records themselves return exactly the
|
|
20
|
+
// records dated inside them (file-day naming and matches-day must agree in
|
|
21
|
+
// any zone — the property the old now/now±24h window construction could not
|
|
22
|
+
// guarantee).
|
|
23
|
+
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const os = require('node:os');
|
|
26
|
+
const path = require('node:path');
|
|
27
|
+
const assert = require('node:assert/strict');
|
|
28
|
+
|
|
29
|
+
const { installLoggingTestEnv, resolveEvidenceDir } = require('./helpers/parallel-logging-integration-5025-env');
|
|
30
|
+
const lane = installLoggingTestEnv({ caseId: 'LINUX-CI-R7-LOG007-DAY', prefix: 'lcr7day' });
|
|
31
|
+
|
|
32
|
+
const { Logger, LogRepository, localDay } = require('../lib/structured-logger');
|
|
33
|
+
const { StorageLayout } = require('../lib/storage-layout');
|
|
34
|
+
|
|
35
|
+
const ROOT = path.resolve(__dirname, '../..');
|
|
36
|
+
const ARTIFACTS = resolveEvidenceDir(path.resolve(ROOT, 'qa-autopilot/artifacts/parallel-20261023/linux-ci-closeout-r7/evidence/log007-day'));
|
|
37
|
+
const PROJECT = 'project-lcr7-day';
|
|
38
|
+
|
|
39
|
+
const shiftDay = (day, delta) => new Date(Date.UTC(Number(day.slice(0, 4)), Number(day.slice(5, 7)) - 1, Number(day.slice(8, 10)) + delta)).toISOString().slice(0, 10);
|
|
40
|
+
|
|
41
|
+
(async () => {
|
|
42
|
+
fs.mkdirSync(ARTIFACTS, { recursive: true });
|
|
43
|
+
const evidence = { caseId: 'LINUX-CI-R7-LOG007-DAY', startedAt: new Date().toISOString(), platform: process.platform };
|
|
44
|
+
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-r7-day-'));
|
|
45
|
+
const layout = new StorageLayout({ dataDir });
|
|
46
|
+
const settings = { levels: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] };
|
|
47
|
+
const logger = new Logger({ layout, settingsProvider: () => settings });
|
|
48
|
+
const repository = new LogRepository({ layout });
|
|
49
|
+
try {
|
|
50
|
+
// --- 1. the CI five-record shape ------------------------------------
|
|
51
|
+
const baseTs = new Date().toISOString();
|
|
52
|
+
const fixtureEvents = ['lcr7.early.info', 'lcr7.early.warn', 'lcr7.late.warn', 'lcr7.late.error'];
|
|
53
|
+
for (const [index, event] of fixtureEvents.entries()) {
|
|
54
|
+
await logger.log(index === 3 ? 'error' : index === 0 ? 'info' : 'warn', event, `LCR7 ${event}`, { ts: baseTs, projectId: PROJECT, component: `lcr7-comp-${'abc'[index % 3]}` });
|
|
55
|
+
}
|
|
56
|
+
// The server's own startup record: project-scoped, same day, written by
|
|
57
|
+
// the same real logger path.
|
|
58
|
+
await logger.log('info', 'bridge_consumer.identity_backfilled', 'identity backfilled', { ts: baseTs, projectId: PROJECT, component: 'bridge-consumer' });
|
|
59
|
+
|
|
60
|
+
const day = localDay(baseTs);
|
|
61
|
+
const window = await repository.query({ projectId: PROJECT, from: day, to: day, pageSize: 100 });
|
|
62
|
+
const events = window.entries.map(entry => entry.event).sort();
|
|
63
|
+
const oldExpectationHeld = window.entries.length === 4;
|
|
64
|
+
assert.equal(window.entries.length, 5, 'the CI shape holds five same-day project records, not four');
|
|
65
|
+
assert.equal(oldExpectationHeld, false, 'the old count === 4 day-window expectation is exactly what failed on CI');
|
|
66
|
+
for (const event of fixtureEvents) assert.ok(events.includes(event), `fixture record ${event} must appear in its day window`);
|
|
67
|
+
for (const entry of window.entries) assert.equal(localDay(entry.ts), day, `every from=X&to=X entry must be dated X (${entry.event})`);
|
|
68
|
+
const next = await repository.query({ projectId: PROJECT, from: shiftDay(day, 1), to: shiftDay(day, 1), pageSize: 100 });
|
|
69
|
+
const prev = await repository.query({ projectId: PROJECT, from: shiftDay(day, -1), to: shiftDay(day, -1), pageSize: 100 });
|
|
70
|
+
assert.equal(next.entries.length, 0, 'the next-day window is empty for same-day records');
|
|
71
|
+
assert.equal(prev.entries.length, 0, 'the previous-day window is empty for same-day records');
|
|
72
|
+
evidence.ciShape = { day, count: window.entries.length, events, oldCountFourWouldFail: true };
|
|
73
|
+
|
|
74
|
+
// --- 2. day-boundary records in whatever zone the machine runs ------
|
|
75
|
+
const beforeMidnight = '2026-01-01T23:59:59.999Z';
|
|
76
|
+
const afterMidnight = '2026-01-02T00:00:00.001Z';
|
|
77
|
+
await logger.log('info', 'lcr7.boundary.before', 'boundary before', { ts: beforeMidnight, projectId: PROJECT, component: 'lcr7-boundary' });
|
|
78
|
+
await logger.log('info', 'lcr7.boundary.after', 'boundary after', { ts: afterMidnight, projectId: PROJECT, component: 'lcr7-boundary' });
|
|
79
|
+
const dayBefore = localDay(beforeMidnight);
|
|
80
|
+
const dayAfter = localDay(afterMidnight);
|
|
81
|
+
const windowBefore = await repository.query({ projectId: PROJECT, component: 'lcr7-boundary', from: dayBefore, to: dayBefore, pageSize: 100 });
|
|
82
|
+
const windowAfter = await repository.query({ projectId: PROJECT, component: 'lcr7-boundary', from: dayAfter, to: dayAfter, pageSize: 100 });
|
|
83
|
+
assert.ok(windowBefore.entries.some(entry => entry.event === 'lcr7.boundary.before'), 'the pre-midnight record is in its own local-day window');
|
|
84
|
+
assert.ok(windowAfter.entries.some(entry => entry.event === 'lcr7.boundary.after'), 'the post-midnight record is in its own local-day window');
|
|
85
|
+
assert.equal(windowBefore.entries.some(entry => entry.event === 'lcr7.boundary.after'), dayBefore === dayAfter,
|
|
86
|
+
'the post-midnight record appears in the pre-midnight window only when the zone maps both timestamps to one local day');
|
|
87
|
+
for (const entry of windowBefore.entries) assert.equal(localDay(entry.ts), dayBefore, `boundary window entry dated outside the window (${entry.event})`);
|
|
88
|
+
for (const entry of windowAfter.entries) assert.equal(localDay(entry.ts), dayAfter, `boundary window entry dated outside the window (${entry.event})`);
|
|
89
|
+
evidence.dayBoundary = { zoneOffsetMinutes: new Date().getTimezoneOffset(), dayBefore, dayAfter, sameLocalDay: dayBefore === dayAfter };
|
|
90
|
+
|
|
91
|
+
// --- 3. the day-string arithmetic the driver now uses ---------------
|
|
92
|
+
assert.equal(shiftDay('2026-01-01', -1), '2025-12-31', 'previous day crosses the year boundary');
|
|
93
|
+
assert.equal(shiftDay('2026-02-28', 1), '2026-03-01', 'next day crosses the non-leap February boundary');
|
|
94
|
+
assert.equal(shiftDay('2026-10-24', 1), '2026-10-25', 'next day of the CI failure date');
|
|
95
|
+
assert.equal(shiftDay('2026-10-24', -1), '2026-10-23', 'previous day of the CI failure date');
|
|
96
|
+
evidence.shiftDay = { yearBoundary: true, februaryBoundary: true };
|
|
97
|
+
|
|
98
|
+
evidence.verdict = 'PASS — five-record CI shape returns all four fixture records with every window entry correctly dated (old count===4 expectation reproduced as false); UTC-millisecond boundary records land in their zone-assigned local-day windows; day-string arithmetic is boundary-correct';
|
|
99
|
+
} finally {
|
|
100
|
+
evidence.finishedAt = new Date().toISOString();
|
|
101
|
+
fs.writeFileSync(path.join(ARTIFACTS, 'log007-day-evidence.json'), JSON.stringify(evidence, null, 2) + '\n', 'utf8');
|
|
102
|
+
fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
103
|
+
lane.cleanup();
|
|
104
|
+
}
|
|
105
|
+
console.log('LINUX-CI-R7 LOG-007 DAY PASS');
|
|
106
|
+
process.exitCode = 0;
|
|
107
|
+
})().catch(error => {
|
|
108
|
+
console.error(`LINUX-CI-R7 LOG-007 DAY FAIL: ${error.message}`);
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
});
|