@rulvar/store-conformance 1.249.0 → 1.250.0
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/dist/index.d.ts +25 -2
- package/dist/index.js +632 -13
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JournalEntry, JournalStore, LeasableStore, QuotaRule, TranscriptStore } from "@rulvar/core";
|
|
1
|
+
import { AdmissionScheduler, EffectLaneStore, JournalEntry, JournalStore, LeasableStore, MemoryAdmissionOptions, QuotaRule, TranscriptStore } from "@rulvar/core";
|
|
2
2
|
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
/**
|
|
@@ -32,6 +32,8 @@ interface TestRegistrar {
|
|
|
32
32
|
/** Registers the suite as one `describe` block with one `it` per check. */
|
|
33
33
|
declare function registerConformance(suite: ConformanceSuite, api: TestRegistrar): void;
|
|
34
34
|
declare function makeSuite(name: string, checks: readonly ConformanceCheck[]): ConformanceSuite;
|
|
35
|
+
/** Assertion helper: conformance failures are plain Errors naming the check. */
|
|
36
|
+
declare function ensure(condition: boolean, checkId: string, message: string): asserts condition;
|
|
35
37
|
/** Canonical JSON with recursively sorted keys (fold-state hashing). */
|
|
36
38
|
declare function stableStringify(value: unknown): string;
|
|
37
39
|
//#endregion
|
|
@@ -73,6 +75,27 @@ declare function fencedWritesConformance(mk: StoreFactory<LeasableStore>): Confo
|
|
|
73
75
|
type QuotaLimiterConstructor = (rules: readonly QuotaRule[]) => unknown;
|
|
74
76
|
declare function quotaRulesConformance(mk: QuotaLimiterConstructor): ConformanceSuite;
|
|
75
77
|
//#endregion
|
|
78
|
+
//#region src/effect-lane.d.ts
|
|
79
|
+
/** The store shape under test: the capability plus the restore verb. */
|
|
80
|
+
interface RestorableEffectLaneStore extends EffectLaneStore {
|
|
81
|
+
bumpRestorationGeneration(): Promise<number>;
|
|
82
|
+
}
|
|
83
|
+
declare function effectLaneStoreConformance(factory: StoreFactory<RestorableEffectLaneStore>): ConformanceSuite;
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/admission-matrix.d.ts
|
|
86
|
+
interface AdmissionSchedulerFixture {
|
|
87
|
+
scheduler: AdmissionScheduler;
|
|
88
|
+
/** A NEW holder over the same durable state (the crash rows). */
|
|
89
|
+
reopen(): AdmissionScheduler | Promise<AdmissionScheduler>;
|
|
90
|
+
close?(): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
type AdmissionConfig = Omit<MemoryAdmissionOptions, "state" | "now">;
|
|
93
|
+
interface AdmissionConformanceOptions {
|
|
94
|
+
/** A fresh, isolated scheduler per call, over the config and clock. */
|
|
95
|
+
make(config: AdmissionConfig, now: () => number): AdmissionSchedulerFixture | Promise<AdmissionSchedulerFixture>;
|
|
96
|
+
}
|
|
97
|
+
declare function admissionConformance(options: AdmissionConformanceOptions): ConformanceSuite;
|
|
98
|
+
//#endregion
|
|
76
99
|
//#region src/fenced-transcripts.d.ts
|
|
77
100
|
/**
|
|
78
101
|
* The paired factory product: the transcript store under test plus the
|
|
@@ -515,4 +538,4 @@ declare function foldStateSha256(entries: readonly JournalEntry[]): string;
|
|
|
515
538
|
/** The reference hash; computed once from the kernel fold and frozen. */
|
|
516
539
|
declare const GOLDEN_FOLD_STATE_SHA256 = "81e6ccff549fb3e6c1de4d34ba65b912162eba6f66403b5d5f23a3e1ec69243c";
|
|
517
540
|
//#endregion
|
|
518
|
-
export { type ConformanceCheck, type ConformanceSuite, DEFAULT_SOAK_QUORUM, type FencedTranscriptsFixture, GOLDEN_FOLD_JOURNAL, GOLDEN_FOLD_STATE_SHA256, KILL_POINT_SCENARIOS, type KillPointConformanceOptions, type KillPointEvent, type KillPointExpectation, type KillPointName, type KillPointObservation, type KillPointPhase, type KillPointScenario, type KillPointScenarioOptions, type KillPointTarget, type KillPointWorkerConfig, type KillPointWorkerHooks, type KillPointWorkflowKind, type MultiProcessSoakOptions, type MultiProcessSoakResult, type QuotaLimiterConstructor, type SoakAcceptSurface, type SoakActivity, type SoakEvent, type SoakProbeSurface, type SoakQuorum, type SoakWriterConfig, type SoakWriterHooks, type StoreFactory, type TestRegistrar, countSoakActivity, fencedTranscriptsConformance, fencedWritesConformance, foldStateSha256, journalStoreConformance, killPointConformance, killPointWorkerConfigFromEnv, leasableStoreConformance, makeSuite, materializeFoldState, parseKillPointReport, parseSoakReport, quotaRulesConformance, registerConformance, runKillPointScenario, runKillPointWorker, runMultiProcessSoak, runSoakWriter, soakWriterConfigFromEnv, stableStringify, verifySoakHistory };
|
|
541
|
+
export { type AdmissionConfig, type AdmissionConformanceOptions, type AdmissionSchedulerFixture, type ConformanceCheck, type ConformanceSuite, DEFAULT_SOAK_QUORUM, type FencedTranscriptsFixture, GOLDEN_FOLD_JOURNAL, GOLDEN_FOLD_STATE_SHA256, KILL_POINT_SCENARIOS, type KillPointConformanceOptions, type KillPointEvent, type KillPointExpectation, type KillPointName, type KillPointObservation, type KillPointPhase, type KillPointScenario, type KillPointScenarioOptions, type KillPointTarget, type KillPointWorkerConfig, type KillPointWorkerHooks, type KillPointWorkflowKind, type MultiProcessSoakOptions, type MultiProcessSoakResult, type QuotaLimiterConstructor, type RestorableEffectLaneStore, type SoakAcceptSurface, type SoakActivity, type SoakEvent, type SoakProbeSurface, type SoakQuorum, type SoakWriterConfig, type SoakWriterHooks, type StoreFactory, type TestRegistrar, admissionConformance, countSoakActivity, effectLaneStoreConformance, ensure, fencedTranscriptsConformance, fencedWritesConformance, foldStateSha256, journalStoreConformance, killPointConformance, killPointWorkerConfigFromEnv, leasableStoreConformance, makeSuite, materializeFoldState, parseKillPointReport, parseSoakReport, quotaRulesConformance, registerConformance, runKillPointScenario, runKillPointWorker, runMultiProcessSoak, runSoakWriter, soakWriterConfigFromEnv, stableStringify, verifySoakHistory };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConfigError, JournalOrderViolation, LeaseHeldError, Replayer, ResolutionFold, agentScope, buildDeriverRegistry, createCanonicalIdMinter, createEngine, defineWorkflow, deriveContentKey, dispositionHook, quotaRuleKey, tool } from "@rulvar/core";
|
|
1
|
+
import { CURRENT_HASH_VERSION, ConfigError, JournalOrderViolation, LeaseHeldError, Replayer, ResolutionFold, agentScope, buildDeriverRegistry, createCanonicalIdMinter, createEngine, defineWorkflow, deriveContentKey, dispositionHook, openEffectLane, quotaRuleKey, tool } from "@rulvar/core";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -46,7 +46,7 @@ function stableStringify(value) {
|
|
|
46
46
|
* requires an explicit hashVersion-bump changeset
|
|
47
47
|
* (scripts/check-frozen-fixtures.mjs).
|
|
48
48
|
*/
|
|
49
|
-
function entry(partial) {
|
|
49
|
+
function entry$1(partial) {
|
|
50
50
|
return {
|
|
51
51
|
hashVersion: 2,
|
|
52
52
|
scope: "",
|
|
@@ -72,12 +72,12 @@ function entry(partial) {
|
|
|
72
72
|
* seq 9 abandon of the spawn again: noop (target_abandoned)
|
|
73
73
|
*/
|
|
74
74
|
const GOLDEN_FOLD_JOURNAL = [
|
|
75
|
-
entry({
|
|
75
|
+
entry$1({
|
|
76
76
|
seq: 0,
|
|
77
77
|
kind: "agent",
|
|
78
78
|
status: "running"
|
|
79
79
|
}),
|
|
80
|
-
entry({
|
|
80
|
+
entry$1({
|
|
81
81
|
seq: 1,
|
|
82
82
|
scope: "agent:0",
|
|
83
83
|
value: {
|
|
@@ -90,11 +90,11 @@ const GOLDEN_FOLD_JOURNAL = [
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
}),
|
|
93
|
-
entry({
|
|
93
|
+
entry$1({
|
|
94
94
|
seq: 2,
|
|
95
95
|
value: { key: "gate-b" }
|
|
96
96
|
}),
|
|
97
|
-
entry({
|
|
97
|
+
entry$1({
|
|
98
98
|
seq: 3,
|
|
99
99
|
kind: "resolution",
|
|
100
100
|
status: "ok",
|
|
@@ -106,7 +106,7 @@ const GOLDEN_FOLD_JOURNAL = [
|
|
|
106
106
|
value: { ok: "yes" }
|
|
107
107
|
}
|
|
108
108
|
}),
|
|
109
|
-
entry({
|
|
109
|
+
entry$1({
|
|
110
110
|
seq: 4,
|
|
111
111
|
kind: "resolution",
|
|
112
112
|
status: "ok",
|
|
@@ -118,7 +118,7 @@ const GOLDEN_FOLD_JOURNAL = [
|
|
|
118
118
|
value: { ok: true }
|
|
119
119
|
}
|
|
120
120
|
}),
|
|
121
|
-
entry({
|
|
121
|
+
entry$1({
|
|
122
122
|
seq: 5,
|
|
123
123
|
kind: "resolution",
|
|
124
124
|
status: "ok",
|
|
@@ -130,7 +130,7 @@ const GOLDEN_FOLD_JOURNAL = [
|
|
|
130
130
|
value: { ok: false }
|
|
131
131
|
}
|
|
132
132
|
}),
|
|
133
|
-
entry({
|
|
133
|
+
entry$1({
|
|
134
134
|
seq: 6,
|
|
135
135
|
kind: "abandon",
|
|
136
136
|
status: "ok",
|
|
@@ -141,7 +141,7 @@ const GOLDEN_FOLD_JOURNAL = [
|
|
|
141
141
|
reason: "golden abandon"
|
|
142
142
|
}
|
|
143
143
|
}),
|
|
144
|
-
entry({
|
|
144
|
+
entry$1({
|
|
145
145
|
seq: 7,
|
|
146
146
|
kind: "resolution",
|
|
147
147
|
status: "ok",
|
|
@@ -152,7 +152,7 @@ const GOLDEN_FOLD_JOURNAL = [
|
|
|
152
152
|
value: { go: 1 }
|
|
153
153
|
}
|
|
154
154
|
}),
|
|
155
|
-
entry({
|
|
155
|
+
entry$1({
|
|
156
156
|
seq: 8,
|
|
157
157
|
kind: "abandon",
|
|
158
158
|
status: "ok",
|
|
@@ -163,7 +163,7 @@ const GOLDEN_FOLD_JOURNAL = [
|
|
|
163
163
|
reason: "late abandon over a resolved gate"
|
|
164
164
|
}
|
|
165
165
|
}),
|
|
166
|
-
entry({
|
|
166
|
+
entry$1({
|
|
167
167
|
seq: 9,
|
|
168
168
|
kind: "abandon",
|
|
169
169
|
status: "ok",
|
|
@@ -961,6 +961,625 @@ function quotaRulesConformance(mk) {
|
|
|
961
961
|
}]);
|
|
962
962
|
}
|
|
963
963
|
//#endregion
|
|
964
|
+
//#region src/effect-lane.ts
|
|
965
|
+
/**
|
|
966
|
+
* Effect lane store conformance (plan 45, rfcs/effects.md section 4.5,
|
|
967
|
+
* item 3): the executable definition of the EffectLaneStore capability
|
|
968
|
+
* and of the writer-store COMPOSITION the RFC's fencing argument rests
|
|
969
|
+
* on. The store half: a restoration generation that lives outside the
|
|
970
|
+
* journal bytes, starts at 0, and bumps monotonically. The composition
|
|
971
|
+
* half: a bumped generation disables every lane append until a fresh
|
|
972
|
+
* epoch cites it (the kill point 25 window), and a lane append under a
|
|
973
|
+
* non-current lease dies on the store's fence with nothing consumed
|
|
974
|
+
* (the kill point 16 shape) — the store cannot recognize lane traffic
|
|
975
|
+
* (obligation A4), so the unleased-append prohibition is enforced by
|
|
976
|
+
* the writer's construction, and THIS suite is where the composition
|
|
977
|
+
* is proven over the real store.
|
|
978
|
+
*/
|
|
979
|
+
const NOW = "2026-08-24T10:00:00.000Z";
|
|
980
|
+
const entry = (seq, rest) => ({
|
|
981
|
+
hashVersion: CURRENT_HASH_VERSION,
|
|
982
|
+
seq,
|
|
983
|
+
scope: "run",
|
|
984
|
+
key: `k${String(seq)}`,
|
|
985
|
+
ordinal: 0,
|
|
986
|
+
spanId: "s",
|
|
987
|
+
startedAt: NOW,
|
|
988
|
+
...rest
|
|
989
|
+
});
|
|
990
|
+
const seedApproval = async (store, runId, seq, licensedKey) => {
|
|
991
|
+
await store.append(runId, entry(seq, {
|
|
992
|
+
kind: "approval",
|
|
993
|
+
status: "suspended",
|
|
994
|
+
deadlineAt: "2026-08-24T12:00:00.000Z",
|
|
995
|
+
value: {
|
|
996
|
+
flavor: "approval",
|
|
997
|
+
toolName: "payout",
|
|
998
|
+
effectLogicalKey: licensedKey
|
|
999
|
+
}
|
|
1000
|
+
}));
|
|
1001
|
+
await store.append(runId, entry(seq + 1, {
|
|
1002
|
+
kind: "resolution",
|
|
1003
|
+
status: "ok",
|
|
1004
|
+
ref: seq,
|
|
1005
|
+
resolution: {
|
|
1006
|
+
target: seq,
|
|
1007
|
+
by: "external",
|
|
1008
|
+
value: { decision: "allow" }
|
|
1009
|
+
}
|
|
1010
|
+
}));
|
|
1011
|
+
};
|
|
1012
|
+
const BUDGETS = {
|
|
1013
|
+
attempts: 3,
|
|
1014
|
+
lookups: 5,
|
|
1015
|
+
receiptWaitMs: 6e4,
|
|
1016
|
+
reconcileBy: "2026-08-25T00:00:00.000Z"
|
|
1017
|
+
};
|
|
1018
|
+
function effectLaneStoreConformance(factory) {
|
|
1019
|
+
return makeSuite("effect-lane-store-conformance", [
|
|
1020
|
+
{
|
|
1021
|
+
id: "ELS1",
|
|
1022
|
+
title: "the restoration generation starts at 0 and reads stably",
|
|
1023
|
+
async run() {
|
|
1024
|
+
const store = await factory();
|
|
1025
|
+
ensure(store.effectLane === true, "ELS1", "the store must declare effectLane: true");
|
|
1026
|
+
ensure(await store.restorationGeneration() === 0, "ELS1", "a store no restore ever touched must report generation 0");
|
|
1027
|
+
ensure(await store.restorationGeneration() === 0, "ELS1", "the read must be stable across calls");
|
|
1028
|
+
}
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
id: "ELS2",
|
|
1032
|
+
title: "the bump is monotonic and durable in the same database",
|
|
1033
|
+
async run() {
|
|
1034
|
+
const store = await factory();
|
|
1035
|
+
ensure(await store.bumpRestorationGeneration() === 1, "ELS2", "the first bump must return 1");
|
|
1036
|
+
ensure(await store.bumpRestorationGeneration() === 2, "ELS2", "the second bump must return 2 (monotonic, never a reset)");
|
|
1037
|
+
ensure(await store.restorationGeneration() === 2, "ELS2", "the read after two bumps must be 2");
|
|
1038
|
+
}
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
id: "ELS3",
|
|
1042
|
+
title: "a bumped generation disables the lane until a fresh epoch cites it (kill 25)",
|
|
1043
|
+
async run() {
|
|
1044
|
+
const store = await factory();
|
|
1045
|
+
const runId = "ELS3-RUN";
|
|
1046
|
+
await seedApproval(store, runId, 0, "pay-1");
|
|
1047
|
+
const writer = await openEffectLane({
|
|
1048
|
+
store,
|
|
1049
|
+
runId,
|
|
1050
|
+
owner: "els3",
|
|
1051
|
+
now: () => NOW
|
|
1052
|
+
});
|
|
1053
|
+
try {
|
|
1054
|
+
await writer.ensureEpoch("gen-1");
|
|
1055
|
+
ensure((await writer.consumeApprovalAndRecordIntent({
|
|
1056
|
+
opId: "els3-intent-1",
|
|
1057
|
+
logicalKey: "pay-1",
|
|
1058
|
+
approvalRef: 0,
|
|
1059
|
+
effectClass: "monetary",
|
|
1060
|
+
capabilityRow: "idempotency-key",
|
|
1061
|
+
argumentsHash: "deadbeef",
|
|
1062
|
+
budgets: BUDGETS
|
|
1063
|
+
})).machine.consumed, "ELS3", "the pre-restore consumption must land");
|
|
1064
|
+
await store.bumpRestorationGeneration();
|
|
1065
|
+
let refused;
|
|
1066
|
+
try {
|
|
1067
|
+
await writer.consumeApprovalAndRecordIntent({
|
|
1068
|
+
opId: "els3-intent-2",
|
|
1069
|
+
logicalKey: "pay-1",
|
|
1070
|
+
approvalRef: 0,
|
|
1071
|
+
effectClass: "monetary",
|
|
1072
|
+
capabilityRow: "idempotency-key",
|
|
1073
|
+
argumentsHash: "deadbeef",
|
|
1074
|
+
budgets: BUDGETS
|
|
1075
|
+
});
|
|
1076
|
+
} catch (thrown) {
|
|
1077
|
+
refused = thrown;
|
|
1078
|
+
}
|
|
1079
|
+
ensure(refused?.rule === "restoration-generation-stale", "ELS3", "the post-restore window must refuse every lane append typed");
|
|
1080
|
+
ensure(!(await writer.ensureEpoch("gen-1")).replayed, "ELS3", "the operator epoch append must be a new entry");
|
|
1081
|
+
ensure(writer.view().currentEpoch()?.restorationGeneration === 1, "ELS3", "the fresh epoch must cite the bumped generation");
|
|
1082
|
+
} finally {
|
|
1083
|
+
await writer.close();
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
id: "ELS4",
|
|
1089
|
+
title: "a lane append under a non-current lease dies on the fence, nothing consumed (kill 16)",
|
|
1090
|
+
async run() {
|
|
1091
|
+
const store = await factory();
|
|
1092
|
+
const runId = "ELS4-RUN";
|
|
1093
|
+
await seedApproval(store, runId, 0, "pay-1");
|
|
1094
|
+
const lease = await store.acquire(runId, "holder");
|
|
1095
|
+
try {
|
|
1096
|
+
let refused;
|
|
1097
|
+
try {
|
|
1098
|
+
await store.append(runId, entry(2, {
|
|
1099
|
+
kind: "decision",
|
|
1100
|
+
status: "ok",
|
|
1101
|
+
value: {
|
|
1102
|
+
decisionType: "effect_epoch",
|
|
1103
|
+
opId: "ghost-epoch",
|
|
1104
|
+
generation: "gen-ghost"
|
|
1105
|
+
}
|
|
1106
|
+
}), {
|
|
1107
|
+
runId,
|
|
1108
|
+
owner: "ghost",
|
|
1109
|
+
epoch: 0
|
|
1110
|
+
});
|
|
1111
|
+
} catch (thrown) {
|
|
1112
|
+
refused = thrown;
|
|
1113
|
+
}
|
|
1114
|
+
ensure(refused instanceof LeaseHeldError, "ELS4", "a lane append carrying a non-current lease must reject with LeaseHeldError");
|
|
1115
|
+
ensure(!(await store.load(runId)).some((e) => e.seq === 2), "ELS4", "the fenced-off append must never become visible");
|
|
1116
|
+
} finally {
|
|
1117
|
+
await store.release(lease);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
]);
|
|
1122
|
+
}
|
|
1123
|
+
//#endregion
|
|
1124
|
+
//#region src/admission-matrix.ts
|
|
1125
|
+
/**
|
|
1126
|
+
* The durable admission conformance matrix (plan 45, rfcs/admission.md
|
|
1127
|
+
* section 7): all twelve rows as named executable checks over a
|
|
1128
|
+
* scheduler factory, so a deployment proves ITS durable admission
|
|
1129
|
+
* behaves like the reference. The fairness rows measure GRANTED RAW
|
|
1130
|
+
* SERVICE, which is what the algorithm actually guarantees; wait times
|
|
1131
|
+
* are reported nowhere as acceptance metrics. The expiry row runs the
|
|
1132
|
+
* fenced-covers arm; the "without fenced covers, auto-refund nothing"
|
|
1133
|
+
* arm is excluded here by design, because every shipped scheduler
|
|
1134
|
+
* fences covers (the in-process reference, and the durable documents
|
|
1135
|
+
* whose cover writes ride the same CASed transaction), and the
|
|
1136
|
+
* exclusion is recorded in this sentence.
|
|
1137
|
+
*/
|
|
1138
|
+
/**
|
|
1139
|
+
* Saturates every tenant's window with a plug ticket so a whole burst
|
|
1140
|
+
* can queue FIRST and one pump then measures pure SFQ order over the
|
|
1141
|
+
* assembled queue, not the accident of who enqueued while capacity was
|
|
1142
|
+
* free. Releasing a plug with zero actuals refunds its whole
|
|
1143
|
+
* reservation, so the burst's own arithmetic is untouched.
|
|
1144
|
+
*/
|
|
1145
|
+
async function plugTenants(scheduler, tenants, capWires) {
|
|
1146
|
+
for (const tenant of tenants) ensure((await scheduler.enqueue({
|
|
1147
|
+
unitId: `plug-${tenant}`,
|
|
1148
|
+
generation: "g1",
|
|
1149
|
+
resolvedTenant: tenant,
|
|
1150
|
+
scope: { tenant },
|
|
1151
|
+
weight: capWires,
|
|
1152
|
+
reservation: { wires: capWires }
|
|
1153
|
+
}, `op-plug-${tenant}`)).state === "granted", "admission.plug", `the ${tenant} plug holds the window`);
|
|
1154
|
+
}
|
|
1155
|
+
async function unplugTenants(scheduler, tenants) {
|
|
1156
|
+
for (const tenant of tenants) await scheduler.release(`plug-${tenant}`, "g1", { wires: 0 }, `op-unplug-${tenant}`);
|
|
1157
|
+
}
|
|
1158
|
+
const request = (unitId, overrides = {}) => ({
|
|
1159
|
+
unitId,
|
|
1160
|
+
generation: "g1",
|
|
1161
|
+
resolvedTenant: "acme",
|
|
1162
|
+
scope: {
|
|
1163
|
+
tenant: "acme",
|
|
1164
|
+
providerAccount: "shared"
|
|
1165
|
+
},
|
|
1166
|
+
reservation: { wires: 1 },
|
|
1167
|
+
...overrides
|
|
1168
|
+
});
|
|
1169
|
+
/** Drains every ticket through grant+release; returns the grant order. */
|
|
1170
|
+
async function drainGrantOrder(scheduler, expected) {
|
|
1171
|
+
const order = [];
|
|
1172
|
+
let stalls = 0;
|
|
1173
|
+
while (order.length < expected && stalls < expected + 10) {
|
|
1174
|
+
const granted = await scheduler.pump(`pump-${String(order.length)}-${String(stalls)}`);
|
|
1175
|
+
if (granted.length === 0) {
|
|
1176
|
+
stalls += 1;
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
for (const ticket of granted) {
|
|
1180
|
+
order.push(ticket.resolvedTenant ?? ticket.unitId);
|
|
1181
|
+
await scheduler.release(ticket.unitId, ticket.generation, ticket.reservation, `release-${ticket.unitId}`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return order;
|
|
1185
|
+
}
|
|
1186
|
+
function admissionConformance(options) {
|
|
1187
|
+
return makeSuite("admission-conformance", [
|
|
1188
|
+
{
|
|
1189
|
+
id: "admission.fairness.sixty-tenant-synchronized-burst",
|
|
1190
|
+
title: "sixty equal tenants: granted raw service equalizes and no inter-grant gap explodes",
|
|
1191
|
+
async run() {
|
|
1192
|
+
const now = { ms: 0 };
|
|
1193
|
+
const perTenant = 2;
|
|
1194
|
+
const fixture = await options.make({
|
|
1195
|
+
levels: { tenant: {
|
|
1196
|
+
algorithm: "sliding-window",
|
|
1197
|
+
capWires: perTenant
|
|
1198
|
+
} },
|
|
1199
|
+
leaseTtlMs: 36e5
|
|
1200
|
+
}, () => now.ms);
|
|
1201
|
+
const tenants = Array.from({ length: 60 }, (_, index) => `t${String(index).padStart(2, "0")}`);
|
|
1202
|
+
await plugTenants(fixture.scheduler, tenants, perTenant);
|
|
1203
|
+
for (let round = 0; round < perTenant; round += 1) for (const tenant of tenants) await fixture.scheduler.enqueue(request(`${tenant}-r${String(round)}`, {
|
|
1204
|
+
resolvedTenant: tenant,
|
|
1205
|
+
scope: { tenant }
|
|
1206
|
+
}), `op-${tenant}-${String(round)}`);
|
|
1207
|
+
await unplugTenants(fixture.scheduler, tenants);
|
|
1208
|
+
const order = await drainGrantOrder(fixture.scheduler, tenants.length * perTenant);
|
|
1209
|
+
ensure(order.length === tenants.length * perTenant, "admission.fairness.1", "every ticket eventually granted");
|
|
1210
|
+
for (const tenant of tenants) ensure(order.filter((granted) => granted === tenant).length === perTenant, "admission.fairness.1", `tenant ${tenant} received its exact share`);
|
|
1211
|
+
for (let start = 0; start + tenants.length <= order.length; start += tenants.length) ensure(new Set(order.slice(start, start + tenants.length)).size === tenants.length, "admission.fairness.1", "no tenant waits past one full round");
|
|
1212
|
+
await fixture.close?.();
|
|
1213
|
+
}
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
id: "admission.fairness.weighted-shares",
|
|
1217
|
+
title: "weights 1/2/4: granted raw service converges to 1:2:4 and weight 1 never starves",
|
|
1218
|
+
async run() {
|
|
1219
|
+
const now = { ms: 0 };
|
|
1220
|
+
const perTenant = 14;
|
|
1221
|
+
const fixture = await options.make({
|
|
1222
|
+
levels: { tenant: {
|
|
1223
|
+
algorithm: "sliding-window",
|
|
1224
|
+
capWires: perTenant
|
|
1225
|
+
} },
|
|
1226
|
+
weights: {
|
|
1227
|
+
x: 1,
|
|
1228
|
+
y: 2,
|
|
1229
|
+
z: 4
|
|
1230
|
+
},
|
|
1231
|
+
leaseTtlMs: 36e5
|
|
1232
|
+
}, () => now.ms);
|
|
1233
|
+
await plugTenants(fixture.scheduler, [
|
|
1234
|
+
"x",
|
|
1235
|
+
"y",
|
|
1236
|
+
"z"
|
|
1237
|
+
], perTenant);
|
|
1238
|
+
for (let round = 0; round < perTenant; round += 1) for (const tenant of [
|
|
1239
|
+
"x",
|
|
1240
|
+
"y",
|
|
1241
|
+
"z"
|
|
1242
|
+
]) await fixture.scheduler.enqueue(request(`${tenant}-r${String(round)}`, {
|
|
1243
|
+
resolvedTenant: tenant,
|
|
1244
|
+
scope: { tenant }
|
|
1245
|
+
}), `op-${tenant}-${String(round)}`);
|
|
1246
|
+
await unplugTenants(fixture.scheduler, [
|
|
1247
|
+
"x",
|
|
1248
|
+
"y",
|
|
1249
|
+
"z"
|
|
1250
|
+
]);
|
|
1251
|
+
const order = await drainGrantOrder(fixture.scheduler, perTenant * 3);
|
|
1252
|
+
const first7 = order.slice(0, 7);
|
|
1253
|
+
ensure(first7.filter((t) => t === "z").length === 4 && first7.filter((t) => t === "y").length === 2 && first7.filter((t) => t === "x").length === 1, "admission.fairness.2", "the first virtual-time cycle grants exactly 1:2:4");
|
|
1254
|
+
ensure(order.slice(0, 14).filter((t) => t === "x").length === 2, "admission.fairness.2", "weight 1 never starves");
|
|
1255
|
+
await fixture.close?.();
|
|
1256
|
+
}
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
id: "admission.window.minute-boundary",
|
|
1260
|
+
title: "the sliding window bounds the epoch boundary burst to the sub-window allowance",
|
|
1261
|
+
async run() {
|
|
1262
|
+
const now = { ms: 55e3 };
|
|
1263
|
+
const fixture = await options.make({
|
|
1264
|
+
levels: { tenant: {
|
|
1265
|
+
algorithm: "sliding-window",
|
|
1266
|
+
capWires: 10,
|
|
1267
|
+
windowMs: 6e4,
|
|
1268
|
+
slots: 6
|
|
1269
|
+
} },
|
|
1270
|
+
leaseTtlMs: 36e5
|
|
1271
|
+
}, () => now.ms);
|
|
1272
|
+
for (let index = 0; index < 10; index += 1) ensure((await fixture.scheduler.enqueue(request(`burst-${String(index)}`, { scope: { tenant: "acme" } }), `op-${String(index)}`)).state === "granted", "admission.window.3", "the cap admits the burst");
|
|
1273
|
+
ensure((await fixture.scheduler.enqueue(request("boundary", { scope: { tenant: "acme" } }), "op-boundary")).state === "queued", "admission.window.3", "the cap is spent");
|
|
1274
|
+
now.ms = 65e3;
|
|
1275
|
+
ensure((await fixture.scheduler.pump("op-p1")).length === 0, "admission.window.3", "no double burst across the boundary");
|
|
1276
|
+
now.ms = 105e3;
|
|
1277
|
+
ensure((await fixture.scheduler.pump("op-p2")).length === 0, "admission.window.3", "the trailing window still holds the spend");
|
|
1278
|
+
now.ms = 115e3;
|
|
1279
|
+
ensure((await fixture.scheduler.pump("op-p3")).length === 1, "admission.window.3", "capacity returns only as the trailing window slides past it");
|
|
1280
|
+
await fixture.close?.();
|
|
1281
|
+
}
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
id: "admission.crash.queued-ticket-survives",
|
|
1285
|
+
title: "a queued ticket reloads with its arrival seq and position; same unit, same ticket",
|
|
1286
|
+
async run() {
|
|
1287
|
+
const now = { ms: 0 };
|
|
1288
|
+
const fixture = await options.make({
|
|
1289
|
+
levels: {
|
|
1290
|
+
tenant: {
|
|
1291
|
+
algorithm: "sliding-window",
|
|
1292
|
+
capWires: 1e4
|
|
1293
|
+
},
|
|
1294
|
+
providerAccount: {
|
|
1295
|
+
algorithm: "sliding-window",
|
|
1296
|
+
capWires: 1e4,
|
|
1297
|
+
concurrency: 1
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
leaseTtlMs: 36e5
|
|
1301
|
+
}, () => now.ms);
|
|
1302
|
+
ensure((await fixture.scheduler.enqueue(request("a"), "op-a")).state === "granted", "admission.crash.4", "a grants");
|
|
1303
|
+
const queued = await fixture.scheduler.enqueue(request("b"), "op-b");
|
|
1304
|
+
ensure(queued.state === "queued", "admission.crash.4", "b queues on the semaphore");
|
|
1305
|
+
const arrivalSeq = queued.state === "queued" ? queued.ticket.arrivalSeq : -1;
|
|
1306
|
+
const successor = await fixture.reopen();
|
|
1307
|
+
const recovered = await successor.recover("b", "g1", "op-r");
|
|
1308
|
+
ensure(recovered.state === "queued", "admission.crash.4", "the queued ticket survives");
|
|
1309
|
+
ensure(recovered.state === "queued" && recovered.ticket.arrivalSeq === arrivalSeq, "admission.crash.4", "no queue jump, no loss: the arrival seq is intact");
|
|
1310
|
+
const again = await successor.enqueue(request("b"), "op-b-retry");
|
|
1311
|
+
ensure(again.state === "queued" && again.ticket.arrivalSeq === arrivalSeq, "admission.crash.4", "re-enqueueing the same unit returns the SAME ticket, never a duplicate");
|
|
1312
|
+
await successor.release("a", "g1", { wires: 1 }, "op-release-a");
|
|
1313
|
+
ensure((await successor.pump("op-pump")).some((ticket) => ticket.unitId === "b"), "admission.crash.4", "the surviving ticket grants in order");
|
|
1314
|
+
await fixture.close?.();
|
|
1315
|
+
}
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
id: "admission.crash.granted-lease-expiry-settles-conservatively",
|
|
1319
|
+
title: "expiry refunds reservation minus the fenced cover; a late settlement lands as debt",
|
|
1320
|
+
async run() {
|
|
1321
|
+
const now = { ms: 0 };
|
|
1322
|
+
const fixture = await options.make({
|
|
1323
|
+
levels: { tenant: {
|
|
1324
|
+
algorithm: "sliding-window",
|
|
1325
|
+
capWires: 4,
|
|
1326
|
+
windowMs: 36e5
|
|
1327
|
+
} },
|
|
1328
|
+
leaseTtlMs: 3e4
|
|
1329
|
+
}, () => now.ms);
|
|
1330
|
+
await fixture.scheduler.enqueue(request("holder", {
|
|
1331
|
+
reservation: { wires: 4 },
|
|
1332
|
+
scope: { tenant: "acme" }
|
|
1333
|
+
}), "op-h");
|
|
1334
|
+
await fixture.scheduler.checkpointCover("holder", "g1", { wires: 2 }, "op-c");
|
|
1335
|
+
now.ms = 31e3;
|
|
1336
|
+
await fixture.scheduler.pump("op-expire");
|
|
1337
|
+
let fenced = false;
|
|
1338
|
+
try {
|
|
1339
|
+
await fixture.scheduler.checkpointCover("holder", "g1", { wires: 4 }, "op-c2");
|
|
1340
|
+
} catch {
|
|
1341
|
+
fenced = true;
|
|
1342
|
+
}
|
|
1343
|
+
ensure(fenced, "admission.crash.5", "the expired lease cover write is fenced off");
|
|
1344
|
+
ensure((await fixture.scheduler.enqueue(request("two", {
|
|
1345
|
+
reservation: { wires: 2 },
|
|
1346
|
+
scope: { tenant: "acme" }
|
|
1347
|
+
}), "op-2")).state === "granted", "admission.crash.5", "the provable refund admits");
|
|
1348
|
+
ensure((await fixture.scheduler.enqueue(request("probe", {
|
|
1349
|
+
reservation: { wires: 1 },
|
|
1350
|
+
scope: { tenant: "acme" }
|
|
1351
|
+
}), "op-probe")).state === "queued", "admission.crash.5", "the refund was exactly the uncovered half, never a blind full refund");
|
|
1352
|
+
await fixture.scheduler.release("holder", "g1", { wires: 3 }, "op-late");
|
|
1353
|
+
await fixture.scheduler.release("holder", "g1", { wires: 3 }, "op-late");
|
|
1354
|
+
await fixture.close?.();
|
|
1355
|
+
}
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
id: "admission.deny.429-parity",
|
|
1359
|
+
title: "the terminal denied verdict is distinguishable from queued by STATE, never timeout",
|
|
1360
|
+
async run() {
|
|
1361
|
+
const now = { ms: 0 };
|
|
1362
|
+
const fixture = await options.make({
|
|
1363
|
+
levels: { tenant: {
|
|
1364
|
+
algorithm: "sliding-window",
|
|
1365
|
+
capWires: 2
|
|
1366
|
+
} },
|
|
1367
|
+
leaseTtlMs: 36e5
|
|
1368
|
+
}, () => now.ms);
|
|
1369
|
+
ensure((await fixture.scheduler.enqueue(request("huge", {
|
|
1370
|
+
reservation: { wires: 5 },
|
|
1371
|
+
scope: { tenant: "acme" }
|
|
1372
|
+
}), "op-huge")).state === "denied", "admission.deny.6", "infeasible refuses terminally");
|
|
1373
|
+
await fixture.scheduler.enqueue(request("fill", {
|
|
1374
|
+
reservation: { wires: 2 },
|
|
1375
|
+
scope: { tenant: "acme" }
|
|
1376
|
+
}), "op-fill");
|
|
1377
|
+
const queued = await fixture.scheduler.enqueue(request("wait", { scope: { tenant: "acme" } }), "op-wait");
|
|
1378
|
+
ensure(queued.state === "queued" && typeof queued.position === "number", "admission.deny.6", "a waiting verdict carries its position; the caller backoff owns the retry");
|
|
1379
|
+
await fixture.close?.();
|
|
1380
|
+
}
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
id: "admission.region.loss",
|
|
1384
|
+
title: "losing a region re-routes admission without double granting in-flight tickets",
|
|
1385
|
+
async run() {
|
|
1386
|
+
const now = { ms: 0 };
|
|
1387
|
+
const fixture = await options.make({
|
|
1388
|
+
levels: {
|
|
1389
|
+
tenant: {
|
|
1390
|
+
algorithm: "sliding-window",
|
|
1391
|
+
capWires: 100
|
|
1392
|
+
},
|
|
1393
|
+
scope: {
|
|
1394
|
+
algorithm: "sliding-window",
|
|
1395
|
+
capWires: 100
|
|
1396
|
+
}
|
|
1397
|
+
},
|
|
1398
|
+
leaseTtlMs: 36e5
|
|
1399
|
+
}, () => now.ms);
|
|
1400
|
+
ensure((await fixture.scheduler.enqueue(request("in-flight", { scope: {
|
|
1401
|
+
tenant: "acme",
|
|
1402
|
+
region: "eu-lost"
|
|
1403
|
+
} }), "op-a")).state === "granted", "admission.region.7", "the in-flight grant");
|
|
1404
|
+
const successor = await fixture.reopen();
|
|
1405
|
+
ensure((await successor.enqueue(request("rerouted", { scope: {
|
|
1406
|
+
tenant: "acme",
|
|
1407
|
+
region: "us-alive"
|
|
1408
|
+
} }), "op-b")).state === "granted", "admission.region.7", "admission re-routes");
|
|
1409
|
+
ensure((await successor.recover("in-flight", "g1", "op-r")).state === "granted", "admission.region.7", "the in-flight ticket stays granted exactly once");
|
|
1410
|
+
ensure((await successor.pump("op-p")).length === 0, "admission.region.7", "no double grant of the surviving ticket");
|
|
1411
|
+
await fixture.close?.();
|
|
1412
|
+
}
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
id: "admission.repair.hundred-percent",
|
|
1416
|
+
title: "a workload of 100 percent repair amplification stays inside caps through debt",
|
|
1417
|
+
async run() {
|
|
1418
|
+
const now = { ms: 0 };
|
|
1419
|
+
const fixture = await options.make({
|
|
1420
|
+
levels: { tenant: {
|
|
1421
|
+
algorithm: "sliding-window",
|
|
1422
|
+
capWires: 4,
|
|
1423
|
+
windowMs: 36e5
|
|
1424
|
+
} },
|
|
1425
|
+
leaseTtlMs: 36e5
|
|
1426
|
+
}, () => now.ms);
|
|
1427
|
+
for (const unit of ["r1", "r2"]) {
|
|
1428
|
+
ensure((await fixture.scheduler.enqueue(request(unit, { scope: { tenant: "acme" } }), `op-${unit}`)).state === "granted", "admission.repair.8", `${unit} admitted`);
|
|
1429
|
+
await fixture.scheduler.release(unit, "g1", { wires: 2 }, `op-release-${unit}`);
|
|
1430
|
+
}
|
|
1431
|
+
ensure((await fixture.scheduler.enqueue(request("r3", { scope: { tenant: "acme" } }), "op-r3")).state === "queued", "admission.repair.8", "the amplified spend depresses the bucket instead of blowing the cap");
|
|
1432
|
+
await fixture.close?.();
|
|
1433
|
+
}
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
id: "admission.scope.foreign-scope-never-consumes",
|
|
1437
|
+
title: "a request that cannot key a configured level refuses fail closed, consuming nothing",
|
|
1438
|
+
async run() {
|
|
1439
|
+
const now = { ms: 0 };
|
|
1440
|
+
const fixture = await options.make({
|
|
1441
|
+
levels: { tenant: {
|
|
1442
|
+
algorithm: "sliding-window",
|
|
1443
|
+
capWires: 2
|
|
1444
|
+
} },
|
|
1445
|
+
leaseTtlMs: 36e5
|
|
1446
|
+
}, () => now.ms);
|
|
1447
|
+
ensure((await fixture.scheduler.enqueue({
|
|
1448
|
+
unitId: "foreign",
|
|
1449
|
+
generation: "g1",
|
|
1450
|
+
reservation: { wires: 2 }
|
|
1451
|
+
}, "op-foreign")).state === "denied", "admission.scope.9", "fail closed, never global");
|
|
1452
|
+
ensure((await fixture.scheduler.enqueue(request("proper", {
|
|
1453
|
+
reservation: { wires: 2 },
|
|
1454
|
+
scope: { tenant: "acme" }
|
|
1455
|
+
}), "op-proper")).state === "granted", "admission.scope.9", "full capacity intact");
|
|
1456
|
+
await fixture.close?.();
|
|
1457
|
+
}
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
id: "admission.atomicity.multi-level-all-or-nothing",
|
|
1461
|
+
title: "a denial at level 2 leaves level 1 and level 3 counters untouched",
|
|
1462
|
+
async run() {
|
|
1463
|
+
const now = { ms: 0 };
|
|
1464
|
+
const fixture = await options.make({
|
|
1465
|
+
levels: {
|
|
1466
|
+
tenant: {
|
|
1467
|
+
algorithm: "sliding-window",
|
|
1468
|
+
capWires: 2,
|
|
1469
|
+
windowMs: 36e5
|
|
1470
|
+
},
|
|
1471
|
+
providerAccount: {
|
|
1472
|
+
algorithm: "sliding-window",
|
|
1473
|
+
capWires: 100,
|
|
1474
|
+
concurrency: 1
|
|
1475
|
+
}
|
|
1476
|
+
},
|
|
1477
|
+
leaseTtlMs: 36e5
|
|
1478
|
+
}, () => now.ms);
|
|
1479
|
+
ensure((await fixture.scheduler.enqueue(request("a", { scope: {
|
|
1480
|
+
tenant: "acme",
|
|
1481
|
+
providerAccount: "acct-1"
|
|
1482
|
+
} }), "op-a")).state === "granted", "admission.atomicity.10", "a grants");
|
|
1483
|
+
ensure((await fixture.scheduler.enqueue(request("b", { scope: {
|
|
1484
|
+
tenant: "acme",
|
|
1485
|
+
providerAccount: "acct-1"
|
|
1486
|
+
} }), "op-b")).state === "queued", "admission.atomicity.10", "b queues on level 2");
|
|
1487
|
+
ensure((await fixture.scheduler.enqueue(request("c", { scope: {
|
|
1488
|
+
tenant: "acme",
|
|
1489
|
+
providerAccount: "acct-2"
|
|
1490
|
+
} }), "op-c")).state === "granted", "admission.atomicity.10", "the denial consumed nothing at any level");
|
|
1491
|
+
await fixture.close?.();
|
|
1492
|
+
}
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
id: "admission.failover.rebind-before-dispatch",
|
|
1496
|
+
title: "the transfer acquires the target slot before the source releases, atomically",
|
|
1497
|
+
async run() {
|
|
1498
|
+
const now = { ms: 0 };
|
|
1499
|
+
const fixture = await options.make({
|
|
1500
|
+
levels: {
|
|
1501
|
+
tenant: {
|
|
1502
|
+
algorithm: "sliding-window",
|
|
1503
|
+
capWires: 100
|
|
1504
|
+
},
|
|
1505
|
+
providerAccount: {
|
|
1506
|
+
algorithm: "sliding-window",
|
|
1507
|
+
capWires: 100,
|
|
1508
|
+
concurrency: 1
|
|
1509
|
+
}
|
|
1510
|
+
},
|
|
1511
|
+
leaseTtlMs: 36e5
|
|
1512
|
+
}, () => now.ms);
|
|
1513
|
+
ensure((await fixture.scheduler.enqueue(request("a", { scope: {
|
|
1514
|
+
tenant: "acme",
|
|
1515
|
+
providerAccount: "acct-1"
|
|
1516
|
+
} }), "op-a")).state === "granted", "admission.failover.11", "a holds acct-1");
|
|
1517
|
+
ensure((await fixture.scheduler.enqueue(request("d", { scope: {
|
|
1518
|
+
tenant: "acme",
|
|
1519
|
+
providerAccount: "acct-2"
|
|
1520
|
+
} }), "op-d")).state === "granted", "admission.failover.11", "d holds acct-2");
|
|
1521
|
+
ensure((await fixture.scheduler.rebind("a", "g1", { scope: {
|
|
1522
|
+
tenant: "acme",
|
|
1523
|
+
providerAccount: "acct-2"
|
|
1524
|
+
} }, "op-rebind-1")).state === "denied", "admission.failover.11", "a failed transfer refuses: the target slot is held");
|
|
1525
|
+
ensure((await fixture.scheduler.recover("a", "g1", "op-r")).state === "granted", "admission.failover.11", "the source binding is unchanged after the refusal");
|
|
1526
|
+
ensure((await fixture.scheduler.enqueue(request("e", { scope: {
|
|
1527
|
+
tenant: "acme",
|
|
1528
|
+
providerAccount: "acct-1"
|
|
1529
|
+
} }), "op-e")).state === "queued", "admission.failover.11", "the source slot is STILL held: no window without a slot");
|
|
1530
|
+
await fixture.scheduler.release("d", "g1", { wires: 1 }, "op-release-d");
|
|
1531
|
+
ensure((await fixture.scheduler.rebind("a", "g1", { scope: {
|
|
1532
|
+
tenant: "acme",
|
|
1533
|
+
providerAccount: "acct-2"
|
|
1534
|
+
} }, "op-rebind-2")).state === "granted", "admission.failover.11", "the transfer lands");
|
|
1535
|
+
ensure((await fixture.scheduler.pump("op-pump")).some((ticket) => ticket.unitId === "e"), "admission.failover.11", "the released source slot admits the waiter in the same transition set");
|
|
1536
|
+
await fixture.close?.();
|
|
1537
|
+
}
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
id: "admission.tenant.resolution-parity",
|
|
1541
|
+
title: "the effective tenant matches the limiter posture; a conflicting pair refuses typed",
|
|
1542
|
+
async run() {
|
|
1543
|
+
const now = { ms: 0 };
|
|
1544
|
+
const fixture = await options.make({
|
|
1545
|
+
levels: { tenant: {
|
|
1546
|
+
algorithm: "sliding-window",
|
|
1547
|
+
capWires: 2,
|
|
1548
|
+
windowMs: 36e5
|
|
1549
|
+
} },
|
|
1550
|
+
leaseTtlMs: 36e5
|
|
1551
|
+
}, () => now.ms);
|
|
1552
|
+
let refused = false;
|
|
1553
|
+
try {
|
|
1554
|
+
await fixture.scheduler.enqueue(request("conflict", {
|
|
1555
|
+
resolvedTenant: "engine-t",
|
|
1556
|
+
scope: { tenant: "other" }
|
|
1557
|
+
}), "op-conflict");
|
|
1558
|
+
} catch (thrown) {
|
|
1559
|
+
refused = thrown instanceof ConfigError || thrown instanceof Error;
|
|
1560
|
+
}
|
|
1561
|
+
ensure(refused, "admission.tenant.12", "the conflicting pair refuses typed");
|
|
1562
|
+
ensure((await fixture.scheduler.enqueue(request("u1", {
|
|
1563
|
+
resolvedTenant: "engine-t",
|
|
1564
|
+
scope: {
|
|
1565
|
+
tenant: "engine-t",
|
|
1566
|
+
project: "p1"
|
|
1567
|
+
},
|
|
1568
|
+
reservation: { wires: 2 }
|
|
1569
|
+
}), "op-u1")).state === "granted", "admission.tenant.12", "the first unit fills the cap");
|
|
1570
|
+
ensure((await fixture.scheduler.enqueue(request("u2", {
|
|
1571
|
+
resolvedTenant: "engine-t",
|
|
1572
|
+
scope: {
|
|
1573
|
+
tenant: "engine-t",
|
|
1574
|
+
project: "p2"
|
|
1575
|
+
}
|
|
1576
|
+
}), "op-u2")).state === "queued", "admission.tenant.12", "the SAME identity debits the SAME bucket, whatever the project");
|
|
1577
|
+
await fixture.close?.();
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
]);
|
|
1581
|
+
}
|
|
1582
|
+
//#endregion
|
|
964
1583
|
//#region src/fenced-transcripts.ts
|
|
965
1584
|
/**
|
|
966
1585
|
* fencedTranscriptsConformance (the fenced run state RFC, F2): the
|
|
@@ -2377,4 +2996,4 @@ function killPointConformance(options) {
|
|
|
2377
2996
|
})));
|
|
2378
2997
|
}
|
|
2379
2998
|
//#endregion
|
|
2380
|
-
export { DEFAULT_SOAK_QUORUM, GOLDEN_FOLD_JOURNAL, GOLDEN_FOLD_STATE_SHA256, KILL_POINT_SCENARIOS, countSoakActivity, fencedTranscriptsConformance, fencedWritesConformance, foldStateSha256, journalStoreConformance, killPointConformance, killPointWorkerConfigFromEnv, leasableStoreConformance, makeSuite, materializeFoldState, parseKillPointReport, parseSoakReport, quotaRulesConformance, registerConformance, runKillPointScenario, runKillPointWorker, runMultiProcessSoak, runSoakWriter, soakWriterConfigFromEnv, stableStringify, verifySoakHistory };
|
|
2999
|
+
export { DEFAULT_SOAK_QUORUM, GOLDEN_FOLD_JOURNAL, GOLDEN_FOLD_STATE_SHA256, KILL_POINT_SCENARIOS, admissionConformance, countSoakActivity, effectLaneStoreConformance, ensure, fencedTranscriptsConformance, fencedWritesConformance, foldStateSha256, journalStoreConformance, killPointConformance, killPointWorkerConfigFromEnv, leasableStoreConformance, makeSuite, materializeFoldState, parseKillPointReport, parseSoakReport, quotaRulesConformance, registerConformance, runKillPointScenario, runKillPointWorker, runMultiProcessSoak, runSoakWriter, soakWriterConfigFromEnv, stableStringify, verifySoakHistory };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/store-conformance",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.250.0",
|
|
4
4
|
"description": "Rulvar executable store conformance kit (DEF-4).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rulvar/core": "1.
|
|
25
|
+
"@rulvar/core": "1.250.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.20.1",
|