@agentplat/inference-control 0.3.0-beta.1 → 0.3.0-beta.3
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 +360 -0
- package/dist/anytime-semantic-guarantees.d.ts +216 -0
- package/dist/anytime-semantic-guarantees.d.ts.map +1 -0
- package/dist/anytime-semantic-guarantees.js +817 -0
- package/dist/anytime-semantic-guarantees.js.map +1 -0
- package/dist/assessor-ensemble-contracts.d.ts +145 -0
- package/dist/assessor-ensemble-contracts.d.ts.map +1 -0
- package/dist/assessor-ensemble-contracts.js +29 -0
- package/dist/assessor-ensemble-contracts.js.map +1 -0
- package/dist/assessor-ensemble-runtime.d.ts +55 -0
- package/dist/assessor-ensemble-runtime.d.ts.map +1 -0
- package/dist/assessor-ensemble-runtime.js +308 -0
- package/dist/assessor-ensemble-runtime.js.map +1 -0
- package/dist/assessor-ensemble-validation.d.ts +12 -0
- package/dist/assessor-ensemble-validation.d.ts.map +1 -0
- package/dist/assessor-ensemble-validation.js +156 -0
- package/dist/assessor-ensemble-validation.js.map +1 -0
- package/dist/assessor-ensemble.d.ts +4 -0
- package/dist/assessor-ensemble.d.ts.map +1 -0
- package/dist/assessor-ensemble.js +4 -0
- package/dist/assessor-ensemble.js.map +1 -0
- package/dist/cognitive-adapters.d.ts +92 -0
- package/dist/cognitive-adapters.d.ts.map +1 -0
- package/dist/cognitive-adapters.js +263 -0
- package/dist/cognitive-adapters.js.map +1 -0
- package/dist/context-integrity-analyzers.d.ts +35 -0
- package/dist/context-integrity-analyzers.d.ts.map +1 -0
- package/dist/context-integrity-analyzers.js +387 -0
- package/dist/context-integrity-analyzers.js.map +1 -0
- package/dist/context-integrity-contracts.d.ts +304 -0
- package/dist/context-integrity-contracts.d.ts.map +1 -0
- package/dist/context-integrity-contracts.js +24 -0
- package/dist/context-integrity-contracts.js.map +1 -0
- package/dist/context-integrity-model.d.ts +25 -0
- package/dist/context-integrity-model.d.ts.map +1 -0
- package/dist/context-integrity-model.js +192 -0
- package/dist/context-integrity-model.js.map +1 -0
- package/dist/context-integrity-portable-agent.d.ts +79 -0
- package/dist/context-integrity-portable-agent.d.ts.map +1 -0
- package/dist/context-integrity-portable-agent.js +511 -0
- package/dist/context-integrity-portable-agent.js.map +1 -0
- package/dist/context-integrity-role-alignment.d.ts +28 -0
- package/dist/context-integrity-role-alignment.d.ts.map +1 -0
- package/dist/context-integrity-role-alignment.js +95 -0
- package/dist/context-integrity-role-alignment.js.map +1 -0
- package/dist/context-integrity-runtime.d.ts +95 -0
- package/dist/context-integrity-runtime.d.ts.map +1 -0
- package/dist/context-integrity-runtime.js +1512 -0
- package/dist/context-integrity-runtime.js.map +1 -0
- package/dist/context-integrity.d.ts +5 -0
- package/dist/context-integrity.d.ts.map +1 -0
- package/dist/context-integrity.js +5 -0
- package/dist/context-integrity.js.map +1 -0
- package/dist/governed-role-evolution.d.ts +172 -0
- package/dist/governed-role-evolution.d.ts.map +1 -0
- package/dist/governed-role-evolution.js +613 -0
- package/dist/governed-role-evolution.js.map +1 -0
- package/dist/heterogeneous-agent-composition.d.ts +96 -0
- package/dist/heterogeneous-agent-composition.d.ts.map +1 -0
- package/dist/heterogeneous-agent-composition.js +512 -0
- package/dist/heterogeneous-agent-composition.js.map +1 -0
- package/dist/index.d.ts +21 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -13
- package/dist/index.js.map +1 -1
- package/dist/intervention-adapters.d.ts +29 -0
- package/dist/intervention-adapters.d.ts.map +1 -0
- package/dist/intervention-adapters.js +107 -0
- package/dist/intervention-adapters.js.map +1 -0
- package/dist/intervention-contracts.d.ts +311 -0
- package/dist/intervention-contracts.d.ts.map +1 -0
- package/dist/intervention-contracts.js +28 -0
- package/dist/intervention-contracts.js.map +1 -0
- package/dist/intervention-runtime.d.ts +86 -0
- package/dist/intervention-runtime.d.ts.map +1 -0
- package/dist/intervention-runtime.js +958 -0
- package/dist/intervention-runtime.js.map +1 -0
- package/dist/intervention-validation.d.ts +27 -0
- package/dist/intervention-validation.d.ts.map +1 -0
- package/dist/intervention-validation.js +273 -0
- package/dist/intervention-validation.js.map +1 -0
- package/dist/intervention.d.ts +6 -0
- package/dist/intervention.d.ts.map +1 -0
- package/dist/intervention.js +6 -0
- package/dist/intervention.js.map +1 -0
- package/dist/model.d.ts +18 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +60 -2
- package/dist/model.js.map +1 -1
- package/dist/operational-cognitive-control.d.ts +208 -0
- package/dist/operational-cognitive-control.d.ts.map +1 -0
- package/dist/operational-cognitive-control.js +590 -0
- package/dist/operational-cognitive-control.js.map +1 -0
- package/dist/portable-agent.d.ts +50 -0
- package/dist/portable-agent.d.ts.map +1 -0
- package/dist/portable-agent.js +243 -0
- package/dist/portable-agent.js.map +1 -0
- package/dist/reference-controllers.d.ts +121 -0
- package/dist/reference-controllers.d.ts.map +1 -0
- package/dist/reference-controllers.js +411 -0
- package/dist/reference-controllers.js.map +1 -0
- package/dist/role-alignment-portable-agent.d.ts +145 -0
- package/dist/role-alignment-portable-agent.d.ts.map +1 -0
- package/dist/role-alignment-portable-agent.js +484 -0
- package/dist/role-alignment-portable-agent.js.map +1 -0
- package/dist/role-alignment.d.ts +226 -0
- package/dist/role-alignment.d.ts.map +1 -0
- package/dist/role-alignment.js +1074 -0
- package/dist/role-alignment.js.map +1 -0
- package/dist/role-realignment-portable-agent.d.ts +163 -0
- package/dist/role-realignment-portable-agent.d.ts.map +1 -0
- package/dist/role-realignment-portable-agent.js +634 -0
- package/dist/role-realignment-portable-agent.js.map +1 -0
- package/dist/role-realignment.d.ts +368 -0
- package/dist/role-realignment.d.ts.map +1 -0
- package/dist/role-realignment.js +1609 -0
- package/dist/role-realignment.js.map +1 -0
- package/dist/semantic-alignment-assessors.d.ts +12 -0
- package/dist/semantic-alignment-assessors.d.ts.map +1 -0
- package/dist/semantic-alignment-assessors.js +55 -0
- package/dist/semantic-alignment-assessors.js.map +1 -0
- package/dist/semantic-alignment-contracts.d.ts +385 -0
- package/dist/semantic-alignment-contracts.d.ts.map +1 -0
- package/dist/semantic-alignment-contracts.js +16 -0
- package/dist/semantic-alignment-contracts.js.map +1 -0
- package/dist/semantic-alignment-memory.d.ts +53 -0
- package/dist/semantic-alignment-memory.d.ts.map +1 -0
- package/dist/semantic-alignment-memory.js +170 -0
- package/dist/semantic-alignment-memory.js.map +1 -0
- package/dist/semantic-alignment-runtime.d.ts +67 -0
- package/dist/semantic-alignment-runtime.d.ts.map +1 -0
- package/dist/semantic-alignment-runtime.js +778 -0
- package/dist/semantic-alignment-runtime.js.map +1 -0
- package/dist/semantic-alignment-validation.d.ts +31 -0
- package/dist/semantic-alignment-validation.d.ts.map +1 -0
- package/dist/semantic-alignment-validation.js +477 -0
- package/dist/semantic-alignment-validation.js.map +1 -0
- package/dist/semantic-alignment.d.ts +8 -0
- package/dist/semantic-alignment.d.ts.map +1 -0
- package/dist/semantic-alignment.js +8 -0
- package/dist/semantic-alignment.js.map +1 -0
- package/dist/semantic-horizon-budget.d.ts +118 -0
- package/dist/semantic-horizon-budget.d.ts.map +1 -0
- package/dist/semantic-horizon-budget.js +536 -0
- package/dist/semantic-horizon-budget.js.map +1 -0
- package/dist/semantic-metric-engine.d.ts +96 -0
- package/dist/semantic-metric-engine.d.ts.map +1 -0
- package/dist/semantic-metric-engine.js +396 -0
- package/dist/semantic-metric-engine.js.map +1 -0
- package/dist/tools.d.ts +69 -23
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +420 -229
- package/dist/tools.js.map +1 -1
- package/package.json +79 -7
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
import { digestControlJsonV1 } from "./canonical.js";
|
|
2
|
+
export const ANYTIME_SEMANTIC_GUARANTEE_STATE_FORMAT_V1 = "agentplat.inference-control.anytime-semantic-guarantee-state.v1";
|
|
3
|
+
export const SEMANTIC_GUARANTEE_METRICS_V1 = Object.freeze([
|
|
4
|
+
"roleCoherence",
|
|
5
|
+
"missionAlignment",
|
|
6
|
+
"contextConflict",
|
|
7
|
+
"uncertainty",
|
|
8
|
+
"courseActionDiversity",
|
|
9
|
+
"courseActionNovelty",
|
|
10
|
+
]);
|
|
11
|
+
const anytimeSemanticGuaranteeEngineInvokersV1 = new WeakMap();
|
|
12
|
+
const semanticHorizonControlInvokersV1 = new WeakMap();
|
|
13
|
+
const EXPECTED_DIRECTIONS = Object.freeze({
|
|
14
|
+
roleCoherence: "higher_is_better",
|
|
15
|
+
missionAlignment: "higher_is_better",
|
|
16
|
+
contextConflict: "lower_is_better",
|
|
17
|
+
uncertainty: "lower_is_better",
|
|
18
|
+
courseActionDiversity: "higher_is_better",
|
|
19
|
+
courseActionNovelty: "higher_is_better",
|
|
20
|
+
});
|
|
21
|
+
const VECTOR_KEYS = Object.freeze({
|
|
22
|
+
roleCoherence: "roleCoherenceBps",
|
|
23
|
+
missionAlignment: "missionAlignmentBps",
|
|
24
|
+
contextConflict: "contextConflictBps",
|
|
25
|
+
uncertainty: "uncertaintyBps",
|
|
26
|
+
courseActionDiversity: "courseActionDiversityBps",
|
|
27
|
+
courseActionNovelty: "courseActionNoveltyBps",
|
|
28
|
+
});
|
|
29
|
+
export function createAnytimeSemanticAssumptionsV1(input) {
|
|
30
|
+
const evidence = uniqueDigests(input.assumptionEvidenceDigests, "assumptionEvidenceDigests");
|
|
31
|
+
const body = {
|
|
32
|
+
schemaVersion: 1,
|
|
33
|
+
estimand: "average_conditional_mean",
|
|
34
|
+
dependenceModel: "bounded_martingale_differences",
|
|
35
|
+
observationRange: "basis_points_0_10000",
|
|
36
|
+
selectionTiming: "before_metric_observation",
|
|
37
|
+
assumptionEvidenceDigests: evidence,
|
|
38
|
+
};
|
|
39
|
+
return deepFreeze({
|
|
40
|
+
...body,
|
|
41
|
+
assumptionsDigest: digestSemanticGuarantee("assumptions", body),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export function createAnytimeSemanticGuaranteePolicyV1(input) {
|
|
45
|
+
const policyId = input.policyId;
|
|
46
|
+
const familywiseErrorBudgetPpm = input.familywiseErrorBudgetPpm;
|
|
47
|
+
const minimumInferenceSamples = input.minimumInferenceSamples;
|
|
48
|
+
const metricInputs = input.metrics;
|
|
49
|
+
const assumptionInputs = input.assumptions;
|
|
50
|
+
identifier(policyId, "policyId");
|
|
51
|
+
integer(familywiseErrorBudgetPpm, "familywiseErrorBudgetPpm", 1, 499_999);
|
|
52
|
+
integer(minimumInferenceSamples, "minimumInferenceSamples", 1, 1_000_000_000);
|
|
53
|
+
const maximumRetainedEvidenceDigests = input.maximumRetainedEvidenceDigests ?? 128;
|
|
54
|
+
const maximumCasAttempts = input.maximumCasAttempts ?? 8;
|
|
55
|
+
integer(maximumRetainedEvidenceDigests, "maximumRetainedEvidenceDigests", 1, 10_000);
|
|
56
|
+
integer(maximumCasAttempts, "maximumCasAttempts", 1, 1_000);
|
|
57
|
+
let allocated = 0;
|
|
58
|
+
const metrics = {};
|
|
59
|
+
for (const metric of SEMANTIC_GUARANTEE_METRICS_V1) {
|
|
60
|
+
const candidate = metricInputs[metric];
|
|
61
|
+
if (!candidate)
|
|
62
|
+
throw new TypeError(`semantic_guarantee_${metric}_direction_invalid`);
|
|
63
|
+
const direction = candidate.direction;
|
|
64
|
+
const errorBudgetPpm = candidate.errorBudgetPpm;
|
|
65
|
+
const missingness = candidate.missingness;
|
|
66
|
+
if (direction !== EXPECTED_DIRECTIONS[metric])
|
|
67
|
+
throw new TypeError(`semantic_guarantee_${metric}_direction_invalid`);
|
|
68
|
+
integer(errorBudgetPpm, `${metric}.errorBudgetPpm`, 1, 499_999);
|
|
69
|
+
if (!["fail_closed", "predictable_skip", "worst_case_imputation"].includes(missingness))
|
|
70
|
+
throw new TypeError(`semantic_guarantee_${metric}_missingness_invalid`);
|
|
71
|
+
allocated += errorBudgetPpm;
|
|
72
|
+
metrics[metric] = Object.freeze({
|
|
73
|
+
direction,
|
|
74
|
+
errorBudgetPpm,
|
|
75
|
+
missingness,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (allocated > familywiseErrorBudgetPpm)
|
|
79
|
+
throw new RangeError("semantic_guarantee_metric_error_budget_exceeds_familywise_budget");
|
|
80
|
+
const assumptions = createAnytimeSemanticAssumptionsV1(assumptionInputs);
|
|
81
|
+
if (Object.values(metrics).some((metric) => metric.missingness === "predictable_skip") &&
|
|
82
|
+
assumptions.assumptionEvidenceDigests.length === 0)
|
|
83
|
+
throw new TypeError("semantic_guarantee_predictable_skip_requires_assumption_evidence");
|
|
84
|
+
const body = {
|
|
85
|
+
schemaVersion: 1,
|
|
86
|
+
policyId,
|
|
87
|
+
familywiseErrorBudgetPpm,
|
|
88
|
+
boundMethod: "hoeffding_azuma_two_sided",
|
|
89
|
+
spendingSchedule: "inverse_quadratic_telescoping",
|
|
90
|
+
minimumInferenceSamples,
|
|
91
|
+
maximumRetainedEvidenceDigests,
|
|
92
|
+
maximumCasAttempts,
|
|
93
|
+
metrics,
|
|
94
|
+
assumptions,
|
|
95
|
+
};
|
|
96
|
+
return deepFreeze({
|
|
97
|
+
...body,
|
|
98
|
+
policyDigest: digestSemanticGuarantee("policy", body),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
export class InMemoryAnytimeSemanticGuaranteeStoreV1 {
|
|
102
|
+
#states = new Map();
|
|
103
|
+
#anchors = new Map();
|
|
104
|
+
async load(stateKey) {
|
|
105
|
+
return this.#states.get(stateKey) ?? null;
|
|
106
|
+
}
|
|
107
|
+
async readAnchor(stateKey) {
|
|
108
|
+
return this.#anchors.get(stateKey) ?? null;
|
|
109
|
+
}
|
|
110
|
+
async compareAndSet(input) {
|
|
111
|
+
const prior = this.#states.get(input.stateKey) ?? null;
|
|
112
|
+
if ((prior?.revision ?? null) !== input.expectedRevision ||
|
|
113
|
+
(prior?.stateDigest ?? null) !== input.expectedStateDigest)
|
|
114
|
+
return false;
|
|
115
|
+
this.#states.set(input.stateKey, deepFreeze(input.next));
|
|
116
|
+
this.#anchors.set(input.stateKey, deepFreeze({
|
|
117
|
+
revision: input.next.revision,
|
|
118
|
+
sequenceHighWater: input.next.sequenceHighWater,
|
|
119
|
+
logicalTimeHighWaterMs: input.next.logicalTimeHighWaterMs,
|
|
120
|
+
stateDigest: input.next.stateDigest,
|
|
121
|
+
}));
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Portable confidence-sequence engine. It uses a fixed-time Hoeffding-Azuma
|
|
127
|
+
* bound at each inference count and an inverse-quadratic alpha-spending union
|
|
128
|
+
* bound, making all emitted intervals simultaneous across metrics and time.
|
|
129
|
+
*/
|
|
130
|
+
export class AnytimeSemanticGuaranteeEngineV1 {
|
|
131
|
+
policy;
|
|
132
|
+
store;
|
|
133
|
+
anchor;
|
|
134
|
+
#load;
|
|
135
|
+
#compareAndSet;
|
|
136
|
+
#readAnchor;
|
|
137
|
+
constructor(options) {
|
|
138
|
+
const policy = validatePolicy(options.policy);
|
|
139
|
+
const store = options.store ?? new InMemoryAnytimeSemanticGuaranteeStoreV1();
|
|
140
|
+
const anchor = options.monotonicAnchor ??
|
|
141
|
+
store;
|
|
142
|
+
if (typeof store.load !== "function" ||
|
|
143
|
+
typeof store.compareAndSet !== "function" ||
|
|
144
|
+
typeof anchor.readAnchor !== "function")
|
|
145
|
+
throw new TypeError("semantic_guarantee_external_monotonic_anchor_required");
|
|
146
|
+
this.policy = policy;
|
|
147
|
+
this.store = store;
|
|
148
|
+
this.anchor = anchor;
|
|
149
|
+
this.#load = store.load.bind(store);
|
|
150
|
+
this.#compareAndSet = store.compareAndSet.bind(store);
|
|
151
|
+
this.#readAnchor = anchor.readAnchor.bind(anchor);
|
|
152
|
+
Object.defineProperties(this, {
|
|
153
|
+
policy: immutableSemanticGuaranteeValue(policy),
|
|
154
|
+
store: immutableSemanticGuaranteeValue(store),
|
|
155
|
+
anchor: immutableSemanticGuaranteeValue(anchor),
|
|
156
|
+
});
|
|
157
|
+
anytimeSemanticGuaranteeEngineInvokersV1.set(this, Object.freeze({
|
|
158
|
+
append: (input) => this.#append(input),
|
|
159
|
+
current: (stateKey) => this.#current(stateKey),
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
async append(input) {
|
|
163
|
+
return invokeAnytimeSemanticGuaranteeAppendV1(this, input);
|
|
164
|
+
}
|
|
165
|
+
async #append(input) {
|
|
166
|
+
identifier(input.stateKey, "stateKey");
|
|
167
|
+
validateSample(input.sample);
|
|
168
|
+
const sampleDigest = digestSemanticGuarantee("sample", input.sample);
|
|
169
|
+
for (let attempt = 0; attempt < this.policy.maximumCasAttempts; attempt += 1) {
|
|
170
|
+
const prior = await this.#load(input.stateKey);
|
|
171
|
+
await this.#assertStateAndAnchor(input.stateKey, prior);
|
|
172
|
+
if (prior && input.sample.sequence === prior.sequenceHighWater) {
|
|
173
|
+
if (sampleDigest !== prior.lastSampleDigest)
|
|
174
|
+
throw new TypeError("semantic_guarantee_sample_equivocation");
|
|
175
|
+
return this.#guaranteeFrom(prior);
|
|
176
|
+
}
|
|
177
|
+
if (prior &&
|
|
178
|
+
(input.sample.sequence < prior.sequenceHighWater ||
|
|
179
|
+
input.sample.logicalTimeMs < prior.logicalTimeHighWaterMs))
|
|
180
|
+
throw new TypeError("semantic_guarantee_sample_order_invalid");
|
|
181
|
+
const next = this.#advance(input.stateKey, prior, input.sample, sampleDigest);
|
|
182
|
+
if (await this.#compareAndSet({
|
|
183
|
+
stateKey: input.stateKey,
|
|
184
|
+
expectedRevision: prior?.revision ?? null,
|
|
185
|
+
expectedStateDigest: prior?.stateDigest ?? null,
|
|
186
|
+
next,
|
|
187
|
+
})) {
|
|
188
|
+
const committed = await this.#load(input.stateKey);
|
|
189
|
+
if (!committed || committed.stateDigest !== next.stateDigest)
|
|
190
|
+
throw new TypeError("semantic_guarantee_commit_not_observable");
|
|
191
|
+
await this.#assertStateAndAnchor(input.stateKey, committed);
|
|
192
|
+
return this.#guaranteeFrom(committed);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
throw new Error("semantic_guarantee_cas_retry_exhausted");
|
|
196
|
+
}
|
|
197
|
+
async current(stateKey) {
|
|
198
|
+
return invokeAnytimeSemanticGuaranteeCurrentV1(this, stateKey);
|
|
199
|
+
}
|
|
200
|
+
async #current(stateKey) {
|
|
201
|
+
identifier(stateKey, "stateKey");
|
|
202
|
+
const state = await this.#load(stateKey);
|
|
203
|
+
if (!state) {
|
|
204
|
+
if (await this.#readAnchor(stateKey))
|
|
205
|
+
throw new TypeError("semantic_guarantee_state_missing_below_anchor");
|
|
206
|
+
throw new TypeError("semantic_guarantee_has_no_samples");
|
|
207
|
+
}
|
|
208
|
+
await this.#assertStateAndAnchor(stateKey, state);
|
|
209
|
+
return this.#guaranteeFrom(state);
|
|
210
|
+
}
|
|
211
|
+
#advance(stateKey, prior, sample, sampleDigest) {
|
|
212
|
+
const metrics = {};
|
|
213
|
+
for (const metric of SEMANTIC_GUARANTEE_METRICS_V1) {
|
|
214
|
+
const existing = prior?.metrics[metric] ?? emptyAccumulator();
|
|
215
|
+
metrics[metric] = advanceAccumulator(existing, sample.metrics[VECTOR_KEYS[metric]], this.policy.metrics[metric]);
|
|
216
|
+
}
|
|
217
|
+
const revision = (prior?.revision ?? 0) + 1;
|
|
218
|
+
const body = {
|
|
219
|
+
format: ANYTIME_SEMANTIC_GUARANTEE_STATE_FORMAT_V1,
|
|
220
|
+
schemaVersion: 1,
|
|
221
|
+
stateKey,
|
|
222
|
+
policyDigest: this.policy.policyDigest,
|
|
223
|
+
assumptionsDigest: this.policy.assumptions.assumptionsDigest,
|
|
224
|
+
revision,
|
|
225
|
+
sampleCount: (prior?.sampleCount ?? 0) + 1,
|
|
226
|
+
sequenceHighWater: sample.sequence,
|
|
227
|
+
logicalTimeHighWaterMs: sample.logicalTimeMs,
|
|
228
|
+
lastSampleDigest: sampleDigest,
|
|
229
|
+
metrics,
|
|
230
|
+
recentEvidenceDigests: appendRecentUnique(prior?.recentEvidenceDigests ?? [], sample.assessmentDigest, this.policy.maximumRetainedEvidenceDigests),
|
|
231
|
+
predecessorStateDigest: prior?.stateDigest ?? null,
|
|
232
|
+
};
|
|
233
|
+
return deepFreeze({
|
|
234
|
+
...body,
|
|
235
|
+
stateDigest: digestSemanticGuarantee("state", body),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
#guaranteeFrom(state) {
|
|
239
|
+
const metric = (key) => boundFor(state.metrics[key], this.policy.metrics[key]);
|
|
240
|
+
return deepFreeze({
|
|
241
|
+
schemaVersion: 1,
|
|
242
|
+
stateKey: state.stateKey,
|
|
243
|
+
policyDigest: state.policyDigest,
|
|
244
|
+
assumptionsDigest: state.assumptionsDigest,
|
|
245
|
+
stateDigest: state.stateDigest,
|
|
246
|
+
throughSequence: state.sequenceHighWater,
|
|
247
|
+
throughLogicalTimeMs: state.logicalTimeHighWaterMs,
|
|
248
|
+
familywiseErrorBudgetPpm: this.policy.familywiseErrorBudgetPpm,
|
|
249
|
+
simultaneousConfidenceBasisPoints: Math.floor((1 - this.policy.familywiseErrorBudgetPpm / 1_000_000) * 10_000),
|
|
250
|
+
minimumInferenceSamples: this.policy.minimumInferenceSamples,
|
|
251
|
+
estimand: "average_conditional_mean",
|
|
252
|
+
roleCoherence: metric("roleCoherence"),
|
|
253
|
+
missionAlignment: metric("missionAlignment"),
|
|
254
|
+
contextConflict: metric("contextConflict"),
|
|
255
|
+
uncertainty: metric("uncertainty"),
|
|
256
|
+
courseActionDiversity: metric("courseActionDiversity"),
|
|
257
|
+
courseActionNovelty: metric("courseActionNovelty"),
|
|
258
|
+
evidenceDigests: state.recentEvidenceDigests,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
async #assertStateAndAnchor(stateKey, state) {
|
|
262
|
+
const anchor = await this.#readAnchor(stateKey);
|
|
263
|
+
if (!state) {
|
|
264
|
+
if (anchor)
|
|
265
|
+
throw new TypeError("semantic_guarantee_state_missing_below_anchor");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
validateState(state, stateKey, this.policy);
|
|
269
|
+
if (!anchor ||
|
|
270
|
+
anchor.revision !== state.revision ||
|
|
271
|
+
anchor.sequenceHighWater !== state.sequenceHighWater ||
|
|
272
|
+
anchor.logicalTimeHighWaterMs !== state.logicalTimeHighWaterMs ||
|
|
273
|
+
anchor.stateDigest !== state.stateDigest)
|
|
274
|
+
throw new TypeError("semantic_guarantee_state_anchor_mismatch");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/** Nominal check for the concrete time-uniform guarantee engine. */
|
|
278
|
+
export function isAnytimeSemanticGuaranteeEngineV1(value) {
|
|
279
|
+
return (typeof value === "object" &&
|
|
280
|
+
value !== null &&
|
|
281
|
+
anytimeSemanticGuaranteeEngineInvokersV1.has(value));
|
|
282
|
+
}
|
|
283
|
+
/** Appends through the construction-time engine closure. */
|
|
284
|
+
export function invokeAnytimeSemanticGuaranteeAppendV1(engine, input) {
|
|
285
|
+
const invoke = typeof engine === "object" && engine !== null
|
|
286
|
+
? anytimeSemanticGuaranteeEngineInvokersV1.get(engine)?.append
|
|
287
|
+
: undefined;
|
|
288
|
+
if (!invoke)
|
|
289
|
+
throw new TypeError("concrete_anytime_semantic_guarantee_engine_required");
|
|
290
|
+
return invoke(input);
|
|
291
|
+
}
|
|
292
|
+
/** Reads through the construction-time engine closure. */
|
|
293
|
+
export function invokeAnytimeSemanticGuaranteeCurrentV1(engine, stateKey) {
|
|
294
|
+
const invoke = typeof engine === "object" && engine !== null
|
|
295
|
+
? anytimeSemanticGuaranteeEngineInvokersV1.get(engine)?.current
|
|
296
|
+
: undefined;
|
|
297
|
+
if (!invoke)
|
|
298
|
+
throw new TypeError("concrete_anytime_semantic_guarantee_engine_required");
|
|
299
|
+
return invoke(stateKey);
|
|
300
|
+
}
|
|
301
|
+
/** Exhaustively validates a portable guarantee before it can authorize control. */
|
|
302
|
+
export function validateAnytimeSemanticGuaranteeV1(input) {
|
|
303
|
+
exactKeys(input, [
|
|
304
|
+
"assumptionsDigest",
|
|
305
|
+
"contextConflict",
|
|
306
|
+
"courseActionDiversity",
|
|
307
|
+
"courseActionNovelty",
|
|
308
|
+
"estimand",
|
|
309
|
+
"evidenceDigests",
|
|
310
|
+
"familywiseErrorBudgetPpm",
|
|
311
|
+
"minimumInferenceSamples",
|
|
312
|
+
"missionAlignment",
|
|
313
|
+
"policyDigest",
|
|
314
|
+
"roleCoherence",
|
|
315
|
+
"schemaVersion",
|
|
316
|
+
"simultaneousConfidenceBasisPoints",
|
|
317
|
+
"stateDigest",
|
|
318
|
+
"stateKey",
|
|
319
|
+
"throughLogicalTimeMs",
|
|
320
|
+
"throughSequence",
|
|
321
|
+
"uncertainty",
|
|
322
|
+
], "semantic guarantee");
|
|
323
|
+
if (input.schemaVersion !== 1)
|
|
324
|
+
throw new TypeError("semantic_guarantee_schema_invalid");
|
|
325
|
+
identifier(input.stateKey, "guarantee.stateKey");
|
|
326
|
+
digest(input.policyDigest, "guarantee.policyDigest");
|
|
327
|
+
digest(input.assumptionsDigest, "guarantee.assumptionsDigest");
|
|
328
|
+
digest(input.stateDigest, "guarantee.stateDigest");
|
|
329
|
+
integer(input.throughSequence, "guarantee.throughSequence", 1, Number.MAX_SAFE_INTEGER);
|
|
330
|
+
integer(input.throughLogicalTimeMs, "guarantee.throughLogicalTimeMs", 0, Number.MAX_SAFE_INTEGER);
|
|
331
|
+
integer(input.familywiseErrorBudgetPpm, "guarantee.familywiseErrorBudgetPpm", 1, 499_999);
|
|
332
|
+
const confidence = Math.floor((1 - input.familywiseErrorBudgetPpm / 1_000_000) * 10_000);
|
|
333
|
+
if (input.simultaneousConfidenceBasisPoints !== confidence)
|
|
334
|
+
throw new TypeError("semantic_guarantee_confidence_invalid");
|
|
335
|
+
integer(input.minimumInferenceSamples, "guarantee.minimumInferenceSamples", 1, 1_000_000_000);
|
|
336
|
+
if (input.estimand !== "average_conditional_mean")
|
|
337
|
+
throw new TypeError("semantic_guarantee_estimand_invalid");
|
|
338
|
+
uniqueDigests(input.evidenceDigests, "guarantee.evidenceDigests", false);
|
|
339
|
+
let sampleCount = null;
|
|
340
|
+
let allocatedErrorPpm = 0;
|
|
341
|
+
for (const metric of SEMANTIC_GUARANTEE_METRICS_V1) {
|
|
342
|
+
const bound = input[metric];
|
|
343
|
+
exactKeys(bound, [
|
|
344
|
+
"currentLookErrorBudgetPpm",
|
|
345
|
+
"errorBudgetPpm",
|
|
346
|
+
"inferenceCount",
|
|
347
|
+
"latestWasMissing",
|
|
348
|
+
"lowerBasisPoints",
|
|
349
|
+
"meanBasisPoints",
|
|
350
|
+
"missingCount",
|
|
351
|
+
"missingness",
|
|
352
|
+
"reportedCount",
|
|
353
|
+
"sumBasisPoints",
|
|
354
|
+
"upperBasisPoints",
|
|
355
|
+
], `semantic guarantee ${metric}`);
|
|
356
|
+
integer(bound.reportedCount, `${metric}.reportedCount`, 0, Number.MAX_SAFE_INTEGER);
|
|
357
|
+
integer(bound.missingCount, `${metric}.missingCount`, 0, Number.MAX_SAFE_INTEGER);
|
|
358
|
+
const total = bound.reportedCount + bound.missingCount;
|
|
359
|
+
if (!Number.isSafeInteger(total))
|
|
360
|
+
throw new TypeError(`semantic_guarantee_${metric}_count_invalid`);
|
|
361
|
+
if (sampleCount === null)
|
|
362
|
+
sampleCount = total;
|
|
363
|
+
else if (sampleCount !== total)
|
|
364
|
+
throw new TypeError("semantic_guarantee_metric_sample_counts_differ");
|
|
365
|
+
integer(bound.inferenceCount, `${metric}.inferenceCount`, 0, total);
|
|
366
|
+
if (!["fail_closed", "predictable_skip", "worst_case_imputation"].includes(bound.missingness) ||
|
|
367
|
+
typeof bound.latestWasMissing !== "boolean")
|
|
368
|
+
throw new TypeError(`semantic_guarantee_${metric}_missingness_invalid`);
|
|
369
|
+
const expectedInferenceCount = bound.missingness === "worst_case_imputation"
|
|
370
|
+
? total
|
|
371
|
+
: bound.reportedCount;
|
|
372
|
+
if (bound.inferenceCount !== expectedInferenceCount)
|
|
373
|
+
throw new TypeError(`semantic_guarantee_${metric}_inference_count_invalid`);
|
|
374
|
+
integer(bound.sumBasisPoints, `${metric}.sumBasisPoints`, 0, bound.inferenceCount * 10_000);
|
|
375
|
+
integer(bound.errorBudgetPpm, `${metric}.errorBudgetPpm`, 1, 499_999);
|
|
376
|
+
allocatedErrorPpm += bound.errorBudgetPpm;
|
|
377
|
+
validateMetricBound(bound, metric);
|
|
378
|
+
}
|
|
379
|
+
if (allocatedErrorPpm > input.familywiseErrorBudgetPpm)
|
|
380
|
+
throw new TypeError("semantic_guarantee_error_budget_invalid");
|
|
381
|
+
return deepFreeze(input);
|
|
382
|
+
}
|
|
383
|
+
/** Exhaustively validates a horizon decision and its exact guarantee binding. */
|
|
384
|
+
export function validateSemanticHorizonDecisionV1(input, guarantee) {
|
|
385
|
+
validateAnytimeSemanticGuaranteeV1(guarantee);
|
|
386
|
+
exactKeys(input, [
|
|
387
|
+
"assumptionsDigest",
|
|
388
|
+
"controlPolicyDigest",
|
|
389
|
+
"directive",
|
|
390
|
+
"guaranteeStateDigest",
|
|
391
|
+
"policyDigest",
|
|
392
|
+
"reasonCodes",
|
|
393
|
+
"recommendedHorizonSteps",
|
|
394
|
+
"replanRequired",
|
|
395
|
+
"schemaVersion",
|
|
396
|
+
], "semantic horizon decision");
|
|
397
|
+
if (input.schemaVersion !== 1 ||
|
|
398
|
+
!["continue", "shorten_horizon", "replan", "safe_stop"].includes(input.directive))
|
|
399
|
+
throw new TypeError("semantic_horizon_decision_directive_invalid");
|
|
400
|
+
integer(input.recommendedHorizonSteps, "semanticHorizon.recommendedHorizonSteps", 0, 1_000_000_000);
|
|
401
|
+
if ((input.directive === "safe_stop" && input.recommendedHorizonSteps !== 0) ||
|
|
402
|
+
((input.directive === "continue" ||
|
|
403
|
+
input.directive === "shorten_horizon") &&
|
|
404
|
+
input.recommendedHorizonSteps === 0))
|
|
405
|
+
throw new TypeError("semantic_horizon_decision_horizon_invalid");
|
|
406
|
+
const replanRequired = input.directive === "replan" || input.directive === "safe_stop";
|
|
407
|
+
if (input.replanRequired !== replanRequired)
|
|
408
|
+
throw new TypeError("semantic_horizon_decision_replan_invalid");
|
|
409
|
+
digest(input.guaranteeStateDigest, "decision.guaranteeStateDigest");
|
|
410
|
+
digest(input.policyDigest, "decision.policyDigest");
|
|
411
|
+
digest(input.assumptionsDigest, "decision.assumptionsDigest");
|
|
412
|
+
digest(input.controlPolicyDigest, "decision.controlPolicyDigest");
|
|
413
|
+
if (input.guaranteeStateDigest !== guarantee.stateDigest ||
|
|
414
|
+
input.policyDigest !== guarantee.policyDigest ||
|
|
415
|
+
input.assumptionsDigest !== guarantee.assumptionsDigest)
|
|
416
|
+
throw new TypeError("semantic_horizon_decision_binding_invalid");
|
|
417
|
+
uniqueTokens(input.reasonCodes, "decision.reasonCodes");
|
|
418
|
+
return deepFreeze(input);
|
|
419
|
+
}
|
|
420
|
+
export function createSemanticHorizonControlPolicyV1(input) {
|
|
421
|
+
const expectedGuaranteePolicyDigest = input.expectedGuaranteePolicyDigest;
|
|
422
|
+
const expectedAssumptionsDigest = input.expectedAssumptionsDigest;
|
|
423
|
+
const nominalHorizonSteps = input.nominalHorizonSteps;
|
|
424
|
+
const cautionHorizonSteps = input.cautionHorizonSteps;
|
|
425
|
+
const replanHorizonSteps = input.replanHorizonSteps;
|
|
426
|
+
const thresholdInputs = input.thresholds;
|
|
427
|
+
digest(expectedGuaranteePolicyDigest, "expectedGuaranteePolicyDigest");
|
|
428
|
+
digest(expectedAssumptionsDigest, "expectedAssumptionsDigest");
|
|
429
|
+
integer(nominalHorizonSteps, "nominalHorizonSteps", 1, 1_000_000_000);
|
|
430
|
+
integer(cautionHorizonSteps, "cautionHorizonSteps", 1, nominalHorizonSteps);
|
|
431
|
+
integer(replanHorizonSteps, "replanHorizonSteps", 0, cautionHorizonSteps);
|
|
432
|
+
const thresholds = {};
|
|
433
|
+
for (const metric of SEMANTIC_GUARANTEE_METRICS_V1) {
|
|
434
|
+
const threshold = thresholdInputs[metric];
|
|
435
|
+
if (!threshold)
|
|
436
|
+
throw new TypeError(`semantic_horizon_${metric}_threshold_invalid`);
|
|
437
|
+
const thresholdBasisPoints = threshold.thresholdBasisPoints;
|
|
438
|
+
const enabled = threshold.enabled;
|
|
439
|
+
if (typeof enabled !== "boolean")
|
|
440
|
+
throw new TypeError(`semantic_horizon_${metric}_threshold_invalid`);
|
|
441
|
+
integer(thresholdBasisPoints, `${metric}.thresholdBasisPoints`, 0, 10_000);
|
|
442
|
+
thresholds[metric] = Object.freeze({
|
|
443
|
+
thresholdBasisPoints,
|
|
444
|
+
enabled,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
const body = {
|
|
448
|
+
schemaVersion: 1,
|
|
449
|
+
expectedGuaranteePolicyDigest,
|
|
450
|
+
expectedAssumptionsDigest,
|
|
451
|
+
nominalHorizonSteps,
|
|
452
|
+
cautionHorizonSteps,
|
|
453
|
+
replanHorizonSteps,
|
|
454
|
+
thresholds,
|
|
455
|
+
};
|
|
456
|
+
return deepFreeze({
|
|
457
|
+
...body,
|
|
458
|
+
controlPolicyDigest: digestSemanticGuarantee("horizon-control-policy", body),
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
export function createSemanticHorizonControlV1(raw) {
|
|
462
|
+
const policy = validateHorizonPolicy(raw);
|
|
463
|
+
const decide = (guarantee) => {
|
|
464
|
+
if (guarantee.policyDigest !== policy.expectedGuaranteePolicyDigest ||
|
|
465
|
+
guarantee.assumptionsDigest !== policy.expectedAssumptionsDigest)
|
|
466
|
+
throw new TypeError("semantic_horizon_guarantee_binding_invalid");
|
|
467
|
+
const missingFailClosed = [];
|
|
468
|
+
const insufficient = [];
|
|
469
|
+
const provenViolation = [];
|
|
470
|
+
const notYetGuaranteed = [];
|
|
471
|
+
for (const metric of SEMANTIC_GUARANTEE_METRICS_V1) {
|
|
472
|
+
const threshold = policy.thresholds[metric];
|
|
473
|
+
if (!threshold.enabled)
|
|
474
|
+
continue;
|
|
475
|
+
const bound = guarantee[metric];
|
|
476
|
+
if (bound.missingCount > 0 && bound.missingness === "fail_closed") {
|
|
477
|
+
missingFailClosed.push(`${metric}_missing_fail_closed`);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (bound.inferenceCount === 0 ||
|
|
481
|
+
bound.inferenceCount < policyMinimum(guarantee, metric)) {
|
|
482
|
+
insufficient.push(`${metric}_insufficient_evidence`);
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (bound.lowerBasisPoints === null || bound.upperBasisPoints === null) {
|
|
486
|
+
insufficient.push(`${metric}_bound_unavailable`);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (EXPECTED_DIRECTIONS[metric] === "higher_is_better") {
|
|
490
|
+
if (bound.upperBasisPoints < threshold.thresholdBasisPoints)
|
|
491
|
+
provenViolation.push(`${metric}_threshold_violated`);
|
|
492
|
+
else if (bound.lowerBasisPoints < threshold.thresholdBasisPoints)
|
|
493
|
+
notYetGuaranteed.push(`${metric}_threshold_not_guaranteed`);
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
if (bound.lowerBasisPoints > threshold.thresholdBasisPoints)
|
|
497
|
+
provenViolation.push(`${metric}_threshold_violated`);
|
|
498
|
+
else if (bound.upperBasisPoints > threshold.thresholdBasisPoints)
|
|
499
|
+
notYetGuaranteed.push(`${metric}_threshold_not_guaranteed`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
let directive;
|
|
503
|
+
let recommendedHorizonSteps;
|
|
504
|
+
if (missingFailClosed.length) {
|
|
505
|
+
directive = "safe_stop";
|
|
506
|
+
recommendedHorizonSteps = 0;
|
|
507
|
+
}
|
|
508
|
+
else if (provenViolation.length) {
|
|
509
|
+
directive = "replan";
|
|
510
|
+
recommendedHorizonSteps = policy.replanHorizonSteps;
|
|
511
|
+
}
|
|
512
|
+
else if (insufficient.length || notYetGuaranteed.length) {
|
|
513
|
+
directive = "shorten_horizon";
|
|
514
|
+
recommendedHorizonSteps = policy.cautionHorizonSteps;
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
directive = "continue";
|
|
518
|
+
recommendedHorizonSteps = policy.nominalHorizonSteps;
|
|
519
|
+
}
|
|
520
|
+
const reasonCodes = [
|
|
521
|
+
...missingFailClosed,
|
|
522
|
+
...provenViolation,
|
|
523
|
+
...insufficient,
|
|
524
|
+
...notYetGuaranteed,
|
|
525
|
+
].sort();
|
|
526
|
+
if (!reasonCodes.length)
|
|
527
|
+
reasonCodes.push("semantic_confidence_sequences_within_thresholds");
|
|
528
|
+
return deepFreeze({
|
|
529
|
+
schemaVersion: 1,
|
|
530
|
+
directive,
|
|
531
|
+
recommendedHorizonSteps,
|
|
532
|
+
replanRequired: directive === "replan" || directive === "safe_stop",
|
|
533
|
+
reasonCodes,
|
|
534
|
+
guaranteeStateDigest: guarantee.stateDigest,
|
|
535
|
+
policyDigest: guarantee.policyDigest,
|
|
536
|
+
assumptionsDigest: guarantee.assumptionsDigest,
|
|
537
|
+
controlPolicyDigest: policy.controlPolicyDigest,
|
|
538
|
+
});
|
|
539
|
+
};
|
|
540
|
+
const control = Object.freeze({
|
|
541
|
+
decide(guarantee) {
|
|
542
|
+
return invokeSemanticHorizonControlV1(control, guarantee);
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
semanticHorizonControlInvokersV1.set(control, decide);
|
|
546
|
+
return control;
|
|
547
|
+
}
|
|
548
|
+
/** Nominal check for a library-created semantic horizon control. */
|
|
549
|
+
export function isSemanticHorizonControlV1(value) {
|
|
550
|
+
return (typeof value === "object" &&
|
|
551
|
+
value !== null &&
|
|
552
|
+
semanticHorizonControlInvokersV1.has(value));
|
|
553
|
+
}
|
|
554
|
+
/** Decides through the immutable policy closure. */
|
|
555
|
+
export function invokeSemanticHorizonControlV1(control, guarantee) {
|
|
556
|
+
const invoke = typeof control === "object" && control !== null
|
|
557
|
+
? semanticHorizonControlInvokersV1.get(control)
|
|
558
|
+
: undefined;
|
|
559
|
+
if (!invoke)
|
|
560
|
+
throw new TypeError("concrete_semantic_horizon_control_required");
|
|
561
|
+
validateAnytimeSemanticGuaranteeV1(guarantee);
|
|
562
|
+
return validateSemanticHorizonDecisionV1(invoke(guarantee), guarantee);
|
|
563
|
+
}
|
|
564
|
+
function policyMinimum(guarantee, _metric) {
|
|
565
|
+
return guarantee.minimumInferenceSamples;
|
|
566
|
+
}
|
|
567
|
+
function boundFor(accumulator, policy) {
|
|
568
|
+
const count = accumulator.inferenceCount;
|
|
569
|
+
if (count === 0)
|
|
570
|
+
return deepFreeze({
|
|
571
|
+
...accumulator,
|
|
572
|
+
meanBasisPoints: null,
|
|
573
|
+
lowerBasisPoints: null,
|
|
574
|
+
upperBasisPoints: null,
|
|
575
|
+
errorBudgetPpm: policy.errorBudgetPpm,
|
|
576
|
+
currentLookErrorBudgetPpm: null,
|
|
577
|
+
missingness: policy.missingness,
|
|
578
|
+
});
|
|
579
|
+
const exactMean = accumulator.sumBasisPoints / count;
|
|
580
|
+
const mean = Math.round(exactMean);
|
|
581
|
+
const lookErrorBudgetPpm = policy.errorBudgetPpm / count / (count + 1);
|
|
582
|
+
const alpha = lookErrorBudgetPpm / 1_000_000;
|
|
583
|
+
const margin = 10_000 * Math.sqrt(Math.log(2 / alpha) / (2 * count));
|
|
584
|
+
return deepFreeze({
|
|
585
|
+
...accumulator,
|
|
586
|
+
meanBasisPoints: mean,
|
|
587
|
+
lowerBasisPoints: clampBps(Math.floor(exactMean - margin)),
|
|
588
|
+
upperBasisPoints: clampBps(Math.ceil(exactMean + margin)),
|
|
589
|
+
errorBudgetPpm: policy.errorBudgetPpm,
|
|
590
|
+
currentLookErrorBudgetPpm: lookErrorBudgetPpm,
|
|
591
|
+
missingness: policy.missingness,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
function advanceAccumulator(prior, value, policy) {
|
|
595
|
+
if (value !== null)
|
|
596
|
+
return deepFreeze({
|
|
597
|
+
reportedCount: prior.reportedCount + 1,
|
|
598
|
+
missingCount: prior.missingCount,
|
|
599
|
+
inferenceCount: prior.inferenceCount + 1,
|
|
600
|
+
sumBasisPoints: prior.sumBasisPoints + value,
|
|
601
|
+
latestWasMissing: false,
|
|
602
|
+
});
|
|
603
|
+
const impute = policy.missingness === "worst_case_imputation";
|
|
604
|
+
const worst = policy.direction === "higher_is_better" ? 0 : 10_000;
|
|
605
|
+
return deepFreeze({
|
|
606
|
+
reportedCount: prior.reportedCount,
|
|
607
|
+
missingCount: prior.missingCount + 1,
|
|
608
|
+
inferenceCount: prior.inferenceCount + (impute ? 1 : 0),
|
|
609
|
+
sumBasisPoints: prior.sumBasisPoints + (impute ? worst : 0),
|
|
610
|
+
latestWasMissing: true,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
function emptyAccumulator() {
|
|
614
|
+
return Object.freeze({
|
|
615
|
+
reportedCount: 0,
|
|
616
|
+
missingCount: 0,
|
|
617
|
+
inferenceCount: 0,
|
|
618
|
+
sumBasisPoints: 0,
|
|
619
|
+
latestWasMissing: false,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
function validateState(state, stateKey, policy) {
|
|
623
|
+
if (state.format !== ANYTIME_SEMANTIC_GUARANTEE_STATE_FORMAT_V1 ||
|
|
624
|
+
state.schemaVersion !== 1 ||
|
|
625
|
+
state.stateKey !== stateKey ||
|
|
626
|
+
state.policyDigest !== policy.policyDigest ||
|
|
627
|
+
state.assumptionsDigest !== policy.assumptions.assumptionsDigest)
|
|
628
|
+
throw new TypeError("semantic_guarantee_state_binding_invalid");
|
|
629
|
+
integer(state.revision, "state.revision", 1, Number.MAX_SAFE_INTEGER);
|
|
630
|
+
integer(state.sampleCount, "state.sampleCount", 1, Number.MAX_SAFE_INTEGER);
|
|
631
|
+
if (state.revision !== state.sampleCount)
|
|
632
|
+
throw new TypeError("semantic_guarantee_state_revision_invalid");
|
|
633
|
+
integer(state.sequenceHighWater, "state.sequenceHighWater", 1, Number.MAX_SAFE_INTEGER);
|
|
634
|
+
integer(state.logicalTimeHighWaterMs, "state.logicalTimeHighWaterMs", 0, Number.MAX_SAFE_INTEGER);
|
|
635
|
+
digest(state.lastSampleDigest, "state.lastSampleDigest");
|
|
636
|
+
if (state.predecessorStateDigest !== null)
|
|
637
|
+
digest(state.predecessorStateDigest, "state.predecessorStateDigest");
|
|
638
|
+
if (state.recentEvidenceDigests.length > policy.maximumRetainedEvidenceDigests)
|
|
639
|
+
throw new TypeError("semantic_guarantee_evidence_bound_exceeded");
|
|
640
|
+
uniqueDigests(state.recentEvidenceDigests, "state.recentEvidenceDigests", false);
|
|
641
|
+
for (const metric of SEMANTIC_GUARANTEE_METRICS_V1) {
|
|
642
|
+
const value = state.metrics[metric];
|
|
643
|
+
if (!value)
|
|
644
|
+
throw new TypeError(`semantic_guarantee_${metric}_state_missing`);
|
|
645
|
+
integer(value.reportedCount, `${metric}.reportedCount`, 0, state.sampleCount);
|
|
646
|
+
integer(value.missingCount, `${metric}.missingCount`, 0, state.sampleCount);
|
|
647
|
+
integer(value.inferenceCount, `${metric}.inferenceCount`, 0, state.sampleCount);
|
|
648
|
+
integer(value.sumBasisPoints, `${metric}.sumBasisPoints`, 0, value.inferenceCount * 10_000);
|
|
649
|
+
if (value.reportedCount + value.missingCount !== state.sampleCount)
|
|
650
|
+
throw new TypeError(`semantic_guarantee_${metric}_count_invalid`);
|
|
651
|
+
const expectedInferenceCount = policy.metrics[metric].missingness === "worst_case_imputation"
|
|
652
|
+
? state.sampleCount
|
|
653
|
+
: value.reportedCount;
|
|
654
|
+
if (value.inferenceCount !== expectedInferenceCount)
|
|
655
|
+
throw new TypeError(`semantic_guarantee_${metric}_inference_count_invalid`);
|
|
656
|
+
}
|
|
657
|
+
const { stateDigest, ...body } = state;
|
|
658
|
+
digest(stateDigest, "state.stateDigest");
|
|
659
|
+
if (digestSemanticGuarantee("state", body) !== stateDigest)
|
|
660
|
+
throw new TypeError("semantic_guarantee_state_digest_invalid");
|
|
661
|
+
}
|
|
662
|
+
function validatePolicy(policy) {
|
|
663
|
+
const schemaVersion = policy.schemaVersion;
|
|
664
|
+
const policyDigest = policy.policyDigest;
|
|
665
|
+
const assumptions = policy.assumptions;
|
|
666
|
+
const recreated = createAnytimeSemanticGuaranteePolicyV1({
|
|
667
|
+
policyId: policy.policyId,
|
|
668
|
+
familywiseErrorBudgetPpm: policy.familywiseErrorBudgetPpm,
|
|
669
|
+
minimumInferenceSamples: policy.minimumInferenceSamples,
|
|
670
|
+
maximumRetainedEvidenceDigests: policy.maximumRetainedEvidenceDigests,
|
|
671
|
+
maximumCasAttempts: policy.maximumCasAttempts,
|
|
672
|
+
metrics: policy.metrics,
|
|
673
|
+
assumptions: {
|
|
674
|
+
assumptionEvidenceDigests: assumptions.assumptionEvidenceDigests,
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
if (schemaVersion !== 1 ||
|
|
678
|
+
policy.boundMethod !== "hoeffding_azuma_two_sided" ||
|
|
679
|
+
policy.spendingSchedule !== "inverse_quadratic_telescoping" ||
|
|
680
|
+
policyDigest !== recreated.policyDigest ||
|
|
681
|
+
assumptions.assumptionsDigest !== recreated.assumptions.assumptionsDigest)
|
|
682
|
+
throw new TypeError("semantic_guarantee_policy_digest_invalid");
|
|
683
|
+
return recreated;
|
|
684
|
+
}
|
|
685
|
+
function validateHorizonPolicy(policy) {
|
|
686
|
+
const schemaVersion = policy.schemaVersion;
|
|
687
|
+
const controlPolicyDigest = policy.controlPolicyDigest;
|
|
688
|
+
const recreated = createSemanticHorizonControlPolicyV1({
|
|
689
|
+
expectedGuaranteePolicyDigest: policy.expectedGuaranteePolicyDigest,
|
|
690
|
+
expectedAssumptionsDigest: policy.expectedAssumptionsDigest,
|
|
691
|
+
nominalHorizonSteps: policy.nominalHorizonSteps,
|
|
692
|
+
cautionHorizonSteps: policy.cautionHorizonSteps,
|
|
693
|
+
replanHorizonSteps: policy.replanHorizonSteps,
|
|
694
|
+
thresholds: policy.thresholds,
|
|
695
|
+
});
|
|
696
|
+
if (schemaVersion !== 1 ||
|
|
697
|
+
controlPolicyDigest !== recreated.controlPolicyDigest)
|
|
698
|
+
throw new TypeError("semantic_horizon_control_policy_digest_invalid");
|
|
699
|
+
return recreated;
|
|
700
|
+
}
|
|
701
|
+
function validateSample(sample) {
|
|
702
|
+
if (!sample ||
|
|
703
|
+
typeof sample !== "object" ||
|
|
704
|
+
!sample.metrics ||
|
|
705
|
+
typeof sample.metrics !== "object")
|
|
706
|
+
throw new TypeError("semantic_guarantee_sample_invalid");
|
|
707
|
+
integer(sample.sequence, "sample.sequence", 1, Number.MAX_SAFE_INTEGER);
|
|
708
|
+
integer(sample.logicalTimeMs, "sample.logicalTimeMs", 0, Number.MAX_SAFE_INTEGER);
|
|
709
|
+
digest(sample.assessmentDigest, "sample.assessmentDigest");
|
|
710
|
+
for (const key of Object.values(VECTOR_KEYS)) {
|
|
711
|
+
const value = sample.metrics[key];
|
|
712
|
+
if (value !== null)
|
|
713
|
+
integer(value, `sample.metrics.${key}`, 0, 10_000);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function immutableSemanticGuaranteeValue(value) {
|
|
717
|
+
return {
|
|
718
|
+
value,
|
|
719
|
+
writable: false,
|
|
720
|
+
configurable: false,
|
|
721
|
+
enumerable: true,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
function validateMetricBound(bound, metric) {
|
|
725
|
+
if (bound.missingCount === 0 && bound.latestWasMissing)
|
|
726
|
+
throw new TypeError(`semantic_guarantee_${metric}_latest_missing_invalid`);
|
|
727
|
+
if (bound.inferenceCount === 0) {
|
|
728
|
+
if (bound.sumBasisPoints !== 0 ||
|
|
729
|
+
bound.meanBasisPoints !== null ||
|
|
730
|
+
bound.lowerBasisPoints !== null ||
|
|
731
|
+
bound.upperBasisPoints !== null ||
|
|
732
|
+
bound.currentLookErrorBudgetPpm !== null)
|
|
733
|
+
throw new TypeError(`semantic_guarantee_${metric}_empty_bound_invalid`);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const count = bound.inferenceCount;
|
|
737
|
+
const exactMean = bound.sumBasisPoints / count;
|
|
738
|
+
const expectedMean = Math.round(exactMean);
|
|
739
|
+
const expectedLook = bound.errorBudgetPpm / count / (count + 1);
|
|
740
|
+
if (typeof bound.currentLookErrorBudgetPpm !== "number" ||
|
|
741
|
+
!Number.isFinite(bound.currentLookErrorBudgetPpm) ||
|
|
742
|
+
bound.currentLookErrorBudgetPpm !== expectedLook)
|
|
743
|
+
throw new TypeError(`semantic_guarantee_${metric}_look_budget_invalid`);
|
|
744
|
+
const alpha = expectedLook / 1_000_000;
|
|
745
|
+
const margin = 10_000 * Math.sqrt(Math.log(2 / alpha) / (2 * count));
|
|
746
|
+
const expectedLower = clampBps(Math.floor(exactMean - margin));
|
|
747
|
+
const expectedUpper = clampBps(Math.ceil(exactMean + margin));
|
|
748
|
+
if (bound.meanBasisPoints !== expectedMean ||
|
|
749
|
+
bound.lowerBasisPoints !== expectedLower ||
|
|
750
|
+
bound.upperBasisPoints !== expectedUpper)
|
|
751
|
+
throw new TypeError(`semantic_guarantee_${metric}_bound_invalid`);
|
|
752
|
+
}
|
|
753
|
+
function exactKeys(value, expected, name) {
|
|
754
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
755
|
+
throw new TypeError(`${name}_invalid`);
|
|
756
|
+
const actual = Object.keys(value).sort();
|
|
757
|
+
const canonical = [...expected].sort();
|
|
758
|
+
if (actual.length !== canonical.length ||
|
|
759
|
+
actual.some((key, index) => key !== canonical[index]))
|
|
760
|
+
throw new TypeError(`${name}_keys_invalid`);
|
|
761
|
+
}
|
|
762
|
+
function uniqueTokens(values, name) {
|
|
763
|
+
if (!Array.isArray(values) || values.length < 1 || values.length > 128)
|
|
764
|
+
throw new TypeError(`${name}_invalid`);
|
|
765
|
+
const prior = new Set();
|
|
766
|
+
for (const value of values) {
|
|
767
|
+
identifier(value, name);
|
|
768
|
+
if (prior.has(value))
|
|
769
|
+
throw new TypeError(`${name}_contains_duplicates`);
|
|
770
|
+
prior.add(value);
|
|
771
|
+
}
|
|
772
|
+
if (values.some((value, index) => index > 0 && values[index - 1] > value))
|
|
773
|
+
throw new TypeError(`${name}_not_canonical`);
|
|
774
|
+
}
|
|
775
|
+
function digestSemanticGuarantee(domain, value) {
|
|
776
|
+
return digestControlJsonV1("state", {
|
|
777
|
+
semanticGuaranteeDomain: domain,
|
|
778
|
+
value,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
function uniqueDigests(values, name, sort = true) {
|
|
782
|
+
if (!Array.isArray(values))
|
|
783
|
+
throw new TypeError(`${name}_must_be_array`);
|
|
784
|
+
for (const value of values)
|
|
785
|
+
digest(value, name);
|
|
786
|
+
const unique = [...new Set(values)];
|
|
787
|
+
if (unique.length !== values.length)
|
|
788
|
+
throw new TypeError(`${name}_contains_duplicates`);
|
|
789
|
+
return Object.freeze(sort ? unique.sort() : unique);
|
|
790
|
+
}
|
|
791
|
+
function appendRecentUnique(prior, next, maximum) {
|
|
792
|
+
return Object.freeze([...prior.filter((value) => value !== next), next].slice(-maximum));
|
|
793
|
+
}
|
|
794
|
+
function digest(value, name) {
|
|
795
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(value))
|
|
796
|
+
throw new TypeError(`${name}_invalid`);
|
|
797
|
+
}
|
|
798
|
+
function identifier(value, name) {
|
|
799
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,239}$/.test(value))
|
|
800
|
+
throw new TypeError(`${name}_invalid`);
|
|
801
|
+
}
|
|
802
|
+
function integer(value, name, minimum, maximum) {
|
|
803
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum)
|
|
804
|
+
throw new RangeError(`${name}_invalid`);
|
|
805
|
+
}
|
|
806
|
+
function clampBps(value) {
|
|
807
|
+
return Math.max(0, Math.min(10_000, value));
|
|
808
|
+
}
|
|
809
|
+
function deepFreeze(value) {
|
|
810
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
811
|
+
Object.freeze(value);
|
|
812
|
+
for (const child of Object.values(value))
|
|
813
|
+
deepFreeze(child);
|
|
814
|
+
}
|
|
815
|
+
return value;
|
|
816
|
+
}
|
|
817
|
+
//# sourceMappingURL=anytime-semantic-guarantees.js.map
|