@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,310 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// NAV-007 — 分辨率矩阵。
|
|
3
|
+
//
|
|
4
|
+
// The six pages of the shell must render without unintended horizontal
|
|
5
|
+
// overflow and without a control being covered, in four desktop viewports
|
|
6
|
+
// and in both themes.
|
|
7
|
+
//
|
|
8
|
+
// The regression drives the real product UI in a real browser against an
|
|
9
|
+
// isolated instance (own port, own data dir, own knowledge dir, own browser
|
|
10
|
+
// profile). Every viewport is a real CDP device metrics override, every page
|
|
11
|
+
// is opened by a real pointer click on the real navigation control, and every
|
|
12
|
+
// screenshot is a real PNG of the real rendered page.
|
|
13
|
+
//
|
|
14
|
+
// The two kinds of evidence are kept apart on purpose:
|
|
15
|
+
//
|
|
16
|
+
// * The geometry evidence is machine checked: the bounding rect of every
|
|
17
|
+
// visible control, the horizontal overflow of every container, and the
|
|
18
|
+
// hit test of every control that proves nothing stands in front of it.
|
|
19
|
+
// These assertions decide the exit code of this test.
|
|
20
|
+
// * The screenshot evidence is for a human reviewer. This repository holds
|
|
21
|
+
// no approved visual baseline for the product, so the test does NOT
|
|
22
|
+
// compare the screenshots against a baseline and does NOT claim a
|
|
23
|
+
// baseline match. It writes the screenshots, their pixel statistics and
|
|
24
|
+
// an index, and the human review of that material stays an open item.
|
|
25
|
+
//
|
|
26
|
+
// Screenshots are written to PK_TEST_EVIDENCE_DIR when the batch sets it, so
|
|
27
|
+
// this tracked file stays free of machine-specific paths.
|
|
28
|
+
|
|
29
|
+
const fs = require('node:fs');
|
|
30
|
+
const path = require('node:path');
|
|
31
|
+
const assert = require('node:assert/strict');
|
|
32
|
+
|
|
33
|
+
const { startHarness, makeRepo } = require('./helpers/parallel-nav-shell');
|
|
34
|
+
|
|
35
|
+
const EVIDENCE = process.env.PK_TEST_EVIDENCE_DIR ? path.resolve(process.env.PK_TEST_EVIDENCE_DIR) : null;
|
|
36
|
+
|
|
37
|
+
const VIEWPORTS = [
|
|
38
|
+
{ name: '1366x768', width: 1366, height: 768 },
|
|
39
|
+
{ name: '1440x900', width: 1440, height: 900 },
|
|
40
|
+
{ name: '1600x900', width: 1600, height: 900 },
|
|
41
|
+
{ name: '1920x1080', width: 1920, height: 1080 },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const THEMES = ['light', 'dark'];
|
|
45
|
+
|
|
46
|
+
const PAGES = [
|
|
47
|
+
{ key: 'home', view: 'home' },
|
|
48
|
+
{ key: 'work', view: 'work' },
|
|
49
|
+
{ key: 'search', view: 'search' },
|
|
50
|
+
{ key: 'memory', view: 'memory' },
|
|
51
|
+
{ key: 'projects', view: 'projects' },
|
|
52
|
+
{ key: 'activity', view: 'activity' },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The bounding rect evidence of the rendered page: the overflow of every
|
|
57
|
+
* container and the rect and hit test of every visible control.
|
|
58
|
+
*/
|
|
59
|
+
const geometry = harness => harness.run(`(() => {
|
|
60
|
+
const active = document.querySelector('.view.active');
|
|
61
|
+
const doc = document.documentElement;
|
|
62
|
+
const describe = el => {
|
|
63
|
+
const parts = [];
|
|
64
|
+
if (el.id) parts.push('#' + el.id);
|
|
65
|
+
if (el.className && typeof el.className === 'string') parts.push('.' + el.className.trim().split(/\\s+/).join('.'));
|
|
66
|
+
return el.tagName.toLowerCase() + parts.join('');
|
|
67
|
+
};
|
|
68
|
+
const overflowing = [];
|
|
69
|
+
for (const el of document.querySelectorAll('html, body, .app, .sidebar, main, .content, .view, .topbar')) {
|
|
70
|
+
const style = getComputedStyle(el);
|
|
71
|
+
if (style.display === 'none') continue;
|
|
72
|
+
if (el.scrollWidth > el.clientWidth + 1) {
|
|
73
|
+
overflowing.push({ element: describe(el), scrollWidth: el.scrollWidth, clientWidth: el.clientWidth });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const controls = [];
|
|
77
|
+
for (const el of document.querySelectorAll('button, select, input, textarea, a[href]')) {
|
|
78
|
+
if (el.closest('iframe')) continue;
|
|
79
|
+
const style = getComputedStyle(el);
|
|
80
|
+
if (style.display === 'none' || style.visibility === 'hidden') continue;
|
|
81
|
+
const rect = el.getBoundingClientRect();
|
|
82
|
+
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
83
|
+
// The part of the control the user can really see. A control that
|
|
84
|
+
// crosses the lower edge of the viewport is not covered: the page
|
|
85
|
+
// scrolls, and the visible part of it must be reachable.
|
|
86
|
+
const visibleLeft = Math.max(rect.left, 0);
|
|
87
|
+
const visibleRight = Math.min(rect.right, innerWidth);
|
|
88
|
+
const visibleTop = Math.max(rect.top, 0);
|
|
89
|
+
const visibleBottom = Math.min(rect.bottom, innerHeight);
|
|
90
|
+
const visibleWidth = Math.round(visibleRight - visibleLeft);
|
|
91
|
+
const visibleHeight = Math.round(visibleBottom - visibleTop);
|
|
92
|
+
const inViewport = visibleWidth > 0 && visibleHeight > 0;
|
|
93
|
+
let covered = false;
|
|
94
|
+
let coveredBy = '';
|
|
95
|
+
let hitX = 0;
|
|
96
|
+
let hitY = 0;
|
|
97
|
+
if (inViewport) {
|
|
98
|
+
// The hit test runs on the centre of the VISIBLE part of the control.
|
|
99
|
+
// The centre of the whole control may lie below the fold, where the
|
|
100
|
+
// browser reports no element at all - that is a scroll position, not
|
|
101
|
+
// an occlusion.
|
|
102
|
+
hitX = Math.round((visibleLeft + visibleRight) / 2);
|
|
103
|
+
hitY = Math.round((visibleTop + visibleBottom) / 2);
|
|
104
|
+
const hit = document.elementFromPoint(hitX, hitY);
|
|
105
|
+
covered = !(hit && (hit === el || el.contains(hit) || el.contains(hit.parentElement)));
|
|
106
|
+
coveredBy = hit ? (hit.id || hit.tagName) : 'nothing';
|
|
107
|
+
}
|
|
108
|
+
controls.push({
|
|
109
|
+
element: describe(el),
|
|
110
|
+
id: el.id || '',
|
|
111
|
+
label: (el.textContent || '').trim().slice(0, 24),
|
|
112
|
+
left: Math.round(rect.left), right: Math.round(rect.right),
|
|
113
|
+
top: Math.round(rect.top), bottom: Math.round(rect.bottom),
|
|
114
|
+
width: Math.round(rect.width), height: Math.round(rect.height),
|
|
115
|
+
visibleWidth, visibleHeight,
|
|
116
|
+
hitX, hitY,
|
|
117
|
+
belowFold: rect.top >= innerHeight,
|
|
118
|
+
outsideHorizontally: rect.left < -1 || rect.right > innerWidth + 1,
|
|
119
|
+
inViewport,
|
|
120
|
+
covered,
|
|
121
|
+
coveredBy,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
viewport: { width: innerWidth, height: innerHeight },
|
|
126
|
+
theme: document.documentElement.dataset.theme || '',
|
|
127
|
+
activeView: active ? active.id : '',
|
|
128
|
+
docScrollWidth: doc.scrollWidth,
|
|
129
|
+
docClientWidth: doc.clientWidth,
|
|
130
|
+
viewScrollWidth: active ? active.scrollWidth : 0,
|
|
131
|
+
viewClientWidth: active ? active.clientWidth : 0,
|
|
132
|
+
overflowing,
|
|
133
|
+
controls,
|
|
134
|
+
};
|
|
135
|
+
})()`);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The pixel statistics of a real screenshot. The PNG is decoded by the
|
|
139
|
+
* browser itself, so the numbers come from the rendered pixels and not from
|
|
140
|
+
* the DOM.
|
|
141
|
+
*/
|
|
142
|
+
async function pixelStats(harness, base64) {
|
|
143
|
+
return harness.run(`(async () => {
|
|
144
|
+
const img = new Image();
|
|
145
|
+
img.src = 'data:image/png;base64,${base64}';
|
|
146
|
+
await img.decode();
|
|
147
|
+
// The picture is sampled on a small grid: the browser still decodes the
|
|
148
|
+
// real PNG, but the statistics cost a fraction of a full-size scan.
|
|
149
|
+
const canvas = document.createElement('canvas');
|
|
150
|
+
canvas.width = 96;
|
|
151
|
+
canvas.height = 54;
|
|
152
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
153
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
154
|
+
const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
155
|
+
let sum = 0;
|
|
156
|
+
const buckets = new Set();
|
|
157
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
158
|
+
const r = data[i], g = data[i + 1], b = data[i + 2];
|
|
159
|
+
sum += 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
160
|
+
buckets.add(((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4));
|
|
161
|
+
}
|
|
162
|
+
const pixels = data.length / 4;
|
|
163
|
+
return {
|
|
164
|
+
width: img.naturalWidth,
|
|
165
|
+
height: img.naturalHeight,
|
|
166
|
+
sampledWidth: canvas.width,
|
|
167
|
+
sampledHeight: canvas.height,
|
|
168
|
+
meanLuminance: Math.round((sum / pixels) * 100) / 100,
|
|
169
|
+
distinctColors: buckets.size,
|
|
170
|
+
};
|
|
171
|
+
})()`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* One line per control: the geometry the case really measured. The runner
|
|
176
|
+
* shows only the last lines of a failure, so a dump of the whole array would
|
|
177
|
+
* hide the control that matters behind the tail of the list.
|
|
178
|
+
*/
|
|
179
|
+
const controlSummary = control => `${control.element}${control.id ? '#' + control.id : ''} "${control.label}" rect=[${control.left},${control.top} ${control.right},${control.bottom}] visible=${control.visibleWidth}x${control.visibleHeight} hit=(${control.hitX},${control.hitY}) coveredBy=${control.coveredBy}`;
|
|
180
|
+
|
|
181
|
+
const overflowSummary = entry => `${entry.element} scrollWidth=${entry.scrollWidth} clientWidth=${entry.clientWidth}`;
|
|
182
|
+
|
|
183
|
+
async function ensureTheme(harness, theme) {
|
|
184
|
+
const current = await harness.run("document.documentElement.dataset.theme || ''");
|
|
185
|
+
if (current === theme) return;
|
|
186
|
+
await harness.nativeClick('#themeToggle');
|
|
187
|
+
await harness.waitFor(
|
|
188
|
+
() => harness.run(`document.documentElement.dataset.theme === ${JSON.stringify(theme)}`),
|
|
189
|
+
`the shell shows the ${theme} theme`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function main() {
|
|
194
|
+
const harness = await startHarness({ tag: 'nav007', evidenceDir: EVIDENCE || undefined });
|
|
195
|
+
const index = [];
|
|
196
|
+
try {
|
|
197
|
+
const repo = makeRepo(harness.fixture, 'nav007-a');
|
|
198
|
+
await harness.importProject(repo, 'NAV007 甲');
|
|
199
|
+
await harness.requestRegistryRefresh();
|
|
200
|
+
await harness.waitFor(
|
|
201
|
+
() => harness.run("document.getElementById('homeContent').textContent.includes('NAV007 甲')"),
|
|
202
|
+
'the imported project is listed',
|
|
203
|
+
20000,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
for (const viewport of VIEWPORTS) {
|
|
207
|
+
await harness.setViewport(viewport.width, viewport.height);
|
|
208
|
+
for (const theme of THEMES) {
|
|
209
|
+
await ensureTheme(harness, theme);
|
|
210
|
+
for (const page of PAGES) {
|
|
211
|
+
await harness.nativeClick(`nav [data-view="${page.view}"]`);
|
|
212
|
+
await harness.waitFor(
|
|
213
|
+
() => harness.run(`document.getElementById('view-${page.view}').classList.contains('active')`),
|
|
214
|
+
`the ${page.view} view is open`,
|
|
215
|
+
10000,
|
|
216
|
+
);
|
|
217
|
+
// The module views fill their frame asynchronously; the shell
|
|
218
|
+
// chrome is laid out already, so one frame of settling is enough
|
|
219
|
+
// for the rects to be final.
|
|
220
|
+
await harness.wait(300);
|
|
221
|
+
|
|
222
|
+
const info = await geometry(harness);
|
|
223
|
+
const label = `${viewport.name}-${theme}-${page.key}`;
|
|
224
|
+
const where = `${label} viewport=${info.viewport.width}x${info.viewport.height} theme=${info.theme} view=${info.activeView}`;
|
|
225
|
+
|
|
226
|
+
// The page that is open is the page the matrix asked for.
|
|
227
|
+
assert.equal(info.activeView, `view-${page.view}`, `the open page is the one under test: ${where}`);
|
|
228
|
+
// The theme under test is the theme the page really shows.
|
|
229
|
+
assert.equal(info.theme, theme, `the page really shows the ${theme} theme: ${where}`);
|
|
230
|
+
|
|
231
|
+
// NAV-007 expected 1: no unintended horizontal overflow.
|
|
232
|
+
assert.equal(info.overflowing.length, 0, `no container overflows horizontally: ${where} ${info.overflowing.map(overflowSummary).join(' | ')}`);
|
|
233
|
+
assert.ok(info.docScrollWidth <= info.docClientWidth + 1, `the document does not scroll sideways: ${where} ${info.docScrollWidth}/${info.docClientWidth}`);
|
|
234
|
+
assert.ok(info.viewScrollWidth <= info.viewClientWidth + 1, `the open page does not scroll sideways: ${where} ${info.viewScrollWidth}/${info.viewClientWidth}`);
|
|
235
|
+
const outside = info.controls.filter(control => control.outsideHorizontally);
|
|
236
|
+
assert.equal(outside.length, 0, `no visible control leaves the viewport sideways: ${where} ${outside.map(controlSummary).join(' | ')}`);
|
|
237
|
+
|
|
238
|
+
// NAV-007 expected 1 (continued): no control is covered by another.
|
|
239
|
+
const covered = info.controls.filter(control => control.covered);
|
|
240
|
+
assert.equal(covered.length, 0, `no visible control is covered: ${where} ${covered.map(controlSummary).join(' | ')}`);
|
|
241
|
+
|
|
242
|
+
// The page really has content: the controls of the open view exist.
|
|
243
|
+
assert.ok(info.controls.length > 0, `the open page exposes controls: ${where} ${info.controls.length} controls`);
|
|
244
|
+
|
|
245
|
+
// The screenshot of the real page, and its real pixels.
|
|
246
|
+
const shot = await harness.browser.send('Page.captureScreenshot', { format: 'png', fromSurface: true, captureBeyondViewport: false });
|
|
247
|
+
const file = path.join(harness.evidence, `nav007-${label}.png`);
|
|
248
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
249
|
+
fs.writeFileSync(file, Buffer.from(shot.data, 'base64'));
|
|
250
|
+
const pixels = await pixelStats(harness, shot.data);
|
|
251
|
+
assert.equal(pixels.width, viewport.width, `the screenshot has the viewport width: ${where}`);
|
|
252
|
+
assert.equal(pixels.height, viewport.height, `the screenshot has the viewport height: ${where}`);
|
|
253
|
+
assert.ok(pixels.distinctColors >= 3, `the screenshot shows a rendered page, not a blank surface: ${where} ${JSON.stringify(pixels)}`);
|
|
254
|
+
|
|
255
|
+
index.push({
|
|
256
|
+
case: 'NAV-007',
|
|
257
|
+
label,
|
|
258
|
+
viewport: { width: viewport.width, height: viewport.height },
|
|
259
|
+
theme,
|
|
260
|
+
page: page.key,
|
|
261
|
+
screenshot: path.basename(file),
|
|
262
|
+
pixels,
|
|
263
|
+
controls: info.controls.length,
|
|
264
|
+
docScrollWidth: info.docScrollWidth,
|
|
265
|
+
docClientWidth: info.docClientWidth,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
assert.equal(index.length, VIEWPORTS.length * THEMES.length * PAGES.length, 'the whole matrix was captured');
|
|
272
|
+
|
|
273
|
+
// The theme really repaints the page: the dark screenshots are darker
|
|
274
|
+
// than the light ones of the same page and viewport.
|
|
275
|
+
for (const viewport of VIEWPORTS) {
|
|
276
|
+
for (const page of PAGES) {
|
|
277
|
+
const light = index.find(entry => entry.viewport.width === viewport.width && entry.theme === 'light' && entry.page === page.key);
|
|
278
|
+
const dark = index.find(entry => entry.viewport.width === viewport.width && entry.theme === 'dark' && entry.page === page.key);
|
|
279
|
+
assert.ok(light && dark, `the matrix holds both themes of the page: ${viewport.name}/${page.key}`);
|
|
280
|
+
assert.ok(dark.pixels.meanLuminance < light.pixels.meanLuminance, `the dark theme really repaints the page: ${viewport.name}/${page.key} ${dark.pixels.meanLuminance} vs ${light.pixels.meanLuminance}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// The screenshots and their pixel statistics are written for the human
|
|
285
|
+
// reviewer. No visual baseline exists in this repository, so no baseline
|
|
286
|
+
// match is claimed here; the review stays an open item.
|
|
287
|
+
fs.writeFileSync(
|
|
288
|
+
path.join(harness.evidence, 'nav007-screenshot-index.json'),
|
|
289
|
+
`${JSON.stringify({
|
|
290
|
+
case: 'NAV-007',
|
|
291
|
+
generatedBy: '_site/_test/parallel-nav-viewport-test.js',
|
|
292
|
+
matrix: { viewports: VIEWPORTS, themes: THEMES, pages: PAGES.map(page => page.key) },
|
|
293
|
+
humanBaseline: {
|
|
294
|
+
state: 'PENDING_HUMAN_REVIEW',
|
|
295
|
+
note: '本仓库没有已批准的产品视觉基线,本用例只产出截图与像素统计,不声称基线一致;人工审阅未完成。',
|
|
296
|
+
},
|
|
297
|
+
screenshots: index,
|
|
298
|
+
}, null, 2)}\n`,
|
|
299
|
+
'utf8',
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
assert.equal(harness.browser.exceptions.length, 0, `no uncaught product JavaScript exceptions: ${JSON.stringify(harness.browser.exceptions)}`);
|
|
303
|
+
|
|
304
|
+
console.log(`parallel-nav-viewport-test PASS (${index.length} screenshots of ${VIEWPORTS.length} viewports x ${THEMES.length} themes x ${PAGES.length} pages, no horizontal overflow, no covered control; the human baseline review stays open)`);
|
|
305
|
+
} finally {
|
|
306
|
+
await harness.cleanup();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// _site/_test/parallel-qa-engine-endpoint-guard-test.js — P-QA-ENGINE-ENDPOINT-FIX-R5
|
|
3
|
+
//
|
|
4
|
+
// Safety regression for the engine endpoint lane. The original defect: tests
|
|
5
|
+
// constructed engines WITHOUT vectorUrl/bridgeUrl, and the coordinator falls
|
|
6
|
+
// back to hardcoded LIVE ports (127.0.0.1:8787 / :8790) regardless of env, so
|
|
7
|
+
// confirm/scopes-sync/index/search chains dialed LIVE services. This test
|
|
8
|
+
// reproduces every one of those chains while an egress guard blocks protected
|
|
9
|
+
// ports BEFORE any I/O — no socket is ever opened toward 5757/5760/5789/5790/
|
|
10
|
+
// 8787/8790. The positive leg runs the same chains against an OWN ephemeral
|
|
11
|
+
// vector stand-in and asserts the actual requests arrived there (fixture-side
|
|
12
|
+
// records, independent expected origin).
|
|
13
|
+
//
|
|
14
|
+
// P-QA-ENGINE-ENDPOINT-REVIEW-R6 legs, appended after the R5 legs:
|
|
15
|
+
// D. IPv6 alignment — the egress judge accepts the bracketed [::1] form of
|
|
16
|
+
// URL.hostname (the same form assertOwnedEndpoint accepts), proven with a
|
|
17
|
+
// STUBBED original fetch so no real IPv6 socket is ever opened;
|
|
18
|
+
// E. PSModuleAnalysisCachePath file shape — parent materialized, target
|
|
19
|
+
// never a directory, and a REAL PowerShell child that writes its own
|
|
20
|
+
// cache file under the isolated root while seeing only the isolated
|
|
21
|
+
// home (its own echoed env is the evidence; no browser involved);
|
|
22
|
+
// F. offline POSIX socket-budget string contract — the CI-long root shape
|
|
23
|
+
// is rejected, the short POSIX components hold with margin, the boundary
|
|
24
|
+
// is exact, and accounting is UTF-8 bytes. Windows cannot prove Linux:
|
|
25
|
+
// actual Linux proof stays with CI; this leg proves the byte math.
|
|
26
|
+
const assert = require('node:assert/strict');
|
|
27
|
+
const fs = require('node:fs');
|
|
28
|
+
const path = require('node:path');
|
|
29
|
+
const { spawnSync } = require('node:child_process');
|
|
30
|
+
const {
|
|
31
|
+
applyDriverIsolation, childEnv, LIVE_HOME,
|
|
32
|
+
POSIX_SOCKET_CHAR_LIMIT, worstCaseSocketPaths, socketBudgetVerdict, assertSocketBudget,
|
|
33
|
+
} = require('./helpers/parallel-qa-engine-endpoint-isolate');
|
|
34
|
+
const {
|
|
35
|
+
REFUSAL_CODE, EGRESS_BLOCK_CODE, PROTECTED_PORTS,
|
|
36
|
+
assertOwnedEndpoint, guardedCreateEngineServer, installEgressGuard,
|
|
37
|
+
createEngineVectorStandin, createEngineBridgeDouble,
|
|
38
|
+
} = require('./helpers/parallel-qa-engine-endpoint-guard');
|
|
39
|
+
|
|
40
|
+
(async () => {
|
|
41
|
+
const applied = applyDriverIsolation({ prefix: 'pk-qa-engine-endpoint-guard' });
|
|
42
|
+
const mkdtemp = name => fs.mkdtempSync(path.join(applied.layout.temp, `${name}-`));
|
|
43
|
+
// Shared-index jobs run on a serialized queue and may chain dirty reruns, so
|
|
44
|
+
// awaiting one job promise does not prove the observable effect landed. Wait
|
|
45
|
+
// bounded on the effect itself.
|
|
46
|
+
async function waitFor(predicate, label, timeoutMs = 5000) {
|
|
47
|
+
const started = Date.now();
|
|
48
|
+
while (!predicate()) {
|
|
49
|
+
if (Date.now() - started > timeoutMs) throw new Error(`timeout waiting for: ${label}`);
|
|
50
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const guard = installEgressGuard();
|
|
54
|
+
let negativeEngine = null, positiveEngine = null, standin = null;
|
|
55
|
+
try {
|
|
56
|
+
// ── Leg A: fail-closed constructor refusals (product code never loads) ──
|
|
57
|
+
for (const [label, build] of [
|
|
58
|
+
['missing vectorUrl', () => guardedCreateEngineServer({})],
|
|
59
|
+
['null vectorUrl', () => guardedCreateEngineServer({ vectorUrl: null })],
|
|
60
|
+
['missing bridge leg', () => guardedCreateEngineServer({ vectorUrl: 'http://127.0.0.1:41234' })],
|
|
61
|
+
['live vectorUrl 8787', () => guardedCreateEngineServer({ vectorUrl: 'http://127.0.0.1:8787', bridgeUrl: 'http://127.0.0.1:41235' })],
|
|
62
|
+
['live bridgeUrl 8790', () => guardedCreateEngineServer({ vectorUrl: 'http://127.0.0.1:41236', bridgeUrl: 'http://127.0.0.1:8790' })],
|
|
63
|
+
['non-loopback vectorUrl', () => guardedCreateEngineServer({ vectorUrl: 'http://10.9.8.7:41237', bridgeUrl: 'http://127.0.0.1:41238' })],
|
|
64
|
+
['non-loopback bridgeUrl', () => guardedCreateEngineServer({ vectorUrl: 'http://127.0.0.1:41239', bridgeUrl: 'http://example.test:9' })],
|
|
65
|
+
['port 0 vectorUrl', () => guardedCreateEngineServer({ vectorUrl: 'http://127.0.0.1:0', bridgeUrl: 'http://127.0.0.1:41240' })],
|
|
66
|
+
['https vectorUrl', () => guardedCreateEngineServer({ vectorUrl: 'https://127.0.0.1:41241', bridgeUrl: 'http://127.0.0.1:41242' })],
|
|
67
|
+
['non-http scheme', () => assertOwnedEndpoint('ws://127.0.0.1:41243', { role: 'vectorUrl' })],
|
|
68
|
+
]) assert.throws(build, error => error.code === REFUSAL_CODE, `refusal: ${label}`);
|
|
69
|
+
for (const port of PROTECTED_PORTS) assert.throws(() => assertOwnedEndpoint(`http://127.0.0.1:${port}`, { role: 'vectorUrl' }), error => error.code === REFUSAL_CODE, `refusal: protected port ${port}`);
|
|
70
|
+
const probeUrl = assertOwnedEndpoint('http://127.0.0.1:41244/foo', { role: 'vectorUrl' });
|
|
71
|
+
assert.equal(probeUrl.port, '41244');
|
|
72
|
+
|
|
73
|
+
// Egress guard blocks non-loopback targets pre-I/O as well.
|
|
74
|
+
await assert.rejects(() => fetch('https://registry.npmjs.org/never-requested-r5'), error => error.code === EGRESS_BLOCK_CODE && error.reason === 'non-loopback-host:registry.npmjs.org');
|
|
75
|
+
|
|
76
|
+
// ── Leg B: negative regression — the ORIGINAL defect, safely held ──
|
|
77
|
+
// Raw product constructor without vectorUrl (exactly the S1/S2/S3 shape).
|
|
78
|
+
const { createEngineServer } = require('../../_modules/knowledge-engine/src/server');
|
|
79
|
+
const negRoot = mkdtemp('neg');
|
|
80
|
+
const negBrainDir = path.join(negRoot, 'brain-shared');
|
|
81
|
+
fs.mkdirSync(negBrainDir, { recursive: true });
|
|
82
|
+
fs.writeFileSync(path.join(negBrainDir, 'note.md'), '# 共享知识\n\n负向回归锚点:缺省构造不得触碰 LIVE 端口。\n');
|
|
83
|
+
negativeEngine = createEngineServer({ dataDir: path.join(negRoot, 'engine'), consume: false, runtimeToken: 'r5-guard-neg' });
|
|
84
|
+
await negativeEngine.ready;
|
|
85
|
+
assert.equal(negativeEngine.coordinator.vectorUrl, 'http://127.0.0.1:8787', 'negative leg must model the defect: hardcoded LIVE fallback');
|
|
86
|
+
|
|
87
|
+
// S3 chain: scopes sync → shared index import at LIVE 8787 — blocked pre-I/O.
|
|
88
|
+
await negativeEngine.coordinator.memories.configure([{ id: 'brain-r5', kind: 'brain', brainId: 'brain-r5', knowledgePath: negBrainDir }]);
|
|
89
|
+
const negScope = negativeEngine.store.state.sharedScopes.find(scope => scope.id === 'brain-r5');
|
|
90
|
+
await negativeEngine.coordinator.indexSharedScope(negScope);
|
|
91
|
+
assert.equal(negScope.index.status, 'pending', 'blocked import must leave the shared index pending');
|
|
92
|
+
assert.equal(negScope.index.error.details.upstreamCode, EGRESS_BLOCK_CODE);
|
|
93
|
+
|
|
94
|
+
// S1/S2 chain: manual memory confirm → materialize → onMaterialized → import.
|
|
95
|
+
const negMemory = await negativeEngine.coordinator.memories.create({ text: '负向回归:确认流必须在 LIVE 8787 之前被拦截', brainId: 'brain-r5' });
|
|
96
|
+
assert.equal(negMemory.status, 'confirmed');
|
|
97
|
+
await waitFor(() => guard.blocked.filter(entry => entry.href === 'http://127.0.0.1:8787/api/import').length >= 2, 'confirm chain attempting LIVE import while blocked');
|
|
98
|
+
// index() chain: run maintenance index → /api/sync at LIVE 8787.
|
|
99
|
+
fs.mkdirSync(path.join(negRoot, 'knowledge'), { recursive: true });
|
|
100
|
+
negativeEngine.store.addProject({ projectId: 'proj-neg', repoPath: path.join(negRoot, 'code'), knowledgePath: path.join(negRoot, 'knowledge') });
|
|
101
|
+
const negRun = negativeEngine.store.addRun({ projectId: 'proj-neg', commitSha: 'a'.repeat(40), trigger: 'manual' });
|
|
102
|
+
await negativeEngine.coordinator.index(negRun);
|
|
103
|
+
const blockedNegRun = negativeEngine.store.getRun(negRun.runId);
|
|
104
|
+
assert.equal(blockedNegRun.state, 'index-pending');
|
|
105
|
+
assert.equal(blockedNegRun.error.details.upstreamCode, EGRESS_BLOCK_CODE);
|
|
106
|
+
// search chain: recallIndex would GET /api/search at LIVE 8787 — blocked too.
|
|
107
|
+
const negSearch = await negativeEngine.coordinator.retrieval.search({ projectId: 'proj-neg', query: '向量索引守护与导入契约' });
|
|
108
|
+
assert.deepEqual(negSearch.retrievalModes, ['lexical'], 'blocked vector must degrade to lexical, never fake success');
|
|
109
|
+
|
|
110
|
+
const blockedImports = guard.blocked.filter(entry => entry.href === 'http://127.0.0.1:8787/api/import');
|
|
111
|
+
assert.ok(blockedImports.length >= 2, `expected the configure+confirm chains to attempt LIVE import at least twice (got ${blockedImports.length})`);
|
|
112
|
+
assert.ok(guard.blocked.some(entry => entry.href === 'http://127.0.0.1:8787/api/sync'), 'index() chain must attempt LIVE sync');
|
|
113
|
+
assert.ok(guard.blocked.some(entry => entry.href.startsWith('http://127.0.0.1:8787/api/search')), 'search chain must attempt LIVE search');
|
|
114
|
+
assert.ok(guard.blocked.every(entry => entry.reason.startsWith('protected-port:') || entry.reason.startsWith('non-loopback-host:')), 'every block must name a protected port or a non-loopback host');
|
|
115
|
+
assert.ok(guard.requests.every(entry => !/:87(87|90)|:57(57|60|89|90)/.test(entry.href)), 'no protected-port request may ever be passed through');
|
|
116
|
+
|
|
117
|
+
// ── Leg C: positive proof at the OWN stand-in ──
|
|
118
|
+
standin = createEngineVectorStandin();
|
|
119
|
+
const standinUrl = await standin.ready;
|
|
120
|
+
const bridgeDouble = createEngineBridgeDouble();
|
|
121
|
+
const posRoot = mkdtemp('pos');
|
|
122
|
+
const posBrainDir = path.join(posRoot, 'brain-shared');
|
|
123
|
+
fs.mkdirSync(posBrainDir, { recursive: true });
|
|
124
|
+
fs.writeFileSync(path.join(posBrainDir, 'note.md'), '# 共享知识\n\n正向证明锚点。\n');
|
|
125
|
+
fs.mkdirSync(path.join(posRoot, 'knowledge'), { recursive: true });
|
|
126
|
+
const blockedBeforePositive = guard.blocked.length;
|
|
127
|
+
positiveEngine = guardedCreateEngineServer({ dataDir: path.join(posRoot, 'engine'), consume: false, runtimeToken: 'r5-guard-pos', vectorUrl: standinUrl, bridge: bridgeDouble });
|
|
128
|
+
await positiveEngine.ready;
|
|
129
|
+
assert.equal(positiveEngine.coordinator.vectorUrl, standinUrl);
|
|
130
|
+
const importCount = () => standin.records.filter(record => record.method === 'POST' && record.path === '/api/import').length;
|
|
131
|
+
|
|
132
|
+
// Scopes sync imports at the OWN stand-in, and the recorded origin (server
|
|
133
|
+
// side) — not a driver-echoed value — proves where the request landed.
|
|
134
|
+
await positiveEngine.coordinator.memories.configure([{ id: 'brain-pos', kind: 'brain', brainId: 'brain-pos', knowledgePath: posBrainDir }]);
|
|
135
|
+
const posScope = positiveEngine.store.state.sharedScopes.find(scope => scope.id === 'brain-pos');
|
|
136
|
+
await positiveEngine.coordinator.indexSharedScope(posScope);
|
|
137
|
+
assert.equal(posScope.index.status, 'ready');
|
|
138
|
+
const importRecord = standin.records.find(record => record.method === 'POST' && record.path === '/api/import');
|
|
139
|
+
assert.equal(importRecord.body.projectName, 'scope-brain-brain-pos');
|
|
140
|
+
assert.equal(importRecord.body.sourceDir, posBrainDir);
|
|
141
|
+
|
|
142
|
+
// Confirm chain: memory create → materialize → real import; unchanged
|
|
143
|
+
// content then takes the GET /api/projects health path and skips import.
|
|
144
|
+
const importsAfterConfigure = importCount();
|
|
145
|
+
const healthGets = () => standin.records.filter(record => record.method === 'GET' && record.path === '/api/projects').length;
|
|
146
|
+
const posMemory = await positiveEngine.coordinator.memories.create({ text: '正向证明:确认流导入必须落到自有替身', brainId: 'brain-pos' });
|
|
147
|
+
assert.equal(posMemory.status, 'confirmed');
|
|
148
|
+
await waitFor(() => importCount() >= importsAfterConfigure + 1, 'confirm chain importing at the OWN stand-in');
|
|
149
|
+
assert.equal(importCount(), importsAfterConfigure + 1, 'materialized memory must trigger exactly one real import at the stand-in');
|
|
150
|
+
const importsAfterConfirm = importCount();
|
|
151
|
+
const getsBeforeHealth = healthGets();
|
|
152
|
+
await positiveEngine.coordinator.indexSharedScope(positiveEngine.store.state.sharedScopes.find(scope => scope.id === 'brain-pos')).catch(() => {});
|
|
153
|
+
await waitFor(() => healthGets() > getsBeforeHealth, 'unchanged fingerprint taking the /api/projects health check');
|
|
154
|
+
assert.equal(importCount(), importsAfterConfirm, 'unchanged fingerprint must skip import via the /api/projects health check');
|
|
155
|
+
assert.ok(healthGets() > getsBeforeHealth, 'health check must have queried the stand-in');
|
|
156
|
+
|
|
157
|
+
// index() chain with an explicit vectorProjectId mapping.
|
|
158
|
+
positiveEngine.store.addProject({ projectId: 'proj-pos', repoPath: path.join(posRoot, 'code'), knowledgePath: path.join(posRoot, 'knowledge'), vectorProjectId: 'vec-proj-pos' });
|
|
159
|
+
const posRun = positiveEngine.store.addRun({ projectId: 'proj-pos', commitSha: 'b'.repeat(40), trigger: 'manual' });
|
|
160
|
+
await positiveEngine.coordinator.index(posRun);
|
|
161
|
+
assert.equal(positiveEngine.store.getRun(posRun.runId).state, 'succeeded');
|
|
162
|
+
assert.equal(positiveEngine.store.readRuntime('proj-pos').index.status, 'ready');
|
|
163
|
+
const syncRecord = standin.records.find(record => record.method === 'POST' && record.path === '/api/sync');
|
|
164
|
+
assert.equal(syncRecord.body.project, 'vec-proj-pos');
|
|
165
|
+
assert.equal(syncRecord.body.sourceDir, path.join(posRoot, 'knowledge'));
|
|
166
|
+
|
|
167
|
+
// search chain: routed to the stand-in, whose honest 503 degrades retrieval
|
|
168
|
+
// to lexical. The stand-in proves routing/contract parity only — never real
|
|
169
|
+
// vector retrieval quality.
|
|
170
|
+
const posSearch = await positiveEngine.coordinator.retrieval.search({ projectId: 'proj-pos', query: '向量索引守护与导入契约' });
|
|
171
|
+
assert.deepEqual(posSearch.retrievalModes, ['lexical']);
|
|
172
|
+
assert.equal(posSearch.backend, 'markdown-hybrid-fallback');
|
|
173
|
+
const searchRecord = standin.records.find(record => record.method === 'GET' && record.path.startsWith('/api/search'));
|
|
174
|
+
assert.ok(searchRecord, 'search must have queried the stand-in');
|
|
175
|
+
const searchQuery = new URL(`http://127.0.0.1${searchRecord.path}`);
|
|
176
|
+
assert.equal(searchQuery.searchParams.get('q'), '向量索引守护与导入契约');
|
|
177
|
+
assert.equal(searchQuery.searchParams.get('projects'), 'vec-proj-pos');
|
|
178
|
+
|
|
179
|
+
assert.equal(guard.blocked.length, blockedBeforePositive, 'positive leg must produce zero blocked egress');
|
|
180
|
+
assert.ok(guard.requests.every(entry => entry.href.startsWith(standinUrl)), 'every positive-leg request must target the OWN stand-in');
|
|
181
|
+
assert.equal(bridgeDouble.calls.length, 0, 'bridge double must never be dialed while consume:false');
|
|
182
|
+
|
|
183
|
+
// ── Leg D: IPv6 alignment against a STUBBED original fetch ──
|
|
184
|
+
{
|
|
185
|
+
const realFetch = global.fetch;
|
|
186
|
+
const dialed = [];
|
|
187
|
+
global.fetch = async (input, init) => {
|
|
188
|
+
dialed.push(input instanceof URL ? input.href : String(input?.url || input));
|
|
189
|
+
return { ok: true, stubbed: true };
|
|
190
|
+
};
|
|
191
|
+
let stubGuard = null;
|
|
192
|
+
try {
|
|
193
|
+
stubGuard = installEgressGuard();
|
|
194
|
+
assert.ok(assertOwnedEndpoint('http://[::1]:41245/own-ipv6', { role: 'vectorUrl' }), 'assertOwnedEndpoint accepts the bracketed [::1] hostname');
|
|
195
|
+
await fetch('http://[::1]:41245/own-ipv6');
|
|
196
|
+
await assert.rejects(() => fetch('http://127.0.0.1:8787/api/import'), error => error.code === EGRESS_BLOCK_CODE && error.reason === 'protected-port:8787');
|
|
197
|
+
assert.deepEqual(dialed, ['http://[::1]:41245/own-ipv6'],
|
|
198
|
+
'only the allowed [::1] target reaches the original (stub) fetch — the 8787 call is blocked before any dialing');
|
|
199
|
+
} finally {
|
|
200
|
+
if (stubGuard) stubGuard.uninstall();
|
|
201
|
+
global.fetch = realFetch;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ── Leg E: PSModuleAnalysisCachePath is a FILE; a real PowerShell child
|
|
206
|
+
// runs under the isolated env and writes its own startup cache inside the
|
|
207
|
+
// isolated home, without touching the live home ──
|
|
208
|
+
{
|
|
209
|
+
assert.ok(fs.statSync(applied.layout.psModuleCacheParent).isDirectory(), 'the cache PARENT is materialized');
|
|
210
|
+
assert.ok(!fs.existsSync(applied.layout.psModuleCache), 'the fresh layout must not pre-create the cache target');
|
|
211
|
+
assert.equal(process.env.PSModuleAnalysisCachePath, applied.layout.psModuleCache, 'the driver env points at the cache FILE path');
|
|
212
|
+
if (process.platform === 'win32') {
|
|
213
|
+
// Windows PowerShell writes ModuleAnalysisCache from its idle loop,
|
|
214
|
+
// which -Command sessions exit before — so the module-analysis file
|
|
215
|
+
// itself is recorded as EVIDENCE, not asserted. The startup cache
|
|
216
|
+
// (StartupProfileData-NonInteractive under <USERPROFILE>\AppData)
|
|
217
|
+
// IS written on every -NonInteractive startup and is the reliable
|
|
218
|
+
// observable proof of a real PowerShell-owned cache write.
|
|
219
|
+
const isolatedStartupCache = path.join(applied.layout.home, 'AppData', 'Local', 'Microsoft', 'Windows', 'PowerShell', 'StartupProfileData-NonInteractive');
|
|
220
|
+
const liveStartupCache = path.join(LIVE_HOME, 'AppData', 'Local', 'Microsoft', 'Windows', 'PowerShell', 'StartupProfileData-NonInteractive');
|
|
221
|
+
const liveMtimeBefore = fs.existsSync(liveStartupCache) ? fs.statSync(liveStartupCache).mtimeMs : null;
|
|
222
|
+
const psCommand = '"$env:PSModuleAnalysisCachePath`n$env:USERPROFILE"; $ErrorActionPreference=\'Stop\'; Get-Module -ListAvailable | Out-Null; Write-Output "PS-CACHE-OK"';
|
|
223
|
+
const probe = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', psCommand],
|
|
224
|
+
{ env: childEnv(), timeout: 120000, windowsHide: true, encoding: 'utf8' });
|
|
225
|
+
const lines = String(probe.stdout || '').trim().split(/\r?\n/);
|
|
226
|
+
assert.equal(probe.status, 0, `PowerShell probe failed (${probe.status}): ${probe.stderr}`);
|
|
227
|
+
assert.equal(lines[0], applied.layout.psModuleCache, 'the child must see the isolated cache FILE path');
|
|
228
|
+
assert.equal(lines[1], applied.layout.home, 'the child must see the isolated home');
|
|
229
|
+
assert.notEqual(lines[1], LIVE_HOME, 'the child home must never be the live home');
|
|
230
|
+
assert.equal(lines[lines.length - 1], 'PS-CACHE-OK', 'the PowerShell child really ran to completion');
|
|
231
|
+
assert.ok(fs.existsSync(isolatedStartupCache) && fs.statSync(isolatedStartupCache).isFile(),
|
|
232
|
+
'the PowerShell child must have written its own startup cache file inside the isolated home');
|
|
233
|
+
const liveMtimeAfter = fs.existsSync(liveStartupCache) ? fs.statSync(liveStartupCache).mtimeMs : null;
|
|
234
|
+
assert.equal(liveMtimeAfter, liveMtimeBefore, 'the isolated child must not touch the live home startup cache');
|
|
235
|
+
assert.ok(!fs.existsSync(applied.layout.psModuleCache) || fs.statSync(applied.layout.psModuleCache).isFile(),
|
|
236
|
+
'the cache target stays a FILE path, never a directory, after the child ran');
|
|
237
|
+
console.log(`leg E evidence: moduleAnalysisCacheWrittenAtTarget=${fs.existsSync(applied.layout.psModuleCache)} (idle-write, not asserted); startupCacheWrittenInsideIsolation=true`);
|
|
238
|
+
} else {
|
|
239
|
+
console.log('leg E: PowerShell child probe is Windows-only; cache-file shape assertions ran');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── Leg F: offline POSIX socket-budget string contract ──
|
|
244
|
+
{
|
|
245
|
+
const ciFailedRoot = '/home/runner/work/_temp/pk-nav-r7-9399-ChZLZ5/pk-parallel-nav-nav004-pUkYfU';
|
|
246
|
+
const failed = socketBudgetVerdict(ciFailedRoot);
|
|
247
|
+
assert.ok(!failed.ok, 'the CI-long root shape must be rejected by the socket budget');
|
|
248
|
+
assert.ok(failed.chromeSocketBytes > POSIX_SOCKET_CHAR_LIMIT, `rejected with the computed worst case (${failed.chromeSocketBytes} bytes)`);
|
|
249
|
+
assert.throws(() => assertSocketBudget(ciFailedRoot, 'ci-long-root'), /socket path budget exceeded/, 'assertSocketBudget fails fast on the over-budget root');
|
|
250
|
+
|
|
251
|
+
const shortRoot = '/home/runner/work/_temp/pkqabcdef';
|
|
252
|
+
const held = socketBudgetVerdict(shortRoot);
|
|
253
|
+
assert.ok(held.ok, `the short POSIX root this helper creates must hold: ${JSON.stringify({ chrome: held.chromeSocketBytes, control: held.controlSocketBytes })}`);
|
|
254
|
+
assert.ok(POSIX_SOCKET_CHAR_LIMIT - held.chromeSocketBytes >= 10, `short root holds with margin (limit ${POSIX_SOCKET_CHAR_LIMIT}, chrome worst case ${held.chromeSocketBytes})`);
|
|
255
|
+
|
|
256
|
+
assert.equal(worstCaseSocketPaths('/tmp').chromeSingletonSocket.length - '/tmp'.length, 49,
|
|
257
|
+
'the Chrome socket tail is pinned at 49 bytes (12-char rand bound)');
|
|
258
|
+
assert.ok(socketBudgetVerdict('/tmp/' + 'a'.repeat(53)).ok, 'a root landing exactly at the byte ceiling is accepted');
|
|
259
|
+
assert.ok(!socketBudgetVerdict('/tmp/' + 'a'.repeat(54)).ok, 'one byte over the ceiling is rejected');
|
|
260
|
+
|
|
261
|
+
const multibyte = socketBudgetVerdict('/tmp/π');
|
|
262
|
+
assert.equal(multibyte.chromeSocketBytes, multibyte.paths.chromeSingletonSocket.length + 1,
|
|
263
|
+
'the budget counts UTF-8 bytes, not chars');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
console.log('parallel-qa-engine-endpoint-guard-test PASS');
|
|
267
|
+
} finally {
|
|
268
|
+
if (negativeEngine) await negativeEngine.close().catch(() => {});
|
|
269
|
+
if (positiveEngine) await positiveEngine.close().catch(() => {});
|
|
270
|
+
if (standin) await standin.close().catch(() => {});
|
|
271
|
+
guard.uninstall();
|
|
272
|
+
}
|
|
273
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });
|