@steve02081504/fount-p2p 0.0.30 → 0.0.32

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.
Files changed (64) hide show
  1. package/AGENTS.md +79 -0
  2. package/docs/evfs.md +32 -0
  3. package/docs/infra.md +25 -0
  4. package/docs/mesh.md +67 -0
  5. package/docs/reputation.md +22 -0
  6. package/docs/runtime.md +62 -0
  7. package/docs/signaling.md +23 -0
  8. package/docs/transports.md +92 -0
  9. package/docs/wire.md +21 -0
  10. package/link/providers/webrtc.mjs +5 -2
  11. package/link/rtc/ice_local_hostname.mjs +15 -47
  12. package/link/rtc/polyfill.mjs +3 -15
  13. package/package.json +2 -9
  14. package/sim/AGENTS.md +29 -0
  15. package/sim/admission.mjs +56 -0
  16. package/sim/anomaly.mjs +39 -0
  17. package/sim/apply.mjs +77 -0
  18. package/sim/attack_space.mjs +113 -0
  19. package/sim/attacks.mjs +614 -0
  20. package/sim/behavior.mjs +78 -0
  21. package/sim/cli.mjs +282 -0
  22. package/sim/coevolution.mjs +245 -0
  23. package/sim/cold_start.md +13 -0
  24. package/sim/constants.mjs +44 -0
  25. package/sim/discovery.mjs +259 -0
  26. package/sim/duration.mjs +64 -0
  27. package/sim/graph_adj.mjs +28 -0
  28. package/sim/integrity.mjs +58 -0
  29. package/sim/metrics.mjs +423 -0
  30. package/sim/model.mjs +1176 -0
  31. package/sim/optimizer.mjs +250 -0
  32. package/sim/propagation.mjs +57 -0
  33. package/sim/report.mjs +176 -0
  34. package/sim/reputation_social.tunables.json +6 -0
  35. package/sim/rng.mjs +62 -0
  36. package/sim/scenarios.mjs +345 -0
  37. package/sim/sim_pool.mjs +169 -0
  38. package/sim/sim_worker.mjs +31 -0
  39. package/sim/social_reputation.mjs +30 -0
  40. package/sim/space.mjs +312 -0
  41. package/sim/test/admission.test.mjs +25 -0
  42. package/sim/test/attack_space.test.mjs +39 -0
  43. package/sim/test/behavior_vector.test.mjs +37 -0
  44. package/sim/test/coevolution.test.mjs +32 -0
  45. package/sim/test/cold_start.test.mjs +46 -0
  46. package/sim/test/defense.test.mjs +320 -0
  47. package/sim/test/discovery.test.mjs +68 -0
  48. package/sim/test/duration.test.mjs +20 -0
  49. package/sim/test/fidelity.test.mjs +151 -0
  50. package/sim/test/integrity.test.mjs +56 -0
  51. package/sim/test/optimizer.test.mjs +24 -0
  52. package/sim/test/propagation.test.mjs +29 -0
  53. package/sim/test/sim_model_regressions.test.mjs +110 -0
  54. package/sim/test/slow_drip.test.mjs +28 -0
  55. package/sim/test/smoke.test.mjs +52 -0
  56. package/sim/test/transport.test.mjs +55 -0
  57. package/sim/test/tunables_apply.test.mjs +64 -0
  58. package/sim/test/tunables_param_space.test.mjs +74 -0
  59. package/sim/test/vulnerability.test.mjs +127 -0
  60. package/sim/transport.mjs +135 -0
  61. package/sim/tunables_bundle.mjs +71 -0
  62. package/sim/vulnerability.mjs +532 -0
  63. package/deno.json +0 -7
  64. package/link/rtc/w3c_bridge.mjs +0 -152
package/sim/AGENTS.md ADDED
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: P2P tunables simulation / co-evolution harness — fidelity boundary between reused real logic and heuristic proxy environment
3
+ globs: sim/**
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # P2P Sim Harness Guide
8
+
9
+ In-process co-evolution of **tunables** (`*.tunables.json`) against an **attack genome**, scored by `metrics.mjs`. Search proxy — **not** wire-protocol replay.
10
+
11
+ ## Fidelity boundary
12
+
13
+ - **Reused verbatim** (import real decision functions; do not re-model): `reputation/engine.mjs`, `reputation/math.mjs`, `sim/social_reputation.mjs` (`*Pure`), `trust_graph/engine.mjs` (`pickTop`), `trust_graph/resolve.mjs`, `governance/join_pow.mjs`.
14
+ - **Tunables source:** `tunables_bundle.mjs` loads in-package `*.tunables.json` plus `sim/reputation_social.tunables.json`. Write social tunables back via `cli.mjs mine --social-tunables PATH` (`apply.mjs`).
15
+ - **`PARAM_SPACE` ↔ defaults:** every `PARAM_SPACE` key must exist in `loadDefaultTunables()`; clear both sides when deleting a key.
16
+ - **`socialRepHideThreshold`:** hide when `score < threshold`. Default `0` (suppress negatives only). Raise the threshold to raise `falsePositiveRate` — never use a negative threshold for "stricter".
17
+ - **Heuristic proxy** (not the real path): `model.mjs`, `discovery.mjs`, `transport.mjs`, `integrity.mjs` — analytical "params → defense" only.
18
+ - **Mesh-first:** explore slots and cold-start join are mandatory; fanout/trust stay separate from link presence. Policy: [docs/mesh.md](../docs/mesh.md). Wiring: [cold_start.md](cold_start.md).
19
+
20
+ ## Anti-drift
21
+
22
+ - Do not hand-copy runtime constants. RTC budget from `transport/rtc_connection_budget.mjs`. `EXPLORE_MAX_PER_SOURCE` mirrors `peer_pool.mjs` but stays local (importing `peer_pool` pulls fs) — `test/fidelity.test.mjs` asserts equality.
23
+ - Signaling source names (`DEFAULT_SIGNALING_SOURCES`) must be real provider ids (`lan` / `nostr` / `bt`). No `tracker` provider.
24
+ - New sim constants that shadow real ones need a matching assertion in `fidelity.test.mjs`.
25
+
26
+ ## Determinism
27
+
28
+ - Seeded via `rng.mjs` (`createRng`). `runSimulation(...)` must be pure — `fidelity.test.mjs` asserts serial == parallel == batched snapshots. Use `simulationContext.now` (virtual clock, +60s/round); no wall-clock or unseeded RNG.
29
+ - Round state object is `simulationContext` (not `ctx`); `buildWorld` returns `{ simulationContext }`.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * 入群 PoW 成本模型:算力预算限速 Sybil/洗白造号。
3
+ */
4
+ import { expectedJoinPowHashes, powVoluntaryBonus } from '../governance/join_pow.mjs'
5
+
6
+ /** 每回合可消耗的期望哈希预算(相对 2^16 基准) */
7
+ const HASH_BUDGET_PER_ROUND = 65536
8
+
9
+ /**
10
+ * @param {import('./tunables_bundle.mjs').TunablesBundle['admission']} admission admission tunables
11
+ * @returns {number} 准入 floor bit
12
+ */
13
+ export function resolvePowFloorBits(admission) {
14
+ return Math.max(1, Math.floor(Number(admission?.powFloorBits ?? admission?.powDifficultyBits ?? 18)))
15
+ }
16
+
17
+ /**
18
+ * @param {number} achievedBits 解达成 bit
19
+ * @param {import('./tunables_bundle.mjs').TunablesBundle['admission']} admission admission tunables
20
+ * @returns {number} 自愿封顶信誉加成
21
+ */
22
+ export function simPowVoluntaryBonus(achievedBits, admission) {
23
+ return powVoluntaryBonus(achievedBits, resolvePowFloorBits(admission), admission)
24
+ }
25
+
26
+ /**
27
+ * @param {number} difficultyBits PoW floor bit
28
+ * @returns {number} 造一个新身份期望回合成本
29
+ */
30
+ export function roundsPerIdentity(difficultyBits) {
31
+ const expected = expectedJoinPowHashes(difficultyBits)
32
+ return Math.max(1, expected / HASH_BUDGET_PER_ROUND)
33
+ }
34
+
35
+ /**
36
+ * @param {number} requested 请求数量
37
+ * @param {number} difficultyBits PoW 难度
38
+ * @param {number} rounds 仿真总回合
39
+ * @returns {number} 预算内可生成的恶意身份数
40
+ */
41
+ export function capMaliciousByPowBudget(requested, difficultyBits, rounds) {
42
+ const cost = roundsPerIdentity(difficultyBits)
43
+ const budget = Math.max(1, Math.floor(rounds / cost))
44
+ return Math.min(requested, budget)
45
+ }
46
+
47
+ /**
48
+ * @param {number} difficultyBits PoW 难度
49
+ * @param {number} rounds 回合数
50
+ * @returns {number} 诚实新人入群延迟惩罚 0..1(越高=越慢)
51
+ */
52
+ export function honestJoinDelayPenalty(difficultyBits, rounds) {
53
+ const cost = roundsPerIdentity(difficultyBits)
54
+ const delay = cost / Math.max(1, rounds * 0.25)
55
+ return Math.min(1, delay)
56
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 仿真器中的本地可逆异常隔离(映射生产 reputation_engine 纯函数)。
3
+ */
4
+ import {
5
+ isQuarantinedPure,
6
+ observeBehaviorSamplePure,
7
+ } from '../reputation/engine.mjs'
8
+
9
+ /**
10
+ * @param {import('../node/reputation_store.mjs').ReputationFile} reputation 观察者信誉表
11
+ * @param {string} peerId 对端 nodeHash
12
+ * @param {number} sample 行为样本
13
+ * @param {number} now 当前时间
14
+ * @param {import('../reputation/tunables.json')} tunables reputation tunables
15
+ * @returns {boolean} 是否触发隔离
16
+ */
17
+ export function simObservePeerBehavior(reputation, peerId, sample, now, tunables) {
18
+ return observeBehaviorSamplePure(reputation, peerId, sample, now, tunables).anomaly
19
+ }
20
+
21
+ /**
22
+ * @param {import('../node/reputation_store.mjs').ReputationFile} reputation 观察者信誉表
23
+ * @param {string} peerId 对端 nodeHash
24
+ * @param {number} now 当前时间
25
+ * @returns {boolean} 是否隔离中
26
+ */
27
+ export function simIsPeerQuarantined(reputation, peerId, now) {
28
+ return isQuarantinedPure(reputation, peerId, now)
29
+ }
30
+
31
+ /**
32
+ * @param {number} discoveryReach 发现可达 0..1
33
+ * @param {() => number} rng 随机源
34
+ * @returns {boolean} 攻击是否触达该观察者
35
+ */
36
+ export function attackReachesObserver(discoveryReach, rng) {
37
+ const reach = Math.max(0, Math.min(1, discoveryReach))
38
+ return rng() < 0.15 + reach * 0.85
39
+ }
package/sim/apply.mjs ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * 按模块写回 tunables JSON。
3
+ */
4
+ import { writeFile } from 'node:fs/promises'
5
+ import path from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
7
+
8
+ import { normalizeBundle, sanitizeBundle } from './space.mjs'
9
+
10
+ /** @typedef {import('./tunables_bundle.mjs').TunablesBundle} TunablesBundle */
11
+
12
+ /** @type {Record<Exclude<keyof TunablesBundle, 'social'>, URL>} */
13
+ const PACKAGE_MODULE_URLS = {
14
+ reputation: new URL('../reputation/tunables.json', import.meta.url),
15
+ trustGraph: new URL('../trust_graph/tunables.json', import.meta.url),
16
+ mailbox: new URL('../mailbox/tunables.json', import.meta.url),
17
+ archive: new URL('../dag/tunables.json', import.meta.url),
18
+ admission: new URL('../governance/tunables.json', import.meta.url),
19
+ }
20
+
21
+ /**
22
+ * @param {TunablesBundle} bundle 完整 tunables
23
+ * @returns {TunablesBundle} 写盘前规整后的 bundle
24
+ */
25
+ export function prepareBundleForApply(bundle) {
26
+ return sanitizeBundle(normalizeBundle(bundle))
27
+ }
28
+
29
+ /**
30
+ * @param {string} socialTunablesPath 外部 social shell 的 reputation_social.tunables.json 绝对或相对路径
31
+ * @returns {string} 规范化绝对路径
32
+ */
33
+ export function resolveSocialTunablesPath(socialTunablesPath) {
34
+ const raw = String(socialTunablesPath ?? '').trim()
35
+ if (!raw) throw new Error('socialTunablesPath required to write social tunables')
36
+ return path.resolve(raw)
37
+ }
38
+
39
+ /**
40
+ * @param {keyof TunablesBundle} module 模块键
41
+ * @param {Record<string, unknown>} data JSON 内容
42
+ * @param {string} [socialTunablesPath] social 模块写入路径(module === 'social' 时必填)
43
+ * @returns {Promise<string>} 写入路径
44
+ */
45
+ export async function writeModuleTunables(module, data, socialTunablesPath) {
46
+ const filePath = module === 'social'
47
+ ? resolveSocialTunablesPath(socialTunablesPath)
48
+ : fileURLToPath(PACKAGE_MODULE_URLS[module])
49
+ await writeFile(filePath, `${JSON.stringify(data, null, '\t')}\n`, 'utf8')
50
+ return filePath
51
+ }
52
+
53
+ /**
54
+ * @param {TunablesBundle} bundle 完整 tunables
55
+ * @param {{ socialTunablesPath: string }} paths 外部模块路径(social 写回 shell 侧 JSON)
56
+ * @returns {Promise<string[]>} 各模块 JSON 写入路径
57
+ */
58
+ export async function applyTunablesBundle(bundle, paths) {
59
+ const socialPath = resolveSocialTunablesPath(paths?.socialTunablesPath)
60
+ const ready = prepareBundleForApply(bundle)
61
+ const written = []
62
+ written.push(await writeModuleTunables('reputation', ready.reputation))
63
+ written.push(await writeModuleTunables('trustGraph', ready.trustGraph))
64
+ written.push(await writeModuleTunables('social', ready.social, socialPath))
65
+ written.push(await writeModuleTunables('mailbox', ready.mailbox))
66
+ written.push(await writeModuleTunables('archive', ready.archive))
67
+ written.push(await writeModuleTunables('admission', ready.admission))
68
+ return written
69
+ }
70
+
71
+ /**
72
+ * @param {string} [dir] sim 目录(默认本模块目录)
73
+ * @returns {string} results 子目录绝对路径
74
+ */
75
+ export function resultsDirFromSim(dir = path.dirname(fileURLToPath(import.meta.url))) {
76
+ return path.join(dir, 'results')
77
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * 攻击者可进化基因(与 space.mjs 采样器共用 latent 语义)。
3
+ */
4
+ import { createRng } from './rng.mjs'
5
+ import { normalizeParam, sampleParam } from './space.mjs'
6
+ import { loadDefaultTunables } from './tunables_bundle.mjs'
7
+
8
+ /** @typedef {import('./attacks.mjs').AttackKind} AttackKind */
9
+
10
+ /**
11
+ * @typedef {{
12
+ * global: { activationRate: number, burstSize: number, targetBiasHighRep: number, eclipseFocus: number },
13
+ * byAttack: Partial<Record<AttackKind, { activationRate?: number, burstSize?: number, collusionAllyRate?: number, sleeperTurnFrac?: number, hintWeightMul?: number }>>,
14
+ * }} AttackGenome
15
+ */
16
+
17
+ /** @type {import('./space.mjs').ParamSpec[]} */
18
+ const GLOBAL_ATTACK_SPECS = [
19
+ { module: 'reputation', key: 'relayRepBump', kind: 'unit' }, // reuse kind; values remapped below
20
+ ]
21
+
22
+ const DEFAULT_GENOME = Object.freeze({
23
+ global: {
24
+ activationRate: 0.55,
25
+ burstSize: 4,
26
+ targetBiasHighRep: 0.35,
27
+ eclipseFocus: 0.65,
28
+ },
29
+ byAttack: {},
30
+ })
31
+
32
+ /**
33
+ * @param {() => number} rng 随机源
34
+ * @returns {AttackGenome} 随机攻击基因组
35
+ */
36
+ export function randomAttackGenome(rng) {
37
+ return {
38
+ global: {
39
+ activationRate: 0.2 + rng() * 0.75,
40
+ burstSize: 1 + Math.floor(rng() * 8),
41
+ targetBiasHighRep: rng() * 0.9,
42
+ eclipseFocus: 0.3 + rng() * 0.65,
43
+ },
44
+ byAttack: {},
45
+ }
46
+ }
47
+
48
+ /**
49
+ * @param {AttackGenome} parent 父代
50
+ * @param {number} seed 种子
51
+ * @returns {AttackGenome} 变异子代
52
+ */
53
+ export function mutateAttackGenome(parent, seed) {
54
+ const rng = createRng(seed)
55
+ const child = structuredClone(parent)
56
+ if (rng() < 0.4) child.global.activationRate = Math.max(0.05, Math.min(0.98, child.global.activationRate + (rng() - 0.5) * 0.2))
57
+ if (rng() < 0.35) child.global.burstSize = Math.max(1, Math.min(12, child.global.burstSize + Math.floor((rng() - 0.5) * 4)))
58
+ if (rng() < 0.3) child.global.targetBiasHighRep = Math.max(0, Math.min(1, child.global.targetBiasHighRep + (rng() - 0.5) * 0.25))
59
+ if (rng() < 0.3) child.global.eclipseFocus = Math.max(0.1, Math.min(1, child.global.eclipseFocus + (rng() - 0.5) * 0.25))
60
+ return child
61
+ }
62
+
63
+ /**
64
+ * @param {AttackKind} attack 攻击类型
65
+ * @param {AttackGenome} genome 基因组
66
+ * @returns {{ activationRate: number, burstSize: number, targetBiasHighRep: number, eclipseFocus: number, collusionAllyRate: number, sleeperTurnFrac: number, hintWeightMul: number }} 有效参数
67
+ */
68
+ export function resolveAttackParams(attack, genome) {
69
+ const g = genome.global
70
+ const row = genome.byAttack[attack] ?? {}
71
+ return {
72
+ activationRate: row.activationRate ?? g.activationRate,
73
+ burstSize: Math.max(1, Math.floor(row.burstSize ?? g.burstSize)),
74
+ targetBiasHighRep: g.targetBiasHighRep,
75
+ eclipseFocus: g.eclipseFocus,
76
+ collusionAllyRate: row.collusionAllyRate ?? 0.25,
77
+ sleeperTurnFrac: row.sleeperTurnFrac ?? 0.35,
78
+ hintWeightMul: row.hintWeightMul ?? 3,
79
+ }
80
+ }
81
+
82
+ /**
83
+ * @param {AttackGenome | undefined} genome 基因组
84
+ * @returns {AttackGenome} 默认补齐
85
+ */
86
+ export function normalizeAttackGenome(genome) {
87
+ if (!genome) return structuredClone(DEFAULT_GENOME)
88
+ return {
89
+ global: { ...DEFAULT_GENOME.global, ...genome.global },
90
+ byAttack: { ...genome.byAttack },
91
+ }
92
+ }
93
+
94
+ /** 名人堂容量 */
95
+ export const ATTACK_HOF_SIZE = 6
96
+
97
+ /**
98
+ * @param {Array<{ genome: AttackGenome, fitness: number }>} hall 名人堂
99
+ * @param {AttackGenome} genome 候选
100
+ * @param {number} fitness 适应度
101
+ * @returns {Array<{ genome: AttackGenome, fitness: number }>} 更新后名人堂
102
+ */
103
+ export function updateAttackHallOfFame(hall, genome, fitness) {
104
+ const next = [...hall, { genome: structuredClone(genome), fitness }]
105
+ next.sort((a, b) => b.fitness - a.fitness)
106
+ return next.slice(0, ATTACK_HOF_SIZE)
107
+ }
108
+
109
+ // silence unused import warning from eslint if any
110
+ void GLOBAL_ATTACK_SPECS
111
+ void loadDefaultTunables
112
+ void normalizeParam
113
+ void sampleParam