@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24

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 (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -0,0 +1,212 @@
1
+ /**
2
+ * 内核清单冻结 + 运行模块证据 自测(issue #234;全离线,fixture 走临时目录,
3
+ * 唯一真实 trace 在 §7 终检且 import-only 零状态写入)。
4
+ *
5
+ * 反证矩阵(issue #234 验收 3:反证必须失败):
6
+ * 改核心(§5a)/删核心文件(§5b)/替换内核为废弃层(§4f)/未加载核心(§6b)/
7
+ * 错 manifestHash(§6c)/错 evidenceHash(§6d)/错 candidateSHA(§6e)/
8
+ * 缺路径覆盖-套件缺失(§5c)/缺路径覆盖-不再 import 内核(§5d)/
9
+ * schema 纪律-空依据/坏组/路径逃逸/重复条目(§4)。
10
+ *
11
+ * 运行:cd ts && npx tsx scripts/kernel-manifest-tests.ts
12
+ */
13
+ import assert from 'node:assert/strict';
14
+ import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
15
+ import { tmpdir } from 'node:os';
16
+ import { join, resolve } from 'node:path';
17
+ import { evidenceHash, importClosure, loadKernelManifest, MANIFEST_REL, manifestHash, validateEvidence, validateFunctionalCoverage, validateKernelIntegrity, validateManifestSchema, } from './kernel-manifest.js';
18
+ import { buildEvidence } from './kernel-manifest-evidence.js';
19
+ import { main as gateMain } from './kernel-manifest-gate.js';
20
+ const repoRoot = resolve(import.meta.dirname, '..', '..');
21
+ const KERNEL_FILES = ['ts/src/unified.ts', 'ts/src/model.ts', 'ts/src/state-ledger.ts', 'ts/src/bookable-facts.ts', 'ts/src/artifact-gate.ts'];
22
+ const SUITE_FILES = ['ts/scripts/unified-tests.ts', 'ts/scripts/ledger-tests.ts', 'ts/scripts/fact-gate-tests.ts'];
23
+ let section = 0;
24
+ function head(title) {
25
+ section += 1;
26
+ console.log(`--- §${section} ${title}`);
27
+ }
28
+ function expectFinding(findings, code, label) {
29
+ assert.ok(findings.some((f) => f.code === code), `${label}:期望出现 ${code},实际 [${findings.map((f) => f.code).join(', ')}]`);
30
+ }
31
+ /** 临时 fixture 仓库:根 package.json(无 files 面) + 内核文件 + 覆盖套件 + 冻结清单,全部从真实仓库逐字节拷贝。 */
32
+ function makeFixtureRepo() {
33
+ const dir = mkdtempSync(join(tmpdir(), 'gotry-kernel-fixture-'));
34
+ writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: 'gotry-kernel-fixture', private: true }, null, 2) + '\n');
35
+ for (const rel of [...KERNEL_FILES, ...SUITE_FILES]) {
36
+ const target = join(dir, rel);
37
+ cpSync(join(repoRoot, rel), target);
38
+ }
39
+ cpSync(join(repoRoot, MANIFEST_REL), join(dir, MANIFEST_REL));
40
+ return dir;
41
+ }
42
+ function fakeTraceAll() {
43
+ return { ok: true, nodeVersion: process.versions.node, moduleCount: KERNEL_FILES.length, repoModuleCount: KERNEL_FILES.length, loadedRepoModules: [...KERNEL_FILES].sort() };
44
+ }
45
+ function baseEvidence(fixture, manifest) {
46
+ return buildEvidence(manifest, {
47
+ repoRoot: fixture,
48
+ manifestRel: MANIFEST_REL,
49
+ now: '2026-09-11T00:00:00.000Z',
50
+ candidateSHA: manifest.baseSHA,
51
+ trace: fakeTraceAll(),
52
+ });
53
+ }
54
+ // §1 真实仓库:冻结清单 schema 纪律绿。
55
+ head('真实仓库 冻结清单 schema 纪律');
56
+ const manifest = loadKernelManifest(repoRoot);
57
+ {
58
+ const result = validateManifestSchema(manifest);
59
+ assert.equal(result.ok, true, `真实清单 schema 应绿:${JSON.stringify(result.findings)}`);
60
+ assert.ok(manifest.entries.length >= 5, '内核条目至少 5 项(宁窄勿宽,但必须覆盖引擎/账本/闸)');
61
+ const groups = new Set(manifest.entries.map((e) => e.group));
62
+ for (const g of ['engine', 'ledger', 'gate'])
63
+ assert.ok(groups.has(g), `清单必须覆盖 group ${g}`);
64
+ // 冻结清单哈希必须确定可复算(规范化 JSON 的哈希绑定)。
65
+ assert.equal(manifestHash(manifest), manifestHash(loadKernelManifest(repoRoot)), 'manifestHash 必须确定');
66
+ }
67
+ // §2 真实仓库:内核零 diff + 打包面绿。
68
+ head('真实仓库 内核零 diff + npm 打包面');
69
+ {
70
+ const result = validateKernelIntegrity(manifest, repoRoot);
71
+ assert.equal(result.ok, true, `真实内核零 diff 应绿:${JSON.stringify(result.findings)}`);
72
+ }
73
+ // §3 真实仓库:功能路径覆盖绿(套件存在 + 静态 import 闭包含内核)。
74
+ head('真实仓库 功能路径覆盖(同引擎/账本/闸路径)');
75
+ {
76
+ const result = validateFunctionalCoverage(manifest, repoRoot);
77
+ assert.equal(result.ok, true, `真实功能路径覆盖应绿:${JSON.stringify(result.findings)}`);
78
+ for (const fp of manifest.functionalPaths) {
79
+ const closure = importClosure(repoRoot, fp.suite);
80
+ for (const km of fp.kernelModules)
81
+ assert.ok(closure.has(km), `${fp.suite} 闭包应含 ${km}`);
82
+ }
83
+ }
84
+ // §4 schema 纪律反证:空依据/坏组/路径逃逸/重复条目/废弃层冒充内核。
85
+ head('schema 纪律反证(全部必须红)');
86
+ {
87
+ const clone = (overrides) => ({ ...manifest, ...overrides });
88
+ const badGroup = validateManifestSchema(clone({ entries: [{ ...manifest.entries[0], group: 'misc' }] }));
89
+ expectFinding(badGroup.findings, 'manifest-group', '坏 group');
90
+ const noBasis = validateManifestSchema(clone({ entries: [{ ...manifest.entries[0], basis: '' }] }));
91
+ expectFinding(noBasis.findings, 'manifest-basis-missing', '空依据');
92
+ const noRationale = validateManifestSchema(clone({ entries: [{ ...manifest.entries[0], rationale: ' ' }] }));
93
+ expectFinding(noRationale.findings, 'manifest-rationale-missing', '空 rationale');
94
+ const escapePath = validateManifestSchema(clone({ entries: [{ ...manifest.entries[0], path: '../outside.js' }] }));
95
+ expectFinding(escapePath.findings, 'manifest-path-shape', '路径逃逸');
96
+ const dup = validateManifestSchema(clone({ entries: [manifest.entries[0], { ...manifest.entries[0] }] }));
97
+ expectFinding(dup.findings, 'manifest-duplicate-path', '重复条目');
98
+ // 废弃兼容层(journey.ts)冒充内核:清单层面直接拒绝。
99
+ const deprecated = validateManifestSchema(clone({ entries: [{ ...manifest.entries[0], path: 'ts/src/journey.ts' }] }));
100
+ expectFinding(deprecated.findings, 'manifest-forbidden-path', '废弃层冒充内核');
101
+ const empty = validateManifestSchema(clone({ entries: [] }));
102
+ expectFinding(empty.findings, 'manifest-empty', '空清单');
103
+ }
104
+ // §5 fixture 内核零 diff + 功能路径反证(红→绿)。
105
+ head('fixture 内核零 diff / 功能路径反证(红)');
106
+ {
107
+ const fixture = makeFixtureRepo();
108
+ try {
109
+ const fx = loadKernelManifest(fixture, MANIFEST_REL);
110
+ // 绿基线:逐字节拷贝的 fixture 必须零 diff、覆盖成立。
111
+ assert.equal(validateKernelIntegrity(fx, fixture).ok, true, 'fixture 基线应绿');
112
+ assert.equal(validateFunctionalCoverage(fx, fixture).ok, true, 'fixture 覆盖基线应绿');
113
+ // (a) 改核心:哈希漂移必须红。
114
+ writeFileSync(join(fixture, 'ts/src/model.ts'), readFileSync(join(fixture, 'ts/src/model.ts'), 'utf8') + '\n// 反证:核心被改动\n');
115
+ const tampered = validateKernelIntegrity(fx, fixture);
116
+ assert.equal(tampered.ok, false, '改核心必须红');
117
+ expectFinding(tampered.findings, 'kernel-hash-mismatch', '改核心');
118
+ // (b) 删核心文件:事后删文件制造零 diff 被封死。
119
+ rmSync(join(fixture, 'ts/src/artifact-gate.ts'));
120
+ const missing = validateKernelIntegrity(fx, fixture);
121
+ expectFinding(missing.findings, 'kernel-file-missing', '删核心文件');
122
+ // (c) 缺路径覆盖:覆盖套件缺失。
123
+ const fxCov = loadKernelManifest(fixture, MANIFEST_REL);
124
+ rmSync(join(fixture, 'ts/scripts/ledger-tests.ts'));
125
+ const cov = validateFunctionalCoverage(fxCov, fixture);
126
+ assert.equal(cov.ok, false, '套件缺失必须红');
127
+ expectFinding(cov.findings, 'coverage-suite-missing', '套件缺失');
128
+ // (d) 缺路径覆盖:套件不再 import 内核模块。
129
+ writeFileSync(join(fixture, 'ts/scripts/fact-gate-tests.ts'), "import { ok } from 'node:assert/strict'\nconsole.log(ok)\n");
130
+ const cov2 = validateFunctionalCoverage(fxCov, fixture);
131
+ assert.equal(cov2.ok, false, '套件不再 import 内核必须红');
132
+ expectFinding(cov2.findings, 'coverage-suite-not-importing-kernel', '覆盖断裂');
133
+ }
134
+ finally {
135
+ rmSync(fixture, { recursive: true, force: true });
136
+ }
137
+ }
138
+ // §6 证据快照哈希绑定 + 加载覆盖反证(红)。
139
+ head('证据快照哈希绑定 / 未加载核心 反证(红)');
140
+ {
141
+ const fixture = makeFixtureRepo();
142
+ try {
143
+ const fx = loadKernelManifest(fixture, MANIFEST_REL);
144
+ // 绿基线:完整证据(全加载)必须绿。
145
+ const good = baseEvidence(fixture, fx);
146
+ const goodResult = validateEvidence(good, fx, fixture, { requireLoadedCoverage: true, expectedCandidateSha: fx.baseSHA });
147
+ assert.equal(goodResult.ok, true, `完整证据基线应绿:${JSON.stringify(goodResult.findings)}`);
148
+ assert.equal(evidenceHash(good), good.evidenceHash, 'evidenceHash 必须自洽');
149
+ // (a) 快照缺内核条目/出现清单外条目。
150
+ const missingEntry = { ...good, kernel: good.kernel.slice(1) };
151
+ missingEntry.evidenceHash = evidenceHash(missingEntry);
152
+ const r0 = validateEvidence(missingEntry, fx, fixture, { requireLoadedCoverage: true });
153
+ expectFinding(r0.findings, 'evidence-kernel-missing', '快照缺内核条目');
154
+ // (b) 未加载核心:trace 缺一个内核模块必须红(issue #234「未加载核心」反证)。
155
+ const partialTrace = { ...fakeTraceAll(), loadedRepoModules: fakeTraceAll().loadedRepoModules.filter((p) => p !== 'ts/src/state-ledger.ts') };
156
+ const partial = buildEvidence(fx, { repoRoot: fixture, manifestRel: MANIFEST_REL, now: '2026-09-11T00:00:00.000Z', candidateSHA: fx.baseSHA, trace: partialTrace });
157
+ const r1 = validateEvidence(partial, fx, fixture, { requireLoadedCoverage: true });
158
+ assert.equal(r1.ok, false, '未加载核心必须红');
159
+ expectFinding(r1.findings, 'kernel-not-loaded', '未加载核心');
160
+ // (c) 错 manifestHash(冻结清单被改动/快照错绑)。
161
+ const wrongManifestHash = { ...good, manifestRef: { ...good.manifestRef, sha256: '0'.repeat(64) } };
162
+ wrongManifestHash.evidenceHash = evidenceHash(wrongManifestHash);
163
+ const r2 = validateEvidence(wrongManifestHash, fx, fixture, { requireLoadedCoverage: true });
164
+ expectFinding(r2.findings, 'manifest-hash-mismatch', '错 manifestHash');
165
+ // (d) 快照内容被改动(evidenceHash 不再自洽)。
166
+ const tamperedSnapshot = { ...good, generatedAt: '2030-01-01T00:00:00.000Z' };
167
+ const r3 = validateEvidence(tamperedSnapshot, fx, fixture, { requireLoadedCoverage: true });
168
+ expectFinding(r3.findings, 'evidence-hash-mismatch', '快照被改动');
169
+ // (e) 错 candidateSHA(绑定面)。
170
+ const r4 = validateEvidence(good, fx, fixture, { requireLoadedCoverage: true, expectedCandidateSha: 'a'.repeat(40) });
171
+ expectFinding(r4.findings, 'evidence-candidate-sha-mismatch', '错 candidateSHA');
172
+ // (f) 空 trace 冒充运行证据。
173
+ const emptyTrace = buildEvidence(fx, { repoRoot: fixture, manifestRel: MANIFEST_REL, now: '2026-09-11T00:00:00.000Z', candidateSHA: fx.baseSHA, trace: { ok: true, nodeVersion: process.versions.node, moduleCount: 0, repoModuleCount: 0, loadedRepoModules: [] } });
174
+ const r5 = validateEvidence(emptyTrace, fx, fixture, { requireLoadedCoverage: true });
175
+ expectFinding(r5.findings, 'evidence-trace-empty', '空 trace');
176
+ expectFinding(r5.findings, 'kernel-not-loaded', '空 trace 必然未加载');
177
+ }
178
+ finally {
179
+ rmSync(fixture, { recursive: true, force: true });
180
+ }
181
+ }
182
+ // §7 真实端到端:闸默认模式(真实运行 trace)→ 快照 → 审计 → 篡改快照必红。
183
+ head('真实端到端 闸 + 证据快照审计');
184
+ {
185
+ const outSnapshot = join(mkdtempSync(join(tmpdir(), 'gotry-kernel-e2e-')), 'evidence.json');
186
+ try {
187
+ const exitGen = gateMain(['--out', outSnapshot]);
188
+ assert.equal(exitGen, 0, `闸默认模式(真实 trace)应绿且 exit 0,得到 ${exitGen}`);
189
+ assert.ok(existsSync(outSnapshot), '闸应写出证据快照');
190
+ const snapshot = JSON.parse(readFileSync(outSnapshot, 'utf8'));
191
+ assert.equal(snapshot.schemaVersion, 'gotry_kernel_evidence_v1');
192
+ assert.equal(snapshot.kernel.length, manifest.entries.length, '快照内核条目数与清单一致');
193
+ for (const k of snapshot.kernel)
194
+ assert.equal(k.loadedInTrace, true, `真实运行应加载内核:${k.path}`);
195
+ assert.ok(snapshot.runtime.moduleCount > 0, 'trace 必须记录到真实模块');
196
+ const exitVerify = gateMain(['--verify', outSnapshot]);
197
+ assert.equal(exitVerify, 0, `快照审计应绿,得到 ${exitVerify}`);
198
+ // 篡改快照(错 SHA 面)必须红:exit 1。
199
+ const tampered = { ...snapshot, kernel: snapshot.kernel.map((k, i) => (i === 0 ? { ...k, sha256Current: '0'.repeat(64) } : k)) };
200
+ const tamperedPath = outSnapshot.replace(/evidence\.json$/, 'evidence-tampered.json');
201
+ writeFileSync(tamperedPath, JSON.stringify(tampered, null, 2) + '\n');
202
+ const exitTampered = gateMain(['--verify', tamperedPath]);
203
+ assert.equal(exitTampered, 1, `篡改快照必须红,得到 ${exitTampered}`);
204
+ // manifestHash 外部钉值校验:对偶面。
205
+ assert.equal(gateMain(['--skip-trace', '--manifest-hash', manifestHash(manifest)]), 0, 'manifestHash 钉值匹配应绿');
206
+ assert.equal(gateMain(['--skip-trace', '--manifest-hash', 'f'.repeat(64)]), 1, 'manifestHash 钉值不符必须红');
207
+ }
208
+ finally {
209
+ rmSync(outSnapshot, { recursive: true, force: true });
210
+ }
211
+ }
212
+ console.log(`KERNEL MANIFEST TESTS: GREEN(§1–§${section};反证矩阵 §4/§5/§6 全部按预期红,§7 真实运行 trace 端到端绿)`);
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 运行模块 trace 入口(issue #234):真实 import 产品运行面——dsh 插件入口
3
+ * ts/src/index.ts。import-only:不调用任何工具、不注册到任何 ctx、零状态写入
4
+ * (gotry-state 红线面零接触);trace 结果由 kernel-manifest-trace-hook.mjs 记录。
5
+ */
6
+ await import('../src/index.js');
7
+ process.stdout.write('KERNEL_TRACE_ENTRY_OK\n');
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 运行模块 trace 钩子(issue #234;由 kernel-manifest.ts runRuntimeTrace 以
3
+ * `node --import tsx/esm --import <本文件>` 装载)。node:module registerHooks
4
+ * 是同步钩子(主线程,Node ≥ 22.15),能记录真实实例化的每个模块 URL;
5
+ * 异步 register() 的钩子跑在独立线程,主进程拿不到数组——这是选同步 API 的原因。
6
+ * 输出:KR_TRACE_OUT 指向的文件,{ loaded: [url, …] }。
7
+ */
8
+ import { registerHooks } from 'node:module'
9
+ import { writeFileSync } from 'node:fs'
10
+
11
+ const loaded = []
12
+
13
+ registerHooks({
14
+ load(url, context, nextLoad) {
15
+ loaded.push(url)
16
+ return nextLoad(url, context)
17
+ },
18
+ })
19
+
20
+ process.on('exit', () => {
21
+ const out = process.env.KR_TRACE_OUT
22
+ if (!out) return
23
+ try {
24
+ writeFileSync(out, JSON.stringify({ loaded }))
25
+ } catch {
26
+ // 输出失败时父进程按「trace 文件缺失」裁决为环境失败(exit 2),不静默。
27
+ }
28
+ })
@@ -0,0 +1,369 @@
1
+ /**
2
+ * GoTry 内核清单冻结 + 运行模块证据(issue #234;M5 备产系列,父单 #136)。
3
+ *
4
+ * 把复用矩阵纪律「哪些模块属于 GoTry 内核(不允许被替换/绕过)」机械化为可校验清单:
5
+ * 1. 冻结清单 ts/data/kernel-manifest.json(gotry_kernel_manifest_v1):预先冻结
6
+ * 内核文件集合 + 文件 SHA256 + 每项依据,绑定 baseSHA——零 diff 证明 = 哈希相等,
7
+ * 不存在「事后删文件制造零 diff」的空间(删文件即 kernel-file-missing 红)。
8
+ * 2. 运行模块证据(gotry_kernel_evidence_v1):对真实产品运行面(ts/src/index.ts
9
+ * dsh 插件入口)做真实运行 import trace(node:module registerHooks 同步钩子,
10
+ * 子进程离线),断言内核清单全部被运行加载;再对照功能路径覆盖(同引擎/账本/闸
11
+ * 路径的 run-all 套件静态 import 闭包)。仅 LOC/字符串扫描不构成复用证明。
12
+ * 3. 证据快照哈希绑定(manifestHash + evidenceHash),离线可复跑、可审计。
13
+ *
14
+ * 边界(诚实声明,对齐 issue #234 与 #137 门注):本面只输出工程复用证据,
15
+ * 不冒充 traveler 效果或 M6 试点签约;精确运行模块证据成为实施门以 #136 M5 Exit
16
+ * 与创始人 P6 批准记录在 #137 为前提——本面是 pre-entry 设计 + 确定性 fixture。
17
+ *
18
+ * 用法:
19
+ * npx tsx scripts/kernel-manifest-evidence.ts 生成证据快照(--out 或 stdout)
20
+ * npx tsx scripts/kernel-manifest-gate.ts 机械闸(默认 HEAD 全量校验)
21
+ * npx tsx scripts/kernel-manifest-tests.ts 反证自测(全离线)
22
+ */
23
+ import { spawnSync } from 'node:child_process';
24
+ import { createHash } from 'node:crypto';
25
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
26
+ import { tmpdir } from 'node:os';
27
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
28
+ import { fileURLToPath } from 'node:url';
29
+ export const MANIFEST_SCHEMA_VERSION = 'gotry_kernel_manifest_v1';
30
+ export const EVIDENCE_SCHEMA_VERSION = 'gotry_kernel_evidence_v1';
31
+ export const MANIFEST_REL = 'ts/data/kernel-manifest.json';
32
+ export const RUNTIME_ENTRY_REL = 'ts/src/index.ts';
33
+ export const TRACE_ENTRY_REL = 'ts/scripts/kernel-manifest-trace-entry.ts';
34
+ export const TRACE_HOOK_REL = 'ts/scripts/kernel-manifest-trace-hook.mjs';
35
+ /** registerHooks 需 Node ≥ 22.15(同步、主线程);低于此版本闸无法运行(exit 2)。 */
36
+ export const MIN_NODE_FOR_REGISTER_HOOKS = [22, 15, 0];
37
+ export const KERNEL_GROUPS = ['engine', 'ledger', 'gate'];
38
+ function toResult(findings) {
39
+ return { ok: findings.length === 0, findings };
40
+ }
41
+ /** 规范化 JSON:键递归排序,消除键序对哈希的扰动(哈希绑定的基础)。 */
42
+ export function canonicalJson(value) {
43
+ if (value === null || typeof value !== 'object')
44
+ return JSON.stringify(value) ?? 'null';
45
+ if (Array.isArray(value))
46
+ return `[${value.map((v) => canonicalJson(v)).join(',')}]`;
47
+ const keys = Object.keys(value).sort();
48
+ const body = keys
49
+ .map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`)
50
+ .join(',');
51
+ return `{${body}}`;
52
+ }
53
+ export function sha256Hex(text) {
54
+ return createHash('sha256').update(text, 'utf8').digest('hex');
55
+ }
56
+ /** 冻结清单的哈希绑定:清单内容(规范化)的 SHA256。 */
57
+ export function manifestHash(manifest) {
58
+ return sha256Hex(canonicalJson(manifest));
59
+ }
60
+ /** 证据快照的哈希绑定:去掉 evidenceHash 字段后(规范化)的 SHA256。 */
61
+ export function evidenceHash(evidence) {
62
+ const { evidenceHash: _ignored, ...rest } = evidence;
63
+ return sha256Hex(canonicalJson(rest));
64
+ }
65
+ export function repoRelative(repoRoot, absPath) {
66
+ return relative(repoRoot, absPath).split(sep).join('/');
67
+ }
68
+ export function defaultRepoRoot() {
69
+ // 本文件位于 <repo>/ts/scripts/,仓库根在其上两级。
70
+ return resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
71
+ }
72
+ export function loadKernelManifest(repoRoot, manifestRel = MANIFEST_REL) {
73
+ const raw = JSON.parse(readFileSync(join(repoRoot, manifestRel), 'utf8'));
74
+ return raw;
75
+ }
76
+ const SHA256_RE = /^[0-9a-f]{64}$/;
77
+ const GIT_SHA_RE = /^[0-9a-f]{40}$/;
78
+ /** 内核条目只允许产品运行面 ts/src/<module>.ts(宁窄勿宽,拒绝目录/逃逸/杂类)。 */
79
+ const KERNEL_PATH_RE = /^ts\/src\/[A-Za-z0-9_-]+\.ts$/;
80
+ /** 废弃兼容层永远不得冒充内核(AGENTS.md:engine.ts / journey.ts deprecated)。 */
81
+ export const FORBIDDEN_KERNEL_PATHS = new Set(['ts/src/engine.ts', 'ts/src/journey.ts']);
82
+ /** 计算文件当前 SHA256;文件缺失返回 null。 */
83
+ export function fileSha256(repoRoot, relPath) {
84
+ const abs = join(repoRoot, relPath);
85
+ if (!existsSync(abs))
86
+ return null;
87
+ return sha256Hex(readFileSync(abs, 'utf8'));
88
+ }
89
+ /**
90
+ * 静态 import 闭包:从 entryRel 出发,沿相对 .ts import(transitive)收集可达文件
91
+ * (仓库相对路径)。仓库约定 import 带 .ts 扩展名;覆盖 `from '...'` 与 `import('...')`。
92
+ */
93
+ export function importClosure(repoRoot, entryRel) {
94
+ const seen = new Set();
95
+ const queue = [entryRel];
96
+ const FROM_RE = /from\s+'([^']+)'/g;
97
+ const DYNAMIC_RE = /import\('([^']+)'\)/g;
98
+ while (queue.length > 0) {
99
+ const current = queue.pop();
100
+ if (seen.has(current))
101
+ continue;
102
+ seen.add(current);
103
+ const abs = join(repoRoot, current);
104
+ if (!existsSync(abs))
105
+ continue;
106
+ const content = readFileSync(abs, 'utf8');
107
+ const specifiers = new Set();
108
+ for (const re of [FROM_RE, DYNAMIC_RE]) {
109
+ re.lastIndex = 0;
110
+ let m;
111
+ while ((m = re.exec(content)) !== null)
112
+ specifiers.add(m[1]);
113
+ }
114
+ for (const spec of specifiers) {
115
+ if (!spec.startsWith('.') || !spec.endsWith('.ts'))
116
+ continue;
117
+ const target = repoRelative(repoRoot, resolve(dirname(abs), spec));
118
+ if (!target.startsWith('..'))
119
+ queue.push(target);
120
+ }
121
+ }
122
+ return seen;
123
+ }
124
+ /** 当前 git HEAD(40 hex);非 git 环境/失败返回 null。 */
125
+ export function currentHeadSha(repoRoot) {
126
+ const result = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' });
127
+ if (result.error || result.status !== 0)
128
+ return null;
129
+ const sha = result.stdout.trim();
130
+ return GIT_SHA_RE.test(sha) ? sha : null;
131
+ }
132
+ function nodeVersionTuple() {
133
+ return process.versions.node.split('.').map((n) => Number(n));
134
+ }
135
+ /** 运行环境是否具备 registerHooks(同步钩子)能力。 */
136
+ export function nodeSupportsRegisterHooks() {
137
+ const [maj, min, patch] = nodeVersionTuple();
138
+ const [rMaj, rMin, rPatch] = MIN_NODE_FOR_REGISTER_HOOKS;
139
+ return maj > rMaj || (maj === rMaj && (min > rMin || (min === rMin && patch >= rPatch)));
140
+ }
141
+ /**
142
+ * 真实运行 import trace:子进程里对真实产品运行面(ts/src/index.ts)执行 import,
143
+ * 用 node:module registerHooks 同步 load 钩子记录全部实例化模块;返回仓库内
144
+ * ts/ 面的相对路径(排序)。import-only,零状态写入,全离线。
145
+ */
146
+ export function runRuntimeTrace(repoRoot) {
147
+ const nodeVersion = process.versions.node;
148
+ if (!nodeSupportsRegisterHooks()) {
149
+ return {
150
+ ok: false,
151
+ error: `Node ${nodeVersion} 缺少 node:module registerHooks(需 ≥ ${MIN_NODE_FOR_REGISTER_HOOKS.join('.')});无法产生运行模块证据`,
152
+ nodeVersion,
153
+ moduleCount: 0,
154
+ repoModuleCount: 0,
155
+ loadedRepoModules: [],
156
+ };
157
+ }
158
+ const tsDir = join(repoRoot, 'ts');
159
+ const outDir = mkdtempSync(join(tmpdir(), 'gotry-kernel-trace-'));
160
+ const outPath = join(outDir, 'trace.json');
161
+ const result = spawnSync(process.execPath, ['--import', 'tsx/esm', '--import', join(tsDir, 'scripts', 'kernel-manifest-trace-hook.mjs'), join(tsDir, 'scripts', 'kernel-manifest-trace-entry.ts')], { cwd: tsDir, encoding: 'utf8', timeout: 180_000, env: { ...process.env, KR_TRACE_OUT: outPath } });
162
+ try {
163
+ if (result.error) {
164
+ return { ok: false, error: `trace 子进程失败:${result.error.message}`, nodeVersion, moduleCount: 0, repoModuleCount: 0, loadedRepoModules: [] };
165
+ }
166
+ if (result.status !== 0) {
167
+ return { ok: false, error: `trace 子进程非零退出 ${result.status}:${(result.stderr ?? '').trim().slice(0, 400)}`, nodeVersion, moduleCount: 0, repoModuleCount: 0, loadedRepoModules: [] };
168
+ }
169
+ if (!(readFileSync(outPath, 'utf8') ?? '').includes('loaded')) {
170
+ return { ok: false, error: 'trace 输出缺失或形状不符(load 钩子未记录任何模块)', nodeVersion, moduleCount: 0, repoModuleCount: 0, loadedRepoModules: [] };
171
+ }
172
+ const parsed = JSON.parse(readFileSync(outPath, 'utf8'));
173
+ const repoModules = parsed.loaded
174
+ .filter((url) => url.startsWith('file://'))
175
+ .map((url) => repoRelative(repoRoot, fileURLToPath(url)))
176
+ .filter((rel) => rel.startsWith('ts/') && !rel.includes('/node_modules/') && !rel.startsWith('ts/dsh-runtime/'))
177
+ .sort();
178
+ const seen = new Set(repoModules);
179
+ return { ok: true, nodeVersion, moduleCount: parsed.loaded.length, repoModuleCount: repoModules.length, loadedRepoModules: [...seen] };
180
+ }
181
+ finally {
182
+ try {
183
+ rmSync(outDir, { recursive: true, force: true });
184
+ }
185
+ catch { /* 临时目录清理失败不影响裁决 */ }
186
+ }
187
+ }
188
+ /** 冻结清单本身的结构纪律(schema/依据/路径形状),与其余校验分离以便单独反证。 */
189
+ export function validateManifestSchema(manifest) {
190
+ const findings = [];
191
+ if (manifest.schemaVersion !== MANIFEST_SCHEMA_VERSION) {
192
+ findings.push({ code: 'manifest-schema-version', detail: `schemaVersion 必须是 ${MANIFEST_SCHEMA_VERSION}(当前 ${manifest.schemaVersion})` });
193
+ }
194
+ if (!GIT_SHA_RE.test(manifest.baseSHA ?? '')) {
195
+ findings.push({ code: 'manifest-base-sha', detail: `baseSHA 必须是 40 位十六进制 git SHA(当前 ${JSON.stringify(manifest.baseSHA)})` });
196
+ }
197
+ if (!manifest.frozenAt || typeof manifest.frozenAt !== 'string') {
198
+ findings.push({ code: 'manifest-frozen-at', detail: 'frozenAt 必须非空(冻结日期)' });
199
+ }
200
+ const entries = manifest.entries ?? [];
201
+ if (entries.length === 0) {
202
+ findings.push({ code: 'manifest-empty', detail: '内核清单为空:宁窄勿宽,但不允许空清单冒充冻结面' });
203
+ }
204
+ const seen = new Set();
205
+ for (const entry of entries) {
206
+ if (!KERNEL_PATH_RE.test(entry.path ?? '') || entry.path.includes('..') || isAbsolute(entry.path)) {
207
+ findings.push({ code: 'manifest-path-shape', detail: `内核路径必须是 ts/src/<module>.ts 形状(当前 ${JSON.stringify(entry.path)})` });
208
+ }
209
+ if (FORBIDDEN_KERNEL_PATHS.has(entry.path ?? '')) {
210
+ findings.push({ code: 'manifest-forbidden-path', detail: `废弃兼容层不得进入内核清单:${entry.path}(AGENTS.md:engine.*/journey.* deprecated)` });
211
+ }
212
+ if (seen.has(entry.path))
213
+ findings.push({ code: 'manifest-duplicate-path', detail: `内核路径重复:${entry.path}` });
214
+ seen.add(entry.path);
215
+ if (!KERNEL_GROUPS.includes(entry.group)) {
216
+ findings.push({ code: 'manifest-group', detail: `group 必须属于 ${KERNEL_GROUPS.join('/')}(当前 ${JSON.stringify(entry.group)})` });
217
+ }
218
+ if (!SHA256_RE.test(entry.sha256 ?? '')) {
219
+ findings.push({ code: 'manifest-sha-shape', detail: `sha256 必须是 64 位十六进制:${entry.path}` });
220
+ }
221
+ if (!entry.rationale || entry.rationale.trim().length === 0) {
222
+ findings.push({ code: 'manifest-rationale-missing', detail: `内核条目缺 rationale(为什么属于内核):${entry.path}` });
223
+ }
224
+ if (!entry.basis || entry.basis.trim().length === 0) {
225
+ findings.push({ code: 'manifest-basis-missing', detail: `内核条目缺 basis(依据:architecture §/ADR/AGENTS 条款):${entry.path}` });
226
+ }
227
+ }
228
+ return toResult(findings);
229
+ }
230
+ /**
231
+ * 内核零 diff 校验:当前工作区每个内核文件存在且 SHA256 与冻结值一致。
232
+ * 改核心 → kernel-hash-mismatch;删文件 → kernel-file-missing(事后删文件
233
+ * 制造零 diff 的反证被此条封死)。
234
+ */
235
+ export function validateKernelIntegrity(manifest, repoRoot, opts = {}) {
236
+ const findings = [];
237
+ for (const entry of manifest.entries ?? []) {
238
+ const current = fileSha256(repoRoot, entry.path);
239
+ if (current === null) {
240
+ findings.push({ code: 'kernel-file-missing', detail: `内核文件缺失(冻结面不允许事后删文件):${entry.path}` });
241
+ continue;
242
+ }
243
+ if (current !== entry.sha256) {
244
+ findings.push({ code: 'kernel-hash-mismatch', detail: `内核文件被改动:${entry.path}(frozen ${entry.sha256.slice(0, 12)}… → current ${current.slice(0, 12)}…)` });
245
+ }
246
+ }
247
+ if (opts.packagingCheck !== false) {
248
+ const rootPkgAbs = join(repoRoot, 'package.json');
249
+ if (existsSync(rootPkgAbs)) {
250
+ const pkg = JSON.parse(readFileSync(rootPkgAbs, 'utf8'));
251
+ if (Array.isArray(pkg.files) && pkg.files.length > 0) {
252
+ for (const entry of manifest.entries ?? []) {
253
+ const packaged = pkg.files.some((f) => f === entry.path || entry.path.startsWith(f.endsWith('/') ? f : `${f}/`));
254
+ if (!packaged) {
255
+ findings.push({ code: 'kernel-not-packaged', detail: `内核文件不在 npm 包 files 面内(发布产物无法内核一致):${entry.path}` });
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+ return toResult(findings);
262
+ }
263
+ /**
264
+ * 功能路径覆盖:同引擎/账本/闸路径各由一个 run-all 套件承载,套件必须真实存在
265
+ * 且其静态 import 闭包包含所声称的内核模块(缺路径覆盖 → 红)。
266
+ */
267
+ export function validateFunctionalCoverage(manifest, repoRoot) {
268
+ const findings = [];
269
+ const paths = manifest.functionalPaths ?? [];
270
+ const entries = manifest.entries ?? [];
271
+ const coveredGroups = new Set();
272
+ for (const fp of paths) {
273
+ if (!KERNEL_GROUPS.includes(fp.group)) {
274
+ findings.push({ code: 'coverage-group', detail: `功能路径 group 非法:${fp.name}(${JSON.stringify(fp.group)})` });
275
+ continue;
276
+ }
277
+ coveredGroups.add(fp.group);
278
+ if (!Number.isInteger(fp.runAllSection) || fp.runAllSection <= 0) {
279
+ findings.push({ code: 'coverage-runall-section', detail: `功能路径缺有效 runAllSection:${fp.name}` });
280
+ }
281
+ if (typeof fp.suite !== 'string' || !fp.suite.startsWith('ts/scripts/') || fp.suite.includes('..')) {
282
+ findings.push({ code: 'coverage-suite-path', detail: `功能路径 suite 必须位于 ts/scripts/:${fp.name}(${JSON.stringify(fp.suite)})` });
283
+ continue;
284
+ }
285
+ if (!existsSync(join(repoRoot, fp.suite))) {
286
+ findings.push({ code: 'coverage-suite-missing', detail: `功能路径套件缺失(缺路径覆盖):${fp.suite}` });
287
+ continue;
288
+ }
289
+ const closure = importClosure(repoRoot, fp.suite);
290
+ for (const kernelPath of fp.kernelModules ?? []) {
291
+ const entry = entries.find((e) => e.path === kernelPath);
292
+ if (!entry) {
293
+ findings.push({ code: 'coverage-foreign-module', detail: `功能路径声称覆盖的模块不在内核清单:${fp.name} → ${kernelPath}` });
294
+ continue;
295
+ }
296
+ if (entry.group !== fp.group) {
297
+ findings.push({ code: 'coverage-group-mismatch', detail: `功能路径 group 与内核条目 group 不一致:${fp.name}(${fp.group}) → ${kernelPath}(${entry.group})` });
298
+ }
299
+ if (!closure.has(kernelPath)) {
300
+ findings.push({ code: 'coverage-suite-not-importing-kernel', detail: `套件静态 import 闭包不含内核模块(路径覆盖断裂):${fp.suite} ↛ ${kernelPath}` });
301
+ }
302
+ }
303
+ }
304
+ for (const entry of entries) {
305
+ if (!coveredGroups.has(entry.group)) {
306
+ findings.push({ code: 'coverage-entry-without-path', detail: `内核条目无任何功能路径覆盖:${entry.path}(group ${entry.group})` });
307
+ }
308
+ }
309
+ return toResult(findings);
310
+ }
311
+ /** 证据快照 ↔ 当前树 ↔ 冻结清单 三方一致性(requireLoadedCoverage=false 用于离线审计无 trace 面)。 */
312
+ export function validateEvidence(evidence, manifest, repoRoot, opts = {}) {
313
+ const findings = [];
314
+ if (evidence.schemaVersion !== EVIDENCE_SCHEMA_VERSION) {
315
+ findings.push({ code: 'evidence-schema-version', detail: `schemaVersion 必须是 ${EVIDENCE_SCHEMA_VERSION}(当前 ${evidence.schemaVersion})` });
316
+ }
317
+ if (!GIT_SHA_RE.test(evidence.candidateSHA ?? '')) {
318
+ findings.push({ code: 'evidence-candidate-sha', detail: `candidateSHA 必须是 40 位十六进制(当前 ${JSON.stringify(evidence.candidateSHA)})` });
319
+ }
320
+ if (opts.expectedCandidateSha && evidence.candidateSHA !== opts.expectedCandidateSha) {
321
+ findings.push({ code: 'evidence-candidate-sha-mismatch', detail: `candidateSHA 与当前 HEAD 不一致:${evidence.candidateSHA} ≠ ${opts.expectedCandidateSha}` });
322
+ }
323
+ if (evidence.baseSHA !== manifest.baseSHA) {
324
+ findings.push({ code: 'evidence-base-sha-mismatch', detail: `baseSHA 与冻结清单不一致:${evidence.baseSHA} ≠ ${manifest.baseSHA}` });
325
+ }
326
+ if (evidence.manifestRef?.sha256 !== manifestHash(manifest)) {
327
+ findings.push({ code: 'manifest-hash-mismatch', detail: `manifestHash 与冻结清单不符(清单被改动或快照错绑):${JSON.stringify(evidence.manifestRef?.sha256)} ≠ ${manifestHash(manifest).slice(0, 12)}…` });
328
+ }
329
+ if (evidence.evidenceHash !== evidenceHash(evidence)) {
330
+ findings.push({ code: 'evidence-hash-mismatch', detail: `evidenceHash 与快照内容不符(快照被改动):${JSON.stringify(evidence.evidenceHash)} ≠ ${evidenceHash(evidence).slice(0, 12)}…` });
331
+ }
332
+ const integrity = validateKernelIntegrity(manifest, repoRoot);
333
+ findings.push(...integrity.findings);
334
+ const coverage = validateFunctionalCoverage(manifest, repoRoot);
335
+ findings.push(...coverage.findings);
336
+ const manifestPaths = new Set((manifest.entries ?? []).map((e) => e.path));
337
+ const evidenceKernel = evidence.kernel ?? [];
338
+ const evidencePaths = new Set(evidenceKernel.map((k) => k.path));
339
+ for (const path of manifestPaths) {
340
+ if (!evidencePaths.has(path))
341
+ findings.push({ code: 'evidence-kernel-missing', detail: `证据快照缺内核条目:${path}` });
342
+ }
343
+ for (const item of evidenceKernel) {
344
+ if (!manifestPaths.has(item.path)) {
345
+ findings.push({ code: 'evidence-kernel-foreign', detail: `证据快照出现清单外内核条目:${item.path}` });
346
+ continue;
347
+ }
348
+ const entry = (manifest.entries ?? []).find((e) => e.path === item.path);
349
+ if (item.sha256Frozen !== entry.sha256) {
350
+ findings.push({ code: 'evidence-frozen-sha-mismatch', detail: `证据快照冻结哈希与清单不符:${item.path}` });
351
+ }
352
+ const current = fileSha256(repoRoot, item.path);
353
+ if (current !== null && item.sha256Current !== current) {
354
+ findings.push({ code: 'evidence-current-sha-stale', detail: `证据快照当前哈希与工作区不符(快照未重算):${item.path}` });
355
+ }
356
+ }
357
+ const loaded = new Set(evidence.runtime?.loadedRepoModules ?? []);
358
+ if (opts.requireLoadedCoverage !== false) {
359
+ if (loaded.size === 0) {
360
+ findings.push({ code: 'evidence-trace-empty', detail: '运行模块 trace 为空:缺真实运行 import 证据' });
361
+ }
362
+ for (const entry of manifest.entries ?? []) {
363
+ if (!loaded.has(entry.path)) {
364
+ findings.push({ code: 'kernel-not-loaded', detail: `内核模块未被真实运行加载(替换/绕过内核的形状):${entry.path}` });
365
+ }
366
+ }
367
+ }
368
+ return toResult(findings);
369
+ }