@sanqianx/project-knowledge 5.0.23 → 5.0.25
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 -1346
- 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 +70 -67
- 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,208 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// P-LOG-ACCEPTANCE-01 LOG-005 acceptance driver (real product page in a real
|
|
3
|
+
// browser against an isolated managed instance).
|
|
4
|
+
//
|
|
5
|
+
// Case (qa-autopilot/config/test-cases.json LOG-005, P1):
|
|
6
|
+
// steps: 写入超过100条日志,查看DOM有界且早期记录仍可查询。
|
|
7
|
+
// expected: 1) DOM 渲染有界(≤100),不随总量增长。
|
|
8
|
+
// 2) 早期(超出首页的)记录仍可通过查询/导出获取。
|
|
9
|
+
//
|
|
10
|
+
// Real behaviour under test: the diagnostics dialog GETs pageSize=100 and
|
|
11
|
+
// appendLog caps #logsContent at 100 entries (ui/product.js appendLog trims
|
|
12
|
+
// firstElementChild), including live stream appends. With 140 seeded records
|
|
13
|
+
// the first page shows exactly 100; three real GOAL updates stream in and the
|
|
14
|
+
// DOM must stay bounded at 100. The oldest records stay reachable through the
|
|
15
|
+
// real query path (unique marker search in the UI), cursor pagination, export
|
|
16
|
+
// and the on-disk JSONL.
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
const crypto = require('node:crypto');
|
|
21
|
+
|
|
22
|
+
// Portable isolation (P-QA-LOG-INTEGRATION-5025): the temp root comes from
|
|
23
|
+
// PK_TEST_TEMP_ROOT or os.tmpdir() and must live outside the source tree;
|
|
24
|
+
// HOME/APPDATA/LOCALAPPDATA/TEMP/CODEX_HOME/CLAUDE_CONFIG_DIR and the dual
|
|
25
|
+
// product data roots are redirected before any other module is loaded.
|
|
26
|
+
const { installLoggingTestEnv, resolveEvidenceDir } = require('./helpers/parallel-logging-integration-5025-env');
|
|
27
|
+
const lane = installLoggingTestEnv({ caseId: 'LOG-005', prefix: 'log005' });
|
|
28
|
+
const laneTmp = lane.laneTmp;
|
|
29
|
+
|
|
30
|
+
const { spawnServer } = require('./helpers/spawn-server');
|
|
31
|
+
const { findChrome } = require('./helpers/find-chrome');
|
|
32
|
+
const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
|
|
33
|
+
const freePort = require('./helpers/free-port');
|
|
34
|
+
const { createLoggingAcceptanceFixture, writeProjectLogs } = require('./helpers/parallel-logging-acceptance-fixture');
|
|
35
|
+
const { createObservationProxy } = require('./helpers/parallel-logging-acceptance-proxy');
|
|
36
|
+
// The logger names its day files by LOCAL day; the disk assertions must use
|
|
37
|
+
// the same clock, not the UTC day (a UTC+8 machine runs 00:00-08:00 on the
|
|
38
|
+
// previous UTC date).
|
|
39
|
+
const { localDay } = require('../lib/structured-logger');
|
|
40
|
+
|
|
41
|
+
const ROOT = path.resolve(__dirname, '../..');
|
|
42
|
+
const ARTIFACTS = resolveEvidenceDir(path.resolve(ROOT, 'qa-autopilot/artifacts/parallel-20261023/logging-acceptance-r1/evidence/log005'));
|
|
43
|
+
|
|
44
|
+
const PROJECT_A = 'project-log005-alpha';
|
|
45
|
+
const run = crypto.randomBytes(6).toString('hex');
|
|
46
|
+
const TOTAL = 140;
|
|
47
|
+
const markerOf = index => `LOG005-R-${index}-${run}`;
|
|
48
|
+
const GOAL_EVENT = 'goal.updated';
|
|
49
|
+
const GOAL_BYTES = { a: 1600, b: 1601, c: 1602 };
|
|
50
|
+
|
|
51
|
+
async function main() {
|
|
52
|
+
fs.mkdirSync(ARTIFACTS, { recursive: true });
|
|
53
|
+
const evidence = { caseId: 'LOG-005', startedAt: new Date().toISOString(), total: TOTAL, oldestMarker: markerOf(0), newestMarker: markerOf(TOTAL - 1), phases: {} };
|
|
54
|
+
|
|
55
|
+
const fixture = await createLoggingAcceptanceFixture({
|
|
56
|
+
caseId: 'log005',
|
|
57
|
+
projects: [{ projectId: PROJECT_A, displayName: 'LOG005甲项目' }],
|
|
58
|
+
});
|
|
59
|
+
await writeProjectLogs(fixture.logger, PROJECT_A, Array.from({ length: TOTAL }, (_, index) => ({
|
|
60
|
+
level: index % 3 === 0 ? 'info' : index % 3 === 1 ? 'warn' : 'debug',
|
|
61
|
+
event: `log005.bulk.${index}`,
|
|
62
|
+
message: `${markerOf(index)} LOG005 bulk record ${index}`,
|
|
63
|
+
operationId: `op-log005-r${index}-${run}`,
|
|
64
|
+
})));
|
|
65
|
+
await fixture.logger.close();
|
|
66
|
+
|
|
67
|
+
const port = await freePort();
|
|
68
|
+
const proxyPort = await freePort();
|
|
69
|
+
const base = `http://127.0.0.1:${port}`;
|
|
70
|
+
const server = spawnServer({ root: ROOT, port, dataDir: fixture.dataDir, tag: 'log005', stdio: 'ignore' });
|
|
71
|
+
const putGoal = async target => {
|
|
72
|
+
const content = `LOG005-LIVE ${run} ${target}`.padEnd(target, '.');
|
|
73
|
+
const res = await fetch(`${base}/api/projects/${PROJECT_A}/goal`, { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ content }) });
|
|
74
|
+
return { status: res.status, bytes: Buffer.byteLength(content, 'utf8') };
|
|
75
|
+
};
|
|
76
|
+
let browser;
|
|
77
|
+
let proxy;
|
|
78
|
+
try {
|
|
79
|
+
await waitFor(async () => { try { return (await fetch(`${base}/api/health`)).ok; } catch { return false; } }, 'isolated web shell', 20000);
|
|
80
|
+
proxy = await createObservationProxy({ targetPort: port, port: proxyPort });
|
|
81
|
+
evidence.server = { pid: server.child.pid, port, proxyPort, dataDir: fixture.dataDir };
|
|
82
|
+
|
|
83
|
+
browser = await launchCdpBrowser({ chrome: findChrome(), profileDir: path.join(laneTmp, 'browser'), url: proxy.url, width: 1440, height: 900 });
|
|
84
|
+
const runExpr = expression => browser.evaluate(expression);
|
|
85
|
+
const click = selector => runExpr(`document.querySelector(${JSON.stringify(selector)}).click()`);
|
|
86
|
+
const readDom = () => runExpr(`JSON.stringify({ count: document.querySelectorAll('#logsContent .log-entry').length, records: [...document.querySelectorAll('#logsContent .log-detail')].map(node => JSON.parse(node.textContent)) })`);
|
|
87
|
+
evidence.browser = await runExpr('navigator.userAgent');
|
|
88
|
+
|
|
89
|
+
await waitFor(() => runExpr(`document.getElementById('projectSelect') && document.getElementById('projectSelect').length >= 1`), 'isolated project registered', 20000);
|
|
90
|
+
await runExpr(`document.getElementById('projectSelect').value = ${JSON.stringify(PROJECT_A)}; document.getElementById('projectSelect').dispatchEvent(new Event('change'))`);
|
|
91
|
+
await waitFor(() => runExpr(`document.getElementById('projectSelect').value === ${JSON.stringify(PROJECT_A)}`), 'project A selected');
|
|
92
|
+
|
|
93
|
+
// 系统状态 → 诊断 (project scope, empty search).
|
|
94
|
+
await click('#openSystem');
|
|
95
|
+
await runExpr(`document.getElementById('logsScope').value = 'project'`);
|
|
96
|
+
await click('#openLogs');
|
|
97
|
+
await waitFor(async () => {
|
|
98
|
+
const dom = JSON.parse(await readDom());
|
|
99
|
+
return dom.count === 100 && dom.records.length === 100;
|
|
100
|
+
}, 'DOM renders exactly the 100-entry first page', 20000);
|
|
101
|
+
const domPage1 = JSON.parse(await readDom());
|
|
102
|
+
const oldestInPage1 = domPage1.records.some(record => (record.message || '').includes(markerOf(0)));
|
|
103
|
+
const newestInPage1 = domPage1.records.some(record => (record.message || '').includes(markerOf(TOTAL - 1)));
|
|
104
|
+
|
|
105
|
+
// expected1 (bounded): live appends must keep the DOM capped at 100.
|
|
106
|
+
const putA = await putGoal(GOAL_BYTES.a);
|
|
107
|
+
await waitFor(async () => {
|
|
108
|
+
const dom = JSON.parse(await readDom());
|
|
109
|
+
return dom.records.some(record => record.event === GOAL_EVENT && record.context && record.context.bytes === GOAL_BYTES.a);
|
|
110
|
+
}, 'live append lands in the open dialog', 20000);
|
|
111
|
+
const putB = await putGoal(GOAL_BYTES.b);
|
|
112
|
+
const putC = await putGoal(GOAL_BYTES.c);
|
|
113
|
+
await waitFor(async () => {
|
|
114
|
+
const dom = JSON.parse(await readDom());
|
|
115
|
+
return dom.records.some(record => record.event === GOAL_EVENT && record.context && record.context.bytes === GOAL_BYTES.c);
|
|
116
|
+
}, 'all live appends received', 20000);
|
|
117
|
+
const domCapped = JSON.parse(await readDom());
|
|
118
|
+
const cappedIds = domCapped.records.map(record => record.id);
|
|
119
|
+
evidence.phases.boundedDom = {
|
|
120
|
+
page1Count: domPage1.count,
|
|
121
|
+
oldestMarkerInPage1: oldestInPage1,
|
|
122
|
+
newestMarkerInPage1: newestInPage1,
|
|
123
|
+
liveAppends: [putA, putB, putC],
|
|
124
|
+
countAfterLiveAppends: domCapped.count,
|
|
125
|
+
cappedAt100: domCapped.count === 100,
|
|
126
|
+
domIdsUnique: new Set(cappedIds).size === cappedIds.length,
|
|
127
|
+
oldestTrimmed: !domCapped.records.some(record => (record.message || '').includes(markerOf(0))) || domCapped.count === 100,
|
|
128
|
+
};
|
|
129
|
+
if (domCapped.count !== 100) throw new Error(`DOM is not bounded at 100 after live appends: ${domCapped.count}`);
|
|
130
|
+
if (!newestInPage1) throw new Error('first page lost the newest seeded record');
|
|
131
|
+
await browser.screenshot(path.join(ARTIFACTS, 'log005-bounded-dom.png'));
|
|
132
|
+
|
|
133
|
+
// expected2 (early records still queryable) — via the real UI search.
|
|
134
|
+
await runExpr(`document.getElementById('logsSearch').value = ${JSON.stringify(markerOf(0))}`);
|
|
135
|
+
await click('#loadLogs');
|
|
136
|
+
await waitFor(async () => {
|
|
137
|
+
const dom = JSON.parse(await readDom());
|
|
138
|
+
return dom.count === 1 && dom.records.length === 1 && (dom.records[0].message || '').includes(markerOf(0));
|
|
139
|
+
}, 'UI search finds the oldest (page-2) record', 20000);
|
|
140
|
+
evidence.phases.uiOldestQuery = { found: true, marker: markerOf(0) };
|
|
141
|
+
await browser.screenshot(path.join(ARTIFACTS, 'log005-oldest-query.png'));
|
|
142
|
+
|
|
143
|
+
// Via cursor pagination (API parity with the UI's own query mechanics).
|
|
144
|
+
const page1 = await fetch(`${base}/api/logs?pageSize=100&q=&projectId=${PROJECT_A}`).then(r => r.json());
|
|
145
|
+
const page2 = await fetch(`${base}/api/logs?pageSize=100&cursor=${encodeURIComponent(page1.nextCursor)}&projectId=${PROJECT_A}`).then(r => r.json());
|
|
146
|
+
const oldestApi = [...page1.entries, ...(page2.entries || [])].filter(entry => (entry.message || '').includes(markerOf(0)));
|
|
147
|
+
evidence.phases.cursorPagination = {
|
|
148
|
+
page1Count: page1.entries.length,
|
|
149
|
+
page1NextCursor: Boolean(page1.nextCursor),
|
|
150
|
+
page2Count: (page2.entries || []).length,
|
|
151
|
+
page2HasOldest: oldestApi.length === 1,
|
|
152
|
+
};
|
|
153
|
+
if (page1.entries.length !== 100 || !page1.nextCursor) throw new Error('first page is not a full bounded page with a cursor');
|
|
154
|
+
if (oldestApi.length !== 1) throw new Error('cursor pagination lost the oldest record');
|
|
155
|
+
|
|
156
|
+
// Via export (real file re-query path).
|
|
157
|
+
const exportRes = await fetch(`${base}/api/logs/export?projectId=${PROJECT_A}`);
|
|
158
|
+
const exportText = await exportRes.text();
|
|
159
|
+
const exportRecords = exportText.trim().split('\n').filter(Boolean).map(line => JSON.parse(line));
|
|
160
|
+
const exportOldest = exportRecords.filter(record => (record.message || '').includes(markerOf(0)));
|
|
161
|
+
evidence.phases.export = {
|
|
162
|
+
status: exportRes.status,
|
|
163
|
+
contentType: exportRes.headers.get('content-type'),
|
|
164
|
+
exportedCount: exportRecords.length,
|
|
165
|
+
hasAllSeeded: exportRecords.filter(record => (record.message || '').includes(`LOG005-R-`)).length >= TOTAL,
|
|
166
|
+
oldestExactlyOnce: exportOldest.length === 1,
|
|
167
|
+
};
|
|
168
|
+
if (exportRes.status !== 200 || exportOldest.length !== 1 || evidence.phases.export.hasAllSeeded === false) throw new Error('export lost early records');
|
|
169
|
+
|
|
170
|
+
// Disk truth.
|
|
171
|
+
const day = localDay();
|
|
172
|
+
const projectFile = path.join(fixture.dataDir, 'logs', 'projects', PROJECT_A, `${day}.jsonl`);
|
|
173
|
+
const diskRecords = fs.readFileSync(projectFile, 'utf8').trim().split('\n').filter(Boolean).map(line => JSON.parse(line));
|
|
174
|
+
const diskOldest = diskRecords.filter(record => (record.message || '').includes(markerOf(0)));
|
|
175
|
+
evidence.phases.disk = {
|
|
176
|
+
diskCount: diskRecords.length,
|
|
177
|
+
diskSeededCount: diskRecords.filter(record => (record.message || '').includes('LOG005-R-')).length,
|
|
178
|
+
oldestExactlyOnceOnDisk: diskOldest.length === 1,
|
|
179
|
+
};
|
|
180
|
+
if (diskRecords.filter(record => (record.message || '').includes('LOG005-R-')).length !== TOTAL) throw new Error('disk does not hold every seeded record');
|
|
181
|
+
if (diskOldest.length !== 1) throw new Error('disk lost or duplicated the oldest record');
|
|
182
|
+
|
|
183
|
+
if (browser.exceptions.length) throw new Error(`browser exceptions: ${JSON.stringify(browser.exceptions)}`);
|
|
184
|
+
evidence.verdict = { 'expected1 DOM有界(≤100)': 'PASS', 'expected2 早期记录仍可查询': 'PASS' };
|
|
185
|
+
console.log('LOG-005 PASS');
|
|
186
|
+
process.exitCode = 0;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
evidence.verdict = { failed: String((error && error.stack) || error) };
|
|
189
|
+
try {
|
|
190
|
+
if (browser) {
|
|
191
|
+
evidence.debugDom = JSON.parse(await browser.evaluate(`JSON.stringify({ dialogOpen: document.getElementById('logsDialog').open, entries: document.querySelectorAll('#logsContent .log-entry').length, search: document.getElementById('logsSearch').value })`));
|
|
192
|
+
evidence.debugConsoleErrors = browser.consoleErrors;
|
|
193
|
+
}
|
|
194
|
+
if (proxy) evidence.debugProxy = proxy.state.snapshot();
|
|
195
|
+
} catch {}
|
|
196
|
+
process.exitCode = 1;
|
|
197
|
+
console.error(`LOG-005 FAIL: ${error.message}`);
|
|
198
|
+
} finally {
|
|
199
|
+
evidence.finishedAt = new Date().toISOString();
|
|
200
|
+
try { if (browser) await browser.screenshot(path.join(ARTIFACTS, 'log005-final.png')); } catch {}
|
|
201
|
+
fs.mkdirSync(ARTIFACTS, { recursive: true });
|
|
202
|
+
fs.writeFileSync(path.join(ARTIFACTS, 'log005-evidence.json'), JSON.stringify(evidence, null, 2) + '\n');
|
|
203
|
+
try { await browser?.close(); } finally { try { if (proxy) await proxy.close(); } finally { try { await server.cleanup(); } finally { fixture.cleanup(); } } }
|
|
204
|
+
lane.cleanup();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
main();
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// P-LOG-ACCEPTANCE-01 LOG-006 acceptance driver (real product page in a real
|
|
3
|
+
// browser against an isolated managed instance).
|
|
4
|
+
//
|
|
5
|
+
// Case (qa-autopilot/config/test-cases.json LOG-006, P0):
|
|
6
|
+
// steps: 使用完全虚构的 canary 密钥(嵌套 error/context),验证写入、
|
|
7
|
+
// 查询、导出全链路脱敏。
|
|
8
|
+
// expected: 1) 写入路径:磁盘 JSONL 不含 canary 明文。
|
|
9
|
+
// 2) 查询与导出路径(API + 真实 UI DOM):不含 canary 明文,
|
|
10
|
+
// 敏感位置显示 [REDACTED]。
|
|
11
|
+
//
|
|
12
|
+
// Canary discipline: every canary is a fully fictional random string prefixed
|
|
13
|
+
// 'pkz-canary-'. Evidence records only sha256 prefixes, never raw canaries.
|
|
14
|
+
// Redaction under test is the real product path: write-time normalizeRecord
|
|
15
|
+
// (key-name pattern), query-time and export-time re-redaction, and the real
|
|
16
|
+
// diagnostics UI rendering the redacted JSON.
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
const crypto = require('node:crypto');
|
|
21
|
+
|
|
22
|
+
// Portable isolation (P-QA-LOG-INTEGRATION-5025): the temp root comes from
|
|
23
|
+
// PK_TEST_TEMP_ROOT or os.tmpdir() and must live outside the source tree;
|
|
24
|
+
// HOME/APPDATA/LOCALAPPDATA/TEMP/CODEX_HOME/CLAUDE_CONFIG_DIR and the dual
|
|
25
|
+
// product data roots are redirected before any other module is loaded.
|
|
26
|
+
const { installLoggingTestEnv, resolveEvidenceDir } = require('./helpers/parallel-logging-integration-5025-env');
|
|
27
|
+
const lane = installLoggingTestEnv({ caseId: 'LOG-006', prefix: 'log006' });
|
|
28
|
+
const laneTmp = lane.laneTmp;
|
|
29
|
+
|
|
30
|
+
const { spawnServer } = require('./helpers/spawn-server');
|
|
31
|
+
const { findChrome } = require('./helpers/find-chrome');
|
|
32
|
+
const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
|
|
33
|
+
const freePort = require('./helpers/free-port');
|
|
34
|
+
const { createLoggingAcceptanceFixture, writeProjectLogs } = require('./helpers/parallel-logging-acceptance-fixture');
|
|
35
|
+
const { createObservationProxy } = require('./helpers/parallel-logging-acceptance-proxy');
|
|
36
|
+
// The logger names its day files by LOCAL day; the disk assertions must use
|
|
37
|
+
// the same clock, not the UTC day (a UTC+8 machine runs 00:00-08:00 on the
|
|
38
|
+
// previous UTC date).
|
|
39
|
+
const { localDay } = require('../lib/structured-logger');
|
|
40
|
+
|
|
41
|
+
const ROOT = path.resolve(__dirname, '../..');
|
|
42
|
+
const ARTIFACTS = resolveEvidenceDir(path.resolve(ROOT, 'qa-autopilot/artifacts/parallel-20261023/logging-acceptance-r1/evidence/log006'));
|
|
43
|
+
|
|
44
|
+
const PROJECT_A = 'project-log006-alpha';
|
|
45
|
+
const run = crypto.randomBytes(6).toString('hex');
|
|
46
|
+
const MARK = `LOG006-REDACT-${run}`;
|
|
47
|
+
const CANARY = {
|
|
48
|
+
apiKey: `pkz-canary-apikey-${run}-KQ7`,
|
|
49
|
+
clientSecret: `pkz-canary-clientsecret-${run}-W21`,
|
|
50
|
+
refreshToken: `pkz-canary-refreshtoken-${run}-ZX9`,
|
|
51
|
+
authorization: `pkz-canary-authorization-${run}-AA3`,
|
|
52
|
+
bearer: `pkz-canary-bearer-${run}-BB4`,
|
|
53
|
+
urlToken: `pkz-canary-urltoken-${run}-CC5`,
|
|
54
|
+
errorBearer: `pkz-canary-errbearer-${run}-DD6`,
|
|
55
|
+
password: `pkz-canary-password-${run}-EE7`,
|
|
56
|
+
};
|
|
57
|
+
const canaryHashes = Object.fromEntries(Object.entries(CANARY).map(([name, value]) => [name, crypto.createHash('sha256').update(value).digest('hex').slice(0, 16)]));
|
|
58
|
+
|
|
59
|
+
async function main() {
|
|
60
|
+
fs.mkdirSync(ARTIFACTS, { recursive: true });
|
|
61
|
+
const evidence = { caseId: 'LOG-006', startedAt: new Date().toISOString(), marker: MARK, canarySha256Prefixes: canaryHashes, phases: {} };
|
|
62
|
+
const canaryValues = Object.values(CANARY);
|
|
63
|
+
const leakedCanaries = text => canaryValues.filter(canary => String(text).includes(canary));
|
|
64
|
+
|
|
65
|
+
const fixture = await createLoggingAcceptanceFixture({
|
|
66
|
+
caseId: 'log006',
|
|
67
|
+
projects: [{ projectId: PROJECT_A, displayName: 'LOG006甲项目' }],
|
|
68
|
+
});
|
|
69
|
+
const plainError = new Error(`auth failed for Bearer ${CANARY.errorBearer}`);
|
|
70
|
+
await writeProjectLogs(fixture.logger, PROJECT_A, [
|
|
71
|
+
{
|
|
72
|
+
level: 'info',
|
|
73
|
+
event: 'log006.redact.keys',
|
|
74
|
+
message: `${MARK} 键名模式脱敏记录`,
|
|
75
|
+
operationId: `op-log006-keys-${run}`,
|
|
76
|
+
context: {
|
|
77
|
+
apiKey: CANARY.apiKey,
|
|
78
|
+
clientSecret: CANARY.clientSecret,
|
|
79
|
+
refreshToken: CANARY.refreshToken,
|
|
80
|
+
nested: { authorization: CANARY.authorization, note: '普通字段' },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
level: 'warn',
|
|
85
|
+
event: 'log006.redact.string',
|
|
86
|
+
message: `${MARK} 字符串值脱敏记录`,
|
|
87
|
+
operationId: `op-log006-string-${run}`,
|
|
88
|
+
context: {
|
|
89
|
+
tokenText: `Bearer ${CANARY.bearer}`,
|
|
90
|
+
callbackUrl: `https://example.test/cb?token=${CANARY.urlToken}`,
|
|
91
|
+
plain: '普通值',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
level: 'error',
|
|
96
|
+
event: 'log006.redact.error',
|
|
97
|
+
message: `${MARK} error 序列化脱敏记录`,
|
|
98
|
+
operationId: `op-log006-error-${run}`,
|
|
99
|
+
context: { password: CANARY.password },
|
|
100
|
+
error: plainError,
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
await fixture.logger.close();
|
|
104
|
+
|
|
105
|
+
const port = await freePort();
|
|
106
|
+
const proxyPort = await freePort();
|
|
107
|
+
const base = `http://127.0.0.1:${port}`;
|
|
108
|
+
const server = spawnServer({ root: ROOT, port, dataDir: fixture.dataDir, tag: 'log006', stdio: 'ignore' });
|
|
109
|
+
let browser;
|
|
110
|
+
let proxy;
|
|
111
|
+
try {
|
|
112
|
+
await waitFor(async () => { try { return (await fetch(`${base}/api/health`)).ok; } catch { return false; } }, 'isolated web shell', 20000);
|
|
113
|
+
proxy = await createObservationProxy({ targetPort: port, port: proxyPort });
|
|
114
|
+
evidence.server = { pid: server.child.pid, port, proxyPort, dataDir: fixture.dataDir };
|
|
115
|
+
|
|
116
|
+
// expected1 (write path): raw disk JSONL never contains a canary.
|
|
117
|
+
const day = localDay();
|
|
118
|
+
const projectFile = path.join(fixture.dataDir, 'logs', 'projects', PROJECT_A, `${day}.jsonl`);
|
|
119
|
+
const diskRaw = fs.readFileSync(projectFile, 'utf8');
|
|
120
|
+
const diskLeak = leakedCanaries(diskRaw);
|
|
121
|
+
const diskRecords = diskRaw.trim().split('\n').filter(Boolean).map(line => JSON.parse(line));
|
|
122
|
+
const redactedRecordCount = diskRecords.filter(record => JSON.stringify(record).includes('[REDACTED]')).length;
|
|
123
|
+
evidence.phases.diskWrite = {
|
|
124
|
+
diskRecordCount: diskRecords.length,
|
|
125
|
+
diskLeakedCanaries: diskLeak.length,
|
|
126
|
+
recordsWithRedactedPlaceholder: redactedRecordCount,
|
|
127
|
+
redactedKeySample: (() => { const record = diskRecords.find(r => r.event === 'log006.redact.keys'); return record ? { apiKey: record.context && record.context.apiKey, nestedAuthorization: record.context && record.context.nested && record.context.nested.authorization } : null; })(),
|
|
128
|
+
};
|
|
129
|
+
if (diskLeak.length) throw new Error(`write path leaked canaries to disk: ${diskLeak.join(', ')}`);
|
|
130
|
+
if (redactedRecordCount < 3) throw new Error('write path did not redact every canary-bearing record');
|
|
131
|
+
|
|
132
|
+
// Query path (API parity before the UI step).
|
|
133
|
+
const getQuery = await fetch(`${base}/api/logs?pageSize=100&q=${encodeURIComponent(MARK)}&projectId=${PROJECT_A}`).then(r => r.json());
|
|
134
|
+
const getLeak = leakedCanaries(JSON.stringify(getQuery));
|
|
135
|
+
evidence.phases.queryApi = {
|
|
136
|
+
entryCount: (getQuery.entries || []).length,
|
|
137
|
+
leakedCanaries: getLeak.length,
|
|
138
|
+
redactedPlaceholdersPerEntry: (getQuery.entries || []).map(entry => (JSON.stringify(entry).match(/\[REDACTED\]/g) || []).length),
|
|
139
|
+
};
|
|
140
|
+
if ((getQuery.entries || []).length !== 3) throw new Error('canary records missing from the query path');
|
|
141
|
+
if (getLeak.length) throw new Error(`query path leaked canaries: ${getLeak.join(', ')}`);
|
|
142
|
+
if (evidence.phases.queryApi.redactedPlaceholdersPerEntry.some(count => count < 1)) throw new Error('query path returned unredacted sensitive fields');
|
|
143
|
+
|
|
144
|
+
// Real UI: open the diagnostics dialog and search the marker.
|
|
145
|
+
browser = await launchCdpBrowser({ chrome: findChrome(), profileDir: path.join(laneTmp, 'browser'), url: proxy.url, width: 1440, height: 900 });
|
|
146
|
+
const runExpr = expression => browser.evaluate(expression);
|
|
147
|
+
const click = selector => runExpr(`document.querySelector(${JSON.stringify(selector)}).click()`);
|
|
148
|
+
const readDom = () => runExpr(`JSON.stringify({ count: document.querySelectorAll('#logsContent .log-entry').length, records: [...document.querySelectorAll('#logsContent .log-detail')].map(node => JSON.parse(node.textContent)), dialogText: document.getElementById('logsDialog').textContent })`);
|
|
149
|
+
evidence.browser = await runExpr('navigator.userAgent');
|
|
150
|
+
|
|
151
|
+
await waitFor(() => runExpr(`document.getElementById('projectSelect') && document.getElementById('projectSelect').length >= 1`), 'isolated project registered', 20000);
|
|
152
|
+
await runExpr(`document.getElementById('projectSelect').value = ${JSON.stringify(PROJECT_A)}; document.getElementById('projectSelect').dispatchEvent(new Event('change'))`);
|
|
153
|
+
await waitFor(() => runExpr(`document.getElementById('projectSelect').value === ${JSON.stringify(PROJECT_A)}`), 'project A selected');
|
|
154
|
+
await click('#openSystem');
|
|
155
|
+
await runExpr(`document.getElementById('logsScope').value = 'project'; document.getElementById('logsSearch').value = ${JSON.stringify(MARK)}`);
|
|
156
|
+
await click('#openLogs');
|
|
157
|
+
await waitFor(async () => {
|
|
158
|
+
const dom = JSON.parse(await readDom());
|
|
159
|
+
return dom.count === 3 && dom.records.length === 3;
|
|
160
|
+
}, 'UI search renders the 3 canary records', 20000);
|
|
161
|
+
const domRedact = JSON.parse(await readDom());
|
|
162
|
+
const domLeak = leakedCanaries(domRedact.dialogText);
|
|
163
|
+
evidence.phases.uiDom = {
|
|
164
|
+
domEntryCount: domRedact.count,
|
|
165
|
+
leakedCanariesInDialog: domLeak.length,
|
|
166
|
+
redactedPlaceholdersPerRecord: domRedact.records.map(record => (JSON.stringify(record).match(/\[REDACTED\]/g) || []).length),
|
|
167
|
+
apiKeyFieldInDom: domRedact.records.find(r => r.event === 'log006.redact.keys')?.context?.apiKey,
|
|
168
|
+
};
|
|
169
|
+
if (domLeak.length) throw new Error(`UI leaked canaries into the DOM: ${domLeak.join(', ')}`);
|
|
170
|
+
if (evidence.phases.uiDom.redactedPlaceholdersPerRecord.some(count => count < 1)) throw new Error('UI rendered unredacted sensitive fields');
|
|
171
|
+
if (evidence.phases.uiDom.apiKeyFieldInDom !== '[REDACTED]') throw new Error('UI apiKey field is not the redaction placeholder');
|
|
172
|
+
await browser.screenshot(path.join(ARTIFACTS, 'log006-ui-redacted.png'));
|
|
173
|
+
|
|
174
|
+
// Export path: the real export file must be redacted end to end.
|
|
175
|
+
const exportRes = await fetch(`${base}/api/logs/export?q=${encodeURIComponent(MARK)}&projectId=${PROJECT_A}`);
|
|
176
|
+
const exportText = await exportRes.text();
|
|
177
|
+
const exportLeak = leakedCanaries(exportText);
|
|
178
|
+
const exportRecords = exportText.trim().split('\n').filter(Boolean).map(line => JSON.parse(line));
|
|
179
|
+
evidence.phases.export = {
|
|
180
|
+
status: exportRes.status,
|
|
181
|
+
exportedCount: exportRecords.length,
|
|
182
|
+
leakedCanaries: exportLeak.length,
|
|
183
|
+
redactedPlaceholdersPerRecord: exportRecords.map(record => (JSON.stringify(record).match(/\[REDACTED\]/g) || []).length),
|
|
184
|
+
};
|
|
185
|
+
if (exportRes.status !== 200 || exportRecords.length !== 3) throw new Error('export path lost canary records');
|
|
186
|
+
if (exportLeak.length) throw new Error(`export path leaked canaries: ${exportLeak.join(', ')}`);
|
|
187
|
+
if (evidence.phases.export.redactedPlaceholdersPerRecord.some(count => count < 1)) throw new Error('export path returned unredacted sensitive fields');
|
|
188
|
+
|
|
189
|
+
if (browser.exceptions.length) throw new Error(`browser exceptions: ${JSON.stringify(browser.exceptions)}`);
|
|
190
|
+
evidence.verdict = { 'expected1 写入路径磁盘无canary明文': 'PASS', 'expected2 查询/导出/API/UI全脱敏': 'PASS' };
|
|
191
|
+
console.log('LOG-006 PASS');
|
|
192
|
+
process.exitCode = 0;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
evidence.verdict = { failed: String((error && error.stack) || error) };
|
|
195
|
+
try {
|
|
196
|
+
if (browser) {
|
|
197
|
+
evidence.debugDom = JSON.parse(await browser.evaluate(`JSON.stringify({ dialogOpen: document.getElementById('logsDialog').open, entries: document.querySelectorAll('#logsContent .log-entry').length })`));
|
|
198
|
+
evidence.debugConsoleErrors = browser.consoleErrors;
|
|
199
|
+
}
|
|
200
|
+
if (proxy) evidence.debugProxy = proxy.state.snapshot();
|
|
201
|
+
} catch {}
|
|
202
|
+
process.exitCode = 1;
|
|
203
|
+
console.error(`LOG-006 FAIL: ${error.message}`);
|
|
204
|
+
} finally {
|
|
205
|
+
evidence.finishedAt = new Date().toISOString();
|
|
206
|
+
try { if (browser) await browser.screenshot(path.join(ARTIFACTS, 'log006-final.png')); } catch {}
|
|
207
|
+
fs.mkdirSync(ARTIFACTS, { recursive: true });
|
|
208
|
+
fs.writeFileSync(path.join(ARTIFACTS, 'log006-evidence.json'), JSON.stringify(evidence, null, 2) + '\n');
|
|
209
|
+
try { await browser?.close(); } finally { try { if (proxy) await proxy.close(); } finally { try { await server.cleanup(); } finally { fixture.cleanup(); } } }
|
|
210
|
+
lane.cleanup();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
main();
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// P-LOG-ACCEPTANCE-01 LOG-007 acceptance driver (real product page in a real
|
|
3
|
+
// browser against an isolated managed instance).
|
|
4
|
+
//
|
|
5
|
+
// Case (qa-autopilot/config/test-cases.json LOG-007, P1):
|
|
6
|
+
// steps: 分别核对API日志级别、时间、component字段与当前UI控件。
|
|
7
|
+
// expected: 1) API支持不等于UI已提供控件(如实记录差异)。
|
|
8
|
+
// 2) README与DOM差异记录而不造按钮。
|
|
9
|
+
//
|
|
10
|
+
// What this driver does NOT do: invent level/time/component controls. The
|
|
11
|
+
// real dialog DOM is inventoried; the real API filter surface is probed with
|
|
12
|
+
// fixture records; the README claims are hashed and diffed against the DOM
|
|
13
|
+
// inventory and the logger source. Differences are recorded, not patched.
|
|
14
|
+
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
const crypto = require('node:crypto');
|
|
18
|
+
|
|
19
|
+
// Portable isolation (P-QA-LOG-INTEGRATION-5025): the temp root comes from
|
|
20
|
+
// PK_TEST_TEMP_ROOT or os.tmpdir() and must live outside the source tree;
|
|
21
|
+
// HOME/APPDATA/LOCALAPPDATA/TEMP/CODEX_HOME/CLAUDE_CONFIG_DIR and the dual
|
|
22
|
+
// product data roots are redirected before any other module is loaded.
|
|
23
|
+
const { installLoggingTestEnv, resolveEvidenceDir } = require('./helpers/parallel-logging-integration-5025-env');
|
|
24
|
+
const lane = installLoggingTestEnv({ caseId: 'LOG-007', prefix: 'log007' });
|
|
25
|
+
const laneTmp = lane.laneTmp;
|
|
26
|
+
|
|
27
|
+
const { spawnServer } = require('./helpers/spawn-server');
|
|
28
|
+
const { findChrome } = require('./helpers/find-chrome');
|
|
29
|
+
const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
|
|
30
|
+
const freePort = require('./helpers/free-port');
|
|
31
|
+
const { createLoggingAcceptanceFixture, writeProjectLogs } = require('./helpers/parallel-logging-acceptance-fixture');
|
|
32
|
+
const { createObservationProxy } = require('./helpers/parallel-logging-acceptance-proxy');
|
|
33
|
+
// The API filters from/to are compared against the record's LOCAL day
|
|
34
|
+
// (LogRepository.matches recomputes localDay(record.ts)). The day probes are
|
|
35
|
+
// anchored to the record ts the server itself echoes — never to a second
|
|
36
|
+
// clock — so day boundaries and machine zones cannot desynchronize them.
|
|
37
|
+
const { localDay } = require('../lib/structured-logger');
|
|
38
|
+
|
|
39
|
+
const ROOT = path.resolve(__dirname, '../..');
|
|
40
|
+
const ARTIFACTS = resolveEvidenceDir(path.resolve(ROOT, 'qa-autopilot/artifacts/parallel-20261023/logging-acceptance-r1/evidence/log007'));
|
|
41
|
+
|
|
42
|
+
const PROJECT_A = 'project-log007-alpha';
|
|
43
|
+
const run = crypto.randomBytes(6).toString('hex');
|
|
44
|
+
const sha256 = file => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
45
|
+
|
|
46
|
+
async function main() {
|
|
47
|
+
fs.mkdirSync(ARTIFACTS, { recursive: true });
|
|
48
|
+
const evidence = { caseId: 'LOG-007', startedAt: new Date().toISOString(), run, phases: {} };
|
|
49
|
+
|
|
50
|
+
// Fixture records engineered so every API filter has an exact expected set.
|
|
51
|
+
// groupEarly and groupLate are separated by a real wall-clock gap so from/to
|
|
52
|
+
// windows split them deterministically.
|
|
53
|
+
const early = [
|
|
54
|
+
{ level: 'info', event: `log007.early.info`, component: 'log007-comp-a', message: `LOG007-EARLY-INFO-${run} 早组信息`, operationId: `op-log007-early-info-${run}`, commitSha: 'a'.repeat(40) },
|
|
55
|
+
{ level: 'warn', event: `log007.early.warn`, component: 'log007-comp-b', message: `LOG007-EARLY-WARN-${run} 早组警告`, operationId: `op-log007-early-warn-${run}`, commitSha: 'b'.repeat(40) },
|
|
56
|
+
];
|
|
57
|
+
const late = [
|
|
58
|
+
{ level: 'warn', event: `log007.late.warn`, component: 'log007-comp-b', message: `LOG007-LATE-WARN-${run} 晚组警告`, operationId: `op-log007-late-warn-${run}`, commitSha: 'c'.repeat(40) },
|
|
59
|
+
{ level: 'error', event: `log007.late.error`, component: 'log007-comp-c', message: `LOG007-LATE-ERROR-${run} 晚组错误`, operationId: `op-log007-late-error-${run}`, commitSha: 'd'.repeat(40) },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const fixture = await createLoggingAcceptanceFixture({
|
|
63
|
+
caseId: 'log007',
|
|
64
|
+
projects: [{ projectId: PROJECT_A, displayName: 'LOG007甲项目' }],
|
|
65
|
+
});
|
|
66
|
+
await writeProjectLogs(fixture.logger, PROJECT_A, early);
|
|
67
|
+
await writeProjectLogs(fixture.logger, PROJECT_A, late);
|
|
68
|
+
await fixture.logger.close();
|
|
69
|
+
|
|
70
|
+
const port = await freePort();
|
|
71
|
+
const proxyPort = await freePort();
|
|
72
|
+
const base = `http://127.0.0.1:${port}`;
|
|
73
|
+
const server = spawnServer({ root: ROOT, port, dataDir: fixture.dataDir, tag: 'log007', stdio: 'ignore' });
|
|
74
|
+
let browser;
|
|
75
|
+
let proxy;
|
|
76
|
+
try {
|
|
77
|
+
await waitFor(async () => { try { return (await fetch(`${base}/api/health`)).ok; } catch { return false; } }, 'isolated web shell', 20000);
|
|
78
|
+
proxy = await createObservationProxy({ targetPort: port, port: proxyPort });
|
|
79
|
+
evidence.server = { pid: server.child.pid, port, proxyPort, dataDir: fixture.dataDir };
|
|
80
|
+
|
|
81
|
+
// API filter surface (real server, exact expected sets).
|
|
82
|
+
const query = async params => {
|
|
83
|
+
const res = await fetch(`${base}/api/logs?${params}`);
|
|
84
|
+
const body = await res.json();
|
|
85
|
+
return { status: res.status, count: (body.entries || []).length, events: (body.entries || []).map(entry => entry.event).sort(), levels: [...new Set((body.entries || []).map(entry => entry.level))], entries: body.entries || [] };
|
|
86
|
+
};
|
|
87
|
+
const apiAll = await query(`pageSize=100&projectId=${PROJECT_A}`);
|
|
88
|
+
const apiWarn = await query(`pageSize=100&projectId=${PROJECT_A}&levels=warn`);
|
|
89
|
+
const apiError = await query(`pageSize=100&projectId=${PROJECT_A}&levels=error`);
|
|
90
|
+
const apiComponent = await query(`pageSize=100&projectId=${PROJECT_A}&component=log007-comp-b`);
|
|
91
|
+
// from/to are local-day strings by design (LogRepository.matches compares
|
|
92
|
+
// localDay(record.ts)). The day windows are anchored to the records the
|
|
93
|
+
// server itself echoed (never a second clock, never now±24h) and moved by
|
|
94
|
+
// pure day-string arithmetic. The live server ALSO writes project-scoped
|
|
95
|
+
// startup records (bridge_consumer.identity_backfilled lands in the same
|
|
96
|
+
// day file ~0.2-1s after health), so a bare count===4 is a race against
|
|
97
|
+
// the server's own async writes — the R7 CI failure on both Linux jobs.
|
|
98
|
+
// The actual contract: every entry of a from=X&to=X window carries
|
|
99
|
+
// localDay(ts)===X, and the four fixture records appear in exactly their
|
|
100
|
+
// own day's window.
|
|
101
|
+
const fixtureEvents = [...early, ...late].map(record => record.event);
|
|
102
|
+
const fixtureDay = localDay(apiError.entries[0].ts);
|
|
103
|
+
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);
|
|
104
|
+
const nextDay = shiftDay(fixtureDay, 1);
|
|
105
|
+
const prevDay = shiftDay(fixtureDay, -1);
|
|
106
|
+
const apiFromToday = await query(`pageSize=100&projectId=${PROJECT_A}&from=${fixtureDay}&to=${fixtureDay}`);
|
|
107
|
+
const apiFromTomorrow = await query(`pageSize=100&projectId=${PROJECT_A}&from=${nextDay}&to=${nextDay}`);
|
|
108
|
+
const apiToYesterday = await query(`pageSize=100&projectId=${PROJECT_A}&from=${prevDay}&to=${prevDay}`);
|
|
109
|
+
const apiEvent = await query(`pageSize=100&projectId=${PROJECT_A}&event=log007.late.error`);
|
|
110
|
+
const apiOperation = await query(`pageSize=100&projectId=${PROJECT_A}&operationId=op-log007-early-warn-${run}`);
|
|
111
|
+
const apiCommit = await query(`pageSize=100&projectId=${PROJECT_A}&commitSha=${'d'.repeat(40)}`);
|
|
112
|
+
const apiFullTextMarker = await query(`pageSize=100&projectId=${PROJECT_A}&q=${encodeURIComponent(`LOG007-EARLY-WARN-${run}`)}`);
|
|
113
|
+
evidence.phases.apiFilters = { apiAll, apiWarn, apiError, apiComponent, apiEvent, apiOperation, apiCommit, apiFullTextMarker };
|
|
114
|
+
evidence.phases.apiDayWindows = {
|
|
115
|
+
fixtureDay, nextDay, prevDay,
|
|
116
|
+
today: { count: apiFromToday.count, events: apiFromToday.events, nonFixtureRecords: apiFromToday.entries.filter(entry => !fixtureEvents.includes(entry.event)).map(entry => ({ event: entry.event, component: entry.component, ts: entry.ts })) },
|
|
117
|
+
tomorrow: { count: apiFromTomorrow.count, events: apiFromTomorrow.events },
|
|
118
|
+
yesterday: { count: apiToYesterday.count, events: apiToYesterday.events },
|
|
119
|
+
};
|
|
120
|
+
const everyEntryOnDay = (result, day) => result.entries.every(entry => localDay(entry.ts) === day);
|
|
121
|
+
const containsAllEvents = (result, events) => events.every(event => result.events.includes(event));
|
|
122
|
+
const excludesFixtureEvents = result => result.entries.every(entry => !fixtureEvents.includes(entry.event));
|
|
123
|
+
const apiSupported = {
|
|
124
|
+
levels: apiWarn.count === 2 && apiWarn.levels.every(level => level === 'warn') && apiError.count === 1,
|
|
125
|
+
component: apiComponent.count === 2 && apiComponent.events.filter(event => event.endsWith('warn')).length === 2,
|
|
126
|
+
timeDayGranularity: apiFromToday.count >= fixtureEvents.length
|
|
127
|
+
&& containsAllEvents(apiFromToday, fixtureEvents)
|
|
128
|
+
&& everyEntryOnDay(apiFromToday, fixtureDay)
|
|
129
|
+
&& excludesFixtureEvents(apiFromTomorrow) && everyEntryOnDay(apiFromTomorrow, nextDay)
|
|
130
|
+
&& excludesFixtureEvents(apiToYesterday) && everyEntryOnDay(apiToYesterday, prevDay),
|
|
131
|
+
event: apiEvent.count === 1,
|
|
132
|
+
operationId: apiOperation.count === 1,
|
|
133
|
+
commitSha: apiCommit.count === 1,
|
|
134
|
+
fullText: apiFullTextMarker.count === 1,
|
|
135
|
+
};
|
|
136
|
+
// component events check: both warn records share comp-b.
|
|
137
|
+
apiSupported.component = apiComponent.count === 2 && apiComponent.events.filter(event => event.endsWith('warn')).length === 2;
|
|
138
|
+
evidence.phases.apiSupported = apiSupported;
|
|
139
|
+
|
|
140
|
+
// Real UI DOM inventory: every control that actually exists.
|
|
141
|
+
browser = await launchCdpBrowser({ chrome: findChrome(), profileDir: path.join(laneTmp, 'browser'), url: proxy.url, width: 1440, height: 900 });
|
|
142
|
+
const runExpr = expression => browser.evaluate(expression);
|
|
143
|
+
const click = selector => runExpr(`document.querySelector(${JSON.stringify(selector)}).click()`);
|
|
144
|
+
evidence.browser = await runExpr('navigator.userAgent');
|
|
145
|
+
await waitFor(() => runExpr(`document.getElementById('projectSelect') && document.getElementById('projectSelect').length >= 1`), 'isolated project registered', 20000);
|
|
146
|
+
await runExpr(`document.getElementById('projectSelect').value = ${JSON.stringify(PROJECT_A)}; document.getElementById('projectSelect').dispatchEvent(new Event('change'))`);
|
|
147
|
+
await waitFor(() => runExpr(`document.getElementById('projectSelect').value === ${JSON.stringify(PROJECT_A)}`), 'project A selected');
|
|
148
|
+
await click('#openSystem');
|
|
149
|
+
await click('#openLogs');
|
|
150
|
+
await waitFor(() => runExpr(`document.getElementById('logsDialog').open === true && document.querySelectorAll('#logsContent .log-entry').length >= 4`), 'diagnostics dialog rendered', 20000);
|
|
151
|
+
const domInventory = JSON.parse(await runExpr(`JSON.stringify({
|
|
152
|
+
controls: [...document.querySelectorAll('#logsDialog button, #logsDialog input, #logsDialog select, #logsDialog textarea')].map(node => ({ tag: node.tagName.toLowerCase(), id: node.id || null, type: node.getAttribute('type'), ariaLabel: node.getAttribute('aria-label'), text: (node.textContent || '').trim().slice(0, 24) })),
|
|
153
|
+
statusText: document.getElementById('logsStatus').textContent,
|
|
154
|
+
searchPlaceholder: document.getElementById('logsSearch').placeholder,
|
|
155
|
+
scopeOptions: [...document.getElementById('logsScope').options].map(option => ({ value: option.value, text: option.textContent })),
|
|
156
|
+
})`));
|
|
157
|
+
evidence.phases.domInventory = domInventory;
|
|
158
|
+
const controls = domInventory.controls;
|
|
159
|
+
const ids = controls.map(control => control.id).filter(Boolean);
|
|
160
|
+
const uiHasOnly = { scope: ids.includes('logsScope'), search: ids.includes('logsSearch'), load: ids.includes('loadLogs'), export: ids.includes('exportLogs') };
|
|
161
|
+
const banned = /level|级别|time|时间|component|组件|event|事件|commit/i;
|
|
162
|
+
const extraFilterControls = controls.filter(control => control.id !== 'logsScope' && control.id !== 'logsSearch' && control.id !== 'loadLogs' && control.id !== 'exportLogs' && !(control.tag === 'button' && !control.id && (control.text === '×' || control.ariaLabel === '关闭')));
|
|
163
|
+
evidence.phases.uiControlSurface = {
|
|
164
|
+
uiHasOnly,
|
|
165
|
+
unexpectedFilterControls: extraFilterControls,
|
|
166
|
+
anyControlMentionsBannedFilter: controls.filter(control => banned.test([control.id, control.ariaLabel, control.text, control.searchPlaceholder || ''].join(' '))).map(control => control.id || control.text),
|
|
167
|
+
};
|
|
168
|
+
// The one input's own placeholder names its full-text nature.
|
|
169
|
+
evidence.phases.uiControlSurface.searchPlaceholder = domInventory.searchPlaceholder;
|
|
170
|
+
await browser.screenshot(path.join(ARTIFACTS, 'log007-dom-inventory.png'));
|
|
171
|
+
|
|
172
|
+
// README vs DOM/source difference record (read-only; never patched here).
|
|
173
|
+
const readmePath = path.join(ROOT, 'docs', 'README.zh-CN.md');
|
|
174
|
+
const readmeHash = sha256(readmePath);
|
|
175
|
+
const readmeLines = fs.readFileSync(readmePath, 'utf8').split('\n');
|
|
176
|
+
const claimLines = readmeLines.slice(170, 181).map((text, offset) => ({ line: offset + 171, text: text.trim() })).filter(entry => entry.text);
|
|
177
|
+
const loggerSource = fs.readFileSync(path.join(ROOT, '_site', 'lib', 'structured-logger.js'), 'utf8');
|
|
178
|
+
const productSourceHash = sha256(path.join(ROOT, 'ui', 'product.js'));
|
|
179
|
+
evidence.phases.readmeVsReality = {
|
|
180
|
+
readmeSha256: readmeHash,
|
|
181
|
+
uiProductSha256: productSourceHash,
|
|
182
|
+
claimLines,
|
|
183
|
+
loggerHas: {
|
|
184
|
+
segmentRotation: /segment|rotat/i.test(loggerSource),
|
|
185
|
+
retentionEnforcement: /retentionDays|prune[A-Z_]|\bdeleteOld|cleanupOld/i.test(loggerSource),
|
|
186
|
+
totalSizePolicy: /maxTotalSize|enforceQuota/i.test(loggerSource),
|
|
187
|
+
},
|
|
188
|
+
domOffers: { scope: true, fullText: true, level: false, time: false, component: false, event: false, commitSha: false, pause: false },
|
|
189
|
+
apiOffers: apiSupported,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
if (browser.exceptions.length) throw new Error(`browser exceptions: ${JSON.stringify(browser.exceptions)}`);
|
|
193
|
+
const expected1 = apiSupported.levels && apiSupported.component && apiSupported.timeDayGranularity && apiSupported.event && apiSupported.operationId && apiSupported.commitSha && uiHasOnly.scope && uiHasOnly.search && extraFilterControls.length === 0;
|
|
194
|
+
const expected2 = claimLines.length > 0 && extraFilterControls.length === 0;
|
|
195
|
+
evidence.verdict = {
|
|
196
|
+
'expected1 API支持而UI未提供控件(差异已记录)': expected1 ? 'PASS' : 'FAIL',
|
|
197
|
+
'expected2 README与DOM差异已记录且未造按钮': expected2 ? 'PASS' : 'FAIL',
|
|
198
|
+
};
|
|
199
|
+
if (!expected1 || !expected2) throw new Error(`difference recording failed: ${JSON.stringify({ apiSupported, uiHasOnly, extraFilterControls: extraFilterControls.length })}`);
|
|
200
|
+
console.log('LOG-007 PASS');
|
|
201
|
+
process.exitCode = 0;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
evidence.verdict = { failed: String((error && error.stack) || error) };
|
|
204
|
+
try {
|
|
205
|
+
if (browser) {
|
|
206
|
+
evidence.debugDom = JSON.parse(await browser.evaluate(`JSON.stringify({ dialogOpen: document.getElementById('logsDialog').open, entries: document.querySelectorAll('#logsContent .log-entry').length })`));
|
|
207
|
+
evidence.debugConsoleErrors = browser.consoleErrors;
|
|
208
|
+
}
|
|
209
|
+
if (proxy) evidence.debugProxy = proxy.state.snapshot();
|
|
210
|
+
} catch {}
|
|
211
|
+
process.exitCode = 1;
|
|
212
|
+
console.error(`LOG-007 FAIL: ${error.message}`);
|
|
213
|
+
} finally {
|
|
214
|
+
evidence.finishedAt = new Date().toISOString();
|
|
215
|
+
try { if (browser) await browser.screenshot(path.join(ARTIFACTS, 'log007-final.png')); } catch {}
|
|
216
|
+
fs.mkdirSync(ARTIFACTS, { recursive: true });
|
|
217
|
+
fs.writeFileSync(path.join(ARTIFACTS, 'log007-evidence.json'), JSON.stringify(evidence, null, 2) + '\n');
|
|
218
|
+
try { await browser?.close(); } finally { try { if (proxy) await proxy.close(); } finally { try { await server.cleanup(); } finally { fixture.cleanup(); } } }
|
|
219
|
+
lane.cleanup();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
main();
|