@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,1310 @@
1
+ /**
2
+ * Explicit opt-in M4 planning lifecycle observation collector.
3
+ *
4
+ * This module owns the deterministic contract behind scripts/memory-lifecycle.ts:
5
+ * - every subject/flow/evidence identifier is HMAC-SHA256 pseudonymous;
6
+ * - the wait-code vocabulary is frozen at dataset init;
7
+ * - lifecycle events are append-only and idempotent;
8
+ * - exports target the memory-value scorer input contract without claiming source attestation.
9
+ */
10
+ import { createHash, createHmac, randomBytes } from 'node:crypto';
11
+ import { chmodSync, closeSync, constants, existsSync, ftruncateSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, statSync, truncateSync, unlinkSync, writeSync, } from 'node:fs';
12
+ import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
13
+ export const MEMORY_LIFECYCLE_STORE_SCHEMA = 'memory_lifecycle_observations.v1';
14
+ export const MEMORY_LIFECYCLE_MANIFEST_SCHEMA = 'memory_lifecycle_manifest.v1';
15
+ export const MEMORY_LIFECYCLE_EVENT_SCHEMA = 'memory_lifecycle_event.v1';
16
+ export const MEMORY_VALUE_FIXTURE_SCHEMA = 'memory_value_fixture.v1';
17
+ export const MEMORY_VALUE_SOURCE_REVIEW_SCHEMA = 'memory_value_source_review.v1';
18
+ export const HMAC_REF_PATTERN = /^hmac-sha256:[0-9a-f]{64}$/;
19
+ export const SHA256_PATTERN = /^[0-9a-f]{64}$/;
20
+ export const WAIT_CODE_PATTERN = /^[a-z][a-z0-9_]{0,31}$/;
21
+ export class MemoryLifecycleError extends Error {
22
+ code;
23
+ constructor(code) {
24
+ super(code);
25
+ this.code = code;
26
+ }
27
+ }
28
+ export const systemClock = {
29
+ now: () => new Date(),
30
+ };
31
+ function fail(code) {
32
+ throw new MemoryLifecycleError(code);
33
+ }
34
+ function isObject(value) {
35
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
36
+ }
37
+ function nonEmptyString(value) {
38
+ return typeof value === 'string' && value.trim().length > 0;
39
+ }
40
+ function assertRawLabel(value, limit = 512) {
41
+ if (!nonEmptyString(value))
42
+ fail('invalid_input');
43
+ if (value.length > limit)
44
+ fail('invalid_input');
45
+ if (/[\u0000-\u001f\u007f]/u.test(value))
46
+ fail('invalid_input');
47
+ return value;
48
+ }
49
+ function assertIsoDate(date) {
50
+ const ms = date.getTime();
51
+ if (!Number.isFinite(ms))
52
+ fail('invalid_input');
53
+ return date.toISOString();
54
+ }
55
+ function parseIsoMs(value) {
56
+ const ms = Date.parse(value);
57
+ if (!Number.isFinite(ms))
58
+ fail('invalid_store');
59
+ if (new Date(ms).toISOString() !== value)
60
+ fail('invalid_store');
61
+ return ms;
62
+ }
63
+ function normalizeHmacKey(key) {
64
+ if (!nonEmptyString(key))
65
+ fail('missing_hmac_key');
66
+ const trimmed = key.trim();
67
+ if (trimmed.length < 32)
68
+ fail('invalid_hmac_key');
69
+ if (/[\u0000-\u001f\u007f]/u.test(trimmed))
70
+ fail('invalid_hmac_key');
71
+ return trimmed;
72
+ }
73
+ function normalizeWaitCodes(codes) {
74
+ if (!Array.isArray(codes) || codes.length === 0)
75
+ fail('invalid_input');
76
+ const seen = new Set();
77
+ for (const raw of codes) {
78
+ const code = assertRawLabel(raw, 32);
79
+ if (!WAIT_CODE_PATTERN.test(code))
80
+ fail('invalid_input');
81
+ if (seen.has(code))
82
+ fail('invalid_input');
83
+ seen.add(code);
84
+ }
85
+ return [...seen].sort();
86
+ }
87
+ function assertSourceKind(value) {
88
+ if (value !== 'synthetic_fixture' && value !== 'observed_private')
89
+ fail('invalid_input');
90
+ return value;
91
+ }
92
+ function assertRefluxKind(value) {
93
+ if (value !== 'recalled' && value !== 'verified_outcome')
94
+ fail('invalid_input');
95
+ return value;
96
+ }
97
+ function assertPreferenceConsumer(value) {
98
+ if (value !== 'ranking' && value !== 'explanation')
99
+ fail('invalid_input');
100
+ return value;
101
+ }
102
+ function assertHmacRef(value) {
103
+ if (!nonEmptyString(value) || !HMAC_REF_PATTERN.test(value))
104
+ fail('invalid_store');
105
+ return value;
106
+ }
107
+ function assertEventObject(value) {
108
+ if (!isObject(value))
109
+ fail('invalid_store');
110
+ if (value.schema_version !== MEMORY_LIFECYCLE_EVENT_SCHEMA)
111
+ fail('invalid_store');
112
+ assertHmacRef(value.event_ref);
113
+ if (!nonEmptyString(value.kind))
114
+ fail('invalid_store');
115
+ if (!nonEmptyString(value.occurred_at))
116
+ fail('invalid_store');
117
+ parseIsoMs(value.occurred_at);
118
+ assertHmacRef(value.consent_ref);
119
+ return value;
120
+ }
121
+ function normalizePolicyPath(path) {
122
+ return resolve(path).replace(/\\/g, '/');
123
+ }
124
+ function assertSafePolicyPath(path) {
125
+ const normalized = normalizePolicyPath(path);
126
+ if (normalized === '/' || normalized.endsWith('/.git') || normalized.includes('/.git/'))
127
+ fail('unsafe_state_root');
128
+ if (normalized.endsWith('/ts/dsh-runtime') || normalized.includes('/ts/dsh-runtime/'))
129
+ fail('unsafe_state_root');
130
+ }
131
+ function nearestExistingAncestor(path) {
132
+ let current = resolve(path);
133
+ for (;;) {
134
+ if (existsSync(current))
135
+ return current;
136
+ const parent = dirname(current);
137
+ if (parent === current)
138
+ return current;
139
+ current = parent;
140
+ }
141
+ }
142
+ function realpathOrUnsafe(path) {
143
+ try {
144
+ return realpathSync.native(path);
145
+ }
146
+ catch {
147
+ fail('unsafe_state_root');
148
+ }
149
+ }
150
+ function sameOrChildPath(parent, child) {
151
+ const distance = relative(parent, child);
152
+ return distance === '' || (!distance.startsWith('..') && !isAbsolute(distance));
153
+ }
154
+ function assertSafeStateRoot(stateRoot) {
155
+ if (!nonEmptyString(stateRoot))
156
+ fail('missing_state_root');
157
+ const resolved = resolve(stateRoot);
158
+ assertSafePolicyPath(resolved);
159
+ assertSafePolicyPath(realpathOrUnsafe(nearestExistingAncestor(resolved)));
160
+ return resolved;
161
+ }
162
+ function assertManagedStoreIsolation(storeRoot) {
163
+ const resolvedStoreRoot = resolve(storeRoot);
164
+ const stateRoot = resolve(resolvedStoreRoot, '..', '..');
165
+ assertSafePolicyPath(stateRoot);
166
+ assertSafePolicyPath(resolvedStoreRoot);
167
+ assertSafePolicyPath(realpathOrUnsafe(nearestExistingAncestor(stateRoot)));
168
+ if (!existsSync(stateRoot)) {
169
+ assertSafePolicyPath(realpathOrUnsafe(nearestExistingAncestor(resolvedStoreRoot)));
170
+ return;
171
+ }
172
+ const stateRootReal = realpathOrUnsafe(stateRoot);
173
+ assertSafePolicyPath(stateRootReal);
174
+ for (const managedPath of [join(stateRoot, 'gotry-state'), resolvedStoreRoot]) {
175
+ const existing = nearestExistingAncestor(managedPath);
176
+ const existingReal = realpathOrUnsafe(existing);
177
+ assertSafePolicyPath(existingReal);
178
+ if (!sameOrChildPath(stateRootReal, existingReal))
179
+ fail('unsafe_state_root');
180
+ }
181
+ }
182
+ function prepareCommon(options) {
183
+ const stateRoot = assertSafeStateRoot(options.stateRoot);
184
+ if (!nonEmptyString(options.consent))
185
+ fail('missing_consent');
186
+ const hmacKey = normalizeHmacKey(options.hmacKey);
187
+ const consent = assertRawLabel(options.consent, 512);
188
+ return {
189
+ consentRef: pseudonymousRef(hmacKey, 'consent', [consent]),
190
+ hmacKey,
191
+ stateRoot,
192
+ storeRoot: join(stateRoot, 'gotry-state', 'memory-lifecycle'),
193
+ };
194
+ }
195
+ export function pseudonymousRef(hmacKey, kind, parts) {
196
+ const key = normalizeHmacKey(hmacKey);
197
+ const hmac = createHmac('sha256', key);
198
+ hmac.update('gotry-memory-lifecycle.v1');
199
+ hmac.update('\0');
200
+ hmac.update(assertRawLabel(kind, 64));
201
+ for (const part of parts) {
202
+ hmac.update('\0');
203
+ hmac.update(assertRawLabel(part, 2048));
204
+ }
205
+ return `hmac-sha256:${hmac.digest('hex')}`;
206
+ }
207
+ export function canonicalJson(value) {
208
+ if (Array.isArray(value))
209
+ return `[${value.map(canonicalJson).join(',')}]`;
210
+ if (isObject(value)) {
211
+ return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
212
+ }
213
+ return JSON.stringify(value);
214
+ }
215
+ export function sha256Hex(value) {
216
+ return createHash('sha256').update(value).digest('hex');
217
+ }
218
+ export function memoryValueSummaryDigest(input) {
219
+ const payload = isObject(input)
220
+ ? Object.fromEntries(Object.entries(input).filter(([key]) => key !== 'source_review'))
221
+ : input;
222
+ return sha256Hex(canonicalJson(payload));
223
+ }
224
+ function eventRef(hmacKey, kind, parts) {
225
+ return pseudonymousRef(hmacKey, `event:${kind}`, parts);
226
+ }
227
+ function subjectRef(hmacKey, subject) {
228
+ return pseudonymousRef(hmacKey, 'subject', [subject]);
229
+ }
230
+ function flowRef(hmacKey, subject, flow) {
231
+ return pseudonymousRef(hmacKey, 'flow', [subjectRef(hmacKey, subject), flow]);
232
+ }
233
+ function waitRef(hmacKey, subject, flow, wait) {
234
+ return pseudonymousRef(hmacKey, 'external-wait', [flowRef(hmacKey, subject, flow), wait]);
235
+ }
236
+ function experienceRef(hmacKey, experience) {
237
+ return pseudonymousRef(hmacKey, 'experience', [experience]);
238
+ }
239
+ function evidenceRef(hmacKey, evidence) {
240
+ return pseudonymousRef(hmacKey, 'evidence', [evidence]);
241
+ }
242
+ function assertionRef(hmacKey, assertion) {
243
+ return pseudonymousRef(hmacKey, 'preference-assertion', [assertion]);
244
+ }
245
+ function datasetRef(hmacKey, dataset) {
246
+ return pseudonymousRef(hmacKey, 'dataset', [dataset]);
247
+ }
248
+ function datasetKeyVerifier(hmacKey) {
249
+ return pseudonymousRef(hmacKey, 'dataset-key-verifier', [MEMORY_LIFECYCLE_STORE_SCHEMA]);
250
+ }
251
+ const OPEN_NOFOLLOW = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
252
+ let tempFileCounter = 0;
253
+ function ensurePrivateDir(path) {
254
+ mkdirSync(path, { recursive: true, mode: 0o700 });
255
+ try {
256
+ chmodSync(path, 0o700);
257
+ }
258
+ catch { /* best effort on non-POSIX filesystems */ }
259
+ }
260
+ function pathExistsByLstat(path) {
261
+ try {
262
+ lstatSync(path);
263
+ return true;
264
+ }
265
+ catch (error) {
266
+ if (isNodeError(error, 'ENOENT'))
267
+ return false;
268
+ fail('unsafe_state_root');
269
+ }
270
+ }
271
+ function assertNoSymlinkLeaf(path) {
272
+ try {
273
+ if (lstatSync(path).isSymbolicLink())
274
+ fail('unsafe_state_root');
275
+ }
276
+ catch (error) {
277
+ if (isNodeError(error, 'ENOENT'))
278
+ return;
279
+ if (error instanceof MemoryLifecycleError)
280
+ throw error;
281
+ fail('unsafe_state_root');
282
+ }
283
+ }
284
+ function assertManagedFilePath(storeRoot, path) {
285
+ const resolvedStoreRoot = resolve(storeRoot);
286
+ const resolvedPath = resolve(path);
287
+ if (!sameOrChildPath(resolvedStoreRoot, resolvedPath))
288
+ fail('unsafe_state_root');
289
+ assertSafePolicyPath(resolvedPath);
290
+ assertManagedStoreIsolation(resolvedStoreRoot);
291
+ const stateRootReal = realpathOrUnsafe(resolve(resolvedStoreRoot, '..', '..'));
292
+ const parentReal = realpathOrUnsafe(nearestExistingAncestor(dirname(resolvedPath)));
293
+ assertSafePolicyPath(parentReal);
294
+ if (!sameOrChildPath(stateRootReal, parentReal))
295
+ fail('unsafe_state_root');
296
+ assertNoSymlinkLeaf(resolvedPath);
297
+ }
298
+ function assertOutputPath(path) {
299
+ const resolvedPath = resolve(path);
300
+ assertSafePolicyPath(resolvedPath);
301
+ const parent = dirname(resolvedPath);
302
+ assertSafePolicyPath(parent);
303
+ assertSafePolicyPath(realpathOrUnsafe(nearestExistingAncestor(parent)));
304
+ if (existsSync(parent))
305
+ assertSafePolicyPath(realpathOrUnsafe(parent));
306
+ return resolvedPath;
307
+ }
308
+ function privateTempPath(path) {
309
+ tempFileCounter += 1;
310
+ return join(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.${tempFileCounter}.${randomBytes(6).toString('hex')}.tmp`);
311
+ }
312
+ function writeAllSync(fd, content) {
313
+ const bytes = Buffer.isBuffer(content) ? content : Buffer.from(content);
314
+ let offset = 0;
315
+ while (offset < bytes.length) {
316
+ const written = writeSync(fd, bytes, offset, bytes.length - offset);
317
+ if (!Number.isInteger(written) || written <= 0)
318
+ fail('internal_error');
319
+ offset += written;
320
+ }
321
+ }
322
+ function writePrivateTempFile(path, content) {
323
+ let fd = -1;
324
+ let created = false;
325
+ let caught;
326
+ try {
327
+ fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | OPEN_NOFOLLOW, 0o600);
328
+ created = true;
329
+ writeAllSync(fd, content);
330
+ }
331
+ catch (error) {
332
+ caught = error;
333
+ }
334
+ finally {
335
+ if (fd >= 0) {
336
+ try {
337
+ closeSync(fd);
338
+ }
339
+ catch (error) {
340
+ caught ??= error;
341
+ }
342
+ }
343
+ if (caught !== undefined && created) {
344
+ try {
345
+ unlinkSync(path);
346
+ }
347
+ catch { /* best effort temp cleanup */ }
348
+ }
349
+ }
350
+ if (caught !== undefined) {
351
+ if (caught instanceof MemoryLifecycleError)
352
+ throw caught;
353
+ if (isNodeError(caught, 'ELOOP'))
354
+ fail('unsafe_state_root');
355
+ fail('internal_error');
356
+ }
357
+ try {
358
+ chmodSync(path, 0o600);
359
+ }
360
+ catch { /* best effort */ }
361
+ }
362
+ function publishTempNoOverwrite(tempPath, finalPath, existsCode) {
363
+ try {
364
+ linkSync(tempPath, finalPath);
365
+ }
366
+ catch (error) {
367
+ if (isNodeError(error, 'EEXIST'))
368
+ fail(existsCode);
369
+ fail('internal_error');
370
+ }
371
+ }
372
+ function writeManagedFileNoOverwrite(storeRoot, path, content, existsCode) {
373
+ assertManagedFilePath(storeRoot, path);
374
+ ensurePrivateDir(dirname(path));
375
+ assertManagedFilePath(storeRoot, path);
376
+ if (pathExistsByLstat(path))
377
+ fail(existsCode);
378
+ const tempPath = privateTempPath(path);
379
+ let tempCreated = false;
380
+ try {
381
+ assertManagedFilePath(storeRoot, tempPath);
382
+ writePrivateTempFile(tempPath, content);
383
+ tempCreated = true;
384
+ assertManagedFilePath(storeRoot, path);
385
+ publishTempNoOverwrite(tempPath, path, existsCode);
386
+ }
387
+ finally {
388
+ if (tempCreated) {
389
+ try {
390
+ unlinkSync(tempPath);
391
+ }
392
+ catch { /* best effort temp cleanup */ }
393
+ }
394
+ }
395
+ try {
396
+ chmodSync(path, 0o600);
397
+ }
398
+ catch { /* best effort */ }
399
+ }
400
+ function writePrivateFileNoOverwrite(path, content) {
401
+ const finalPath = assertOutputPath(path);
402
+ ensurePrivateDir(dirname(finalPath));
403
+ assertOutputPath(finalPath);
404
+ if (pathExistsByLstat(finalPath))
405
+ fail('output_exists');
406
+ const tempPath = privateTempPath(finalPath);
407
+ let tempCreated = false;
408
+ try {
409
+ assertOutputPath(tempPath);
410
+ writePrivateTempFile(tempPath, content);
411
+ tempCreated = true;
412
+ assertOutputPath(finalPath);
413
+ if (pathExistsByLstat(finalPath))
414
+ fail('output_exists');
415
+ publishTempNoOverwrite(tempPath, finalPath, 'output_exists');
416
+ }
417
+ finally {
418
+ if (tempCreated) {
419
+ try {
420
+ unlinkSync(tempPath);
421
+ }
422
+ catch { /* best effort temp cleanup */ }
423
+ }
424
+ }
425
+ try {
426
+ chmodSync(finalPath, 0o600);
427
+ }
428
+ catch { /* best effort */ }
429
+ }
430
+ function appendPrivateLine(storeRoot, path, line) {
431
+ assertManagedFilePath(storeRoot, path);
432
+ ensurePrivateDir(dirname(path));
433
+ assertManagedFilePath(storeRoot, path);
434
+ const existedBefore = pathExistsByLstat(path);
435
+ const originalSize = existedBefore ? statSync(path).size : 0;
436
+ const payload = `${line}\n`;
437
+ let fd = -1;
438
+ let caught;
439
+ try {
440
+ fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND | OPEN_NOFOLLOW, 0o600);
441
+ writeAllSync(fd, payload);
442
+ }
443
+ catch (error) {
444
+ caught = error;
445
+ if (fd >= 0) {
446
+ try {
447
+ ftruncateSync(fd, originalSize);
448
+ }
449
+ catch { /* keep original error */ }
450
+ }
451
+ }
452
+ finally {
453
+ if (fd >= 0) {
454
+ try {
455
+ closeSync(fd);
456
+ }
457
+ catch { /* close is non-semantic after append/truncate */ }
458
+ }
459
+ if (caught !== undefined && !existedBefore) {
460
+ try {
461
+ unlinkSync(path);
462
+ }
463
+ catch { /* best effort rollback */ }
464
+ }
465
+ }
466
+ if (caught !== undefined) {
467
+ if (caught instanceof MemoryLifecycleError)
468
+ throw caught;
469
+ if (isNodeError(caught, 'ELOOP'))
470
+ fail('unsafe_state_root');
471
+ fail('internal_error');
472
+ }
473
+ try {
474
+ chmodSync(path, 0o600);
475
+ }
476
+ catch { /* best effort */ }
477
+ }
478
+ function isNodeError(error, code) {
479
+ return isObject(error) && error.code === code;
480
+ }
481
+ function withStoreLock(storeRoot, createStore, body) {
482
+ assertManagedStoreIsolation(storeRoot);
483
+ if (createStore)
484
+ ensurePrivateDir(storeRoot);
485
+ else if (!existsSync(storeRoot))
486
+ fail('dataset_not_initialized');
487
+ assertManagedStoreIsolation(storeRoot);
488
+ const lockPath = join(storeRoot, '.writer.lock');
489
+ assertManagedFilePath(storeRoot, lockPath);
490
+ let fd = -1;
491
+ let createdLock = false;
492
+ try {
493
+ fd = openSync(lockPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | OPEN_NOFOLLOW, 0o600);
494
+ createdLock = true;
495
+ writeAllSync(fd, JSON.stringify({ schema_version: 'memory_lifecycle_lock.v1', pid: process.pid }));
496
+ }
497
+ catch (error) {
498
+ if (fd >= 0) {
499
+ try {
500
+ closeSync(fd);
501
+ }
502
+ catch { /* preserve original error */ }
503
+ fd = -1;
504
+ }
505
+ if (createdLock) {
506
+ try {
507
+ unlinkSync(lockPath);
508
+ }
509
+ catch { /* best effort for this writer's failed lock */ }
510
+ }
511
+ if (isNodeError(error, 'EEXIST'))
512
+ fail('lock_busy');
513
+ fail('internal_error');
514
+ }
515
+ finally {
516
+ if (fd >= 0)
517
+ closeSync(fd);
518
+ }
519
+ try {
520
+ assertManagedStoreIsolation(storeRoot);
521
+ return body();
522
+ }
523
+ finally {
524
+ try {
525
+ unlinkSync(lockPath);
526
+ }
527
+ catch { /* lock cleanup is best effort */ }
528
+ }
529
+ }
530
+ function manifestPath(storeRoot) {
531
+ return join(storeRoot, 'manifest.json');
532
+ }
533
+ function eventsPath(storeRoot) {
534
+ return join(storeRoot, 'events.jsonl');
535
+ }
536
+ function readManifest(storeRoot) {
537
+ const path = manifestPath(storeRoot);
538
+ assertManagedFilePath(storeRoot, path);
539
+ if (!existsSync(path))
540
+ fail('dataset_not_initialized');
541
+ let parsed;
542
+ try {
543
+ parsed = JSON.parse(readFileSync(path, 'utf8'));
544
+ }
545
+ catch {
546
+ fail('invalid_store');
547
+ }
548
+ return assertManifest(parsed);
549
+ }
550
+ function assertManifest(value) {
551
+ if (!isObject(value))
552
+ fail('invalid_store');
553
+ if (value.schema_version !== MEMORY_LIFECYCLE_MANIFEST_SCHEMA)
554
+ fail('invalid_store');
555
+ if (value.collector_schema !== MEMORY_LIFECYCLE_STORE_SCHEMA)
556
+ fail('invalid_store');
557
+ const ref = assertHmacRef(value.dataset_ref);
558
+ const keyVerifier = assertHmacRef(value.dataset_key_verifier);
559
+ const source = value.source_kind;
560
+ if (source !== 'synthetic_fixture' && source !== 'observed_private')
561
+ fail('invalid_store');
562
+ if (!nonEmptyString(value.created_at))
563
+ fail('invalid_store');
564
+ parseIsoMs(value.created_at);
565
+ const consentRef = assertHmacRef(value.consent_ref);
566
+ if (!isObject(value.measurement_policy))
567
+ fail('invalid_store');
568
+ const policy = value.measurement_policy;
569
+ if (policy.quantile_method !== 'nearest_rank')
570
+ fail('invalid_store');
571
+ if (!Array.isArray(policy.predeclared_external_wait_codes))
572
+ fail('invalid_store');
573
+ const waitCodes = normalizeWaitCodes(policy.predeclared_external_wait_codes.map(String));
574
+ if (policy.minimum_pair_count_for_exit !== 5)
575
+ fail('invalid_store');
576
+ if (policy.target_median_reduction_ratio !== 0.5)
577
+ fail('invalid_store');
578
+ if (!isObject(value.p4) || value.p4.state !== 'closed' || !isObject(value.p4.triggers))
579
+ fail('invalid_store');
580
+ if (value.p4.triggers.real_usage !== false || value.p4.triggers.multi_user !== false)
581
+ fail('invalid_store');
582
+ return {
583
+ schema_version: MEMORY_LIFECYCLE_MANIFEST_SCHEMA,
584
+ collector_schema: MEMORY_LIFECYCLE_STORE_SCHEMA,
585
+ dataset_ref: ref,
586
+ dataset_key_verifier: keyVerifier,
587
+ source_kind: source,
588
+ created_at: value.created_at,
589
+ consent_ref: consentRef,
590
+ measurement_policy: {
591
+ quantile_method: 'nearest_rank',
592
+ predeclared_external_wait_codes: waitCodes,
593
+ minimum_pair_count_for_exit: 5,
594
+ target_median_reduction_ratio: 0.5,
595
+ },
596
+ p4: { state: 'closed', triggers: { real_usage: false, multi_user: false } },
597
+ };
598
+ }
599
+ function readEvents(storeRoot, recoverUncommittedTail = false) {
600
+ const path = eventsPath(storeRoot);
601
+ assertManagedFilePath(storeRoot, path);
602
+ if (!existsSync(path))
603
+ return [];
604
+ let raw;
605
+ try {
606
+ raw = readFileSync(path, 'utf8');
607
+ }
608
+ catch {
609
+ fail('invalid_store');
610
+ }
611
+ if (recoverUncommittedTail)
612
+ raw = recoverCommittedEventLog(storeRoot, path, raw);
613
+ return parseEventLines(raw.split('\n'));
614
+ }
615
+ function recoverCommittedEventLog(storeRoot, path, raw) {
616
+ if (raw === '' || raw.endsWith('\n'))
617
+ return raw;
618
+ const committedLength = raw.lastIndexOf('\n') + 1;
619
+ const committedRaw = raw.slice(0, committedLength);
620
+ parseEventLines(committedRaw.split('\n'));
621
+ const tail = raw.slice(committedLength);
622
+ if (tail.trim() !== '') {
623
+ try {
624
+ parseEventLines([...committedRaw.split('\n').filter(line => line.trim() !== ''), tail]);
625
+ appendPrivateLine(storeRoot, path, '');
626
+ return `${raw}\n`;
627
+ }
628
+ catch (error) {
629
+ if (!(error instanceof MemoryLifecycleError) || error.code !== 'invalid_store')
630
+ throw error;
631
+ }
632
+ }
633
+ try {
634
+ assertManagedFilePath(storeRoot, path);
635
+ truncateSync(path, committedLength);
636
+ }
637
+ catch (error) {
638
+ if (error instanceof MemoryLifecycleError)
639
+ throw error;
640
+ fail('invalid_store');
641
+ }
642
+ return committedRaw;
643
+ }
644
+ function parseEventLines(lines) {
645
+ const events = [];
646
+ const seen = new Map();
647
+ for (const line of lines) {
648
+ if (line.trim() === '')
649
+ continue;
650
+ let parsed;
651
+ try {
652
+ parsed = JSON.parse(line);
653
+ }
654
+ catch {
655
+ fail('invalid_store');
656
+ }
657
+ const event = assertLifecycleEvent(parsed);
658
+ const canonical = canonicalJson(event);
659
+ const prior = seen.get(event.event_ref);
660
+ if (prior !== undefined) {
661
+ if (prior !== canonical)
662
+ fail('invalid_store');
663
+ continue;
664
+ }
665
+ seen.set(event.event_ref, canonical);
666
+ events.push(event);
667
+ }
668
+ return events;
669
+ }
670
+ function assertLifecycleEvent(value) {
671
+ const raw = assertEventObject(value);
672
+ switch (raw.kind) {
673
+ case 'flow_started':
674
+ if (raw.eligible !== true)
675
+ fail('invalid_store');
676
+ return {
677
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
678
+ event_ref: raw.event_ref,
679
+ kind: 'flow_started',
680
+ occurred_at: raw.occurred_at,
681
+ consent_ref: raw.consent_ref,
682
+ subject_ref: assertHmacRef(raw.subject_ref),
683
+ flow_ref: assertHmacRef(raw.flow_ref),
684
+ eligible: true,
685
+ };
686
+ case 'external_wait_started':
687
+ if (!nonEmptyString(raw.code) || !WAIT_CODE_PATTERN.test(raw.code))
688
+ fail('invalid_store');
689
+ return {
690
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
691
+ event_ref: raw.event_ref,
692
+ kind: 'external_wait_started',
693
+ occurred_at: raw.occurred_at,
694
+ consent_ref: raw.consent_ref,
695
+ subject_ref: assertHmacRef(raw.subject_ref),
696
+ flow_ref: assertHmacRef(raw.flow_ref),
697
+ wait_ref: assertHmacRef(raw.wait_ref),
698
+ code: raw.code,
699
+ };
700
+ case 'external_wait_ended':
701
+ return {
702
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
703
+ event_ref: raw.event_ref,
704
+ kind: 'external_wait_ended',
705
+ occurred_at: raw.occurred_at,
706
+ consent_ref: raw.consent_ref,
707
+ subject_ref: assertHmacRef(raw.subject_ref),
708
+ flow_ref: assertHmacRef(raw.flow_ref),
709
+ wait_ref: assertHmacRef(raw.wait_ref),
710
+ };
711
+ case 'flow_completed':
712
+ if (raw.status !== 'completed')
713
+ fail('invalid_store');
714
+ return {
715
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
716
+ event_ref: raw.event_ref,
717
+ kind: 'flow_completed',
718
+ occurred_at: raw.occurred_at,
719
+ consent_ref: raw.consent_ref,
720
+ subject_ref: assertHmacRef(raw.subject_ref),
721
+ flow_ref: assertHmacRef(raw.flow_ref),
722
+ status: 'completed',
723
+ };
724
+ case 'experience_reflux_recorded': {
725
+ const kind = raw.reflux_kind;
726
+ if (kind !== 'recalled' && kind !== 'verified_outcome')
727
+ fail('invalid_store');
728
+ return {
729
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
730
+ event_ref: raw.event_ref,
731
+ kind: 'experience_reflux_recorded',
732
+ occurred_at: raw.occurred_at,
733
+ consent_ref: raw.consent_ref,
734
+ reflux_kind: kind,
735
+ experience_ref: assertHmacRef(raw.experience_ref),
736
+ evidence_ref: assertHmacRef(raw.evidence_ref),
737
+ };
738
+ }
739
+ case 'preference_recorded': {
740
+ const consumer = raw.consumer;
741
+ if (consumer !== 'ranking' && consumer !== 'explanation')
742
+ fail('invalid_store');
743
+ if (raw.hard_filter !== false)
744
+ fail('invalid_store');
745
+ return {
746
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
747
+ event_ref: raw.event_ref,
748
+ kind: 'preference_recorded',
749
+ occurred_at: raw.occurred_at,
750
+ consent_ref: raw.consent_ref,
751
+ assertion_ref: assertHmacRef(raw.assertion_ref),
752
+ evidence_ref: assertHmacRef(raw.evidence_ref),
753
+ consumer,
754
+ hard_filter: false,
755
+ };
756
+ }
757
+ default:
758
+ fail('invalid_store');
759
+ }
760
+ }
761
+ function eventExists(events, ref) {
762
+ return events.some(event => event.event_ref === ref);
763
+ }
764
+ function appendEvent(storeRoot, events, event) {
765
+ assertCandidateProjection(events, event);
766
+ assertManagedStoreIsolation(storeRoot);
767
+ appendPrivateLine(storeRoot, eventsPath(storeRoot), JSON.stringify(event));
768
+ }
769
+ function assertCandidateProjection(events, event) {
770
+ try {
771
+ projectLifecycleEvents([...events, event]);
772
+ }
773
+ catch (error) {
774
+ if (error instanceof MemoryLifecycleError && error.code === 'invalid_store')
775
+ fail('invalid_transition');
776
+ throw error;
777
+ }
778
+ }
779
+ export function projectLifecycleEvents(events) {
780
+ const flows = new Map();
781
+ const flowsBySubject = new Map();
782
+ const refluxEvents = [];
783
+ const preferences = [];
784
+ const refluxKinds = new Set();
785
+ const assertionRefs = new Set();
786
+ for (const event of events) {
787
+ switch (event.kind) {
788
+ case 'flow_started': {
789
+ if (flows.has(event.flow_ref))
790
+ fail('invalid_store');
791
+ const subjectFlows = flowsBySubject.get(event.subject_ref) ?? [];
792
+ if (subjectFlows.some(existing => existing.completedAt === undefined))
793
+ fail('invalid_store');
794
+ const startedAtMs = parseIsoMs(event.occurred_at);
795
+ const lastCompleted = subjectFlows
796
+ .filter(existing => existing.completedAt !== undefined)
797
+ .sort(compareFlows)
798
+ .at(-1);
799
+ if (lastCompleted && startedAtMs <= parseIsoMs(lastCompleted.completedAt))
800
+ fail('invalid_store');
801
+ const flow = {
802
+ subjectRef: event.subject_ref,
803
+ flowRef: event.flow_ref,
804
+ eligible: true,
805
+ startedAt: event.occurred_at,
806
+ waits: new Map(),
807
+ };
808
+ subjectFlows.push(flow);
809
+ flowsBySubject.set(event.subject_ref, subjectFlows);
810
+ flows.set(event.flow_ref, flow);
811
+ break;
812
+ }
813
+ case 'external_wait_started': {
814
+ const flow = flows.get(event.flow_ref);
815
+ if (!flow || flow.subjectRef !== event.subject_ref || flow.completedAt !== undefined)
816
+ fail('invalid_store');
817
+ if (flow.waits.has(event.wait_ref))
818
+ fail('invalid_store');
819
+ if ([...flow.waits.values()].some(wait => wait.completedAt === undefined))
820
+ fail('invalid_store');
821
+ const startedAtMs = parseIsoMs(event.occurred_at);
822
+ if (startedAtMs < parseIsoMs(flow.startedAt))
823
+ fail('invalid_store');
824
+ for (const wait of flow.waits.values()) {
825
+ if (wait.completedAt && startedAtMs < parseIsoMs(wait.completedAt))
826
+ fail('invalid_store');
827
+ }
828
+ flow.waits.set(event.wait_ref, { waitRef: event.wait_ref, code: event.code, startedAt: event.occurred_at });
829
+ break;
830
+ }
831
+ case 'external_wait_ended': {
832
+ const flow = flows.get(event.flow_ref);
833
+ const wait = flow?.waits.get(event.wait_ref);
834
+ if (!flow || flow.subjectRef !== event.subject_ref || flow.completedAt !== undefined || !wait || wait.completedAt !== undefined) {
835
+ fail('invalid_store');
836
+ }
837
+ if (parseIsoMs(event.occurred_at) <= parseIsoMs(wait.startedAt))
838
+ fail('invalid_store');
839
+ wait.completedAt = event.occurred_at;
840
+ break;
841
+ }
842
+ case 'flow_completed': {
843
+ const flow = flows.get(event.flow_ref);
844
+ if (!flow || flow.subjectRef !== event.subject_ref || flow.completedAt !== undefined)
845
+ fail('invalid_store');
846
+ if ([...flow.waits.values()].some(wait => wait.completedAt === undefined))
847
+ fail('invalid_store');
848
+ if (parseIsoMs(event.occurred_at) <= parseIsoMs(flow.startedAt))
849
+ fail('invalid_store');
850
+ const completedAtMs = parseIsoMs(event.occurred_at);
851
+ for (const wait of flow.waits.values()) {
852
+ if (parseIsoMs(wait.startedAt) < parseIsoMs(flow.startedAt) || parseIsoMs(wait.completedAt) > completedAtMs)
853
+ fail('invalid_store');
854
+ }
855
+ flow.completedAt = event.occurred_at;
856
+ break;
857
+ }
858
+ case 'experience_reflux_recorded': {
859
+ const key = `${event.reflux_kind}:${event.experience_ref}`;
860
+ if (refluxKinds.has(key))
861
+ fail('invalid_store');
862
+ refluxKinds.add(key);
863
+ refluxEvents.push({ refluxKind: event.reflux_kind, experienceRef: event.experience_ref, evidenceRef: event.evidence_ref });
864
+ break;
865
+ }
866
+ case 'preference_recorded':
867
+ if (assertionRefs.has(event.assertion_ref))
868
+ fail('invalid_store');
869
+ assertionRefs.add(event.assertion_ref);
870
+ preferences.push({
871
+ assertionRef: event.assertion_ref,
872
+ evidenceRef: event.evidence_ref,
873
+ consumer: event.consumer,
874
+ hardFilter: false,
875
+ });
876
+ break;
877
+ }
878
+ }
879
+ return { flows, flowsBySubject, refluxEvents, preferences };
880
+ }
881
+ function buildManifest(options, common, clock) {
882
+ return {
883
+ schema_version: MEMORY_LIFECYCLE_MANIFEST_SCHEMA,
884
+ collector_schema: MEMORY_LIFECYCLE_STORE_SCHEMA,
885
+ dataset_ref: datasetRef(common.hmacKey, assertRawLabel(options.dataset)),
886
+ dataset_key_verifier: datasetKeyVerifier(common.hmacKey),
887
+ source_kind: assertSourceKind(options.sourceKind),
888
+ created_at: assertIsoDate(clock.now()),
889
+ consent_ref: common.consentRef,
890
+ measurement_policy: {
891
+ quantile_method: 'nearest_rank',
892
+ predeclared_external_wait_codes: normalizeWaitCodes(options.waitCodes),
893
+ minimum_pair_count_for_exit: 5,
894
+ target_median_reduction_ratio: 0.5,
895
+ },
896
+ p4: { state: 'closed', triggers: { real_usage: false, multi_user: false } },
897
+ };
898
+ }
899
+ function sameFrozenDataset(existing, next) {
900
+ return existing.dataset_ref === next.dataset_ref
901
+ && existing.dataset_key_verifier === next.dataset_key_verifier
902
+ && existing.source_kind === next.source_kind
903
+ && existing.consent_ref === next.consent_ref
904
+ && canonicalJson(existing.measurement_policy) === canonicalJson(next.measurement_policy);
905
+ }
906
+ function assertDatasetKeyMatches(manifest, hmacKey) {
907
+ if (manifest.dataset_key_verifier !== datasetKeyVerifier(hmacKey))
908
+ fail('dataset_key_mismatch');
909
+ }
910
+ function assertDatasetConsentMatches(manifest, consentRef) {
911
+ if (manifest.consent_ref !== consentRef)
912
+ fail('dataset_consent_mismatch');
913
+ }
914
+ function assertEventsBelongToDataset(manifest, events) {
915
+ if (events.some(event => event.consent_ref !== manifest.consent_ref))
916
+ fail('invalid_store');
917
+ }
918
+ export function initMemoryLifecycleDataset(options, clock = systemClock) {
919
+ const common = prepareCommon(options);
920
+ const next = buildManifest(options, common, clock);
921
+ return withStoreLock(common.storeRoot, true, () => {
922
+ const path = manifestPath(common.storeRoot);
923
+ if (existsSync(path)) {
924
+ const existing = readManifest(common.storeRoot);
925
+ assertDatasetKeyMatches(existing, common.hmacKey);
926
+ if (existing.source_kind !== next.source_kind)
927
+ fail('dataset_source_mismatch');
928
+ if (existing.consent_ref !== next.consent_ref)
929
+ fail('dataset_consent_mismatch');
930
+ if (!sameFrozenDataset(existing, next))
931
+ fail('dataset_already_initialized');
932
+ return {
933
+ schema: 'memory_lifecycle_cli_result.v1',
934
+ ok: true,
935
+ command: 'init',
936
+ status: 'unchanged',
937
+ dataset_ref: existing.dataset_ref,
938
+ source_kind: existing.source_kind,
939
+ wait_code_count: existing.measurement_policy.predeclared_external_wait_codes.length,
940
+ };
941
+ }
942
+ writeManagedFileNoOverwrite(common.storeRoot, path, `${JSON.stringify(next, null, 2)}\n`, 'dataset_already_initialized');
943
+ return {
944
+ schema: 'memory_lifecycle_cli_result.v1',
945
+ ok: true,
946
+ command: 'init',
947
+ status: 'recorded',
948
+ dataset_ref: next.dataset_ref,
949
+ source_kind: next.source_kind,
950
+ wait_code_count: next.measurement_policy.predeclared_external_wait_codes.length,
951
+ };
952
+ });
953
+ }
954
+ function loadLockedStore(common) {
955
+ const manifest = readManifest(common.storeRoot);
956
+ assertDatasetKeyMatches(manifest, common.hmacKey);
957
+ assertDatasetConsentMatches(manifest, common.consentRef);
958
+ const events = readEvents(common.storeRoot, true);
959
+ assertEventsBelongToDataset(manifest, events);
960
+ return { manifest, events, projection: projectLifecycleEvents(events) };
961
+ }
962
+ function completedSubjectFlows(projection, subject) {
963
+ return (projection.flowsBySubject.get(subject) ?? [])
964
+ .filter(flow => flow.completedAt !== undefined)
965
+ .sort(compareFlows);
966
+ }
967
+ function compareFlows(a, b) {
968
+ return parseIsoMs(a.startedAt) - parseIsoMs(b.startedAt) || parseIsoMs(a.completedAt ?? a.startedAt) - parseIsoMs(b.completedAt ?? b.startedAt) || a.flowRef.localeCompare(b.flowRef);
969
+ }
970
+ function activeSubjectFlow(projection, subject) {
971
+ return (projection.flowsBySubject.get(subject) ?? []).find(flow => flow.completedAt === undefined);
972
+ }
973
+ export function startMemoryLifecycleFlow(options, clock = systemClock) {
974
+ const common = prepareCommon(options);
975
+ const subject = subjectRef(common.hmacKey, assertRawLabel(options.subject));
976
+ const flow = flowRef(common.hmacKey, options.subject, assertRawLabel(options.flow));
977
+ const ref = eventRef(common.hmacKey, 'flow_started', [subject, flow]);
978
+ return withStoreLock(common.storeRoot, false, () => {
979
+ const { events, projection } = loadLockedStore(common);
980
+ if (eventExists(events, ref))
981
+ return eventResult('start', 'unchanged', ref);
982
+ if (activeSubjectFlow(projection, subject))
983
+ fail('invalid_transition');
984
+ if (completedSubjectFlows(projection, subject).length >= 2)
985
+ fail('flow_limit_reached');
986
+ if (projection.flows.has(flow))
987
+ fail('invalid_transition');
988
+ const event = {
989
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
990
+ event_ref: ref,
991
+ kind: 'flow_started',
992
+ occurred_at: assertIsoDate(clock.now()),
993
+ consent_ref: common.consentRef,
994
+ subject_ref: subject,
995
+ flow_ref: flow,
996
+ eligible: true,
997
+ };
998
+ appendEvent(common.storeRoot, events, event);
999
+ return eventResult('start', 'recorded', ref);
1000
+ });
1001
+ }
1002
+ export function startExternalWait(options, clock = systemClock) {
1003
+ const common = prepareCommon(options);
1004
+ const subject = subjectRef(common.hmacKey, assertRawLabel(options.subject));
1005
+ const flow = flowRef(common.hmacKey, options.subject, assertRawLabel(options.flow));
1006
+ const wait = waitRef(common.hmacKey, options.subject, options.flow, assertRawLabel(options.wait));
1007
+ const code = assertRawLabel(options.code, 32);
1008
+ if (!WAIT_CODE_PATTERN.test(code))
1009
+ fail('invalid_input');
1010
+ const ref = eventRef(common.hmacKey, 'external_wait_started', [subject, flow, wait, code]);
1011
+ return withStoreLock(common.storeRoot, false, () => {
1012
+ const { manifest, events, projection } = loadLockedStore(common);
1013
+ if (eventExists(events, ref))
1014
+ return eventResult('wait-start', 'unchanged', ref);
1015
+ if (!manifest.measurement_policy.predeclared_external_wait_codes.includes(code))
1016
+ fail('invalid_input');
1017
+ const active = activeSubjectFlow(projection, subject);
1018
+ if (!active || active.flowRef !== flow)
1019
+ fail('invalid_transition');
1020
+ if (active.waits.has(wait))
1021
+ fail('invalid_transition');
1022
+ if ([...active.waits.values()].some(existing => existing.completedAt === undefined))
1023
+ fail('invalid_transition');
1024
+ const occurredAt = assertIsoDate(clock.now());
1025
+ if (parseIsoMs(occurredAt) < parseIsoMs(active.startedAt))
1026
+ fail('invalid_transition');
1027
+ const event = {
1028
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
1029
+ event_ref: ref,
1030
+ kind: 'external_wait_started',
1031
+ occurred_at: occurredAt,
1032
+ consent_ref: common.consentRef,
1033
+ subject_ref: subject,
1034
+ flow_ref: flow,
1035
+ wait_ref: wait,
1036
+ code,
1037
+ };
1038
+ appendEvent(common.storeRoot, events, event);
1039
+ return eventResult('wait-start', 'recorded', ref);
1040
+ });
1041
+ }
1042
+ export function endExternalWait(options, clock = systemClock) {
1043
+ const common = prepareCommon(options);
1044
+ const subject = subjectRef(common.hmacKey, assertRawLabel(options.subject));
1045
+ const flow = flowRef(common.hmacKey, options.subject, assertRawLabel(options.flow));
1046
+ const wait = waitRef(common.hmacKey, options.subject, options.flow, assertRawLabel(options.wait));
1047
+ const ref = eventRef(common.hmacKey, 'external_wait_ended', [subject, flow, wait]);
1048
+ return withStoreLock(common.storeRoot, false, () => {
1049
+ const { events, projection } = loadLockedStore(common);
1050
+ if (eventExists(events, ref))
1051
+ return eventResult('wait-end', 'unchanged', ref);
1052
+ const active = activeSubjectFlow(projection, subject);
1053
+ const interval = active?.waits.get(wait);
1054
+ if (!active || active.flowRef !== flow || !interval || interval.completedAt !== undefined)
1055
+ fail('invalid_transition');
1056
+ const occurredAt = assertIsoDate(clock.now());
1057
+ if (parseIsoMs(occurredAt) <= parseIsoMs(interval.startedAt))
1058
+ fail('invalid_transition');
1059
+ const event = {
1060
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
1061
+ event_ref: ref,
1062
+ kind: 'external_wait_ended',
1063
+ occurred_at: occurredAt,
1064
+ consent_ref: common.consentRef,
1065
+ subject_ref: subject,
1066
+ flow_ref: flow,
1067
+ wait_ref: wait,
1068
+ };
1069
+ appendEvent(common.storeRoot, events, event);
1070
+ return eventResult('wait-end', 'recorded', ref);
1071
+ });
1072
+ }
1073
+ export function completeMemoryLifecycleFlow(options, clock = systemClock) {
1074
+ const common = prepareCommon(options);
1075
+ const subject = subjectRef(common.hmacKey, assertRawLabel(options.subject));
1076
+ const flow = flowRef(common.hmacKey, options.subject, assertRawLabel(options.flow));
1077
+ const ref = eventRef(common.hmacKey, 'flow_completed', [subject, flow]);
1078
+ return withStoreLock(common.storeRoot, false, () => {
1079
+ const { events, projection } = loadLockedStore(common);
1080
+ if (eventExists(events, ref))
1081
+ return eventResult('complete', 'unchanged', ref);
1082
+ const active = activeSubjectFlow(projection, subject);
1083
+ if (!active || active.flowRef !== flow)
1084
+ fail('invalid_transition');
1085
+ if ([...active.waits.values()].some(wait => wait.completedAt === undefined))
1086
+ fail('invalid_transition');
1087
+ const occurredAt = assertIsoDate(clock.now());
1088
+ if (parseIsoMs(occurredAt) <= parseIsoMs(active.startedAt))
1089
+ fail('invalid_transition');
1090
+ const event = {
1091
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
1092
+ event_ref: ref,
1093
+ kind: 'flow_completed',
1094
+ occurred_at: occurredAt,
1095
+ consent_ref: common.consentRef,
1096
+ subject_ref: subject,
1097
+ flow_ref: flow,
1098
+ status: 'completed',
1099
+ };
1100
+ appendEvent(common.storeRoot, events, event);
1101
+ return eventResult('complete', 'recorded', ref);
1102
+ });
1103
+ }
1104
+ export function recordExperienceReflux(options, clock = systemClock) {
1105
+ const common = prepareCommon(options);
1106
+ const kind = assertRefluxKind(options.kind);
1107
+ const experience = experienceRef(common.hmacKey, assertRawLabel(options.experience));
1108
+ const evidence = evidenceRef(common.hmacKey, assertRawLabel(options.evidence, 2048));
1109
+ const ref = eventRef(common.hmacKey, 'experience_reflux_recorded', [kind, experience, evidence]);
1110
+ return withStoreLock(common.storeRoot, false, () => {
1111
+ const { events, projection } = loadLockedStore(common);
1112
+ if (eventExists(events, ref))
1113
+ return eventResult('record-reflux', 'unchanged', ref);
1114
+ if (projection.refluxEvents.some(event => event.experienceRef === experience && event.refluxKind === kind))
1115
+ fail('invalid_transition');
1116
+ if (kind === 'verified_outcome' && !projection.refluxEvents.some(event => event.experienceRef === experience && event.refluxKind === 'recalled')) {
1117
+ fail('invalid_transition');
1118
+ }
1119
+ const event = {
1120
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
1121
+ event_ref: ref,
1122
+ kind: 'experience_reflux_recorded',
1123
+ occurred_at: assertIsoDate(clock.now()),
1124
+ consent_ref: common.consentRef,
1125
+ reflux_kind: kind,
1126
+ experience_ref: experience,
1127
+ evidence_ref: evidence,
1128
+ };
1129
+ appendEvent(common.storeRoot, events, event);
1130
+ return eventResult('record-reflux', 'recorded', ref);
1131
+ });
1132
+ }
1133
+ export function recordPreferenceAssertion(options, clock = systemClock) {
1134
+ const common = prepareCommon(options);
1135
+ const consumer = assertPreferenceConsumer(options.consumer);
1136
+ const assertion = assertionRef(common.hmacKey, assertRawLabel(options.assertion));
1137
+ const evidence = evidenceRef(common.hmacKey, assertRawLabel(options.evidence, 2048));
1138
+ const ref = eventRef(common.hmacKey, 'preference_recorded', [assertion, evidence, consumer]);
1139
+ return withStoreLock(common.storeRoot, false, () => {
1140
+ const { events, projection } = loadLockedStore(common);
1141
+ if (eventExists(events, ref))
1142
+ return eventResult('record-preference', 'unchanged', ref);
1143
+ if (projection.preferences.some(preference => preference.assertionRef === assertion))
1144
+ fail('invalid_transition');
1145
+ const event = {
1146
+ schema_version: MEMORY_LIFECYCLE_EVENT_SCHEMA,
1147
+ event_ref: ref,
1148
+ kind: 'preference_recorded',
1149
+ occurred_at: assertIsoDate(clock.now()),
1150
+ consent_ref: common.consentRef,
1151
+ assertion_ref: assertion,
1152
+ evidence_ref: evidence,
1153
+ consumer,
1154
+ hard_filter: false,
1155
+ };
1156
+ appendEvent(common.storeRoot, events, event);
1157
+ return eventResult('record-preference', 'recorded', ref);
1158
+ });
1159
+ }
1160
+ function eventResult(command, status, ref) {
1161
+ return { schema: 'memory_lifecycle_cli_result.v1', ok: true, command, status, event_ref: ref };
1162
+ }
1163
+ export function buildMemoryValueFixture(manifest, events, hmacKey) {
1164
+ assertDatasetKeyMatches(manifest, hmacKey);
1165
+ assertEventsBelongToDataset(manifest, events);
1166
+ const projection = projectLifecycleEvents(events);
1167
+ const pairs = [];
1168
+ for (const [subject, flows] of [...projection.flowsBySubject.entries()].sort(([a], [b]) => a.localeCompare(b))) {
1169
+ const completed = flows.filter(flow => flow.completedAt !== undefined).sort(compareFlows);
1170
+ if (completed.length < 2)
1171
+ continue;
1172
+ const first = completed[0];
1173
+ const returning = completed[1];
1174
+ if (parseIsoMs(returning.startedAt) <= parseIsoMs(first.completedAt))
1175
+ fail('invalid_store');
1176
+ const pairId = pseudonymousRef(hmacKey, 'pair', [subject, first.flowRef, returning.flowRef]);
1177
+ pairs.push({
1178
+ pair_id: pairId,
1179
+ subject_ref: subject,
1180
+ first: flowToMemoryValue(first, 1),
1181
+ returning: flowToMemoryValue(returning, 2),
1182
+ });
1183
+ }
1184
+ const base = {
1185
+ schema: MEMORY_VALUE_FIXTURE_SCHEMA,
1186
+ evidence_kind: manifest.source_kind,
1187
+ measurement_policy: manifest.measurement_policy,
1188
+ pairs,
1189
+ experience_reflux_events: projection.refluxEvents
1190
+ .slice()
1191
+ .sort((a, b) => a.experienceRef.localeCompare(b.experienceRef) || a.refluxKind.localeCompare(b.refluxKind))
1192
+ .map(event => ({ experience_id: event.experienceRef, kind: event.refluxKind, evidence_ref: event.evidenceRef })),
1193
+ preference_assertions: projection.preferences
1194
+ .slice()
1195
+ .sort((a, b) => a.assertionRef.localeCompare(b.assertionRef))
1196
+ .map(preference => ({
1197
+ assertion_id: preference.assertionRef,
1198
+ evidence_ref: preference.evidenceRef,
1199
+ consumer: preference.consumer,
1200
+ hard_filter: false,
1201
+ })),
1202
+ p4: manifest.p4,
1203
+ };
1204
+ const summaryDigest = memoryValueSummaryDigest(base);
1205
+ return {
1206
+ schema: base.schema,
1207
+ evidence_kind: base.evidence_kind,
1208
+ source_review: sourceReviewFor(manifest.source_kind, summaryDigest),
1209
+ measurement_policy: base.measurement_policy,
1210
+ pairs: base.pairs,
1211
+ experience_reflux_events: base.experience_reflux_events,
1212
+ preference_assertions: base.preference_assertions,
1213
+ p4: base.p4,
1214
+ };
1215
+ }
1216
+ function flowToMemoryValue(flow, eligiblePlanningIndex) {
1217
+ if (!flow.completedAt)
1218
+ fail('invalid_store');
1219
+ const waits = [...flow.waits.values()]
1220
+ .sort((a, b) => parseIsoMs(a.startedAt) - parseIsoMs(b.startedAt) || a.waitRef.localeCompare(b.waitRef))
1221
+ .map(wait => {
1222
+ if (!wait.completedAt)
1223
+ fail('invalid_store');
1224
+ return { code: wait.code, started_at: wait.startedAt, completed_at: wait.completedAt };
1225
+ });
1226
+ for (let index = 1; index < waits.length; index += 1) {
1227
+ if (parseIsoMs(waits[index].started_at) < parseIsoMs(waits[index - 1].completed_at))
1228
+ fail('invalid_store');
1229
+ }
1230
+ return {
1231
+ flow_id: flow.flowRef,
1232
+ eligible_planning_index: eligiblePlanningIndex,
1233
+ eligible: true,
1234
+ status: 'completed',
1235
+ started_at: flow.startedAt,
1236
+ completed_at: flow.completedAt,
1237
+ external_waits: waits,
1238
+ };
1239
+ }
1240
+ function sourceReviewFor(sourceKind, summaryDigest) {
1241
+ if (!SHA256_PATTERN.test(summaryDigest))
1242
+ fail('internal_error');
1243
+ if (sourceKind === 'synthetic_fixture') {
1244
+ return {
1245
+ schema_version: MEMORY_VALUE_SOURCE_REVIEW_SCHEMA,
1246
+ state: 'not_required_for_synthetic',
1247
+ reviewed_at: null,
1248
+ reviewer_ref: null,
1249
+ attestation_ref: null,
1250
+ private_source_digest_sha256: null,
1251
+ reviewed_summary_digest_sha256: null,
1252
+ checks: {
1253
+ raw_private_material_excluded: false,
1254
+ paired_cohort_source_reviewed: false,
1255
+ summary_matches_private_source: false,
1256
+ no_synthetic_or_test_subjects: false,
1257
+ },
1258
+ };
1259
+ }
1260
+ return {
1261
+ schema_version: MEMORY_VALUE_SOURCE_REVIEW_SCHEMA,
1262
+ state: 'candidate',
1263
+ reviewed_at: null,
1264
+ reviewer_ref: null,
1265
+ attestation_ref: null,
1266
+ private_source_digest_sha256: null,
1267
+ reviewed_summary_digest_sha256: summaryDigest,
1268
+ checks: {
1269
+ raw_private_material_excluded: true,
1270
+ paired_cohort_source_reviewed: false,
1271
+ summary_matches_private_source: false,
1272
+ no_synthetic_or_test_subjects: false,
1273
+ },
1274
+ };
1275
+ }
1276
+ export function exportMemoryLifecycleFixture(options) {
1277
+ const common = prepareCommon(options);
1278
+ return withStoreLock(common.storeRoot, false, () => {
1279
+ const { manifest, events } = loadLockedStore(common);
1280
+ const fixture = buildMemoryValueFixture(manifest, events, common.hmacKey);
1281
+ if (options.out) {
1282
+ const outputPath = resolve(options.out);
1283
+ writePrivateFileNoOverwrite(outputPath, `${JSON.stringify(fixture, null, 2)}\n`);
1284
+ }
1285
+ return {
1286
+ fixture,
1287
+ result: {
1288
+ schema: 'memory_lifecycle_cli_result.v1',
1289
+ ok: true,
1290
+ command: 'export',
1291
+ status: 'recorded',
1292
+ dataset_ref: manifest.dataset_ref,
1293
+ source_kind: manifest.source_kind,
1294
+ pair_count: fixture.pairs.length,
1295
+ output_written: Boolean(options.out),
1296
+ },
1297
+ };
1298
+ });
1299
+ }
1300
+ export function readStoreForTests(stateRoot) {
1301
+ const safeRoot = assertSafeStateRoot(stateRoot);
1302
+ const storeRoot = join(safeRoot, 'gotry-state', 'memory-lifecycle');
1303
+ return { manifest: readManifest(storeRoot), events: readEvents(storeRoot) };
1304
+ }
1305
+ export function storeRootForStateRoot(stateRoot) {
1306
+ return join(assertSafeStateRoot(stateRoot), 'gotry-state', 'memory-lifecycle');
1307
+ }
1308
+ export function fileMode(path) {
1309
+ return statSync(path).mode & 0o777;
1310
+ }