@mindrian_os/install 1.13.0-beta.11
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/.claude-plugin/plugin.json +21 -0
- package/.mcp.json +9 -0
- package/CHANGELOG.md +3333 -0
- package/LICENSE +123 -0
- package/README.md +673 -0
- package/agents/brain-query.md +80 -0
- package/agents/framework-runner.md +237 -0
- package/agents/grading.md +188 -0
- package/agents/investor.md +128 -0
- package/agents/larry-extended.md +135 -0
- package/agents/opportunity-scanner.md +91 -0
- package/agents/persona-analyst.md +132 -0
- package/agents/research.md +89 -0
- package/agents/reverse-salient-agent.md +27 -0
- package/bin/cli.js +142 -0
- package/bin/mindrian-mcp-server.cjs +182 -0
- package/bin/mindrian-tools.cjs +765 -0
- package/commands/act.md +439 -0
- package/commands/admin.md +404 -0
- package/commands/analyze-needs.md +42 -0
- package/commands/analyze-systems.md +39 -0
- package/commands/analyze-timing.md +42 -0
- package/commands/auto-explore.md +64 -0
- package/commands/beautiful-question.md +40 -0
- package/commands/brain-derive.md +78 -0
- package/commands/build-knowledge.md +42 -0
- package/commands/build-thesis.md +46 -0
- package/commands/causal.md +234 -0
- package/commands/challenge-assumptions.md +33 -0
- package/commands/compare-ventures.md +83 -0
- package/commands/dashboard.md +110 -0
- package/commands/deep-grade.md +82 -0
- package/commands/diagnose.md +58 -0
- package/commands/diagnostics.md +151 -0
- package/commands/doctor.md +151 -0
- package/commands/dominant-designs.md +40 -0
- package/commands/explain-decision.md +87 -0
- package/commands/explore-domains.md +42 -0
- package/commands/explore-futures.md +40 -0
- package/commands/explore-trends.md +42 -0
- package/commands/export.md +103 -0
- package/commands/file-meeting.md +724 -0
- package/commands/find-analogies.md +188 -0
- package/commands/find-bottlenecks.md +62 -0
- package/commands/find-connections.md +76 -0
- package/commands/funding.md +81 -0
- package/commands/grade.md +203 -0
- package/commands/graph.md +128 -0
- package/commands/hat-briefing.md +125 -0
- package/commands/heal.md +196 -0
- package/commands/help.md +399 -0
- package/commands/hmi-status.md +172 -0
- package/commands/jtbd.md +241 -0
- package/commands/leadership.md +73 -0
- package/commands/lean-canvas.md +40 -0
- package/commands/macro-trends.md +40 -0
- package/commands/map-unknowns.md +40 -0
- package/commands/memory.md +173 -0
- package/commands/models.md +175 -0
- package/commands/mos-reason.md +285 -0
- package/commands/mullins.md +120 -0
- package/commands/new-project.md +481 -0
- package/commands/onboard.md +434 -0
- package/commands/operator.md +149 -0
- package/commands/opportunities.md +144 -0
- package/commands/organize.md +497 -0
- package/commands/persona.md +198 -0
- package/commands/pipeline.md +112 -0
- package/commands/present.md +91 -0
- package/commands/publish.md +201 -0
- package/commands/query.md +124 -0
- package/commands/radar.md +72 -0
- package/commands/reanalyze.md +91 -0
- package/commands/research.md +196 -0
- package/commands/room.md +352 -0
- package/commands/rooms.md +598 -0
- package/commands/root-cause.md +40 -0
- package/commands/rs-experts.md +85 -0
- package/commands/rs-explain.md +100 -0
- package/commands/rs-fetch.md +94 -0
- package/commands/rs-thesis.md +85 -0
- package/commands/scenario-plan.md +40 -0
- package/commands/scheduled-tasks.md +285 -0
- package/commands/score-innovation.md +43 -0
- package/commands/scout.md +239 -0
- package/commands/setup.md +618 -0
- package/commands/snapshot.md +147 -0
- package/commands/speakers.md +84 -0
- package/commands/splash.md +28 -0
- package/commands/status.md +75 -0
- package/commands/structure-argument.md +42 -0
- package/commands/suggest-next.md +80 -0
- package/commands/systems-thinking.md +40 -0
- package/commands/think-hats.md +42 -0
- package/commands/update.md +181 -0
- package/commands/user-needs.md +40 -0
- package/commands/validate.md +40 -0
- package/commands/value-proposition.md +61 -0
- package/commands/vault.md +180 -0
- package/commands/visualize.md +52 -0
- package/commands/whitespace.md +507 -0
- package/commands/wiki.md +69 -0
- package/hooks/hooks.json +381 -0
- package/hooks/run-hook.cmd +64 -0
- package/lib/__init__.py +0 -0
- package/lib/__pycache__/__init__.cpython-312.pyc +0 -0
- package/lib/agents/auto-explore-agent.cjs +1043 -0
- package/lib/agents/reverse-salient-agent.cjs +679 -0
- package/lib/agents/tension-hook-agent.cjs +544 -0
- package/lib/brain/ROOM.md +44 -0
- package/lib/brain/chain-recommender.cjs +301 -0
- package/lib/chat/chat-context.js +185 -0
- package/lib/chat/chat-panel.js +721 -0
- package/lib/chat/fabric-chat.cjs +288 -0
- package/lib/chat/generative-tools.js +219 -0
- package/lib/conversation/ROOM.md +39 -0
- package/lib/conversation/classifier-rules.json +38 -0
- package/lib/conversation/classifier.cjs +264 -0
- package/lib/conversation/operator.cjs +287 -0
- package/lib/copy/115-spec-strings.cjs +55 -0
- package/lib/core/__init__.py +0 -0
- package/lib/core/__nav-stub.cjs +14 -0
- package/lib/core/__pycache__/__init__.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs-math.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_cache.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_corpus.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_hybrid.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_math.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_rooms.cpython-312.pyc +0 -0
- package/lib/core/artifact-id.cjs +148 -0
- package/lib/core/asset-ops.cjs +151 -0
- package/lib/core/auto-commit-throttle.cjs +129 -0
- package/lib/core/bearer-token.cjs +199 -0
- package/lib/core/brain-client.cjs +865 -0
- package/lib/core/brain-derivation-prompts.cjs +326 -0
- package/lib/core/brain-derivation-queue.cjs +431 -0
- package/lib/core/brain-derivation.cjs +580 -0
- package/lib/core/brain-md-schema.cjs +528 -0
- package/lib/core/brain-md-staleness.cjs +357 -0
- package/lib/core/brain-response-sanitize.cjs +188 -0
- package/lib/core/bridge-writer.cjs +477 -0
- package/lib/core/chat-context-builder.cjs +253 -0
- package/lib/core/cross-room-aggregator.cjs +762 -0
- package/lib/core/daily-briefing.cjs +438 -0
- package/lib/core/decision-capture.cjs +618 -0
- package/lib/core/deep-links.cjs +82 -0
- package/lib/core/dispatch-optimizer.cjs +354 -0
- package/lib/core/dual-path-detector.cjs +84 -0
- package/lib/core/dual-path-detector.test.cjs +334 -0
- package/lib/core/exports-log.cjs +79 -0
- package/lib/core/feynman-minto-invariants.cjs +605 -0
- package/lib/core/folder-memory-async.cjs +338 -0
- package/lib/core/folder-memory-shared.cjs +890 -0
- package/lib/core/folder-memory.cjs +416 -0
- package/lib/core/framework-chain-composer.cjs +411 -0
- package/lib/core/frontmatter-schemas.cjs +330 -0
- package/lib/core/git-ops.cjs +141 -0
- package/lib/core/graph-ops.cjs +258 -0
- package/lib/core/hat-persistence.cjs +362 -0
- package/lib/core/index.cjs +60 -0
- package/lib/core/integration-registry.cjs +232 -0
- package/lib/core/intelligence-cascade.cjs +661 -0
- package/lib/core/lazygraph-ops.cjs +1057 -0
- package/lib/core/lru-cache.cjs +139 -0
- package/lib/core/mcp-profiles.cjs +182 -0
- package/lib/core/meeting-ops.cjs +54 -0
- package/lib/core/memory-ops.cjs +600 -0
- package/lib/core/migrations/ROOM.md +33 -0
- package/lib/core/migrations/phase-109-nodes-provenance.cjs +339 -0
- package/lib/core/migrations/phase-109-session-focus.cjs +99 -0
- package/lib/core/model-profiles.cjs +246 -0
- package/lib/core/mullins-scaffold.cjs +160 -0
- package/lib/core/nav-dial.cjs +316 -0
- package/lib/core/navigation/ROOM.md +15 -0
- package/lib/core/navigation/explanation.cjs +43 -0
- package/lib/core/navigation/focus.cjs +135 -0
- package/lib/core/navigation/ingestion.cjs +82 -0
- package/lib/core/navigation/insights.cjs +350 -0
- package/lib/core/navigation/memory-events.cjs +118 -0
- package/lib/core/navigation/neighborhood.cjs +78 -0
- package/lib/core/navigation/packet.cjs +182 -0
- package/lib/core/navigation/room-home.cjs +127 -0
- package/lib/core/navigation/transitions.cjs +82 -0
- package/lib/core/navigation-engine-shared.cjs +242 -0
- package/lib/core/navigation-engine.cjs +664 -0
- package/lib/core/navigation.cjs +60 -0
- package/lib/core/nl-graph-queries.cjs +164 -0
- package/lib/core/offer-presenter.cjs +406 -0
- package/lib/core/opportunity-extractor.cjs +183 -0
- package/lib/core/opportunity-ops.cjs +1371 -0
- package/lib/core/persona-ops.cjs +537 -0
- package/lib/core/persona-taxonomy.cjs +190 -0
- package/lib/core/platform-gates.cjs +120 -0
- package/lib/core/platform.cjs +257 -0
- package/lib/core/proactive-intelligence.cjs +528 -0
- package/lib/core/problem-type-router.cjs +315 -0
- package/lib/core/reasoning-ops.cjs +639 -0
- package/lib/core/reverse-salient-persona-suffix.cjs +115 -0
- package/lib/core/room-classifier-strict-mode.cjs +229 -0
- package/lib/core/room-db.cjs +127 -0
- package/lib/core/room-ops-async.cjs +92 -0
- package/lib/core/room-ops-shared.cjs +64 -0
- package/lib/core/room-ops-sync.cjs +70 -0
- package/lib/core/room-ops.cjs +32 -0
- package/lib/core/room-type-detector.cjs +386 -0
- package/lib/core/rs-brain-substrate-prompts.cjs +129 -0
- package/lib/core/rs-brain-substrate.cjs +570 -0
- package/lib/core/rs-breakthrough-scorer.cjs +255 -0
- package/lib/core/rs-canon-violations.cjs +82 -0
- package/lib/core/rs-chain-feeder.cjs +343 -0
- package/lib/core/rs-commercial-assessor.cjs +280 -0
- package/lib/core/rs-differential-scorer.cjs +376 -0
- package/lib/core/rs-domain-analyzer.cjs +385 -0
- package/lib/core/rs-egress-prompts.cjs +113 -0
- package/lib/core/rs-egress-telemetry.cjs +225 -0
- package/lib/core/rs-egress-violations.cjs +53 -0
- package/lib/core/rs-expert-mapper.cjs +467 -0
- package/lib/core/rs-fetcher-academic.cjs +697 -0
- package/lib/core/rs-fetcher-experts.cjs +314 -0
- package/lib/core/rs-fetcher-industry.cjs +731 -0
- package/lib/core/rs-fetcher-patents.cjs +564 -0
- package/lib/core/rs-innovation-classifier.cjs +194 -0
- package/lib/core/rs-mind-map.cjs +656 -0
- package/lib/core/rs-neo4j-writer.cjs +388 -0
- package/lib/core/rs-nl-to-query.cjs +425 -0
- package/lib/core/rs-pinecone-bridge.cjs +303 -0
- package/lib/core/rs-preprocessor.cjs +350 -0
- package/lib/core/rs-query-matrix.cjs +316 -0
- package/lib/core/rs-query-to-text.cjs +438 -0
- package/lib/core/rs-sqlite-mirror.cjs +443 -0
- package/lib/core/rs-thesis-generator.cjs +188 -0
- package/lib/core/rs_cache.py +479 -0
- package/lib/core/rs_corpus.py +468 -0
- package/lib/core/rs_hybrid.py +586 -0
- package/lib/core/rs_math.py +287 -0
- package/lib/core/rs_rooms.py +193 -0
- package/lib/core/scheduled-scanner.cjs +463 -0
- package/lib/core/scratchpad-ops.cjs +201 -0
- package/lib/core/section-8-trace-schema.cjs +138 -0
- package/lib/core/section-registry.cjs +111 -0
- package/lib/core/session-state.cjs +144 -0
- package/lib/core/shallow-doc-parser.cjs +174 -0
- package/lib/core/shallow-doc-parser.test.cjs +226 -0
- package/lib/core/skill-activation-router.cjs +284 -0
- package/lib/core/state-ops.cjs +46 -0
- package/lib/core/statusline-cache.cjs +266 -0
- package/lib/core/token-estimator.cjs +348 -0
- package/lib/core/user-archetype.cjs +239 -0
- package/lib/core/user-md-ops.cjs +524 -0
- package/lib/core/visual-ops.cjs +624 -0
- package/lib/core/write-lock.cjs +149 -0
- package/lib/graph/canvas-graph.js +467 -0
- package/lib/graph/constellation-config.cjs +299 -0
- package/lib/graph/graph-detail-panel.js +165 -0
- package/lib/hmi/ROOM.md +47 -0
- package/lib/hmi/across-session-memory.cjs +604 -0
- package/lib/hmi/cross-room-memory.cjs +575 -0
- package/lib/hmi/decoy-tier.cjs +395 -0
- package/lib/hmi/jtbd-classifier.cjs +219 -0
- package/lib/hmi/jtbd-state.cjs +199 -0
- package/lib/hmi/jtbd-taxonomy.json +392 -0
- package/lib/hmi/selector-dispatcher.cjs +546 -0
- package/lib/hmi/selector-telemetry.cjs +263 -0
- package/lib/hmi/shape-f0-renderer.cjs +139 -0
- package/lib/hmi/shape-f1-fallback.cjs +80 -0
- package/lib/hmi/shape-f1-renderer.cjs +138 -0
- package/lib/hmi/shape-f2-renderer.cjs +132 -0
- package/lib/hmi/shape-f3-renderer.cjs +66 -0
- package/lib/hmi/shape-f4-renderer.cjs +72 -0
- package/lib/hmi/shape-f5-renderer.cjs +155 -0
- package/lib/hmi/shape-f6-plan-review-renderer.cjs +312 -0
- package/lib/hmi/shape-f6-renderer.cjs +144 -0
- package/lib/hmi/shape-g-renderer.cjs +219 -0
- package/lib/hmi/shape-h-renderer.cjs +222 -0
- package/lib/hmi/tier-check.cjs +63 -0
- package/lib/import/PRECONDITIONS.md +41 -0
- package/lib/import/branding.cjs +210 -0
- package/lib/import/branding.test.cjs +235 -0
- package/lib/import/classifications-sync.cjs +104 -0
- package/lib/import/classifications-sync.test.cjs +129 -0
- package/lib/import/enricher.cjs +296 -0
- package/lib/import/enricher.test.cjs +273 -0
- package/lib/import/integration.test.cjs +376 -0
- package/lib/import/manifest.cjs +129 -0
- package/lib/import/manifest.schema.json +185 -0
- package/lib/import/manifest.test.cjs +123 -0
- package/lib/import/meeting-detector.cjs +92 -0
- package/lib/import/meeting-detector.test.cjs +100 -0
- package/lib/import/person-detector.cjs +229 -0
- package/lib/import/person-detector.test.cjs +149 -0
- package/lib/import/report.cjs +186 -0
- package/lib/import/report.test.cjs +186 -0
- package/lib/import/room-md-scaffolder.cjs +49 -0
- package/lib/import/router.cjs +224 -0
- package/lib/import/router.test.cjs +356 -0
- package/lib/import/run-all-tests.cjs +36 -0
- package/lib/import/smoke-test.cjs +213 -0
- package/lib/import/smoke-test.test.cjs +148 -0
- package/lib/import/test-fixtures/collision-vault/preexisting-room/STATE.md +8 -0
- package/lib/import/test-fixtures/collision-vault/preexisting-room/problem-definition/onboarding/onboarding.md +7 -0
- package/lib/import/test-fixtures/collision-vault/source/onboarding.md +5 -0
- package/lib/import/test-fixtures/obsidian-vault/.obsidian/workspace.json +1 -0
- package/lib/import/test-fixtures/obsidian-vault/notes/with-wikilinks.md +4 -0
- package/lib/import/test-fixtures/tiny-vault/notes/2026-01-15-team-sync.md +9 -0
- package/lib/import/test-fixtures/tiny-vault/notes/empty.md +3 -0
- package/lib/import/test-fixtures/tiny-vault/notes/onboarding.md +5 -0
- package/lib/import/test-fixtures/tiny-vault/notes/pricing.md +5 -0
- package/lib/import/test-fixtures/tiny-vault/notes/random.md +4 -0
- package/lib/import/undo.test.cjs +199 -0
- package/lib/import/vault-scanner.cjs +105 -0
- package/lib/import/vault-scanner.test.cjs +67 -0
- package/lib/mcp/app-html/dashboard.html +316 -0
- package/lib/mcp/app-html/graph.html +428 -0
- package/lib/mcp/app-html/mindrian-platform.html +1841 -0
- package/lib/mcp/app-html/wiki.html +383 -0
- package/lib/mcp/app-views.cjs +322 -0
- package/lib/mcp/brain-router.cjs +418 -0
- package/lib/mcp/capability-registry.cjs +62 -0
- package/lib/mcp/larry-context.cjs +46 -0
- package/lib/mcp/larry-server-instructions.md +114 -0
- package/lib/mcp/pipeline-state.cjs +275 -0
- package/lib/mcp/prompts.cjs +302 -0
- package/lib/mcp/resources.cjs +227 -0
- package/lib/mcp/session-catchup.cjs +327 -0
- package/lib/mcp/surface-detect.cjs +75 -0
- package/lib/mcp/tool-router.cjs +1034 -0
- package/lib/memory/aaak-compress.cjs +403 -0
- package/lib/memory/aaak-compress.test.cjs +288 -0
- package/lib/memory/async-artifact-auto-commit.test.cjs +223 -0
- package/lib/memory/bearer-token.test.cjs +315 -0
- package/lib/memory/brain-cache-lru.test.cjs +259 -0
- package/lib/memory/brain-client-query-shape.test.cjs +160 -0
- package/lib/memory/brain-derivation-graceful-degradation.test.cjs +1019 -0
- package/lib/memory/brain-derivation-queue.test.cjs +539 -0
- package/lib/memory/brain-derivation.test.cjs +634 -0
- package/lib/memory/brain-derive-command.test.cjs +534 -0
- package/lib/memory/brain-md-invariants-validator.test.cjs +704 -0
- package/lib/memory/brain-md-schema.test.cjs +467 -0
- package/lib/memory/brain-md-staleness.test.cjs +525 -0
- package/lib/memory/brain-server-resolution.test.cjs +314 -0
- package/lib/memory/chain-recommender.test.cjs +233 -0
- package/lib/memory/chat-context.test.cjs +128 -0
- package/lib/memory/command-registry.test.cjs +220 -0
- package/lib/memory/cross-room-aggregator.test.cjs +909 -0
- package/lib/memory/dashboard-server.test.cjs +256 -0
- package/lib/memory/debouncer-drain-at-prompt.test.cjs +389 -0
- package/lib/memory/decision-capture.test.cjs +632 -0
- package/lib/memory/decision-capture.worker.cjs +70 -0
- package/lib/memory/explain-decision-command.test.cjs +521 -0
- package/lib/memory/explain-decision-footer.test.cjs +316 -0
- package/lib/memory/explored-materials-store.cjs +392 -0
- package/lib/memory/feynman-minto-guardian.test.cjs +736 -0
- package/lib/memory/feynman-minto-invariants.test.cjs +511 -0
- package/lib/memory/feynman-prompts-drift.test.cjs +144 -0
- package/lib/memory/feynman-prompts.cjs +151 -0
- package/lib/memory/feynman-prompts.test.cjs +96 -0
- package/lib/memory/folder-memory-quadruple.test.cjs +548 -0
- package/lib/memory/folder-memory.test.cjs +503 -0
- package/lib/memory/framework-chain-composer.test.cjs +515 -0
- package/lib/memory/frontmatter-schema-validator.test.cjs +290 -0
- package/lib/memory/heal-command.test.cjs +604 -0
- package/lib/memory/index-artifact-transaction.test.cjs +333 -0
- package/lib/memory/lazygraph-rs-discoveries-view.test.cjs +122 -0
- package/lib/memory/mcp-input-validation.test.cjs +240 -0
- package/lib/memory/mcp-server-brain-deps.test.cjs +270 -0
- package/lib/memory/mcp-stack-fallback.test.cjs +433 -0
- package/lib/memory/minto-debouncer.test.cjs +407 -0
- package/lib/memory/minto-debouncer.worker.cjs +46 -0
- package/lib/memory/minto-migration-v88.test.cjs +265 -0
- package/lib/memory/minto-schema-v88.test.cjs +390 -0
- package/lib/memory/mos-status-renderer.test.cjs +631 -0
- package/lib/memory/narrative-schema.cjs +376 -0
- package/lib/memory/narrative-schema.test.cjs +209 -0
- package/lib/memory/nav-dial.test.cjs +414 -0
- package/lib/memory/navigation-engine-core.test.cjs +722 -0
- package/lib/memory/navigation-invariants.test.cjs +483 -0
- package/lib/memory/offer-presenter.test.cjs +554 -0
- package/lib/memory/on-stop-snapshot.test.cjs +404 -0
- package/lib/memory/pending-tension-store.cjs +373 -0
- package/lib/memory/post-compact-reinjection.test.cjs +854 -0
- package/lib/memory/post-write-triple.test.cjs +317 -0
- package/lib/memory/pre-compact-snapshot.test.cjs +495 -0
- package/lib/memory/problem-type-router.test.cjs +656 -0
- package/lib/memory/query-efficiency-telemetry.test.cjs +370 -0
- package/lib/memory/recompile-room-references.test.cjs +392 -0
- package/lib/memory/recompile-room-references.worker.cjs +42 -0
- package/lib/memory/record-decision-dual-write.test.cjs +454 -0
- package/lib/memory/room-classifier-strict-mode.test.cjs +417 -0
- package/lib/memory/room-minto-hook.test.cjs +398 -0
- package/lib/memory/rs-discovery-engine.test.cjs +323 -0
- package/lib/memory/run-feynman-tests.cjs +1247 -0
- package/lib/memory/security-trifecta.test.cjs +312 -0
- package/lib/memory/session-start-brain-staleness.test.cjs +363 -0
- package/lib/memory/session-start-triple-injection.test.cjs +514 -0
- package/lib/memory/sessionstart-banner-formatter.cjs +318 -0
- package/lib/memory/sessionstart-minto-banner.test.cjs +373 -0
- package/lib/memory/skill-activation-router.test.cjs +419 -0
- package/lib/memory/stamp-artifact-write.test.cjs +304 -0
- package/lib/memory/statusline-active-room.test.cjs +315 -0
- package/lib/memory/statusline-minto-segment.test.cjs +292 -0
- package/lib/memory/sync-async-entry-points.test.cjs +204 -0
- package/lib/memory/test-bridge-writer-enhanced.cjs +452 -0
- package/lib/memory/test-rs-brain-substrate-shape.cjs +529 -0
- package/lib/memory/test-rs-brain-substrate.cjs +636 -0
- package/lib/memory/test-rs-breakthrough-scorer.cjs +375 -0
- package/lib/memory/test-rs-canon-violations.cjs +218 -0
- package/lib/memory/test-rs-chain-feeder-core.cjs +344 -0
- package/lib/memory/test-rs-chain-feeder-skill-spawn.cjs +297 -0
- package/lib/memory/test-rs-commercial-assessor.cjs +385 -0
- package/lib/memory/test-rs-differential-scorer.cjs +480 -0
- package/lib/memory/test-rs-discovery-engine.cjs +603 -0
- package/lib/memory/test-rs-domain-analyzer.cjs +492 -0
- package/lib/memory/test-rs-egress-primitives.cjs +420 -0
- package/lib/memory/test-rs-expert-mapper.cjs +547 -0
- package/lib/memory/test-rs-explain-command.cjs +443 -0
- package/lib/memory/test-rs-fetcher-academic.cjs +848 -0
- package/lib/memory/test-rs-fetcher-experts.cjs +496 -0
- package/lib/memory/test-rs-fetcher-industry.cjs +702 -0
- package/lib/memory/test-rs-fetcher-patents.cjs +674 -0
- package/lib/memory/test-rs-innovation-classifier.cjs +301 -0
- package/lib/memory/test-rs-mind-map.cjs +646 -0
- package/lib/memory/test-rs-neo4j-writer.cjs +518 -0
- package/lib/memory/test-rs-nl-to-query.cjs +449 -0
- package/lib/memory/test-rs-pinecone-bridge.cjs +277 -0
- package/lib/memory/test-rs-preprocessor.cjs +433 -0
- package/lib/memory/test-rs-query-matrix.cjs +391 -0
- package/lib/memory/test-rs-query-to-text.cjs +551 -0
- package/lib/memory/test-rs-sqlite-mirror.cjs +649 -0
- package/lib/memory/test-rs-thesis-generator.cjs +360 -0
- package/lib/memory/triple-context-formatter.cjs +473 -0
- package/lib/memory/triple-context-formatter.test.cjs +442 -0
- package/lib/memory/user-md-persona.test.cjs +565 -0
- package/lib/memory/userpromptsubmit-integration.test.cjs +690 -0
- package/lib/memory/validators/README.md +157 -0
- package/lib/memory/validators/brain-md-invariants.cjs +475 -0
- package/lib/memory/validators/brain-substrate-invariants.cjs +285 -0
- package/lib/memory/validators/external-academic-invariants.cjs +249 -0
- package/lib/memory/validators/external-industry-invariants.cjs +271 -0
- package/lib/memory/validators/external-patents-invariants.cjs +266 -0
- package/lib/memory/validators/minto-invariants.cjs +62 -0
- package/lib/memory/validators/navigation-invariants.cjs +340 -0
- package/lib/memory/validators/queue-health.cjs +95 -0
- package/lib/memory/validators/snapshot-integrity.cjs +129 -0
- package/lib/memory/validators/stale-lifecycle.cjs +116 -0
- package/lib/memory/vault-section-minto-generator-atomic.test.cjs +556 -0
- package/lib/memory/vault-section-minto-generator-atomic.worker.cjs +73 -0
- package/lib/memory/write-lock-atomic.test.cjs +137 -0
- package/lib/memory/write-lock-atomic.worker.cjs +55 -0
- package/lib/parity/check-parity.cjs +83 -0
- package/lib/presentation/presentation-server.cjs +101 -0
- package/lib/presentation/presentation-watcher.cjs +123 -0
- package/lib/quickview/hub-server.cjs +719 -0
- package/lib/quickview/server.cjs +533 -0
- package/lib/render/JTBD-PALETTES.md +145 -0
- package/lib/render/ROOM.md +59 -0
- package/lib/render/render-v2.cjs +486 -0
- package/lib/render/render-v2.test.cjs +267 -0
- package/lib/render/render.cjs +65 -0
- package/lib/state/ROOM.md +46 -0
- package/lib/state/state-md-parser.cjs +215 -0
- package/lib/statusline/ROOM.md +38 -0
- package/lib/statusline/banner-suppression.cjs +50 -0
- package/lib/statusline/surface-detect.cjs +85 -0
- package/lib/update-bootstrap.sh.template +145 -0
- package/lib/vault/frontmatter-schema.cjs +297 -0
- package/lib/vault/room-scanner.cjs +352 -0
- package/lib/vault/wikilink-builder.cjs +231 -0
- package/lib/vault/wikilink-builder.test.cjs +182 -0
- package/lib/wiki/graph-links.cjs +281 -0
- package/lib/wiki/page-renderer.cjs +229 -0
- package/lib/wiki/wiki-chat.cjs +81 -0
- package/lib/wiki/wiki-layout.cjs +1459 -0
- package/lib/wiki/wiki-search.cjs +142 -0
- package/lib/wiki/wiki-server.cjs +678 -0
- package/lib/wiki/wiki-watcher.cjs +105 -0
- package/lib/workflow/ROOM.md +47 -0
- package/lib/workflow/command-resolver.cjs +155 -0
- package/lib/workflow/command-resolver.test.cjs +235 -0
- package/package.json +44 -0
- package/pipelines/analogy/01-decompose.md +80 -0
- package/pipelines/analogy/02-abstract.md +87 -0
- package/pipelines/analogy/03-search.md +135 -0
- package/pipelines/analogy/04-transfer.md +101 -0
- package/pipelines/analogy/05-validate.md +106 -0
- package/pipelines/analogy/CHAIN.md +56 -0
- package/pipelines/discovery/01-explore-domains.md +44 -0
- package/pipelines/discovery/02-think-hats.md +50 -0
- package/pipelines/discovery/03-analyze-needs.md +54 -0
- package/pipelines/discovery/CHAIN.md +37 -0
- package/pipelines/thesis/01-structure-argument.md +45 -0
- package/pipelines/thesis/02-challenge-assumptions.md +48 -0
- package/pipelines/thesis/03-build-thesis.md +54 -0
- package/pipelines/thesis/CHAIN.md +37 -0
- package/references/brain/causal-directives.md +91 -0
- package/references/brain/causal-enrichment.cypher +165 -0
- package/references/brain/command-triggers-schema.md +226 -0
- package/references/brain/graph-architecture.md +317 -0
- package/references/brain/query-patterns.md +460 -0
- package/references/brain/room-hierarchy-schema.md +218 -0
- package/references/brain/schema.md +76 -0
- package/references/capability-radar/capabilities-index.md +241 -0
- package/references/capability-radar/changelog-cache.md +81 -0
- package/references/causal/causal-schema.md +103 -0
- package/references/design/email-template-standard.md +155 -0
- package/references/design/graph-visualization-standard.md +178 -0
- package/references/document-generation.md +179 -0
- package/references/hsi/HSI-TOOLS-REFERENCE.md +222 -0
- package/references/import-config.md +141 -0
- package/references/integrations/detection-patterns.md +101 -0
- package/references/meeting/artifact-template.md +377 -0
- package/references/meeting/cross-meeting-intelligence.md +216 -0
- package/references/meeting/cross-relationship-patterns.md +202 -0
- package/references/meeting/live-join-interface.md +244 -0
- package/references/meeting/section-mapping.md +192 -0
- package/references/meeting/segment-classification.md +258 -0
- package/references/meeting/speaker-profile-template.md +219 -0
- package/references/meeting/summary-template.md +348 -0
- package/references/meeting/transcript-patterns.md +226 -0
- package/references/methodology/analyze-needs.md +135 -0
- package/references/methodology/analyze-systems.md +121 -0
- package/references/methodology/analyze-timing.md +149 -0
- package/references/methodology/beautiful-question.md +109 -0
- package/references/methodology/build-knowledge.md +161 -0
- package/references/methodology/build-thesis.md +237 -0
- package/references/methodology/challenge-assumptions.md +127 -0
- package/references/methodology/diagnose.md +169 -0
- package/references/methodology/dominant-designs.md +212 -0
- package/references/methodology/explore-domains.md +147 -0
- package/references/methodology/explore-futures.md +163 -0
- package/references/methodology/explore-trends.md +129 -0
- package/references/methodology/find-bottlenecks.md +131 -0
- package/references/methodology/grade.md +211 -0
- package/references/methodology/index.md +97 -0
- package/references/methodology/leadership.md +200 -0
- package/references/methodology/lean-canvas.md +116 -0
- package/references/methodology/macro-trends.md +192 -0
- package/references/methodology/map-unknowns.md +137 -0
- package/references/methodology/mullins-7-domains.md +104 -0
- package/references/methodology/problem-types.md +65 -0
- package/references/methodology/root-cause.md +178 -0
- package/references/methodology/sapphire-encoding.md +355 -0
- package/references/methodology/scenario-plan.md +178 -0
- package/references/methodology/score-innovation.md +154 -0
- package/references/methodology/structure-argument.md +158 -0
- package/references/methodology/systems-thinking.md +159 -0
- package/references/methodology/think-hats.md +147 -0
- package/references/methodology/triz-matrix.json +751 -0
- package/references/methodology/triz-principles.md +501 -0
- package/references/methodology/user-needs.md +199 -0
- package/references/methodology/validate.md +163 -0
- package/references/methodology/value-proposition.md +244 -0
- package/references/opportunities/funding-lifecycle.md +103 -0
- package/references/opportunities/grant-api-patterns.md +99 -0
- package/references/opportunities/opportunity-template.md +84 -0
- package/references/personality/assessment-philosophy.md +72 -0
- package/references/personality/lexicon.md +100 -0
- package/references/personality/persona-chains.md +56 -0
- package/references/personality/pws-lexicon-full.md +499 -0
- package/references/personality/voice-dna.md +156 -0
- package/references/personas/hat-perspectives.md +76 -0
- package/references/personas/persona-template.md +63 -0
- package/references/pipeline/act-output-contract.md +88 -0
- package/references/pipeline/chains-index.md +39 -0
- package/references/pws-profile-generation.md +79 -0
- package/references/reasoning/reasoning-schema.md +143 -0
- package/references/reasoning/reasoning-template.md +68 -0
- package/references/reasoning/run-template.md +38 -0
- package/references/research/RESEARCH_14_CLAUDE_CODE_SOURCE_ARCHITECTURE.md +209 -0
- package/references/research/RESEARCH_15_V1.8_OPTIMIZATION_JTBD.md +375 -0
- package/references/research/RESEARCH_16_NATIVE_FIRST_PLUGIN_ARCHITECTURE.md +575 -0
- package/references/research/RESEARCH_17_MCP_UI_FRAMEWORKS.md +272 -0
- package/references/taxonomy/TAXONOMY.md +192 -0
- package/references/templates/MINTO.md +36 -0
- package/references/user-research/2026-04-05-leah-lawrence-session.md +202 -0
- package/references/vault-kit/README.md +35 -0
- package/references/vault-kit/app.json +12 -0
- package/references/vault-kit/appearance.json +12 -0
- package/references/vault-kit/graph.json +35 -0
- package/references/vault-kit/snippets/mindrian-destijl.css +297 -0
- package/references/vault-kit/templates/new-artifact.md +37 -0
- package/references/vault-kit/templates/new-meeting-note.md +35 -0
- package/references/vault-kit/templates/new-team-profile.md +29 -0
- package/references/vault-kit/templates/new-xref.md +35 -0
- package/references/visual/symbol-system.md +151 -0
- package/skills/MOSDeckEngine/SKILL.md +325 -0
- package/skills/brain-connector/SKILL.md +114 -0
- package/skills/context-engine/SKILL.md +147 -0
- package/skills/conversation-mode/SKILL.md +102 -0
- package/skills/larry-personality/SKILL.md +219 -0
- package/skills/larry-personality/framework-chains.md +92 -0
- package/skills/larry-personality/mode-engine.md +185 -0
- package/skills/mullins-scaffold/SKILL.md +61 -0
- package/skills/mullins-scaffold/scaffold.json +146 -0
- package/skills/pws-methodology/SKILL.md +49 -0
- package/skills/room-passive/SKILL.md +165 -0
- package/skills/room-proactive/SKILL.md +250 -0
- package/skills/ui-system/SKILL.md +277 -0
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
6
|
+
* Phase 89.2 Plan 04 -- industry fetcher fixture suite.
|
|
7
|
+
*
|
|
8
|
+
* 17 scenarios total: 11 fetcher tests + 6 validator tests.
|
|
9
|
+
*
|
|
10
|
+
* Test 1 happy path Tavily orchestration (3 refined sub-queries per query)
|
|
11
|
+
* Test 2 dedup determinism (same input twice -> identical output)
|
|
12
|
+
* Test 3 TAVILY_API_KEY absent graceful (api_key_missing telemetry)
|
|
13
|
+
* Test 4 Tavily 429 rate-limit graceful (no throw; rate_limited telemetry)
|
|
14
|
+
* Test 5 Tavily timeout graceful (timeout telemetry; empty signals)
|
|
15
|
+
* Test 6 Tavily malformed JSON graceful (api_error telemetry)
|
|
16
|
+
* Test 7 per-source budget exhausted (Tavily skipped; budget_exhausted)
|
|
17
|
+
* Test 8 CANON PART 8 adversarial #1 -- leaked-meeting in user query
|
|
18
|
+
* Test 9 CANON PART 8 adversarial #2 -- leaked-quoted-person in user query
|
|
19
|
+
* Test 10 CANON PART 8 adversarial #3 -- meeting pattern via refined sub-query
|
|
20
|
+
* opts.refinement_template_override = 'meeting with {query}'
|
|
21
|
+
* Audits the refined sub-query layer (TWO-LAYER defense).
|
|
22
|
+
* Test 11 CANON PART 8 adversarial #4 -- phone-like pattern via refined sub-query
|
|
23
|
+
* opts.refinement_template_override = '{query} contact 555-867-5309 sales'
|
|
24
|
+
* Audits second layer with FORBIDDEN_PATTERNS phone regex.
|
|
25
|
+
*
|
|
26
|
+
* V1 validator Check A: telemetry file absent -> {severity: null}
|
|
27
|
+
* V2 validator Check B: per-source budget exceeded -> warning
|
|
28
|
+
* V3 validator Check C: status enum violation -> warning
|
|
29
|
+
* V4 validator Check D: query_text literal present -> CRITICAL canon_boundary
|
|
30
|
+
* V5 validator Check E: query_text_hash format violation -> warning
|
|
31
|
+
* V6 validator Check F: fetched_at malformed ISO-8601 -> warning
|
|
32
|
+
*
|
|
33
|
+
* Suite-end audits:
|
|
34
|
+
* A1 every captured outbound URL + every captured telemetry record
|
|
35
|
+
* JSON.stringify scanned against FORBIDDEN_PATTERNS; ZERO hits
|
|
36
|
+
* A2 parity gate: rs-egress-prompts FORBIDDEN_PATTERNS byte-for-byte
|
|
37
|
+
* === cross-room-aggregator FORBIDDEN_PATTERNS at every regex.source
|
|
38
|
+
*
|
|
39
|
+
* Pure CJS, zero npm deps, node built-ins only (assert, fs, os, path, crypto).
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const assert = require('node:assert/strict');
|
|
43
|
+
const fs = require('node:fs');
|
|
44
|
+
const os = require('node:os');
|
|
45
|
+
const path = require('node:path');
|
|
46
|
+
const crypto = require('node:crypto');
|
|
47
|
+
|
|
48
|
+
// ---------- Suite bookkeeping ----------
|
|
49
|
+
|
|
50
|
+
let passed = 0;
|
|
51
|
+
let failed = 0;
|
|
52
|
+
const failures = [];
|
|
53
|
+
|
|
54
|
+
const SCENARIO_RESULTS = [];
|
|
55
|
+
|
|
56
|
+
const CAPTURED_URLS = [];
|
|
57
|
+
const CAPTURED_URLS_ALL = [];
|
|
58
|
+
const CAPTURED_BODIES = [];
|
|
59
|
+
const CAPTURED_BODIES_ALL = [];
|
|
60
|
+
|
|
61
|
+
let testHomeDir = null;
|
|
62
|
+
let originalHome = null;
|
|
63
|
+
let originalApiKey = null;
|
|
64
|
+
|
|
65
|
+
const ALL_TMP_ROOTS = [];
|
|
66
|
+
|
|
67
|
+
process.on('exit', function () {
|
|
68
|
+
if (originalHome !== null) {
|
|
69
|
+
process.env.HOME = originalHome;
|
|
70
|
+
}
|
|
71
|
+
if (originalApiKey === null) {
|
|
72
|
+
delete process.env.TAVILY_API_KEY;
|
|
73
|
+
} else {
|
|
74
|
+
process.env.TAVILY_API_KEY = originalApiKey;
|
|
75
|
+
}
|
|
76
|
+
for (const d of ALL_TMP_ROOTS) {
|
|
77
|
+
try { fs.rmSync(d, { recursive: true, force: true }); } catch (_e) { /* best effort */ }
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function setupScopedHome() {
|
|
82
|
+
if (originalHome === null) {
|
|
83
|
+
originalHome = process.env.HOME;
|
|
84
|
+
}
|
|
85
|
+
if (originalApiKey === null && process.env.TAVILY_API_KEY !== undefined) {
|
|
86
|
+
originalApiKey = process.env.TAVILY_API_KEY;
|
|
87
|
+
}
|
|
88
|
+
testHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rsfetind-home-'));
|
|
89
|
+
ALL_TMP_ROOTS.push(testHomeDir);
|
|
90
|
+
process.env.HOME = testHomeDir;
|
|
91
|
+
// Default for most tests: a key is present. Tests that require absence
|
|
92
|
+
// explicitly delete process.env.TAVILY_API_KEY after this call.
|
|
93
|
+
process.env.TAVILY_API_KEY = 'test-tavily-key-' + Date.now();
|
|
94
|
+
resetRequireCache();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resetRequireCache() {
|
|
98
|
+
const targets = [
|
|
99
|
+
'../core/rs-egress-violations.cjs',
|
|
100
|
+
'../core/rs-egress-prompts.cjs',
|
|
101
|
+
'../core/rs-egress-telemetry.cjs',
|
|
102
|
+
'../core/rs-fetcher-industry.cjs',
|
|
103
|
+
'../core/cross-room-aggregator.cjs',
|
|
104
|
+
'./validators/external-industry-invariants.cjs',
|
|
105
|
+
];
|
|
106
|
+
for (const t of targets) {
|
|
107
|
+
try {
|
|
108
|
+
const p = require.resolve(t);
|
|
109
|
+
delete require.cache[p];
|
|
110
|
+
} catch (_e) { /* best effort */ }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------- Mock fetch ----------
|
|
115
|
+
|
|
116
|
+
let originalFetch = null;
|
|
117
|
+
const mockResponses = new Map();
|
|
118
|
+
|
|
119
|
+
function installMockFetch(responses) {
|
|
120
|
+
if (originalFetch === null) {
|
|
121
|
+
originalFetch = global.fetch;
|
|
122
|
+
}
|
|
123
|
+
mockResponses.clear();
|
|
124
|
+
CAPTURED_URLS.length = 0;
|
|
125
|
+
CAPTURED_BODIES.length = 0;
|
|
126
|
+
for (const [k, v] of Object.entries(responses)) {
|
|
127
|
+
mockResponses.set(k, v);
|
|
128
|
+
}
|
|
129
|
+
global.fetch = async function (url, opts) {
|
|
130
|
+
CAPTURED_URLS.push(String(url));
|
|
131
|
+
CAPTURED_URLS_ALL.push(String(url));
|
|
132
|
+
if (opts && typeof opts.body === 'string') {
|
|
133
|
+
CAPTURED_BODIES.push(opts.body);
|
|
134
|
+
CAPTURED_BODIES_ALL.push(opts.body);
|
|
135
|
+
}
|
|
136
|
+
for (const [prefix, handler] of mockResponses.entries()) {
|
|
137
|
+
if (String(url).indexOf(prefix) >= 0) {
|
|
138
|
+
return handler(url, opts);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { ok: false, status: 404, headers: new Map(), async json() { return {}; }, async text() { return ''; } };
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function restoreFetch() {
|
|
146
|
+
if (originalFetch !== null) {
|
|
147
|
+
global.fetch = originalFetch;
|
|
148
|
+
}
|
|
149
|
+
mockResponses.clear();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ---------- Scenario runner ----------
|
|
153
|
+
|
|
154
|
+
async function runScenario(name, fn) {
|
|
155
|
+
const start = Date.now();
|
|
156
|
+
try {
|
|
157
|
+
await fn();
|
|
158
|
+
passed += 1;
|
|
159
|
+
process.stdout.write(' ok ' + name + ' (' + (Date.now() - start) + 'ms)\n');
|
|
160
|
+
} catch (err) {
|
|
161
|
+
failed += 1;
|
|
162
|
+
failures.push({ name: name, err: err });
|
|
163
|
+
process.stderr.write(' FAIL ' + name + '\n');
|
|
164
|
+
process.stderr.write(' ' + (err && err.stack ? err.stack : err) + '\n');
|
|
165
|
+
} finally {
|
|
166
|
+
restoreFetch();
|
|
167
|
+
resetRequireCache();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ---------- Helpers ----------
|
|
172
|
+
|
|
173
|
+
function makeTavilyResponse(query, n) {
|
|
174
|
+
// Simulated api.tavily.com/search JSON payload. Real Tavily returns
|
|
175
|
+
// { results: [{ title, url, content, score, ... }, ... ], ... }.
|
|
176
|
+
const results = [];
|
|
177
|
+
for (let i = 0; i < n; i++) {
|
|
178
|
+
const tag = crypto.createHash('sha256').update(query + ':' + i).digest('hex').slice(0, 6);
|
|
179
|
+
results.push({
|
|
180
|
+
title: 'Tavily result ' + i + ' for ' + query.slice(0, 30),
|
|
181
|
+
url: 'https://example-' + tag + '.com/article-' + i,
|
|
182
|
+
content: 'Company NewCo' + i + ' raised seed funding for project related to '
|
|
183
|
+
+ query.slice(0, 40) + ' according to public reports.',
|
|
184
|
+
score: 0.9 - (i * 0.05),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return { results: results, query: query, response_time: 0.5 };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildTavilyMockOk(query, perSubquery) {
|
|
191
|
+
return {
|
|
192
|
+
'api.tavily.com': async function (url, opts) {
|
|
193
|
+
// Inspect body to derive which refined sub-query we received so the
|
|
194
|
+
// response can echo a variant set per template.
|
|
195
|
+
let q = query;
|
|
196
|
+
try {
|
|
197
|
+
if (opts && typeof opts.body === 'string') {
|
|
198
|
+
const parsed = JSON.parse(opts.body);
|
|
199
|
+
if (parsed && typeof parsed.query === 'string') q = parsed.query;
|
|
200
|
+
}
|
|
201
|
+
} catch (_e) { /* fall back to user-query echo */ }
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
status: 200,
|
|
205
|
+
headers: new Map([['x-ratelimit-remaining', '29']]),
|
|
206
|
+
async json() { return makeTavilyResponse(q, perSubquery); },
|
|
207
|
+
async text() { return JSON.stringify(makeTavilyResponse(q, perSubquery)); },
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---------- A1/A2 sweep helpers ----------
|
|
214
|
+
|
|
215
|
+
function getForbiddenPatternsFromAggregator() {
|
|
216
|
+
const aggregator = require('../core/cross-room-aggregator.cjs');
|
|
217
|
+
return aggregator.FORBIDDEN_PATTERNS;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function scanAgainstForbidden(stringified) {
|
|
221
|
+
const patterns = getForbiddenPatternsFromAggregator();
|
|
222
|
+
for (const re of patterns) {
|
|
223
|
+
if (re.test(stringified)) {
|
|
224
|
+
return { hit: true, pattern: re.source };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return { hit: false };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ---------- Begin scenarios ----------
|
|
231
|
+
|
|
232
|
+
console.log('=== 89.2-04 fetcher-industry suite: starting ===');
|
|
233
|
+
|
|
234
|
+
(async function main() {
|
|
235
|
+
|
|
236
|
+
// ---------- Test 1: happy path Tavily orchestration ----------
|
|
237
|
+
await runScenario('Test 1: happy path Tavily orchestration (3 refined sub-queries)', async function () {
|
|
238
|
+
setupScopedHome();
|
|
239
|
+
const queries = ['cancer immunotherapy startups', 'CRISPR funding rounds 2024'];
|
|
240
|
+
installMockFetch(buildTavilyMockOk('any', 5));
|
|
241
|
+
const fetcher = require('../core/rs-fetcher-industry.cjs');
|
|
242
|
+
const out = await fetcher.fetchIndustry(queries, {});
|
|
243
|
+
assert.ok(out && Array.isArray(out.signals), 'signals is array');
|
|
244
|
+
assert.ok(out.signals.length >= 10,
|
|
245
|
+
'expected >= 10 signals after dedup across 2 user queries x 3 sub-queries x 5 results; got ' + out.signals.length);
|
|
246
|
+
const sample = out.signals[0];
|
|
247
|
+
for (const f of ['company', 'signal', 'source', 'url', 'fetched_at']) {
|
|
248
|
+
assert.ok(Object.prototype.hasOwnProperty.call(sample, f),
|
|
249
|
+
'signal missing field: ' + f);
|
|
250
|
+
}
|
|
251
|
+
assert.equal(sample.source, 'tavily', 'source is tavily');
|
|
252
|
+
assert.ok(typeof sample.company === 'string' && sample.company.length > 0,
|
|
253
|
+
'company non-empty: ' + JSON.stringify(sample.company));
|
|
254
|
+
assert.ok(typeof sample.signal === 'string' && sample.signal.length >= 1 && sample.signal.length <= 200,
|
|
255
|
+
'signal length 1..200; got ' + sample.signal.length);
|
|
256
|
+
assert.ok(/^https?:\/\//.test(sample.url), 'url is HTTP(S); got ' + sample.url);
|
|
257
|
+
assert.ok(!Number.isNaN(Date.parse(sample.fetched_at)),
|
|
258
|
+
'fetched_at is parseable ISO-8601; got ' + sample.fetched_at);
|
|
259
|
+
// Verify the orchestration: 2 queries x 3 templates = 6 fetch calls
|
|
260
|
+
assert.equal(CAPTURED_URLS.length, 6,
|
|
261
|
+
'expected 6 fetch calls (2 queries x 3 refined templates); got ' + CAPTURED_URLS.length);
|
|
262
|
+
SCENARIO_RESULTS.push({ test: 'T1', surface: 'industry', payload: out });
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ---------- Test 2: dedup determinism ----------
|
|
266
|
+
await runScenario('Test 2: dedup determinism (same input twice == identical output)', async function () {
|
|
267
|
+
setupScopedHome();
|
|
268
|
+
const queries = ['biotech series A 2024'];
|
|
269
|
+
installMockFetch(buildTavilyMockOk('any', 5));
|
|
270
|
+
const fetcher = require('../core/rs-fetcher-industry.cjs');
|
|
271
|
+
const r1 = await fetcher.fetchIndustry(queries, {});
|
|
272
|
+
CAPTURED_URLS.length = 0;
|
|
273
|
+
setupScopedHome();
|
|
274
|
+
installMockFetch(buildTavilyMockOk('any', 5));
|
|
275
|
+
const fetcher2 = require('../core/rs-fetcher-industry.cjs');
|
|
276
|
+
const r2 = await fetcher2.fetchIndustry(queries, {});
|
|
277
|
+
const stripT = function (signals) {
|
|
278
|
+
return signals.map(s => {
|
|
279
|
+
const c = Object.assign({}, s);
|
|
280
|
+
delete c.fetched_at;
|
|
281
|
+
return c;
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
assert.equal(JSON.stringify(stripT(r1.signals)), JSON.stringify(stripT(r2.signals)),
|
|
285
|
+
'dedup output must be deterministic across runs');
|
|
286
|
+
SCENARIO_RESULTS.push({ test: 'T2', surface: 'industry', payload: stripT(r1.signals) });
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// ---------- Test 3: TAVILY_API_KEY missing graceful ----------
|
|
290
|
+
await runScenario('Test 3: TAVILY_API_KEY missing -> graceful empty + api_key_missing telemetry', async function () {
|
|
291
|
+
setupScopedHome();
|
|
292
|
+
delete process.env.TAVILY_API_KEY;
|
|
293
|
+
const queries = ['quantum computing startups'];
|
|
294
|
+
installMockFetch(buildTavilyMockOk('any', 5));
|
|
295
|
+
const fetcher = require('../core/rs-fetcher-industry.cjs');
|
|
296
|
+
const out = await fetcher.fetchIndustry(queries, {});
|
|
297
|
+
assert.ok(Array.isArray(out.signals), 'signals is array');
|
|
298
|
+
assert.equal(out.signals.length, 0, 'signals empty when TAVILY_API_KEY absent');
|
|
299
|
+
assert.equal(CAPTURED_URLS.length, 0, 'NO fetch() calls when API key absent');
|
|
300
|
+
// Telemetry must record api_key_missing.
|
|
301
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
302
|
+
if (fs.existsSync(telemetry.TELEMETRY_FILE)) {
|
|
303
|
+
const payload = JSON.parse(fs.readFileSync(telemetry.TELEMETRY_FILE, 'utf8'));
|
|
304
|
+
const missing = payload.entries.filter(e => e.status === 'api_key_missing');
|
|
305
|
+
assert.ok(missing.length >= 1, 'api_key_missing telemetry recorded');
|
|
306
|
+
}
|
|
307
|
+
const out2Tel = out.telemetry || [];
|
|
308
|
+
const inMem = out2Tel.filter(e => e.status === 'api_key_missing');
|
|
309
|
+
assert.ok(inMem.length >= 1, 'in-memory telemetry has api_key_missing');
|
|
310
|
+
SCENARIO_RESULTS.push({ test: 'T3', surface: 'industry', payload: out });
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// ---------- Test 4: Tavily 429 rate-limit graceful ----------
|
|
314
|
+
await runScenario('Test 4: Tavily 429 rate-limit graceful (no throw; rate_limited telemetry)', async function () {
|
|
315
|
+
setupScopedHome();
|
|
316
|
+
const queries = ['SaaS funding rounds Q1 2024'];
|
|
317
|
+
installMockFetch({
|
|
318
|
+
'api.tavily.com': async function () {
|
|
319
|
+
return {
|
|
320
|
+
ok: false,
|
|
321
|
+
status: 429,
|
|
322
|
+
headers: new Map([['retry-after', '60']]),
|
|
323
|
+
async text() { return 'rate limited'; },
|
|
324
|
+
async json() { return { error: 'rate limited' }; },
|
|
325
|
+
};
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
const fetcher = require('../core/rs-fetcher-industry.cjs');
|
|
329
|
+
const out = await fetcher.fetchIndustry(queries, {});
|
|
330
|
+
assert.equal(out.signals.length, 0, 'signals empty after 429');
|
|
331
|
+
|
|
332
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
333
|
+
const payload = JSON.parse(fs.readFileSync(telemetry.TELEMETRY_FILE, 'utf8'));
|
|
334
|
+
const rateLimited = payload.entries.filter(e => e.status === 'rate_limited');
|
|
335
|
+
assert.ok(rateLimited.length >= 1, 'rate_limited telemetry recorded');
|
|
336
|
+
assert.equal(rateLimited[0].http_status, 429, 'http_status 429 recorded');
|
|
337
|
+
SCENARIO_RESULTS.push({ test: 'T4', surface: 'industry', payload: out });
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// ---------- Test 5: Tavily timeout graceful ----------
|
|
341
|
+
await runScenario('Test 5: Tavily timeout graceful (no throw; timeout telemetry)', async function () {
|
|
342
|
+
setupScopedHome();
|
|
343
|
+
const queries = ['edtech AI tutors'];
|
|
344
|
+
installMockFetch({
|
|
345
|
+
'api.tavily.com': async function () {
|
|
346
|
+
const err = new Error('aborted');
|
|
347
|
+
err.name = 'AbortError';
|
|
348
|
+
throw err;
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
const fetcher = require('../core/rs-fetcher-industry.cjs');
|
|
352
|
+
const out = await fetcher.fetchIndustry(queries, {});
|
|
353
|
+
assert.equal(out.signals.length, 0, 'signals empty after timeout');
|
|
354
|
+
|
|
355
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
356
|
+
const payload = JSON.parse(fs.readFileSync(telemetry.TELEMETRY_FILE, 'utf8'));
|
|
357
|
+
const timed = payload.entries.filter(e => e.status === 'timeout');
|
|
358
|
+
assert.ok(timed.length >= 1, 'timeout telemetry recorded');
|
|
359
|
+
SCENARIO_RESULTS.push({ test: 'T5', surface: 'industry', payload: out });
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// ---------- Test 6: Tavily malformed JSON graceful ----------
|
|
363
|
+
await runScenario('Test 6: Tavily malformed JSON graceful (api_error telemetry)', async function () {
|
|
364
|
+
setupScopedHome();
|
|
365
|
+
const queries = ['climate tech startups'];
|
|
366
|
+
installMockFetch({
|
|
367
|
+
'api.tavily.com': async function () {
|
|
368
|
+
return {
|
|
369
|
+
ok: true,
|
|
370
|
+
status: 200,
|
|
371
|
+
headers: new Map(),
|
|
372
|
+
async json() { throw new SyntaxError('invalid JSON'); },
|
|
373
|
+
async text() { return '<<<not-json>>>'; },
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
const fetcher = require('../core/rs-fetcher-industry.cjs');
|
|
378
|
+
const out = await fetcher.fetchIndustry(queries, {});
|
|
379
|
+
assert.equal(out.signals.length, 0, 'signals empty after parse failure');
|
|
380
|
+
|
|
381
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
382
|
+
const payload = JSON.parse(fs.readFileSync(telemetry.TELEMETRY_FILE, 'utf8'));
|
|
383
|
+
const errd = payload.entries.filter(e => e.status === 'api_error');
|
|
384
|
+
assert.ok(errd.length >= 1, 'api_error telemetry recorded');
|
|
385
|
+
SCENARIO_RESULTS.push({ test: 'T6', surface: 'industry', payload: out });
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// ---------- Test 7: per-source budget exhausted ----------
|
|
389
|
+
await runScenario('Test 7: per-source budget exhausted (Tavily skipped)', async function () {
|
|
390
|
+
setupScopedHome();
|
|
391
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
392
|
+
// Seed 30 tavily entries within last 24h (default budget = 30).
|
|
393
|
+
const now = Date.now();
|
|
394
|
+
const entries = [];
|
|
395
|
+
for (let i = 0; i < 30; i++) {
|
|
396
|
+
entries.push({
|
|
397
|
+
source: 'tavily',
|
|
398
|
+
query_text_hash: 'aaaaaaaaaaaaaaaa',
|
|
399
|
+
status: 'ok',
|
|
400
|
+
fetched_at: new Date(now - (i * 1000)).toISOString(),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
fs.mkdirSync(path.dirname(telemetry.TELEMETRY_FILE), { recursive: true });
|
|
404
|
+
fs.writeFileSync(telemetry.TELEMETRY_FILE,
|
|
405
|
+
JSON.stringify({ schema_version: '1.0', entries: entries }, null, 2));
|
|
406
|
+
|
|
407
|
+
const queries = ['fintech series B'];
|
|
408
|
+
installMockFetch(buildTavilyMockOk('any', 3));
|
|
409
|
+
const fetcher = require('../core/rs-fetcher-industry.cjs');
|
|
410
|
+
const out = await fetcher.fetchIndustry(queries, {});
|
|
411
|
+
assert.equal(out.signals.length, 0, 'signals empty after budget exhausted');
|
|
412
|
+
assert.equal(CAPTURED_URLS.length, 0, 'no fetch when budget exhausted');
|
|
413
|
+
|
|
414
|
+
const inMem = (out.telemetry || []).filter(e => e.status === 'budget_exhausted');
|
|
415
|
+
assert.ok(inMem.length >= 1, 'in-memory telemetry has budget_exhausted');
|
|
416
|
+
SCENARIO_RESULTS.push({ test: 'T7', surface: 'industry', payload: out });
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// ---------- Test 8: CANON PART 8 adversarial #1 leaked-meeting in user query ----------
|
|
420
|
+
await runScenario('Test 8: CANON PART 8 adversarial #1 leaked-meeting throws (ZERO fetch)', async function () {
|
|
421
|
+
setupScopedHome();
|
|
422
|
+
const queries = ['CRISPR <<artifact: meeting with Genentech Q4 financials>>'];
|
|
423
|
+
installMockFetch(buildTavilyMockOk('clean', 1));
|
|
424
|
+
const fetcher = require('../core/rs-fetcher-industry.cjs');
|
|
425
|
+
let threw = null;
|
|
426
|
+
try {
|
|
427
|
+
await fetcher.fetchIndustry(queries, {});
|
|
428
|
+
} catch (e) {
|
|
429
|
+
threw = e;
|
|
430
|
+
}
|
|
431
|
+
assert.ok(threw, 'expected throw on adversarial query');
|
|
432
|
+
assert.equal(threw.name, 'ExternalEgressViolation', 'class name');
|
|
433
|
+
assert.equal(threw.meta.surface, 'industry', 'meta.surface');
|
|
434
|
+
assert.ok(typeof threw.meta.matched_pattern === 'string'
|
|
435
|
+
&& threw.meta.matched_pattern.length > 0, 'meta.matched_pattern present');
|
|
436
|
+
|
|
437
|
+
assert.equal(CAPTURED_URLS.length, 0,
|
|
438
|
+
'NO fetch() calls allowed before throw; got ' + CAPTURED_URLS.length);
|
|
439
|
+
|
|
440
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
441
|
+
if (fs.existsSync(telemetry.TELEMETRY_FILE)) {
|
|
442
|
+
const payload = JSON.parse(fs.readFileSync(telemetry.TELEMETRY_FILE, 'utf8'));
|
|
443
|
+
assert.equal(payload.entries.length, 0,
|
|
444
|
+
'NO telemetry entries allowed; got ' + payload.entries.length);
|
|
445
|
+
}
|
|
446
|
+
SCENARIO_RESULTS.push({ test: 'T8', surface: 'industry', payload: { threw: threw.meta.matched_pattern } });
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// ---------- Test 9: CANON PART 8 adversarial #2 leaked-quoted-person ----------
|
|
450
|
+
await runScenario('Test 9: CANON PART 8 adversarial #2 leaked-quoted-person throws', async function () {
|
|
451
|
+
setupScopedHome();
|
|
452
|
+
const queries = ['Lawrence said our competition raised funding'];
|
|
453
|
+
installMockFetch(buildTavilyMockOk('clean', 1));
|
|
454
|
+
const fetcher = require('../core/rs-fetcher-industry.cjs');
|
|
455
|
+
let threw = null;
|
|
456
|
+
try {
|
|
457
|
+
await fetcher.fetchIndustry(queries, {});
|
|
458
|
+
} catch (e) {
|
|
459
|
+
threw = e;
|
|
460
|
+
}
|
|
461
|
+
assert.ok(threw, 'expected throw on adversarial query');
|
|
462
|
+
assert.equal(threw.name, 'ExternalEgressViolation', 'class name');
|
|
463
|
+
assert.equal(threw.meta.surface, 'industry', 'meta.surface');
|
|
464
|
+
assert.equal(CAPTURED_URLS.length, 0, 'NO fetch() calls allowed');
|
|
465
|
+
SCENARIO_RESULTS.push({ test: 'T9', surface: 'industry', payload: { threw: threw.meta.matched_pattern } });
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// ---------- Test 10: CANON PART 8 adversarial #3 forbidden via REFINED sub-query ----------
|
|
469
|
+
await runScenario('Test 10: CANON PART 8 adversarial #3 meeting-via-refined-sub-query throws', async function () {
|
|
470
|
+
setupScopedHome();
|
|
471
|
+
// User query is clean. Adversarial template smuggles a forbidden
|
|
472
|
+
// pattern. The TWO-LAYER audit must throw on the second layer
|
|
473
|
+
// (refined sub-query), proving defense-in-depth.
|
|
474
|
+
const queries = ['biotech startups'];
|
|
475
|
+
installMockFetch(buildTavilyMockOk('clean', 1));
|
|
476
|
+
const fetcher = require('../core/rs-fetcher-industry.cjs');
|
|
477
|
+
let threw = null;
|
|
478
|
+
try {
|
|
479
|
+
await fetcher.fetchIndustry(queries, {
|
|
480
|
+
refinement_template_override: 'meeting with {query}',
|
|
481
|
+
});
|
|
482
|
+
} catch (e) {
|
|
483
|
+
threw = e;
|
|
484
|
+
}
|
|
485
|
+
assert.ok(threw, 'expected throw on adversarial refined sub-query');
|
|
486
|
+
assert.equal(threw.name, 'ExternalEgressViolation', 'class name');
|
|
487
|
+
assert.equal(threw.meta.surface, 'industry', 'meta.surface');
|
|
488
|
+
assert.ok(typeof threw.meta.matched_pattern === 'string'
|
|
489
|
+
&& threw.meta.matched_pattern.length > 0, 'meta.matched_pattern present');
|
|
490
|
+
assert.equal(CAPTURED_URLS.length, 0,
|
|
491
|
+
'NO fetch() calls allowed; second-layer audit fires before fetch; got ' + CAPTURED_URLS.length);
|
|
492
|
+
SCENARIO_RESULTS.push({ test: 'T10', surface: 'industry', payload: { threw: threw.meta.matched_pattern } });
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// ---------- Test 11: CANON PART 8 adversarial #4 phone-via-refined-sub-query ----------
|
|
496
|
+
await runScenario('Test 11: CANON PART 8 adversarial #4 phone-via-refined-sub-query throws', async function () {
|
|
497
|
+
setupScopedHome();
|
|
498
|
+
// User query is clean. Template injects a phone-like pattern that
|
|
499
|
+
// would only surface in the refined sub-query. This proves the
|
|
500
|
+
// second-layer audit catches forbidden patterns ACROSS THE FULL
|
|
501
|
+
// FAMILY of FORBIDDEN_PATTERNS (Tests 8 + 9 covered meeting +
|
|
502
|
+
// quoted-person; Test 10 covered meeting via override; Test 11
|
|
503
|
+
// covers phone-like via override).
|
|
504
|
+
const queries = ['biotech startups'];
|
|
505
|
+
installMockFetch(buildTavilyMockOk('clean', 1));
|
|
506
|
+
const fetcher = require('../core/rs-fetcher-industry.cjs');
|
|
507
|
+
let threw = null;
|
|
508
|
+
try {
|
|
509
|
+
await fetcher.fetchIndustry(queries, {
|
|
510
|
+
refinement_template_override: '{query} contact 555-867-5309 sales',
|
|
511
|
+
});
|
|
512
|
+
} catch (e) {
|
|
513
|
+
threw = e;
|
|
514
|
+
}
|
|
515
|
+
assert.ok(threw, 'expected throw on phone-pattern adversarial');
|
|
516
|
+
assert.equal(threw.name, 'ExternalEgressViolation', 'class name');
|
|
517
|
+
assert.equal(threw.meta.surface, 'industry', 'meta.surface');
|
|
518
|
+
assert.ok(typeof threw.meta.matched_pattern === 'string'
|
|
519
|
+
&& threw.meta.matched_pattern.indexOf('d{3}') >= 0,
|
|
520
|
+
'matched_pattern is the phone regex source containing d{3}; got ' + threw.meta.matched_pattern);
|
|
521
|
+
assert.equal(CAPTURED_URLS.length, 0,
|
|
522
|
+
'NO fetch() calls allowed; got ' + CAPTURED_URLS.length);
|
|
523
|
+
|
|
524
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
525
|
+
if (fs.existsSync(telemetry.TELEMETRY_FILE)) {
|
|
526
|
+
const payload = JSON.parse(fs.readFileSync(telemetry.TELEMETRY_FILE, 'utf8'));
|
|
527
|
+
assert.equal(payload.entries.length, 0,
|
|
528
|
+
'NO telemetry entries allowed; got ' + payload.entries.length);
|
|
529
|
+
}
|
|
530
|
+
SCENARIO_RESULTS.push({ test: 'T11', surface: 'industry', payload: { threw: threw.meta.matched_pattern } });
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// ---------- V1: validator Check A telemetry-file-absent ----------
|
|
534
|
+
await runScenario('V1: validator Check A telemetry-file-absent -> {severity: null}', async function () {
|
|
535
|
+
setupScopedHome();
|
|
536
|
+
const validator = require('./validators/external-industry-invariants.cjs');
|
|
537
|
+
const result = validator.validate('/dev/null', {});
|
|
538
|
+
assert.equal(result.severity, null, 'severity null when telemetry absent');
|
|
539
|
+
assert.equal(result.violations.length, 0, 'no violations when telemetry absent');
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// ---------- V2: validator Check B per-source budget exceeded ----------
|
|
543
|
+
await runScenario('V2: validator Check B per-source budget exceeded -> warning', async function () {
|
|
544
|
+
setupScopedHome();
|
|
545
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
546
|
+
// Seed 50 tavily entries within last 24h (over default 30 budget).
|
|
547
|
+
const now = Date.now();
|
|
548
|
+
const entries = [];
|
|
549
|
+
for (let i = 0; i < 50; i++) {
|
|
550
|
+
entries.push({
|
|
551
|
+
source: 'tavily',
|
|
552
|
+
query_text_hash: 'aaaaaaaaaaaaaaaa',
|
|
553
|
+
status: 'ok',
|
|
554
|
+
fetched_at: new Date(now - (i * 1000)).toISOString(),
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
fs.mkdirSync(path.dirname(telemetry.TELEMETRY_FILE), { recursive: true });
|
|
558
|
+
fs.writeFileSync(telemetry.TELEMETRY_FILE,
|
|
559
|
+
JSON.stringify({ schema_version: '1.0', entries: entries }, null, 2));
|
|
560
|
+
|
|
561
|
+
const validator = require('./validators/external-industry-invariants.cjs');
|
|
562
|
+
const result = validator.validate('/dev/null', {});
|
|
563
|
+
assert.ok(result.violations.length >= 1, 'expected at least one violation');
|
|
564
|
+
const budgetViolations = result.violations.filter(v => v.category === 'budget_exceeded');
|
|
565
|
+
assert.ok(budgetViolations.length >= 1, 'expected budget_exceeded violation');
|
|
566
|
+
assert.equal(budgetViolations[0].severity, 'warning', 'budget_exceeded severity is warning');
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// ---------- V3: validator Check C status enum violation ----------
|
|
570
|
+
await runScenario('V3: validator Check C status enum violation -> warning', async function () {
|
|
571
|
+
setupScopedHome();
|
|
572
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
573
|
+
const entries = [{
|
|
574
|
+
source: 'tavily',
|
|
575
|
+
query_text_hash: 'aaaaaaaaaaaaaaaa',
|
|
576
|
+
status: 'unknown_status',
|
|
577
|
+
fetched_at: new Date().toISOString(),
|
|
578
|
+
}];
|
|
579
|
+
fs.mkdirSync(path.dirname(telemetry.TELEMETRY_FILE), { recursive: true });
|
|
580
|
+
fs.writeFileSync(telemetry.TELEMETRY_FILE,
|
|
581
|
+
JSON.stringify({ schema_version: '1.0', entries: entries }, null, 2));
|
|
582
|
+
|
|
583
|
+
const validator = require('./validators/external-industry-invariants.cjs');
|
|
584
|
+
const result = validator.validate('/dev/null', {});
|
|
585
|
+
const statusViolations = result.violations.filter(v => v.category === 'status_enum_violation');
|
|
586
|
+
assert.ok(statusViolations.length >= 1, 'expected status_enum_violation');
|
|
587
|
+
assert.equal(statusViolations[0].severity, 'warning', 'status_enum severity is warning');
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// ---------- V4: validator Check D query_text literal -> CRITICAL ----------
|
|
591
|
+
await runScenario('V4: validator Check D query_text literal present -> CRITICAL canon_boundary', async function () {
|
|
592
|
+
setupScopedHome();
|
|
593
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
594
|
+
const entries = [{
|
|
595
|
+
source: 'tavily',
|
|
596
|
+
query_text: 'literal industry-signal query',
|
|
597
|
+
query_text_hash: 'aaaaaaaaaaaaaaaa',
|
|
598
|
+
status: 'ok',
|
|
599
|
+
fetched_at: new Date().toISOString(),
|
|
600
|
+
}];
|
|
601
|
+
fs.mkdirSync(path.dirname(telemetry.TELEMETRY_FILE), { recursive: true });
|
|
602
|
+
fs.writeFileSync(telemetry.TELEMETRY_FILE,
|
|
603
|
+
JSON.stringify({ schema_version: '1.0', entries: entries }, null, 2));
|
|
604
|
+
|
|
605
|
+
const validator = require('./validators/external-industry-invariants.cjs');
|
|
606
|
+
const result = validator.validate('/dev/null', {});
|
|
607
|
+
const canonViolations = result.violations.filter(v => v.category === 'canon_boundary');
|
|
608
|
+
assert.ok(canonViolations.length >= 1, 'expected canon_boundary violation');
|
|
609
|
+
assert.equal(canonViolations[0].severity, 'critical',
|
|
610
|
+
'canon_boundary severity is critical');
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// ---------- V5: validator Check E query_text_hash format ----------
|
|
614
|
+
await runScenario('V5: validator Check E query_text_hash format violation -> warning', async function () {
|
|
615
|
+
setupScopedHome();
|
|
616
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
617
|
+
const entries = [{
|
|
618
|
+
source: 'tavily',
|
|
619
|
+
query_text_hash: 'NOT-A-HEX-HASH-XYZ',
|
|
620
|
+
status: 'ok',
|
|
621
|
+
fetched_at: new Date().toISOString(),
|
|
622
|
+
}];
|
|
623
|
+
fs.mkdirSync(path.dirname(telemetry.TELEMETRY_FILE), { recursive: true });
|
|
624
|
+
fs.writeFileSync(telemetry.TELEMETRY_FILE,
|
|
625
|
+
JSON.stringify({ schema_version: '1.0', entries: entries }, null, 2));
|
|
626
|
+
|
|
627
|
+
const validator = require('./validators/external-industry-invariants.cjs');
|
|
628
|
+
const result = validator.validate('/dev/null', {});
|
|
629
|
+
const hashViolations = result.violations.filter(v => v.category === 'hash_format_invalid');
|
|
630
|
+
assert.ok(hashViolations.length >= 1, 'expected hash_format_invalid violation');
|
|
631
|
+
assert.equal(hashViolations[0].severity, 'warning', 'severity is warning');
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// ---------- V6: validator Check F fetched_at malformed ISO-8601 ----------
|
|
635
|
+
await runScenario('V6: validator Check F fetched_at malformed -> warning', async function () {
|
|
636
|
+
setupScopedHome();
|
|
637
|
+
const telemetry = require('../core/rs-egress-telemetry.cjs');
|
|
638
|
+
const entries = [{
|
|
639
|
+
source: 'tavily',
|
|
640
|
+
query_text_hash: 'aaaaaaaaaaaaaaaa',
|
|
641
|
+
status: 'ok',
|
|
642
|
+
fetched_at: 'NOT-A-VALID-DATE',
|
|
643
|
+
}];
|
|
644
|
+
fs.mkdirSync(path.dirname(telemetry.TELEMETRY_FILE), { recursive: true });
|
|
645
|
+
fs.writeFileSync(telemetry.TELEMETRY_FILE,
|
|
646
|
+
JSON.stringify({ schema_version: '1.0', entries: entries }, null, 2));
|
|
647
|
+
|
|
648
|
+
const validator = require('./validators/external-industry-invariants.cjs');
|
|
649
|
+
const result = validator.validate('/dev/null', {});
|
|
650
|
+
const dateViolations = result.violations.filter(v => v.category === 'fetched_at_malformed');
|
|
651
|
+
assert.ok(dateViolations.length >= 1, 'expected fetched_at_malformed violation');
|
|
652
|
+
assert.equal(dateViolations[0].severity, 'warning', 'severity is warning');
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
// ---------- A1 sweep: zero forbidden matches in any captured output ----------
|
|
656
|
+
await runScenario('A1: zero forbidden matches across captured payloads + URLs + bodies', async function () {
|
|
657
|
+
for (const rec of SCENARIO_RESULTS) {
|
|
658
|
+
const stringified = JSON.stringify(rec.payload);
|
|
659
|
+
const hit = scanAgainstForbidden(stringified);
|
|
660
|
+
assert.equal(hit.hit, false,
|
|
661
|
+
'A1 violation in ' + rec.test + '/' + rec.surface +
|
|
662
|
+
' against pattern ' + (hit.pattern || 'n/a'));
|
|
663
|
+
}
|
|
664
|
+
for (const u of CAPTURED_URLS_ALL) {
|
|
665
|
+
const hit = scanAgainstForbidden(u);
|
|
666
|
+
assert.equal(hit.hit, false,
|
|
667
|
+
'A1 outbound URL leaked forbidden pattern ' + (hit.pattern || 'n/a') +
|
|
668
|
+
' in ' + u.slice(0, 100));
|
|
669
|
+
}
|
|
670
|
+
for (const b of CAPTURED_BODIES_ALL) {
|
|
671
|
+
const hit = scanAgainstForbidden(b);
|
|
672
|
+
assert.equal(hit.hit, false,
|
|
673
|
+
'A1 outbound body leaked forbidden pattern ' + (hit.pattern || 'n/a') +
|
|
674
|
+
' in ' + b.slice(0, 100));
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// ---------- A2 sweep: parity gate ----------
|
|
679
|
+
await runScenario('A2: FORBIDDEN_PATTERNS parity (rs-egress-prompts === cross-room-aggregator)', async function () {
|
|
680
|
+
const promptsModule = require('../core/rs-egress-prompts.cjs');
|
|
681
|
+
const aggregator = require('../core/cross-room-aggregator.cjs');
|
|
682
|
+
const ours = promptsModule.FORBIDDEN_PATTERNS;
|
|
683
|
+
const truth = aggregator.FORBIDDEN_PATTERNS;
|
|
684
|
+
assert.equal(ours.length, truth.length, 'lengths match');
|
|
685
|
+
for (let i = 0; i < truth.length; i++) {
|
|
686
|
+
assert.equal(ours[i].source, truth[i].source, 'pattern source mismatch at index ' + i);
|
|
687
|
+
assert.equal(ours[i].flags, truth[i].flags, 'pattern flags mismatch at index ' + i);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
// ---------- Final report ----------
|
|
692
|
+
if (failed > 0) {
|
|
693
|
+
console.error('\n=== ' + failed + ' FAILURES ===');
|
|
694
|
+
for (const f of failures) {
|
|
695
|
+
console.error(' ' + f.name);
|
|
696
|
+
}
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
console.log('=== 89.2-04 fetcher-industry suite: 17/17 passed ===');
|
|
701
|
+
process.exit(0);
|
|
702
|
+
})();
|