@cortexkit/antigravity-auth-core 3.0.11 → 3.0.12
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/agy-transport.d.ts.map +1 -1
- package/dist/agy-transport.js +4 -0
- package/dist/agy-transport.js.map +1 -1
- package/dist/common-auth-embedded/claustrum/consumer.d.ts +7 -1
- package/dist/common-auth-embedded/claustrum/consumer.js +17 -2
- package/dist/common-auth-embedded/claustrum/errors.d.ts +1 -1
- package/dist/common-auth-embedded/claustrum/index.d.ts +1 -1
- package/dist/common-auth-embedded/claustrum/index.js +1 -1
- package/dist/common-auth-embedded/claustrum/quota-writer.js +4 -54
- package/dist/common-auth-embedded/claustrum/roster.d.ts +31 -0
- package/dist/common-auth-embedded/claustrum/roster.js +72 -0
- package/dist/common-auth-embedded/commands/builtins.d.ts +37 -0
- package/dist/common-auth-embedded/commands/builtins.js +95 -13
- package/dist/common-auth-embedded/commands/index.d.ts +1 -1
- package/dist/common-auth-embedded/quota/pending.js +48 -0
- package/dist/common-auth-embedded/source-output.json +74 -58
- package/dist/common-auth-embedded/store/attribution.d.ts +5 -0
- package/dist/common-auth-embedded/store/attribution.js +39 -25
- package/dist/common-auth-embedded/store/pool.d.ts +17 -0
- package/dist/common-auth-embedded/store/pool.js +18 -3
- package/dist/common-auth-embedded/store/pull.js +3 -1
- package/dist/common-auth-embedded/store/quota-writer.js +298 -0
- package/dist/common-auth-embedded/store/runtime.d.ts +2 -0
- package/dist/pre-response-deadline.d.ts.map +1 -1
- package/dist/pre-response-deadline.js +6 -5
- package/dist/pre-response-deadline.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Use the same independently dated parts as mergeQuotaObservation. A header
|
|
2
|
+
// reading cannot erase a poll-only window, counter, credits record or budget.
|
|
3
|
+
export function quotaFields(publication, witness = '') {
|
|
4
|
+
const result = new Map();
|
|
5
|
+
const { observation } = publication;
|
|
6
|
+
const add = (key, at, part) => result.set(`${witness}:${key}`, {
|
|
7
|
+
at,
|
|
8
|
+
order: 0,
|
|
9
|
+
publication: { ...publication, observation: part },
|
|
10
|
+
});
|
|
11
|
+
const keyFor = (scope, label) => JSON.stringify([scope ?? 'all', label]);
|
|
12
|
+
const read = new Set();
|
|
13
|
+
for (const reading of observation.readings ?? []) {
|
|
14
|
+
const key = keyFor(reading.scope, reading.label);
|
|
15
|
+
read.add(key);
|
|
16
|
+
add(key, observation.checkedAt, {
|
|
17
|
+
checkedAt: observation.checkedAt,
|
|
18
|
+
readings: [{ ...reading }],
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
for (const pair of observation.coverage ?? []) {
|
|
22
|
+
const key = keyFor(pair.scope, pair.label);
|
|
23
|
+
if (!read.has(key))
|
|
24
|
+
add(key, observation.checkedAt, {
|
|
25
|
+
checkedAt: observation.checkedAt,
|
|
26
|
+
coverage: [{ ...pair }],
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (observation.budget)
|
|
30
|
+
add('budget', observation.checkedAt, {
|
|
31
|
+
checkedAt: observation.checkedAt,
|
|
32
|
+
budget: { ...observation.budget },
|
|
33
|
+
});
|
|
34
|
+
for (const [name, counter] of Object.entries(observation.counters ?? {})) {
|
|
35
|
+
add(`counter:${name}`, counter.checkedAt, {
|
|
36
|
+
checkedAt: observation.checkedAt,
|
|
37
|
+
counters: { [name]: { ...counter } },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (observation.credits)
|
|
41
|
+
add('credits', observation.credits.checkedAt, {
|
|
42
|
+
checkedAt: observation.checkedAt,
|
|
43
|
+
credits: { ...observation.credits },
|
|
44
|
+
});
|
|
45
|
+
if (!result.size)
|
|
46
|
+
add('empty', observation.checkedAt, { checkedAt: observation.checkedAt });
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"package": "@cortexkit/common-auth",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.26.0",
|
|
5
5
|
"artifactStatus": "published",
|
|
6
6
|
"publicRoots": {
|
|
7
7
|
"./store": {
|
|
@@ -37,15 +37,15 @@
|
|
|
37
37
|
"import": "./dist/pi-slot/index.js"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
|
-
"input": "tools/common-auth-build/inputs/cortexkit-common-auth-0.
|
|
41
|
-
"tarballSha256": "
|
|
42
|
-
"sri": "sha512-
|
|
43
|
-
"canonicalFiles":
|
|
44
|
-
"canonicalBytes":
|
|
40
|
+
"input": "tools/common-auth-build/inputs/cortexkit-common-auth-0.26.0.tgz",
|
|
41
|
+
"tarballSha256": "a5a6cb7051ee8c45e86e19cf7a918d0e28ce1b6a80301e982efbc7bb4b98208d",
|
|
42
|
+
"sri": "sha512-qTmQ9aMG+HcyDbhKie8Nq9wxKMRQ6GXH2hKq8Fab9kHqFfzswdp2NpXofGY52RhQ2VxuyMOQr5T6lUrkbny5VQ==",
|
|
43
|
+
"canonicalFiles": 141,
|
|
44
|
+
"canonicalBytes": 888503,
|
|
45
45
|
"generator": {
|
|
46
46
|
"path": "packages/opencode/scripts/embed-common-auth.ts",
|
|
47
47
|
"version": 3,
|
|
48
|
-
"sha256": "
|
|
48
|
+
"sha256": "89dbeb07d77e0657abeb72a40fcc03a900e98292956865252349a01a15999ed0"
|
|
49
49
|
},
|
|
50
50
|
"files": [
|
|
51
51
|
{
|
|
@@ -227,17 +227,17 @@
|
|
|
227
227
|
{
|
|
228
228
|
"source": "package/dist/claustrum/consumer.d.ts",
|
|
229
229
|
"output": "claustrum/consumer.d.ts",
|
|
230
|
-
"bytes":
|
|
231
|
-
"sourceSha256": "
|
|
232
|
-
"outputSha256": "
|
|
230
|
+
"bytes": 9907,
|
|
231
|
+
"sourceSha256": "8001ce3f928af1bc9124223071eb9c85ea3273436cd7be073c588217f942b248",
|
|
232
|
+
"outputSha256": "8001ce3f928af1bc9124223071eb9c85ea3273436cd7be073c588217f942b248",
|
|
233
233
|
"transform": "verbatim"
|
|
234
234
|
},
|
|
235
235
|
{
|
|
236
236
|
"source": "package/dist/claustrum/consumer.js",
|
|
237
237
|
"output": "claustrum/consumer.js",
|
|
238
|
-
"bytes":
|
|
239
|
-
"sourceSha256": "
|
|
240
|
-
"outputSha256": "
|
|
238
|
+
"bytes": 26470,
|
|
239
|
+
"sourceSha256": "b2087accd6e8759fb49173dc744243ed703520d5011386ef514672be8fe79fbe",
|
|
240
|
+
"outputSha256": "b2087accd6e8759fb49173dc744243ed703520d5011386ef514672be8fe79fbe",
|
|
241
241
|
"transform": "verbatim"
|
|
242
242
|
},
|
|
243
243
|
{
|
|
@@ -275,9 +275,9 @@
|
|
|
275
275
|
{
|
|
276
276
|
"source": "package/dist/claustrum/errors.d.ts",
|
|
277
277
|
"output": "claustrum/errors.d.ts",
|
|
278
|
-
"bytes":
|
|
279
|
-
"sourceSha256": "
|
|
280
|
-
"outputSha256": "
|
|
278
|
+
"bytes": 1163,
|
|
279
|
+
"sourceSha256": "a6e5ec8be3ae7160b78456e642b440494604c2e2cdc987550744521dc873fb40",
|
|
280
|
+
"outputSha256": "a6e5ec8be3ae7160b78456e642b440494604c2e2cdc987550744521dc873fb40",
|
|
281
281
|
"transform": "verbatim"
|
|
282
282
|
},
|
|
283
283
|
{
|
|
@@ -307,17 +307,17 @@
|
|
|
307
307
|
{
|
|
308
308
|
"source": "package/dist/claustrum/index.d.ts",
|
|
309
309
|
"output": "claustrum/index.d.ts",
|
|
310
|
-
"bytes":
|
|
311
|
-
"sourceSha256": "
|
|
312
|
-
"outputSha256": "
|
|
310
|
+
"bytes": 3844,
|
|
311
|
+
"sourceSha256": "36c0b94c5e18972463f48bfd7e1506d9655372aa8df065786b3a47d199cdb5e0",
|
|
312
|
+
"outputSha256": "36c0b94c5e18972463f48bfd7e1506d9655372aa8df065786b3a47d199cdb5e0",
|
|
313
313
|
"transform": "verbatim"
|
|
314
314
|
},
|
|
315
315
|
{
|
|
316
316
|
"source": "package/dist/claustrum/index.js",
|
|
317
317
|
"output": "claustrum/index.js",
|
|
318
|
-
"bytes":
|
|
319
|
-
"sourceSha256": "
|
|
320
|
-
"outputSha256": "
|
|
318
|
+
"bytes": 2529,
|
|
319
|
+
"sourceSha256": "42c9e67bb5ec6749de488933a56fb3ee253c3b9e3588d692f171d87c59bcbd89",
|
|
320
|
+
"outputSha256": "42c9e67bb5ec6749de488933a56fb3ee253c3b9e3588d692f171d87c59bcbd89",
|
|
321
321
|
"transform": "verbatim"
|
|
322
322
|
},
|
|
323
323
|
{
|
|
@@ -363,25 +363,25 @@
|
|
|
363
363
|
{
|
|
364
364
|
"source": "package/dist/claustrum/quota-writer.js",
|
|
365
365
|
"output": "claustrum/quota-writer.js",
|
|
366
|
-
"bytes":
|
|
367
|
-
"sourceSha256": "
|
|
368
|
-
"outputSha256": "
|
|
366
|
+
"bytes": 7579,
|
|
367
|
+
"sourceSha256": "0054a12ca94aab2eecf50f9b5e090879ff184e7f286cc979f32d311e22011906",
|
|
368
|
+
"outputSha256": "0054a12ca94aab2eecf50f9b5e090879ff184e7f286cc979f32d311e22011906",
|
|
369
369
|
"transform": "verbatim"
|
|
370
370
|
},
|
|
371
371
|
{
|
|
372
372
|
"source": "package/dist/claustrum/roster.d.ts",
|
|
373
373
|
"output": "claustrum/roster.d.ts",
|
|
374
|
-
"bytes":
|
|
375
|
-
"sourceSha256": "
|
|
376
|
-
"outputSha256": "
|
|
374
|
+
"bytes": 12574,
|
|
375
|
+
"sourceSha256": "c9ab3a89e04f72de09c5f15ccf1436b91f8a245e2253f24ec5998120058aebe7",
|
|
376
|
+
"outputSha256": "c9ab3a89e04f72de09c5f15ccf1436b91f8a245e2253f24ec5998120058aebe7",
|
|
377
377
|
"transform": "verbatim"
|
|
378
378
|
},
|
|
379
379
|
{
|
|
380
380
|
"source": "package/dist/claustrum/roster.js",
|
|
381
381
|
"output": "claustrum/roster.js",
|
|
382
|
-
"bytes":
|
|
383
|
-
"sourceSha256": "
|
|
384
|
-
"outputSha256": "
|
|
382
|
+
"bytes": 30876,
|
|
383
|
+
"sourceSha256": "d02184fd4d32aa8917369919edea4c44328e7332e30f3ddc41ce350e5fd1df07",
|
|
384
|
+
"outputSha256": "d02184fd4d32aa8917369919edea4c44328e7332e30f3ddc41ce350e5fd1df07",
|
|
385
385
|
"transform": "verbatim"
|
|
386
386
|
},
|
|
387
387
|
{
|
|
@@ -403,25 +403,25 @@
|
|
|
403
403
|
{
|
|
404
404
|
"source": "package/dist/commands/builtins.d.ts",
|
|
405
405
|
"output": "commands/builtins.d.ts",
|
|
406
|
-
"bytes":
|
|
407
|
-
"sourceSha256": "
|
|
408
|
-
"outputSha256": "
|
|
406
|
+
"bytes": 4576,
|
|
407
|
+
"sourceSha256": "5ab6cbab696c91fef0fc7288d4b82803cd0bac809b68f939d73671505fc1d894",
|
|
408
|
+
"outputSha256": "5ab6cbab696c91fef0fc7288d4b82803cd0bac809b68f939d73671505fc1d894",
|
|
409
409
|
"transform": "verbatim"
|
|
410
410
|
},
|
|
411
411
|
{
|
|
412
412
|
"source": "package/dist/commands/builtins.js",
|
|
413
413
|
"output": "commands/builtins.js",
|
|
414
|
-
"bytes":
|
|
415
|
-
"sourceSha256": "
|
|
416
|
-
"outputSha256": "
|
|
414
|
+
"bytes": 25155,
|
|
415
|
+
"sourceSha256": "c5f7fca0e36dc2f3253b88e7600d3dbed6a193d2b8abbf07d16913b6b46551e6",
|
|
416
|
+
"outputSha256": "c5f7fca0e36dc2f3253b88e7600d3dbed6a193d2b8abbf07d16913b6b46551e6",
|
|
417
417
|
"transform": "verbatim"
|
|
418
418
|
},
|
|
419
419
|
{
|
|
420
420
|
"source": "package/dist/commands/index.d.ts",
|
|
421
421
|
"output": "commands/index.d.ts",
|
|
422
|
-
"bytes":
|
|
423
|
-
"sourceSha256": "
|
|
424
|
-
"outputSha256": "
|
|
422
|
+
"bytes": 1100,
|
|
423
|
+
"sourceSha256": "4be7eec728211b7ad707121b1f2bbe2c219574aaa4e1c5be3f8964b185d4377c",
|
|
424
|
+
"outputSha256": "4be7eec728211b7ad707121b1f2bbe2c219574aaa4e1c5be3f8964b185d4377c",
|
|
425
425
|
"transform": "verbatim"
|
|
426
426
|
},
|
|
427
427
|
{
|
|
@@ -776,6 +776,14 @@
|
|
|
776
776
|
"outputSha256": "b1d76faf1fb480b97ef1886ce75dbd2156002baedc39249468559cb4eece5bbb",
|
|
777
777
|
"transform": "verbatim"
|
|
778
778
|
},
|
|
779
|
+
{
|
|
780
|
+
"source": "package/dist/quota/pending.js",
|
|
781
|
+
"output": "quota/pending.js",
|
|
782
|
+
"bytes": 1876,
|
|
783
|
+
"sourceSha256": "e757a3fa9f8440190668c6154468a5baec8a84e20d6c831a6d7935c3b9bebe29",
|
|
784
|
+
"outputSha256": "e757a3fa9f8440190668c6154468a5baec8a84e20d6c831a6d7935c3b9bebe29",
|
|
785
|
+
"transform": "verbatim"
|
|
786
|
+
},
|
|
779
787
|
{
|
|
780
788
|
"source": "package/dist/quota/projection.d.ts",
|
|
781
789
|
"output": "quota/projection.d.ts",
|
|
@@ -875,17 +883,17 @@
|
|
|
875
883
|
{
|
|
876
884
|
"source": "package/dist/store/attribution.d.ts",
|
|
877
885
|
"output": "store/attribution.d.ts",
|
|
878
|
-
"bytes":
|
|
879
|
-
"sourceSha256": "
|
|
880
|
-
"outputSha256": "
|
|
886
|
+
"bytes": 1951,
|
|
887
|
+
"sourceSha256": "16919a55fe944b459ff511c125ac2ba34714949d38db006c8ab047dd48ab51a6",
|
|
888
|
+
"outputSha256": "16919a55fe944b459ff511c125ac2ba34714949d38db006c8ab047dd48ab51a6",
|
|
881
889
|
"transform": "verbatim"
|
|
882
890
|
},
|
|
883
891
|
{
|
|
884
892
|
"source": "package/dist/store/attribution.js",
|
|
885
893
|
"output": "store/attribution.js",
|
|
886
|
-
"bytes":
|
|
887
|
-
"sourceSha256": "
|
|
888
|
-
"outputSha256": "
|
|
894
|
+
"bytes": 3280,
|
|
895
|
+
"sourceSha256": "5b54980a4f158c9b61cd90c4a83a14e83268fe98101fc88409500bb73d1126bb",
|
|
896
|
+
"outputSha256": "5b54980a4f158c9b61cd90c4a83a14e83268fe98101fc88409500bb73d1126bb",
|
|
889
897
|
"transform": "verbatim"
|
|
890
898
|
},
|
|
891
899
|
{
|
|
@@ -971,17 +979,17 @@
|
|
|
971
979
|
{
|
|
972
980
|
"source": "package/dist/store/pool.d.ts",
|
|
973
981
|
"output": "store/pool.d.ts",
|
|
974
|
-
"bytes":
|
|
975
|
-
"sourceSha256": "
|
|
976
|
-
"outputSha256": "
|
|
982
|
+
"bytes": 11743,
|
|
983
|
+
"sourceSha256": "0693e165ef937c22c375a3f63d01209c844d101ddf1a6702c8acc99bb5fc2a9d",
|
|
984
|
+
"outputSha256": "0693e165ef937c22c375a3f63d01209c844d101ddf1a6702c8acc99bb5fc2a9d",
|
|
977
985
|
"transform": "verbatim"
|
|
978
986
|
},
|
|
979
987
|
{
|
|
980
988
|
"source": "package/dist/store/pool.js",
|
|
981
989
|
"output": "store/pool.js",
|
|
982
|
-
"bytes":
|
|
983
|
-
"sourceSha256": "
|
|
984
|
-
"outputSha256": "
|
|
990
|
+
"bytes": 6332,
|
|
991
|
+
"sourceSha256": "45a696753691027dfcadd8e94b38b111ad5085ba1cb4f769225b15efc68125d7",
|
|
992
|
+
"outputSha256": "45a696753691027dfcadd8e94b38b111ad5085ba1cb4f769225b15efc68125d7",
|
|
985
993
|
"transform": "verbatim"
|
|
986
994
|
},
|
|
987
995
|
{
|
|
@@ -1027,9 +1035,17 @@
|
|
|
1027
1035
|
{
|
|
1028
1036
|
"source": "package/dist/store/pull.js",
|
|
1029
1037
|
"output": "store/pull.js",
|
|
1030
|
-
"bytes":
|
|
1031
|
-
"sourceSha256": "
|
|
1032
|
-
"outputSha256": "
|
|
1038
|
+
"bytes": 5602,
|
|
1039
|
+
"sourceSha256": "512a723a49dae3be50aa8c67ca8f6a5c165ec72e8a991a166d8d93e33e895350",
|
|
1040
|
+
"outputSha256": "512a723a49dae3be50aa8c67ca8f6a5c165ec72e8a991a166d8d93e33e895350",
|
|
1041
|
+
"transform": "verbatim"
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
"source": "package/dist/store/quota-writer.js",
|
|
1045
|
+
"output": "store/quota-writer.js",
|
|
1046
|
+
"bytes": 12396,
|
|
1047
|
+
"sourceSha256": "ade5fdda516011baf11e40f672263364cea07c45bdcbfc11107e22794e9aef21",
|
|
1048
|
+
"outputSha256": "ade5fdda516011baf11e40f672263364cea07c45bdcbfc11107e22794e9aef21",
|
|
1033
1049
|
"transform": "verbatim"
|
|
1034
1050
|
},
|
|
1035
1051
|
{
|
|
@@ -1091,9 +1107,9 @@
|
|
|
1091
1107
|
{
|
|
1092
1108
|
"source": "package/dist/store/runtime.d.ts",
|
|
1093
1109
|
"output": "store/runtime.d.ts",
|
|
1094
|
-
"bytes":
|
|
1095
|
-
"sourceSha256": "
|
|
1096
|
-
"outputSha256": "
|
|
1110
|
+
"bytes": 2100,
|
|
1111
|
+
"sourceSha256": "34efe7252b6c0aead222cab6164340e76e9c22202049158164c56c3b46bab91f",
|
|
1112
|
+
"outputSha256": "34efe7252b6c0aead222cab6164340e76e9c22202049158164c56c3b46bab91f",
|
|
1097
1113
|
"transform": "verbatim"
|
|
1098
1114
|
},
|
|
1099
1115
|
{
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type StoreContext } from './mutate.js';
|
|
1
2
|
import { type StoreRuntime } from './runtime.js';
|
|
3
|
+
import { type PoolRow } from './schema.js';
|
|
2
4
|
/**
|
|
3
5
|
* What a pull or refresh captured about its row (named by id alongside) when
|
|
4
6
|
* it was issued. A result applies only while the row with that id still has
|
|
@@ -20,6 +22,9 @@ export interface Attribution {
|
|
|
20
22
|
credentialEpoch: number;
|
|
21
23
|
identity?: string;
|
|
22
24
|
}
|
|
25
|
+
export declare function assertQuotaEpoch(id: string, attribution: Attribution): void;
|
|
26
|
+
export declare function assertQuotaRow(id: string, attribution: Attribution, row: PoolRow | undefined): asserts row is PoolRow;
|
|
27
|
+
export declare function mergedQuota(ctx: StoreContext, id: string, stored: unknown, observation: unknown): unknown;
|
|
23
28
|
/**
|
|
24
29
|
* Merges a quota observation into a row's stored map under the store locks,
|
|
25
30
|
* after attribution passes. Needs no row lock, so it is permitted from
|
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
import { PoolOperationError } from './errors.js';
|
|
2
|
-
import { toFailure, withTransaction } from './mutate.js';
|
|
2
|
+
import { toFailure, withTransaction, } from './mutate.js';
|
|
3
3
|
import { LockStack } from './refresh-lock.js';
|
|
4
4
|
import { refusal, requireBound, unknownRow, } from './runtime.js';
|
|
5
5
|
import { isCredentialEpoch } from './schema.js';
|
|
6
|
+
export function assertQuotaEpoch(id, attribution) {
|
|
7
|
+
if (!isCredentialEpoch(attribution?.credentialEpoch))
|
|
8
|
+
throw refusal('pull', id, 'invalid-input', 'the credential epoch the reading was issued for must be a positive safe integer');
|
|
9
|
+
}
|
|
10
|
+
// Shared by preflight and the locked commit: accepting into memory must never
|
|
11
|
+
// hide a refusal the immediate writer would have returned to its caller.
|
|
12
|
+
export function assertQuotaRow(id, attribution, row) {
|
|
13
|
+
if (!row)
|
|
14
|
+
throw unknownRow('pull', id);
|
|
15
|
+
if (row.invalid)
|
|
16
|
+
throw refusal('pull', id, 'invalid-row', `row ${id} is invalid`);
|
|
17
|
+
if (!row.credential)
|
|
18
|
+
throw refusal('pull', id, 'no-credential', `row ${id} holds no credential`);
|
|
19
|
+
requireBound('pull', row);
|
|
20
|
+
if (row.torn ||
|
|
21
|
+
!row.hasEntry ||
|
|
22
|
+
row.credentialEpoch !== attribution.credentialEpoch ||
|
|
23
|
+
row.identity !== attribution.identity)
|
|
24
|
+
throw new PoolOperationError({
|
|
25
|
+
operation: 'pull',
|
|
26
|
+
rowId: id,
|
|
27
|
+
phase: 'pull',
|
|
28
|
+
retryable: true,
|
|
29
|
+
kind: 'attribution',
|
|
30
|
+
message: `quota for ${id} was issued for a credential the row no longer holds`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function mergedQuota(ctx, id, stored, observation) {
|
|
34
|
+
const merged = ctx.codec.merge(stored, observation);
|
|
35
|
+
if (!ctx.codec.validate(merged))
|
|
36
|
+
throw refusal('pull', id, 'invalid-quota', 'the quota codec rejected the merged map');
|
|
37
|
+
return merged;
|
|
38
|
+
}
|
|
6
39
|
/**
|
|
7
40
|
* Merges a quota observation into a row's stored map under the store locks,
|
|
8
41
|
* after attribution passes. Needs no row lock, so it is permitted from
|
|
@@ -13,37 +46,18 @@ export async function recordQuota(rt, id, attribution, observation) {
|
|
|
13
46
|
const locks = new LockStack(ctx.lockDefaults, ctx.lockEnv);
|
|
14
47
|
const progress = { writes: 0 };
|
|
15
48
|
try {
|
|
16
|
-
|
|
17
|
-
throw refusal('pull', id, 'invalid-input', 'the credential epoch the reading was issued for must be a positive safe integer');
|
|
49
|
+
assertQuotaEpoch(id, attribution);
|
|
18
50
|
await withTransaction(ctx, locks, progress, { operation: 'pull', rowId: id }, async (tx) => {
|
|
19
51
|
tx.assertNotStaged(id);
|
|
20
52
|
const row = tx.row(id);
|
|
21
|
-
if (!row)
|
|
22
|
-
throw unknownRow('pull', id);
|
|
23
|
-
if (row.invalid)
|
|
24
|
-
throw refusal('pull', id, 'invalid-row', `row ${id} is invalid`);
|
|
25
53
|
// A reading belongs to one (epoch, identity, credential). A row
|
|
26
54
|
// holding no credential, or torn between the writes of a replace,
|
|
27
55
|
// has no such triple on disk, so no reading is recorded for it.
|
|
28
|
-
|
|
29
|
-
throw refusal('pull', id, 'no-credential', `row ${id} holds no credential`);
|
|
30
|
-
requireBound('pull', row);
|
|
56
|
+
assertQuotaRow(id, attribution, row);
|
|
31
57
|
const entry = tx.entry(id);
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
row.identity !== attribution.identity)
|
|
36
|
-
throw new PoolOperationError({
|
|
37
|
-
operation: 'pull',
|
|
38
|
-
rowId: id,
|
|
39
|
-
phase: 'pull',
|
|
40
|
-
retryable: true,
|
|
41
|
-
kind: 'attribution',
|
|
42
|
-
message: `quota for ${id} was issued for a credential the row no longer holds`,
|
|
43
|
-
});
|
|
44
|
-
const merged = ctx.codec.merge(entry.quota, observation);
|
|
45
|
-
if (!ctx.codec.validate(merged))
|
|
46
|
-
throw refusal('pull', id, 'invalid-quota', 'the quota codec rejected the merged map');
|
|
58
|
+
if (!entry)
|
|
59
|
+
throw unknownRow('pull', id);
|
|
60
|
+
const merged = mergedQuota(ctx, id, entry.quota, observation);
|
|
47
61
|
tx.setEntry(id, { ...entry, quota: merged, needsFirstReading: false });
|
|
48
62
|
await tx.commitConfig();
|
|
49
63
|
},
|
|
@@ -16,6 +16,19 @@ export interface OpenPoolStoreOptions {
|
|
|
16
16
|
configPath: string;
|
|
17
17
|
statePath: string;
|
|
18
18
|
quota: QuotaCodec;
|
|
19
|
+
/**
|
|
20
|
+
* Opt in to detached, coalesced quota publication, with any `quota` codec.
|
|
21
|
+
* Each accepted observation is queued whole for its row and binding, and
|
|
22
|
+
* the queue is applied in arrival order through `quota.merge` under the
|
|
23
|
+
* store locks, so the codec's own merge decides which fields a newer or
|
|
24
|
+
* older reading replaces. Accepted readings appear in read/load
|
|
25
|
+
* immediately, merged the same way; the returned promise still rejects
|
|
26
|
+
* current-row refusals before queueing. Await dispose or close at shutdown
|
|
27
|
+
* to drain.
|
|
28
|
+
*/
|
|
29
|
+
coalesceQuota?: boolean;
|
|
30
|
+
/** Bounded quota drain on close/dispose, in milliseconds; defaults to 1,000. */
|
|
31
|
+
quotaDrainTimeoutMs?: number;
|
|
19
32
|
/**
|
|
20
33
|
* The codec of the provider state kept beside each row's credential (see
|
|
21
34
|
* `ProviderStateCodec`). Without it no row shows a provider state, and
|
|
@@ -189,6 +202,10 @@ export interface PoolStore {
|
|
|
189
202
|
}>;
|
|
190
203
|
refresh(id: string, provider: ProviderRefresh, options?: RefreshOptions): Promise<RefreshOutcome>;
|
|
191
204
|
recordQuota(id: string, attribution: Attribution, observation: unknown): Promise<void>;
|
|
205
|
+
/** Drain opted-in quota publication; subsequent coalesced readings refuse. Idempotent. */
|
|
206
|
+
close(): Promise<void>;
|
|
207
|
+
/** Same bounded quota drain as close(). Does not wait for provider pull hooks. */
|
|
208
|
+
dispose(): Promise<void>;
|
|
192
209
|
/** Fires a pull for a row admission refused for want of a reading. */
|
|
193
210
|
requestReading(id: string): void;
|
|
194
211
|
/** Resolves once every pull fired so far has settled. */
|
|
@@ -3,6 +3,7 @@ import { initializePool, readPool, } from './mutate.js';
|
|
|
3
3
|
import { updateProviderStateRow, } from './provider-state.js';
|
|
4
4
|
import { publication, publishRoster, } from './publication.js';
|
|
5
5
|
import { PullScheduler } from './pull.js';
|
|
6
|
+
import { PoolQuotaWriter } from './quota-writer.js';
|
|
6
7
|
import { refreshRow, } from './refresh.js';
|
|
7
8
|
import { POOL_LOCK_DEFAULTS, } from './refresh-lock.js';
|
|
8
9
|
import { addRow, disableRow, enableRow, recordRowIdentity, removeRow, reorderRows, replaceRow, rotateRow, } from './rows.js';
|
|
@@ -80,9 +81,21 @@ export function openPoolStore(options) {
|
|
|
80
81
|
rowLockOptions: options.rowLockOptions ?? {},
|
|
81
82
|
firePull: (id, reason) => pulls.fire(id, reason),
|
|
82
83
|
};
|
|
84
|
+
const quotaWriter = options.coalesceQuota
|
|
85
|
+
? new PoolQuotaWriter(rt, options.quotaDrainTimeoutMs)
|
|
86
|
+
: undefined;
|
|
87
|
+
const record = (id, attribution, observation) => quotaWriter
|
|
88
|
+
? quotaWriter.record(id, attribution, observation)
|
|
89
|
+
: recordQuota(rt, id, attribution, observation);
|
|
90
|
+
rt.publishQuota = record;
|
|
91
|
+
const overlay = (result) => {
|
|
92
|
+
if (result.status === 'ready' && quotaWriter)
|
|
93
|
+
return { ...result, rows: quotaWriter.overlay(result.rows) };
|
|
94
|
+
return result;
|
|
95
|
+
};
|
|
83
96
|
return {
|
|
84
97
|
async load() {
|
|
85
|
-
const result = await readPool(ctx);
|
|
98
|
+
const result = overlay(await readPool(ctx));
|
|
86
99
|
if (result.status === 'ready') {
|
|
87
100
|
// A torn row is no candidate, but its pull is fired too: the pull
|
|
88
101
|
// completes the row on disk before reading it, so an interrupted
|
|
@@ -96,7 +109,7 @@ export function openPoolStore(options) {
|
|
|
96
109
|
return toLoad(result);
|
|
97
110
|
},
|
|
98
111
|
async read() {
|
|
99
|
-
return toLoad(await readPool(ctx));
|
|
112
|
+
return toLoad(overlay(await readPool(ctx)));
|
|
100
113
|
},
|
|
101
114
|
async initialize(input = {}) {
|
|
102
115
|
return { status: await initializePool(ctx, input.dropKeys ?? []) };
|
|
@@ -115,7 +128,9 @@ export function openPoolStore(options) {
|
|
|
115
128
|
updateSettings: (mutator, callOptions) => updatePoolSettings(rt, mutator, callOptions),
|
|
116
129
|
recordIdentity: (id, identity, attribution, callOptions) => recordRowIdentity(rt, id, identity, attribution, callOptions),
|
|
117
130
|
refresh: (id, provider, callOptions) => refreshRow(rt, id, provider, callOptions),
|
|
118
|
-
recordQuota:
|
|
131
|
+
recordQuota: record,
|
|
132
|
+
close: () => quotaWriter?.close() ?? Promise.resolve(),
|
|
133
|
+
dispose: () => quotaWriter?.close() ?? Promise.resolve(),
|
|
119
134
|
requestReading: (id) => pulls.fire(id, 'admission'),
|
|
120
135
|
pullsSettled: () => pulls.settled(),
|
|
121
136
|
};
|
|
@@ -105,7 +105,9 @@ export class PullScheduler {
|
|
|
105
105
|
cause,
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
const publish = rt.publishQuota ??
|
|
109
|
+
((rowId, attribution, value) => recordQuota(rt, rowId, attribution, value));
|
|
110
|
+
await publish(id, {
|
|
109
111
|
credentialEpoch: request.credentialEpoch,
|
|
110
112
|
...(request.identity !== undefined
|
|
111
113
|
? { identity: request.identity }
|