@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.
- package/README.md +102 -194
- package/README.zh-CN.md +115 -210
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-bootstrap.js +466 -35
- package/bin/gotry-inner.js +255 -36
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +2 -2
- package/client/client.js +174 -0
- package/cordis.gotry-patch.yml +70 -93
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +63 -148
- package/dist/capabilities/anything.js +89 -75
- package/dist/capabilities/artifacts.js +123 -104
- package/dist/capabilities/channel-health.js +65 -45
- package/dist/capabilities/channel-registry.js +108 -87
- package/dist/capabilities/doctor.js +357 -235
- package/dist/capabilities/effect.js +223 -309
- package/dist/capabilities/fact-log.js +28 -14
- package/dist/capabilities/flyai.js +245 -156
- package/dist/capabilities/ground-transfer.js +509 -0
- package/dist/capabilities/hbcli.js +144 -148
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/model-override.js +30 -10
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/resilience.js +50 -32
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
- package/dist/capabilities/session/adapters/dida-portal.js +206 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +169 -112
- package/dist/capabilities/session/benchmark.js +85 -149
- package/dist/capabilities/session/extension-bridge.js +320 -259
- package/dist/capabilities/session/extension-channel.js +57 -76
- package/dist/capabilities/session/extension-distribution.js +115 -119
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +55 -33
- package/dist/capabilities/session/health-watch.js +146 -154
- package/dist/capabilities/session/read-guard.js +55 -47
- package/dist/capabilities/session/static-flight-golden.js +49 -36
- package/dist/capabilities/session/transport.js +54 -61
- package/dist/capabilities/session/wizard.js +63 -93
- package/dist/capabilities/session-consent.js +143 -47
- package/dist/capabilities/session-login.js +95 -126
- package/dist/capabilities/session-search.js +391 -253
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +87 -73
- package/dist/capabilities/weather.js +163 -235
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
- package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
- package/dist/scripts/anything-tests.js +58 -62
- package/dist/scripts/artifact-client-contract-tests.js +69 -0
- package/dist/scripts/artifacts-capability-tests.js +235 -0
- package/dist/scripts/async-collect.js +26 -15
- package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
- package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
- package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
- package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
- package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
- package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
- package/dist/scripts/booking-executor-tests.js +80 -0
- package/dist/scripts/booking-recovery-chain-tests.js +182 -0
- package/dist/scripts/booking-saga-tests.js +61 -123
- package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
- package/dist/scripts/bootstrap-tests.js +1383 -128
- package/dist/scripts/build-changelog.js +89 -130
- package/dist/scripts/build-metrics-report.js +135 -143
- package/dist/scripts/changelog-tests.js +35 -82
- package/dist/scripts/channel-probe-tests.js +44 -116
- package/dist/scripts/channel-probe.js +84 -167
- package/dist/scripts/channel-registry-tests.js +97 -185
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +428 -120
- package/dist/scripts/dsh-artifact-e2e.js +150 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
- package/dist/scripts/dsh-runtime-closure.js +75 -63
- package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +165 -552
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +69 -242
- package/dist/scripts/evaluation-contract-tests.js +193 -499
- package/dist/scripts/extension-distribution-cli.js +19 -15
- package/dist/scripts/extension-distribution-tests.js +98 -211
- package/dist/scripts/extension-tests.js +366 -389
- package/dist/scripts/fact-gate-tests.js +725 -263
- package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
- package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
- package/dist/scripts/flight-malformed-tests.js +159 -0
- package/dist/scripts/flyai-tests.js +325 -68
- package/dist/scripts/g5-guard-tests.js +131 -0
- package/dist/scripts/g5-guard.js +189 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +71 -90
- package/dist/scripts/hbcli-live-optin-tests.js +96 -0
- package/dist/scripts/hbcli-release-contract-tests.js +122 -0
- package/dist/scripts/hbcli-tests.js +41 -115
- package/dist/scripts/health-watch-cli.js +42 -28
- package/dist/scripts/hotel-date-gate-tests.js +496 -0
- package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
- package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
- package/dist/scripts/i18n-tests.js +16 -9
- package/dist/scripts/incident-tests.js +281 -87
- package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
- package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
- package/dist/scripts/issue-271-liveness-tests.js +547 -0
- package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
- package/dist/scripts/issue-289-model-retry-tests.js +441 -0
- package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
- package/dist/scripts/issue-338-home-city-e2e.js +577 -0
- package/dist/scripts/issue343-real-entry-e2e.js +160 -0
- package/dist/scripts/journey-tests.js +22 -27
- package/dist/scripts/kernel-manifest-evidence.js +109 -0
- package/dist/scripts/kernel-manifest-gate.js +126 -0
- package/dist/scripts/kernel-manifest-tests.js +212 -0
- package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
- package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
- package/dist/scripts/kernel-manifest.js +369 -0
- package/dist/scripts/ledger-repair-apply-tests.js +316 -0
- package/dist/scripts/ledger-repair-plan-tests.js +375 -0
- package/dist/scripts/ledger-tests.js +325 -340
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +18 -52
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +24 -12
- package/dist/scripts/memory-value-report.js +474 -222
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +62 -199
- package/dist/scripts/model-override-e2e.js +66 -105
- package/dist/scripts/nightly-evidence-tests.js +64 -89
- package/dist/scripts/nightly-evidence.js +108 -111
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/onboarding-tests.js +63 -88
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/persona-surface-guard-tests.js +37 -12
- package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +126 -227
- package/dist/scripts/price-drift-watch.js +209 -253
- package/dist/scripts/probe-poi-tests.js +52 -45
- package/dist/scripts/product-metrics.js +187 -273
- package/dist/scripts/publish-preverify.js +47 -41
- package/dist/scripts/realtime-pricing-tests.js +37 -76
- package/dist/scripts/replay-async.js +20 -18
- package/dist/scripts/replay-real.js +16 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +16 -12
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +66 -147
- package/dist/scripts/session-dida-live-e2e.js +151 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-login.js +14 -13
- package/dist/scripts/session-tests.js +545 -583
- package/dist/scripts/sf-live-benchmark.js +92 -110
- package/dist/scripts/sf-live-cli-tests.js +2 -9
- package/dist/scripts/sf-soft-score-tests.js +19 -60
- package/dist/scripts/sf-summary-tests.js +149 -0
- package/dist/scripts/sf-summary.js +466 -84
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +11 -10
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +12 -20
- package/dist/scripts/smoke.js +541 -422
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +549 -212
- package/dist/scripts/static-golden-tests.js +46 -180
- package/dist/scripts/time-eval-tests.js +210 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +46 -93
- package/dist/scripts/turn-handoff-collect.js +71 -99
- package/dist/scripts/turn-policy-tests.js +28 -29
- package/dist/scripts/typed-contract-canary.js +77 -162
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +23 -19
- package/dist/scripts/visa-policy-tests.js +31 -44
- package/dist/scripts/weather-tests.js +121 -543
- package/dist/scripts/wish-channel-gate-tests.js +29 -86
- package/dist/scripts/write-gate-tests.js +522 -0
- package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
- package/dist/scripts/z3-lifecycle-tests.js +170 -0
- package/dist/scripts/z3-race-repeat-tests.js +39 -0
- package/dist/scripts/z3-race-tests.js +18 -12
- package/dist/src/artifact-gate.js +652 -221
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +55 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +194 -0
- package/dist/src/benchmark-agent-conformance.js +296 -262
- package/dist/src/benchmark-environment-bridge.js +298 -413
- package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
- package/dist/src/benchmark-tool-isolation.js +94 -67
- package/dist/src/bookable-facts.js +299 -174
- package/dist/src/booking-saga.js +64 -120
- package/dist/src/booking-surface/availability-policy.js +443 -576
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/contracts.js +24 -80
- package/dist/src/booking-surface/dsh-planner.js +510 -281
- package/dist/src/booking-surface/dsh-plugin.js +17 -1
- package/dist/src/booking-surface/error-codes.js +82 -10
- package/dist/src/booking-surface/index.js +0 -3
- package/dist/src/booking-surface/profile.js +10 -54
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +765 -891
- package/dist/src/booking-surface/server.js +200 -251
- package/dist/src/booking-surface/startup.js +58 -54
- package/dist/src/booking-surface/validation.js +150 -218
- package/dist/src/bridge.js +20 -13
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +82 -120
- package/dist/src/engine.js +253 -239
- package/dist/src/evaluation-cadence.js +72 -209
- package/dist/src/evaluation-contracts.js +220 -843
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/gotry-backend.js +70 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +18 -7
- package/dist/src/index.js +1425 -1629
- package/dist/src/journey.js +132 -81
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +257 -166
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +30 -14
- package/dist/src/memory-lifecycle.js +1310 -0
- package/dist/src/memory-utility.js +27 -40
- package/dist/src/mock-llm.js +36 -61
- package/dist/src/model.js +81 -36
- package/dist/src/realtime-pricing.js +63 -72
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +310 -471
- package/dist/src/subagent-job-id-guard.js +58 -0
- package/dist/src/time-anchor.js +231 -33
- package/dist/src/tool-packet.js +20 -8
- package/dist/src/travel-slots.js +58 -35
- package/dist/src/travel-timeline.js +44 -63
- package/dist/src/turn-deadline.js +121 -127
- package/dist/src/turn-policy.js +61 -89
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +504 -381
- package/dist/src/wish-pool.js +39 -14
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +273 -7
- package/extension/README.md +4 -3
- package/extension/background.js +125 -14
- package/extension/content-bridge.js +23 -1
- package/extension/content-main.js +15 -4
- package/extension/manifest.json +16 -8
- package/package.json +251 -220
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +1 -1
- package/ts/capabilities/artifacts.ts +46 -9
- package/ts/capabilities/flyai.ts +156 -37
- package/ts/capabilities/hbcli.ts +1 -1
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
- package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
- package/ts/capabilities/session/extension-bridge.ts +226 -101
- package/ts/capabilities/session/extension-channel.ts +22 -9
- package/ts/capabilities/session/wizard.ts +2 -2
- package/ts/capabilities/session-consent.ts +98 -14
- package/ts/capabilities/session-login.ts +1 -0
- package/ts/capabilities/session-search.ts +315 -31
- package/ts/dsh-runtime/vendor/README.md +8 -4
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
- package/ts/package.json +236 -16
- package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
- package/ts/scripts/state-cli.ts +418 -51
- package/ts/src/artifact-gate.ts +561 -15
- package/ts/src/benchmark-environment-bridge.ts +37 -20
- package/ts/src/bookable-facts.ts +183 -7
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/dsh-planner.ts +240 -32
- package/ts/src/booking-surface/dsh-plugin.js +17 -1
- package/ts/src/booking-surface/error-codes.ts +75 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/server.ts +84 -51
- package/ts/src/booking-surface/validation.ts +2 -0
- package/ts/src/contracts.ts +12 -2
- package/ts/src/dsh-llm.ts +4 -8
- package/ts/src/index.ts +609 -90
- package/ts/src/loop.ts +116 -7
- package/ts/src/memory-capture.ts +227 -3
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/state-ledger.ts +47 -29
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +217 -13
- package/ts/src/unified.ts +316 -54
|
@@ -1,108 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M4 memory value evidence scorer (GitHub Issue #20/#223).
|
|
3
|
+
*
|
|
4
|
+
* The scorer is deliberately read-only. It accepts a public fixture or a
|
|
5
|
+
* private observed cohort manifest and emits one deterministic JSON report.
|
|
6
|
+
* Synthetic fixtures can prove the contract and calculations, but can never
|
|
7
|
+
* satisfy M4 Exit. Observed-private inputs only become exit-eligible when a
|
|
8
|
+
* human source-review attestation contract is present; the scorer validates
|
|
9
|
+
* that contract shape but does not inspect private raw material.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* npx tsx scripts/memory-value-report.ts data/memory-value-fixture.json
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
1
15
|
import { readFileSync } from 'node:fs';
|
|
2
16
|
import { resolve } from 'node:path';
|
|
3
17
|
import { pathToFileURL } from 'node:url';
|
|
18
|
+
const M4_ACCEPTANCE = {
|
|
19
|
+
minimumPairCountForExit: 5,
|
|
20
|
+
targetMedianReductionRatio: 0.5,
|
|
21
|
+
};
|
|
22
|
+
const HMAC_REF = /^hmac-sha256:[0-9a-f]{64}$/;
|
|
23
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
24
|
+
const ISO_UTC_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
4
25
|
function isObject(value) {
|
|
5
26
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
27
|
}
|
|
7
28
|
function nonEmptyString(value) {
|
|
8
29
|
return typeof value === 'string' && value.trim().length > 0;
|
|
9
30
|
}
|
|
10
|
-
function finiteNumber(value) {
|
|
11
|
-
return typeof value === 'number' && Number.isFinite(value);
|
|
12
|
-
}
|
|
13
31
|
function round(value) {
|
|
14
32
|
return Math.round((value + Number.EPSILON) * 1_000_000) / 1_000_000;
|
|
15
33
|
}
|
|
16
|
-
function
|
|
17
|
-
if (values.length === 0)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
].sort((a, b)=>a - b);
|
|
34
|
+
function nearestRankRaw(values, percentile) {
|
|
35
|
+
if (values.length === 0)
|
|
36
|
+
return 0;
|
|
37
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
21
38
|
const index = Math.max(0, Math.ceil(percentile * sorted.length) - 1);
|
|
22
|
-
return
|
|
39
|
+
return sorted[index];
|
|
40
|
+
}
|
|
41
|
+
function nearestRank(values, percentile) {
|
|
42
|
+
return round(nearestRankRaw(values, percentile));
|
|
43
|
+
}
|
|
44
|
+
function canonical(value) {
|
|
45
|
+
if (Array.isArray(value))
|
|
46
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
47
|
+
if (isObject(value)) {
|
|
48
|
+
return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
|
|
49
|
+
}
|
|
50
|
+
return JSON.stringify(value);
|
|
51
|
+
}
|
|
52
|
+
export function memoryValueSummaryDigest(input) {
|
|
53
|
+
const payload = isObject(input)
|
|
54
|
+
? Object.fromEntries(Object.entries(input).filter(([key]) => key !== 'source_review'))
|
|
55
|
+
: input;
|
|
56
|
+
return createHash('sha256').update(canonical(payload)).digest('hex');
|
|
57
|
+
}
|
|
58
|
+
function exactKeys(value, label, keys, errors) {
|
|
59
|
+
const expected = new Set(keys);
|
|
60
|
+
if (Object.keys(value).some(key => !expected.has(key))) {
|
|
61
|
+
errors.push(`${label} contains undeclared field(s)`);
|
|
62
|
+
}
|
|
63
|
+
if (keys.some(key => !(key in value))) {
|
|
64
|
+
errors.push(`${label} is missing required field(s)`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function object(value, label, errors) {
|
|
68
|
+
if (!isObject(value)) {
|
|
69
|
+
errors.push(`${label} must be an object`);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function array(value, label, errors, options = {}) {
|
|
75
|
+
if (!Array.isArray(value)) {
|
|
76
|
+
errors.push(`${label} must be an array`);
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
if (options.nonEmpty && value.length === 0)
|
|
80
|
+
errors.push(`${label} must be a non-empty array`);
|
|
81
|
+
return value;
|
|
23
82
|
}
|
|
24
|
-
function
|
|
83
|
+
function string(value, label, errors) {
|
|
25
84
|
if (!nonEmptyString(value)) {
|
|
26
|
-
errors.push(`${
|
|
85
|
+
errors.push(`${label} must be a non-empty string`);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function nullableString(value, label, errors) {
|
|
91
|
+
if (value === null)
|
|
92
|
+
return null;
|
|
93
|
+
return string(value, label, errors);
|
|
94
|
+
}
|
|
95
|
+
function boolean(value, label, errors) {
|
|
96
|
+
if (typeof value !== 'boolean') {
|
|
97
|
+
errors.push(`${label} must be boolean`);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
function finiteNumber(value, label, errors) {
|
|
103
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
104
|
+
errors.push(`${label} must be a finite number`);
|
|
27
105
|
return null;
|
|
28
106
|
}
|
|
29
|
-
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
function integer(value, label, errors) {
|
|
110
|
+
const result = finiteNumber(value, label, errors);
|
|
111
|
+
if (result === null)
|
|
112
|
+
return null;
|
|
113
|
+
if (!Number.isInteger(result)) {
|
|
114
|
+
errors.push(`${label} must be an integer`);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function literal(value, label, allowed, errors) {
|
|
120
|
+
const result = string(value, label, errors);
|
|
121
|
+
if (result === null)
|
|
122
|
+
return null;
|
|
123
|
+
if (!allowed.includes(result)) {
|
|
124
|
+
errors.push(`${label} must be one of the declared enum values`);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
function hmacRef(value, label, errors) {
|
|
130
|
+
const result = string(value, label, errors);
|
|
131
|
+
if (result === null)
|
|
132
|
+
return null;
|
|
133
|
+
if (!HMAC_REF.test(result)) {
|
|
134
|
+
errors.push(`${label} must be an hmac-sha256 pseudonymous reference`);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
function nullableHmacRef(value, label, errors) {
|
|
140
|
+
if (value === null)
|
|
141
|
+
return null;
|
|
142
|
+
return hmacRef(value, label, errors);
|
|
143
|
+
}
|
|
144
|
+
function nullableSha256(value, label, errors) {
|
|
145
|
+
if (value === null)
|
|
146
|
+
return null;
|
|
147
|
+
const result = string(value, label, errors);
|
|
148
|
+
if (result === null)
|
|
149
|
+
return null;
|
|
150
|
+
if (!SHA256.test(result)) {
|
|
151
|
+
errors.push(`${label} must be a lowercase SHA-256 digest`);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
function parseTimestamp(value, label, errors) {
|
|
157
|
+
const result = string(value, label, errors);
|
|
158
|
+
if (result === null)
|
|
159
|
+
return null;
|
|
160
|
+
if (!ISO_UTC_TIMESTAMP.test(result)) {
|
|
161
|
+
errors.push(`${label} must be a strict UTC ISO timestamp`);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const parsed = Date.parse(result);
|
|
30
165
|
if (!Number.isFinite(parsed)) {
|
|
31
|
-
errors.push(`${
|
|
166
|
+
errors.push(`${label} must be a valid UTC ISO timestamp`);
|
|
32
167
|
return null;
|
|
33
168
|
}
|
|
34
|
-
|
|
169
|
+
const canonical = new Date(parsed).toISOString();
|
|
170
|
+
const normalized = result.includes('.') ? result : result.replace('Z', '.000Z');
|
|
171
|
+
if (canonical !== normalized) {
|
|
172
|
+
errors.push(`${label} must be a valid UTC ISO timestamp`);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
return { text: result, ms: parsed };
|
|
35
176
|
}
|
|
36
|
-
function
|
|
37
|
-
if (
|
|
38
|
-
|
|
177
|
+
function nullableTimestamp(value, label, errors) {
|
|
178
|
+
if (value === null)
|
|
179
|
+
return null;
|
|
180
|
+
return parseTimestamp(value, label, errors);
|
|
181
|
+
}
|
|
182
|
+
function assertUnique(value, seen, label, errors) {
|
|
183
|
+
if (value === null)
|
|
184
|
+
return;
|
|
185
|
+
if (seen.has(value))
|
|
186
|
+
errors.push(`${label} must be unique`);
|
|
187
|
+
else
|
|
188
|
+
seen.add(value);
|
|
189
|
+
}
|
|
190
|
+
function waitCode(value, label, allowedWaitCodes, errors) {
|
|
191
|
+
const code = string(value, label, errors);
|
|
192
|
+
if (code === null)
|
|
193
|
+
return null;
|
|
194
|
+
if (!/^[a-z][a-z0-9_]{0,31}$/.test(code) || !allowedWaitCodes.has(code)) {
|
|
195
|
+
errors.push(`${label} must be predeclared by measurement_policy`);
|
|
39
196
|
return null;
|
|
40
197
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
errors.push(`${path}.eligible_planning_index must be ${expectedEligibleIndex}`);
|
|
47
|
-
}
|
|
48
|
-
const startedAtMs = parseTimestamp(value.started_at, `${path}.started_at`, errors);
|
|
49
|
-
const completedAtMs = parseTimestamp(value.completed_at, `${path}.completed_at`, errors);
|
|
50
|
-
if (startedAtMs === null || completedAtMs === null) return null;
|
|
51
|
-
if (completedAtMs <= startedAtMs) {
|
|
52
|
-
errors.push(`${path}.completed_at must be after started_at`);
|
|
198
|
+
return code;
|
|
199
|
+
}
|
|
200
|
+
function scoreFlow(value, path, expectedEligibleIndex, allowedWaitCodes, errors) {
|
|
201
|
+
const raw = object(value, path, errors);
|
|
202
|
+
if (raw === null)
|
|
53
203
|
return null;
|
|
204
|
+
exactKeys(raw, path, ['flow_id', 'eligible_planning_index', 'eligible', 'status', 'started_at', 'completed_at', 'external_waits'], errors);
|
|
205
|
+
const flowId = hmacRef(raw.flow_id, `${path}.flow_id`, errors);
|
|
206
|
+
const eligible = boolean(raw.eligible, `${path}.eligible`, errors);
|
|
207
|
+
if (eligible !== true)
|
|
208
|
+
errors.push(`${path}.eligible must be true`);
|
|
209
|
+
literal(raw.status, `${path}.status`, ['completed'], errors);
|
|
210
|
+
const eligiblePlanningIndex = integer(raw.eligible_planning_index, `${path}.eligible_planning_index`, errors);
|
|
211
|
+
if (eligiblePlanningIndex !== null && eligiblePlanningIndex !== expectedEligibleIndex) {
|
|
212
|
+
errors.push(`${path}.eligible_planning_index must match the paired flow position`);
|
|
54
213
|
}
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
214
|
+
const startedAt = parseTimestamp(raw.started_at, `${path}.started_at`, errors);
|
|
215
|
+
const completedAt = parseTimestamp(raw.completed_at, `${path}.completed_at`, errors);
|
|
216
|
+
if (startedAt === null || completedAt === null)
|
|
217
|
+
return null;
|
|
218
|
+
if (completedAt.ms <= startedAt.ms) {
|
|
219
|
+
errors.push(`${path}.completed_at must be after started_at`);
|
|
58
220
|
return null;
|
|
59
221
|
}
|
|
60
222
|
const waits = [];
|
|
61
|
-
for (const [index, rawWait] of
|
|
223
|
+
for (const [index, rawWait] of array(raw.external_waits, `${path}.external_waits`, errors).entries()) {
|
|
62
224
|
const waitPath = `${path}.external_waits[${index}]`;
|
|
63
|
-
|
|
64
|
-
|
|
225
|
+
const wait = object(rawWait, waitPath, errors);
|
|
226
|
+
if (wait === null)
|
|
65
227
|
continue;
|
|
66
|
-
|
|
67
|
-
const code =
|
|
68
|
-
|
|
69
|
-
|
|
228
|
+
exactKeys(wait, waitPath, ['code', 'started_at', 'completed_at'], errors);
|
|
229
|
+
const code = waitCode(wait.code, `${waitPath}.code`, allowedWaitCodes, errors);
|
|
230
|
+
const waitStartedAt = parseTimestamp(wait.started_at, `${waitPath}.started_at`, errors);
|
|
231
|
+
const waitCompletedAt = parseTimestamp(wait.completed_at, `${waitPath}.completed_at`, errors);
|
|
232
|
+
if (code === null || waitStartedAt === null || waitCompletedAt === null)
|
|
70
233
|
continue;
|
|
71
|
-
|
|
72
|
-
const waitStartedAtMs = parseTimestamp(rawWait.started_at, `${waitPath}.started_at`, errors);
|
|
73
|
-
const waitCompletedAtMs = parseTimestamp(rawWait.completed_at, `${waitPath}.completed_at`, errors);
|
|
74
|
-
if (waitStartedAtMs === null || waitCompletedAtMs === null) continue;
|
|
75
|
-
if (waitCompletedAtMs <= waitStartedAtMs) {
|
|
234
|
+
if (waitCompletedAt.ms <= waitStartedAt.ms) {
|
|
76
235
|
errors.push(`${waitPath}.completed_at must be after started_at`);
|
|
77
236
|
continue;
|
|
78
237
|
}
|
|
79
|
-
if (
|
|
238
|
+
if (waitStartedAt.ms < startedAt.ms || waitCompletedAt.ms > completedAt.ms) {
|
|
80
239
|
errors.push(`${waitPath} must stay inside the planning flow`);
|
|
81
240
|
continue;
|
|
82
241
|
}
|
|
83
|
-
waits.push({
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
waits.sort((a, b)=>a.startedAtMs - b.startedAtMs);
|
|
90
|
-
for(let index = 1; index < waits.length; index += 1){
|
|
242
|
+
waits.push({ code, startedAtMs: waitStartedAt.ms, completedAtMs: waitCompletedAt.ms });
|
|
243
|
+
}
|
|
244
|
+
waits.sort((a, b) => a.startedAtMs - b.startedAtMs);
|
|
245
|
+
for (let index = 1; index < waits.length; index += 1) {
|
|
91
246
|
if (waits[index].startedAtMs < waits[index - 1].completedAtMs) {
|
|
92
247
|
errors.push(`${path}.external_waits must not overlap`);
|
|
93
248
|
}
|
|
94
249
|
}
|
|
95
|
-
const externalWaitMs = waits.reduce((total, wait)=>total + wait.completedAtMs - wait.startedAtMs, 0);
|
|
96
|
-
const activeSeconds = (
|
|
97
|
-
if (activeSeconds <= 0)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
flowId,
|
|
102
|
-
startedAtMs
|
|
103
|
-
} : null;
|
|
250
|
+
const externalWaitMs = waits.reduce((total, wait) => total + wait.completedAtMs - wait.startedAtMs, 0);
|
|
251
|
+
const activeSeconds = (completedAt.ms - startedAt.ms - externalWaitMs) / 1_000;
|
|
252
|
+
if (activeSeconds <= 0)
|
|
253
|
+
errors.push(`${path} must have positive active planning duration`);
|
|
254
|
+
return flowId === null
|
|
255
|
+
? null
|
|
256
|
+
: { activeSeconds, completedAtMs: completedAt.ms, flowId, startedAtMs: startedAt.ms };
|
|
104
257
|
}
|
|
105
|
-
function
|
|
258
|
+
function sourceReviewReport(evidenceKind, state, contractMet, fields = {}) {
|
|
259
|
+
const shared = {
|
|
260
|
+
reviewed_at: fields.reviewedAt ?? null,
|
|
261
|
+
reviewer_ref: fields.reviewerRef ?? null,
|
|
262
|
+
attestation_ref: fields.attestationRef ?? null,
|
|
263
|
+
private_source_digest_sha256: fields.privateSourceDigest ?? null,
|
|
264
|
+
reviewed_summary_digest_sha256: fields.reviewedSummaryDigest ?? null,
|
|
265
|
+
current_summary_digest_sha256: fields.currentSummaryDigest ?? null,
|
|
266
|
+
};
|
|
267
|
+
if (evidenceKind === 'synthetic_fixture') {
|
|
268
|
+
return {
|
|
269
|
+
required_for_exit: false,
|
|
270
|
+
state,
|
|
271
|
+
contract_met: false,
|
|
272
|
+
provenance_verified_by_scorer: false,
|
|
273
|
+
...shared,
|
|
274
|
+
reason: 'synthetic_fixture validates formulas only and is never business evidence',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (contractMet) {
|
|
278
|
+
return {
|
|
279
|
+
required_for_exit: true,
|
|
280
|
+
state,
|
|
281
|
+
contract_met: true,
|
|
282
|
+
provenance_verified_by_scorer: false,
|
|
283
|
+
...shared,
|
|
284
|
+
reason: 'manual source-review attestation contract is present for this scoring payload; raw private source review remains a human responsibility',
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
required_for_exit: evidenceKind === 'observed_private',
|
|
289
|
+
state,
|
|
290
|
+
contract_met: false,
|
|
291
|
+
provenance_verified_by_scorer: false,
|
|
292
|
+
...shared,
|
|
293
|
+
reason: evidenceKind === 'observed_private'
|
|
294
|
+
? 'observed_private remains a candidate until the manual source-review attestation contract matches this scoring payload'
|
|
295
|
+
: 'source kind is not exit-eligible',
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function scoreSourceReview(value, evidenceKind, currentSummaryDigest, errors) {
|
|
299
|
+
const raw = object(value, 'source_review', errors);
|
|
300
|
+
if (raw === null) {
|
|
301
|
+
return { report: sourceReviewReport(evidenceKind, 'unknown', false, { currentSummaryDigest }) };
|
|
302
|
+
}
|
|
303
|
+
exactKeys(raw, 'source_review', [
|
|
304
|
+
'schema_version',
|
|
305
|
+
'state',
|
|
306
|
+
'reviewed_at',
|
|
307
|
+
'reviewer_ref',
|
|
308
|
+
'attestation_ref',
|
|
309
|
+
'private_source_digest_sha256',
|
|
310
|
+
'reviewed_summary_digest_sha256',
|
|
311
|
+
'checks',
|
|
312
|
+
], errors);
|
|
313
|
+
literal(raw.schema_version, 'source_review.schema_version', ['memory_value_source_review.v1'], errors);
|
|
314
|
+
const state = literal(raw.state, 'source_review.state', ['not_required_for_synthetic', 'candidate', 'manual_attested'], errors) ?? 'unknown';
|
|
315
|
+
const reviewedAt = nullableTimestamp(raw.reviewed_at, 'source_review.reviewed_at', errors);
|
|
316
|
+
const reviewerRef = nullableHmacRef(raw.reviewer_ref, 'source_review.reviewer_ref', errors);
|
|
317
|
+
const attestationRef = nullableHmacRef(raw.attestation_ref, 'source_review.attestation_ref', errors);
|
|
318
|
+
const privateSourceDigest = nullableSha256(raw.private_source_digest_sha256, 'source_review.private_source_digest_sha256', errors);
|
|
319
|
+
const reviewedSummaryDigest = nullableSha256(raw.reviewed_summary_digest_sha256, 'source_review.reviewed_summary_digest_sha256', errors);
|
|
320
|
+
if (reviewedSummaryDigest !== null && reviewedSummaryDigest !== currentSummaryDigest) {
|
|
321
|
+
errors.push('source_review.reviewed_summary_digest_sha256 must match the current scoring payload digest');
|
|
322
|
+
}
|
|
323
|
+
const checks = object(raw.checks, 'source_review.checks', errors);
|
|
324
|
+
const parsedChecks = {};
|
|
325
|
+
if (checks !== null) {
|
|
326
|
+
exactKeys(checks, 'source_review.checks', [
|
|
327
|
+
'raw_private_material_excluded',
|
|
328
|
+
'paired_cohort_source_reviewed',
|
|
329
|
+
'summary_matches_private_source',
|
|
330
|
+
'no_synthetic_or_test_subjects',
|
|
331
|
+
], errors);
|
|
332
|
+
for (const key of [
|
|
333
|
+
'raw_private_material_excluded',
|
|
334
|
+
'paired_cohort_source_reviewed',
|
|
335
|
+
'summary_matches_private_source',
|
|
336
|
+
'no_synthetic_or_test_subjects',
|
|
337
|
+
]) {
|
|
338
|
+
const parsed = boolean(checks[key], `source_review.checks.${key}`, errors);
|
|
339
|
+
if (parsed !== null)
|
|
340
|
+
parsedChecks[key] = parsed;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (evidenceKind === 'synthetic_fixture' && state !== 'not_required_for_synthetic') {
|
|
344
|
+
errors.push('source_review.state must match synthetic evidence');
|
|
345
|
+
}
|
|
346
|
+
if (evidenceKind === 'observed_private' && state === 'not_required_for_synthetic') {
|
|
347
|
+
errors.push('source_review.state must require observed private review');
|
|
348
|
+
}
|
|
349
|
+
const allReviewChecksMet = [
|
|
350
|
+
parsedChecks.raw_private_material_excluded,
|
|
351
|
+
parsedChecks.paired_cohort_source_reviewed,
|
|
352
|
+
parsedChecks.summary_matches_private_source,
|
|
353
|
+
parsedChecks.no_synthetic_or_test_subjects,
|
|
354
|
+
].every(Boolean);
|
|
355
|
+
const contractMet = evidenceKind === 'observed_private'
|
|
356
|
+
&& state === 'manual_attested'
|
|
357
|
+
&& reviewedAt !== null
|
|
358
|
+
&& reviewerRef !== null
|
|
359
|
+
&& attestationRef !== null
|
|
360
|
+
&& privateSourceDigest !== null
|
|
361
|
+
&& reviewedSummaryDigest === currentSummaryDigest
|
|
362
|
+
&& allReviewChecksMet;
|
|
363
|
+
return {
|
|
364
|
+
report: sourceReviewReport(evidenceKind, state, contractMet, {
|
|
365
|
+
reviewedAt: reviewedAt?.text ?? null,
|
|
366
|
+
reviewerRef,
|
|
367
|
+
attestationRef,
|
|
368
|
+
privateSourceDigest,
|
|
369
|
+
reviewedSummaryDigest,
|
|
370
|
+
currentSummaryDigest,
|
|
371
|
+
}),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function invalidReport(errors, schema = '', evidenceKind = '', sourceReview = sourceReviewReport(evidenceKind || null, 'unknown', false)) {
|
|
106
375
|
return {
|
|
107
376
|
schema: 'memory_value_report.v1',
|
|
108
377
|
contract_valid: false,
|
|
@@ -111,91 +380,75 @@ function invalidReport(errors, schema = '', evidenceKind = '') {
|
|
|
111
380
|
fixture_schema: schema,
|
|
112
381
|
evidence_kind: evidenceKind,
|
|
113
382
|
quantile_method: 'nearest_rank',
|
|
114
|
-
active_duration_rule: 'wall_clock_minus_non_overlapping_predeclared_external_waits'
|
|
383
|
+
active_duration_rule: 'wall_clock_minus_non_overlapping_predeclared_external_waits',
|
|
384
|
+
id_format: 'hmac-sha256:<64lowerhex>',
|
|
385
|
+
source_review: sourceReview,
|
|
115
386
|
},
|
|
116
387
|
cohort: {
|
|
117
388
|
eligible_pair_count: 0,
|
|
118
|
-
first_active_seconds: {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
},
|
|
122
|
-
returning_active_seconds: {
|
|
123
|
-
p50: 0,
|
|
124
|
-
p75: 0
|
|
125
|
-
},
|
|
126
|
-
paired_reduction_ratio: {
|
|
127
|
-
p50: 0,
|
|
128
|
-
p75: 0
|
|
129
|
-
},
|
|
389
|
+
first_active_seconds: { p50: 0, p75: 0 },
|
|
390
|
+
returning_active_seconds: { p50: 0, p75: 0 },
|
|
391
|
+
paired_reduction_ratio: { p50: 0, p75: 0 },
|
|
130
392
|
target_median_reduction_ratio: 0,
|
|
131
393
|
target_met: false,
|
|
132
394
|
minimum_pair_count_for_exit: 0,
|
|
133
|
-
sample_size_met: false
|
|
395
|
+
sample_size_met: false,
|
|
134
396
|
},
|
|
135
397
|
experience_reflux: {
|
|
136
398
|
recalled_experience_count: 0,
|
|
137
399
|
verified_experience_count: 0,
|
|
138
400
|
baseline: null,
|
|
139
401
|
baseline_available: false,
|
|
140
|
-
real_evidence: false
|
|
402
|
+
real_evidence: false,
|
|
141
403
|
},
|
|
142
404
|
preference_assertions: {
|
|
143
405
|
total_count: 0,
|
|
144
406
|
traceable_count: 0,
|
|
145
407
|
traceable_ratio: 0,
|
|
146
408
|
hard_filter_violation_count: 0,
|
|
147
|
-
contract_met: false
|
|
148
|
-
},
|
|
149
|
-
p4: {
|
|
150
|
-
state: 'unknown',
|
|
151
|
-
trigger_observed: false,
|
|
152
|
-
contract_met: false
|
|
409
|
+
contract_met: false,
|
|
153
410
|
},
|
|
411
|
+
p4: { state: 'unknown', trigger_observed: false, contract_met: false },
|
|
154
412
|
exit_evidence_eligible: false,
|
|
155
|
-
exit_ready: false
|
|
413
|
+
exit_ready: false,
|
|
156
414
|
};
|
|
157
415
|
}
|
|
158
416
|
export function scoreMemoryValue(input) {
|
|
159
417
|
const errors = [];
|
|
160
|
-
if (!isObject(input))
|
|
161
|
-
'root must be an object'
|
|
162
|
-
]);
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
'observed_private'
|
|
169
|
-
].includes(evidenceKind)) {
|
|
170
|
-
errors.push('evidence_kind must be synthetic_fixture or observed_private');
|
|
171
|
-
}
|
|
172
|
-
const policy = input.measurement_policy;
|
|
173
|
-
if (!isObject(policy)) return invalidReport([
|
|
174
|
-
...errors,
|
|
175
|
-
'measurement_policy must be an object'
|
|
176
|
-
], schema, evidenceKind);
|
|
177
|
-
if (policy.quantile_method !== 'nearest_rank') errors.push('measurement_policy.quantile_method must be nearest_rank');
|
|
178
|
-
const rawWaitCodes = policy.predeclared_external_wait_codes;
|
|
418
|
+
if (!isObject(input))
|
|
419
|
+
return invalidReport(['root must be an object']);
|
|
420
|
+
exactKeys(input, 'root', ['schema', 'evidence_kind', 'source_review', 'measurement_policy', 'pairs', 'experience_reflux_events', 'preference_assertions', 'p4'], errors);
|
|
421
|
+
const currentSummaryDigest = memoryValueSummaryDigest(input);
|
|
422
|
+
const schema = literal(input.schema, 'schema', ['memory_value_fixture.v1'], errors);
|
|
423
|
+
const evidenceKind = literal(input.evidence_kind, 'evidence_kind', ['synthetic_fixture', 'observed_private'], errors);
|
|
424
|
+
const sourceReview = scoreSourceReview(input.source_review, evidenceKind, currentSummaryDigest, errors).report;
|
|
425
|
+
const policy = object(input.measurement_policy, 'measurement_policy', errors);
|
|
179
426
|
const allowedWaitCodes = new Set();
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
427
|
+
if (policy !== null) {
|
|
428
|
+
exactKeys(policy, 'measurement_policy', [
|
|
429
|
+
'quantile_method',
|
|
430
|
+
'predeclared_external_wait_codes',
|
|
431
|
+
'minimum_pair_count_for_exit',
|
|
432
|
+
'target_median_reduction_ratio',
|
|
433
|
+
], errors);
|
|
434
|
+
literal(policy.quantile_method, 'measurement_policy.quantile_method', ['nearest_rank'], errors);
|
|
435
|
+
for (const code of array(policy.predeclared_external_wait_codes, 'measurement_policy.predeclared_external_wait_codes', errors, { nonEmpty: true })) {
|
|
436
|
+
if (!nonEmptyString(code) || !/^[a-z][a-z0-9_]{0,31}$/.test(code)) {
|
|
437
|
+
errors.push('measurement_policy.predeclared_external_wait_codes must contain declared wait-code strings');
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (allowedWaitCodes.has(code))
|
|
441
|
+
errors.push('measurement_policy.predeclared_external_wait_codes must not contain duplicates');
|
|
442
|
+
allowedWaitCodes.add(code);
|
|
443
|
+
}
|
|
444
|
+
const minimumPairCount = integer(policy.minimum_pair_count_for_exit, 'measurement_policy.minimum_pair_count_for_exit', errors);
|
|
445
|
+
if (minimumPairCount !== null && minimumPairCount !== M4_ACCEPTANCE.minimumPairCountForExit) {
|
|
446
|
+
errors.push(`measurement_policy.minimum_pair_count_for_exit is frozen at ${M4_ACCEPTANCE.minimumPairCountForExit}`);
|
|
447
|
+
}
|
|
448
|
+
const targetReduction = finiteNumber(policy.target_median_reduction_ratio, 'measurement_policy.target_median_reduction_ratio', errors);
|
|
449
|
+
if (targetReduction !== null && targetReduction !== M4_ACCEPTANCE.targetMedianReductionRatio) {
|
|
450
|
+
errors.push(`measurement_policy.target_median_reduction_ratio is frozen at ${M4_ACCEPTANCE.targetMedianReductionRatio}`);
|
|
186
451
|
}
|
|
187
|
-
}
|
|
188
|
-
const minimumPairCount = policy.minimum_pair_count_for_exit;
|
|
189
|
-
const targetReduction = policy.target_median_reduction_ratio;
|
|
190
|
-
if (!Number.isInteger(minimumPairCount) || minimumPairCount <= 0) {
|
|
191
|
-
errors.push('measurement_policy.minimum_pair_count_for_exit must be a positive integer');
|
|
192
|
-
}
|
|
193
|
-
if (!finiteNumber(targetReduction) || targetReduction < 0 || targetReduction > 1) {
|
|
194
|
-
errors.push('measurement_policy.target_median_reduction_ratio must be between 0 and 1');
|
|
195
|
-
}
|
|
196
|
-
const rawPairs = input.pairs;
|
|
197
|
-
if (!Array.isArray(rawPairs) || rawPairs.length === 0) {
|
|
198
|
-
errors.push('pairs must be a non-empty array');
|
|
199
452
|
}
|
|
200
453
|
const firstDurations = [];
|
|
201
454
|
const returningDurations = [];
|
|
@@ -203,103 +456,110 @@ export function scoreMemoryValue(input) {
|
|
|
203
456
|
const pairIds = new Set();
|
|
204
457
|
const subjectRefs = new Set();
|
|
205
458
|
const flowIds = new Set();
|
|
206
|
-
for (const [index, rawPair] of (
|
|
459
|
+
for (const [index, rawPair] of array(input.pairs, 'pairs', errors, { nonEmpty: true }).entries()) {
|
|
207
460
|
const pairPath = `pairs[${index}]`;
|
|
208
|
-
|
|
209
|
-
|
|
461
|
+
const pair = object(rawPair, pairPath, errors);
|
|
462
|
+
if (pair === null)
|
|
210
463
|
continue;
|
|
464
|
+
exactKeys(pair, pairPath, ['pair_id', 'subject_ref', 'first', 'returning'], errors);
|
|
465
|
+
const pairId = hmacRef(pair.pair_id, `${pairPath}.pair_id`, errors);
|
|
466
|
+
const subjectRef = hmacRef(pair.subject_ref, `${pairPath}.subject_ref`, errors);
|
|
467
|
+
assertUnique(pairId, pairIds, `${pairPath}.pair_id`, errors);
|
|
468
|
+
assertUnique(subjectRef, subjectRefs, `${pairPath}.subject_ref`, errors);
|
|
469
|
+
const first = scoreFlow(pair.first, `${pairPath}.first`, 1, allowedWaitCodes, errors);
|
|
470
|
+
const returning = scoreFlow(pair.returning, `${pairPath}.returning`, 2, allowedWaitCodes, errors);
|
|
471
|
+
for (const flow of [first, returning]) {
|
|
472
|
+
if (!flow)
|
|
473
|
+
continue;
|
|
474
|
+
assertUnique(flow.flowId, flowIds, `${pairPath}.flow_id`, errors);
|
|
211
475
|
}
|
|
212
|
-
if (!
|
|
213
|
-
|
|
214
|
-
else pairIds.add(rawPair.pair_id);
|
|
215
|
-
if (!nonEmptyString(rawPair.subject_ref)) {
|
|
216
|
-
errors.push(`${pairPath}.subject_ref must be a non-empty pseudonymous reference`);
|
|
217
|
-
} else if (subjectRefs.has(rawPair.subject_ref)) {
|
|
218
|
-
errors.push(`${pairPath}.subject_ref must be unique across the paired cohort`);
|
|
219
|
-
} else {
|
|
220
|
-
subjectRefs.add(rawPair.subject_ref);
|
|
221
|
-
}
|
|
222
|
-
const first = scoreFlow(rawPair.first, `${pairPath}.first`, 1, allowedWaitCodes, errors);
|
|
223
|
-
const returning = scoreFlow(rawPair.returning, `${pairPath}.returning`, 2, allowedWaitCodes, errors);
|
|
224
|
-
for (const flow of [
|
|
225
|
-
first,
|
|
226
|
-
returning
|
|
227
|
-
]){
|
|
228
|
-
if (!flow) continue;
|
|
229
|
-
if (flowIds.has(flow.flowId)) errors.push(`${pairPath} flow_id values must be globally unique`);
|
|
230
|
-
flowIds.add(flow.flowId);
|
|
231
|
-
}
|
|
232
|
-
if (!first || !returning || first.activeSeconds <= 0 || returning.activeSeconds <= 0) continue;
|
|
476
|
+
if (!first || !returning || first.activeSeconds <= 0 || returning.activeSeconds <= 0)
|
|
477
|
+
continue;
|
|
233
478
|
if (returning.startedAtMs <= first.completedAtMs) {
|
|
234
479
|
errors.push(`${pairPath}.returning must start after the first completed flow`);
|
|
235
480
|
}
|
|
236
481
|
firstDurations.push(first.activeSeconds);
|
|
237
482
|
returningDurations.push(returning.activeSeconds);
|
|
238
|
-
reductions.push(
|
|
483
|
+
reductions.push((first.activeSeconds - returning.activeSeconds) / first.activeSeconds);
|
|
239
484
|
}
|
|
240
|
-
const rawRefluxEvents = input.experience_reflux_events;
|
|
241
|
-
if (!Array.isArray(rawRefluxEvents)) errors.push('experience_reflux_events must be an array');
|
|
242
485
|
const recalled = new Set();
|
|
243
486
|
const verified = new Set();
|
|
244
|
-
|
|
487
|
+
const refluxEventKeys = new Set();
|
|
488
|
+
const refluxEvidenceRefs = new Set();
|
|
489
|
+
for (const [index, rawEvent] of array(input.experience_reflux_events, 'experience_reflux_events', errors).entries()) {
|
|
245
490
|
const eventPath = `experience_reflux_events[${index}]`;
|
|
246
|
-
|
|
247
|
-
|
|
491
|
+
const event = object(rawEvent, eventPath, errors);
|
|
492
|
+
if (event === null)
|
|
248
493
|
continue;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
494
|
+
exactKeys(event, eventPath, ['experience_id', 'kind', 'evidence_ref'], errors);
|
|
495
|
+
const experienceId = hmacRef(event.experience_id, `${eventPath}.experience_id`, errors);
|
|
496
|
+
const kind = literal(event.kind, `${eventPath}.kind`, ['recalled', 'verified_outcome'], errors);
|
|
497
|
+
const evidenceRef = hmacRef(event.evidence_ref, `${eventPath}.evidence_ref`, errors);
|
|
498
|
+
assertUnique(evidenceRef, refluxEvidenceRefs, `${eventPath}.evidence_ref`, errors);
|
|
499
|
+
if (experienceId === null || kind === null)
|
|
252
500
|
continue;
|
|
253
|
-
}
|
|
254
|
-
if (
|
|
255
|
-
|
|
256
|
-
else
|
|
257
|
-
|
|
501
|
+
assertUnique(`${kind}:${experienceId}`, refluxEventKeys, `${eventPath}.experience_id+kind`, errors);
|
|
502
|
+
if (kind === 'recalled')
|
|
503
|
+
recalled.add(experienceId);
|
|
504
|
+
else
|
|
505
|
+
verified.add(experienceId);
|
|
258
506
|
}
|
|
259
|
-
for (const experienceId of verified){
|
|
260
|
-
if (!recalled.has(experienceId))
|
|
507
|
+
for (const experienceId of verified) {
|
|
508
|
+
if (!recalled.has(experienceId))
|
|
509
|
+
errors.push('each verified_outcome experience must have a recalled event');
|
|
261
510
|
}
|
|
262
|
-
const verifiedRecalledCount = [
|
|
263
|
-
...verified
|
|
264
|
-
].filter((id)=>recalled.has(id)).length;
|
|
511
|
+
const verifiedRecalledCount = [...verified].filter(id => recalled.has(id)).length;
|
|
265
512
|
const refluxBaseline = recalled.size > 0 ? round(verifiedRecalledCount / recalled.size) : null;
|
|
266
|
-
const
|
|
267
|
-
if (!Array.isArray(rawAssertions)) errors.push('preference_assertions must be an array');
|
|
513
|
+
const assertionsRaw = array(input.preference_assertions, 'preference_assertions', errors);
|
|
268
514
|
let traceableCount = 0;
|
|
269
515
|
let hardFilterViolationCount = 0;
|
|
270
|
-
const
|
|
271
|
-
for (const [index, rawAssertion] of
|
|
516
|
+
const assertionIds = new Set();
|
|
517
|
+
for (const [index, rawAssertion] of assertionsRaw.entries()) {
|
|
272
518
|
const assertionPath = `preference_assertions[${index}]`;
|
|
273
|
-
|
|
274
|
-
|
|
519
|
+
const assertion = object(rawAssertion, assertionPath, errors);
|
|
520
|
+
if (assertion === null)
|
|
275
521
|
continue;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
522
|
+
exactKeys(assertion, assertionPath, ['assertion_id', 'evidence_ref', 'consumer', 'hard_filter'], errors);
|
|
523
|
+
const assertionId = hmacRef(assertion.assertion_id, `${assertionPath}.assertion_id`, errors);
|
|
524
|
+
const evidenceRef = hmacRef(assertion.evidence_ref, `${assertionPath}.evidence_ref`, errors);
|
|
525
|
+
const consumer = literal(assertion.consumer, `${assertionPath}.consumer`, ['ranking', 'explanation', 'hard_filter'], errors);
|
|
526
|
+
const hardFilter = boolean(assertion.hard_filter, `${assertionPath}.hard_filter`, errors);
|
|
527
|
+
assertUnique(assertionId, assertionIds, `${assertionPath}.assertion_id`, errors);
|
|
528
|
+
if (evidenceRef !== null)
|
|
529
|
+
traceableCount += 1;
|
|
530
|
+
if (hardFilter === true || consumer === 'hard_filter')
|
|
531
|
+
hardFilterViolationCount += 1;
|
|
280
532
|
}
|
|
281
|
-
const traceableRatio =
|
|
282
|
-
const assertionContractMet =
|
|
283
|
-
const rawP4 = input.p4;
|
|
533
|
+
const traceableRatio = assertionsRaw.length > 0 ? round(traceableCount / assertionsRaw.length) : 0;
|
|
534
|
+
const assertionContractMet = assertionsRaw.length > 0 && traceableRatio === 1 && hardFilterViolationCount === 0;
|
|
284
535
|
let p4State = 'unknown';
|
|
285
536
|
let p4TriggerObserved = false;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
537
|
+
const rawP4 = object(input.p4, 'p4', errors);
|
|
538
|
+
if (rawP4 !== null) {
|
|
539
|
+
exactKeys(rawP4, 'p4', ['state', 'triggers'], errors);
|
|
540
|
+
p4State = literal(rawP4.state, 'p4.state', ['closed', 'open'], errors) ?? 'unknown';
|
|
541
|
+
const triggers = object(rawP4.triggers, 'p4.triggers', errors);
|
|
542
|
+
if (triggers !== null) {
|
|
543
|
+
exactKeys(triggers, 'p4.triggers', ['real_usage', 'multi_user'], errors);
|
|
544
|
+
const realUsage = boolean(triggers.real_usage, 'p4.triggers.real_usage', errors);
|
|
545
|
+
const multiUser = boolean(triggers.multi_user, 'p4.triggers.multi_user', errors);
|
|
546
|
+
p4TriggerObserved = realUsage === true || multiUser === true;
|
|
547
|
+
}
|
|
292
548
|
}
|
|
293
549
|
const p4ContractMet = p4TriggerObserved || p4State === 'closed';
|
|
294
|
-
if (errors.length > 0)
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
const exitEvidenceEligible = evidenceKind === 'observed_private';
|
|
550
|
+
if (errors.length > 0)
|
|
551
|
+
return invalidReport(errors, schema ?? '', evidenceKind ?? '', sourceReview);
|
|
552
|
+
const medianReduction = nearestRankRaw(reductions, 0.5);
|
|
553
|
+
const sampleSizeMet = firstDurations.length >= M4_ACCEPTANCE.minimumPairCountForExit;
|
|
554
|
+
const targetMet = medianReduction >= M4_ACCEPTANCE.targetMedianReductionRatio;
|
|
555
|
+
const sourceEvidenceEligible = evidenceKind === 'observed_private' && sourceReview.contract_met;
|
|
301
556
|
const baselineAvailable = refluxBaseline !== null;
|
|
302
|
-
const exitReady =
|
|
557
|
+
const exitReady = sourceEvidenceEligible
|
|
558
|
+
&& sampleSizeMet
|
|
559
|
+
&& targetMet
|
|
560
|
+
&& baselineAvailable
|
|
561
|
+
&& assertionContractMet
|
|
562
|
+
&& p4ContractMet;
|
|
303
563
|
return {
|
|
304
564
|
schema: 'memory_value_report.v1',
|
|
305
565
|
contract_valid: true,
|
|
@@ -308,48 +568,41 @@ export function scoreMemoryValue(input) {
|
|
|
308
568
|
fixture_schema: schema,
|
|
309
569
|
evidence_kind: evidenceKind,
|
|
310
570
|
quantile_method: 'nearest_rank',
|
|
311
|
-
active_duration_rule: 'wall_clock_minus_non_overlapping_predeclared_external_waits'
|
|
571
|
+
active_duration_rule: 'wall_clock_minus_non_overlapping_predeclared_external_waits',
|
|
572
|
+
id_format: 'hmac-sha256:<64lowerhex>',
|
|
573
|
+
source_review: sourceReview,
|
|
312
574
|
},
|
|
313
575
|
cohort: {
|
|
314
576
|
eligible_pair_count: firstDurations.length,
|
|
315
|
-
first_active_seconds: {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
returning_active_seconds: {
|
|
320
|
-
p50: nearestRank(returningDurations, 0.5),
|
|
321
|
-
p75: nearestRank(returningDurations, 0.75)
|
|
322
|
-
},
|
|
323
|
-
paired_reduction_ratio: {
|
|
324
|
-
p50: medianReduction,
|
|
325
|
-
p75: nearestRank(reductions, 0.75)
|
|
326
|
-
},
|
|
327
|
-
target_median_reduction_ratio: numericTargetReduction,
|
|
577
|
+
first_active_seconds: { p50: nearestRank(firstDurations, 0.5), p75: nearestRank(firstDurations, 0.75) },
|
|
578
|
+
returning_active_seconds: { p50: nearestRank(returningDurations, 0.5), p75: nearestRank(returningDurations, 0.75) },
|
|
579
|
+
paired_reduction_ratio: { p50: round(medianReduction), p75: nearestRank(reductions, 0.75) },
|
|
580
|
+
target_median_reduction_ratio: M4_ACCEPTANCE.targetMedianReductionRatio,
|
|
328
581
|
target_met: targetMet,
|
|
329
|
-
minimum_pair_count_for_exit:
|
|
330
|
-
sample_size_met: sampleSizeMet
|
|
582
|
+
minimum_pair_count_for_exit: M4_ACCEPTANCE.minimumPairCountForExit,
|
|
583
|
+
sample_size_met: sampleSizeMet,
|
|
331
584
|
},
|
|
332
585
|
experience_reflux: {
|
|
333
586
|
recalled_experience_count: recalled.size,
|
|
334
587
|
verified_experience_count: verifiedRecalledCount,
|
|
335
588
|
baseline: refluxBaseline,
|
|
336
589
|
baseline_available: baselineAvailable,
|
|
337
|
-
real_evidence:
|
|
590
|
+
real_evidence: sourceEvidenceEligible,
|
|
338
591
|
},
|
|
339
592
|
preference_assertions: {
|
|
340
|
-
total_count:
|
|
593
|
+
total_count: assertionsRaw.length,
|
|
341
594
|
traceable_count: traceableCount,
|
|
342
595
|
traceable_ratio: traceableRatio,
|
|
343
596
|
hard_filter_violation_count: hardFilterViolationCount,
|
|
344
|
-
contract_met: assertionContractMet
|
|
597
|
+
contract_met: assertionContractMet,
|
|
345
598
|
},
|
|
346
599
|
p4: {
|
|
347
600
|
state: p4State,
|
|
348
601
|
trigger_observed: p4TriggerObserved,
|
|
349
|
-
contract_met: p4ContractMet
|
|
602
|
+
contract_met: p4ContractMet,
|
|
350
603
|
},
|
|
351
|
-
exit_evidence_eligible:
|
|
352
|
-
exit_ready: exitReady
|
|
604
|
+
exit_evidence_eligible: sourceEvidenceEligible,
|
|
605
|
+
exit_ready: exitReady,
|
|
353
606
|
};
|
|
354
607
|
}
|
|
355
608
|
function main() {
|
|
@@ -362,18 +615,17 @@ function main() {
|
|
|
362
615
|
let input;
|
|
363
616
|
try {
|
|
364
617
|
input = JSON.parse(readFileSync(resolve(inputPath), 'utf8'));
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
console.error(
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
console.error('MEMORY_VALUE_INPUT_INVALID: input file could not be read or parsed');
|
|
368
621
|
process.exitCode = 2;
|
|
369
622
|
return;
|
|
370
623
|
}
|
|
371
624
|
const report = scoreMemoryValue(input);
|
|
372
625
|
console.log(JSON.stringify(report, null, 2));
|
|
373
|
-
if (!report.contract_valid)
|
|
626
|
+
if (!report.contract_valid)
|
|
627
|
+
process.exitCode = 2;
|
|
374
628
|
}
|
|
375
629
|
const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : '';
|
|
376
|
-
if (import.meta.url === invokedPath)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
//# sourceURL=ts/scripts/memory-value-report.ts
|
|
630
|
+
if (import.meta.url === invokedPath)
|
|
631
|
+
main();
|