@openlimiter/core 0.2.0 → 1.2.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/cache.d.ts +64 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +230 -32
- package/dist/cache.js.map +1 -1
- package/dist/collection.d.ts +151 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +218 -0
- package/dist/collection.js.map +1 -0
- package/dist/connection-state.d.ts +229 -0
- package/dist/connection-state.d.ts.map +1 -0
- package/dist/connection-state.js +393 -0
- package/dist/connection-state.js.map +1 -0
- package/dist/failures.d.ts +2 -1
- package/dist/failures.d.ts.map +1 -1
- package/dist/failures.js +7 -3
- package/dist/failures.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/merge.d.ts +1 -0
- package/dist/merge.d.ts.map +1 -1
- package/dist/merge.js +29 -5
- package/dist/merge.js.map +1 -1
- package/dist/normalizer.d.ts.map +1 -1
- package/dist/normalizer.js +50 -4
- package/dist/normalizer.js.map +1 -1
- package/dist/schedule.d.ts +75 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +131 -0
- package/dist/schedule.js.map +1 -0
- package/dist/types.d.ts +93 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +61 -0
- package/dist/types.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { snapshotIdentity } from "./merge.js";
|
|
2
|
+
import type { ProviderCode, Snapshot } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* What one collection run achieved, and what the cache does about it.
|
|
5
|
+
*
|
|
6
|
+
* The product's whole honesty problem in one file. A provider answering with a
|
|
7
|
+
* 200 is not a reading, and a provider answering with a well formed body whose
|
|
8
|
+
* meaning has changed is worse than a provider answering with nothing: the
|
|
9
|
+
* first is visibly unknown, the second is confidently wrong. So a run reports
|
|
10
|
+
* what it achieved rather than what it received, and drift is a first class
|
|
11
|
+
* outcome with a first class consequence.
|
|
12
|
+
*
|
|
13
|
+
* That consequence is a SUPPRESSION. Removing the affected rows is not enough
|
|
14
|
+
* on its own, because a cache is shared: the desktop, the command line tool,
|
|
15
|
+
* the adapters and the dashboard all read it, and any one of them could write a
|
|
16
|
+
* stale row back before the others noticed. A suppression is a standing
|
|
17
|
+
* instruction in the document itself, so every reader on the machine reaches
|
|
18
|
+
* the same unknown at the same instant, and a row that predates it can never
|
|
19
|
+
* become visible again.
|
|
20
|
+
*
|
|
21
|
+
* Everything here is pure: no clock, no file system, no network. The caller
|
|
22
|
+
* supplies the instant, so a whole history can be replayed and land on the same
|
|
23
|
+
* answer every time.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Why a collection run produced nothing usable.
|
|
27
|
+
*
|
|
28
|
+
* `drift` is separated from every other reason on purpose, and is the only one
|
|
29
|
+
* that suppresses. The rest are ordinary interruptions: the provider was busy,
|
|
30
|
+
* the machine was offline, a credential lapsed. None of those say anything
|
|
31
|
+
* about whether the reading we already hold is still true, so none of them
|
|
32
|
+
* throws it away; the reading ages out through the ordinary freshness rule
|
|
33
|
+
* instead. Drift is the one that says the previous reading's MEANING is in
|
|
34
|
+
* doubt, and that cannot be waited out.
|
|
35
|
+
*/
|
|
36
|
+
export declare const COLLECTION_FAILURE_REASONS: readonly ["drift", "network", "authentication", "rate_limited", "remote_error", "local_io"];
|
|
37
|
+
export type CollectionFailureReason = (typeof COLLECTION_FAILURE_REASONS)[number];
|
|
38
|
+
export type CollectionReport = {
|
|
39
|
+
ok: true;
|
|
40
|
+
provider: ProviderCode;
|
|
41
|
+
accountId?: string;
|
|
42
|
+
observedAt: string;
|
|
43
|
+
snapshots: readonly Snapshot[];
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
provider: ProviderCode;
|
|
47
|
+
accountId?: string;
|
|
48
|
+
observedAt: string;
|
|
49
|
+
reason: CollectionFailureReason;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* A standing instruction to distrust one identity's cached rows.
|
|
53
|
+
*
|
|
54
|
+
* `reason` is a single literal rather than the full failure vocabulary,
|
|
55
|
+
* deliberately: drift is the only failure that suppresses, and a type that
|
|
56
|
+
* could hold "network" would invite a future caller to suppress on a dropped
|
|
57
|
+
* packet, which would throw away a perfectly good reading every time a train
|
|
58
|
+
* went into a tunnel.
|
|
59
|
+
*/
|
|
60
|
+
export interface CacheSuppression {
|
|
61
|
+
provider: ProviderCode;
|
|
62
|
+
accountId?: string;
|
|
63
|
+
reason: "drift";
|
|
64
|
+
suppressedAt: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* How many suppressions one cache document may carry.
|
|
68
|
+
*
|
|
69
|
+
* The same order of magnitude as the row bound, because there is at most one
|
|
70
|
+
* suppression per identity and identities are what rows are keyed by. A
|
|
71
|
+
* document claiming more than this is not a cache with a lot of drift, it is a
|
|
72
|
+
* document somebody wrote by hand.
|
|
73
|
+
*/
|
|
74
|
+
export declare const MAX_CACHE_SUPPRESSIONS = 64;
|
|
75
|
+
/** The cache as this module reasons about it: rows, and what to distrust. */
|
|
76
|
+
export interface CacheState {
|
|
77
|
+
snapshots: readonly Snapshot[];
|
|
78
|
+
suppressions: readonly CacheSuppression[];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The one place provider and account identity is decided.
|
|
82
|
+
*
|
|
83
|
+
* A row, a report and a suppression all have to agree on what "the same thing"
|
|
84
|
+
* means, and the way that goes wrong is three near identical comparisons in
|
|
85
|
+
* three files, one of which forgets that an absent account is not the account
|
|
86
|
+
* called "default". So there is one function, everything calls it, and an
|
|
87
|
+
* absent account keys exactly as it always did.
|
|
88
|
+
*/
|
|
89
|
+
export declare function collectionIdentity(provider: ProviderCode, accountId?: string): string;
|
|
90
|
+
/** Whether this snapshot belongs to this provider and account. */
|
|
91
|
+
export declare function snapshotBelongsTo(snapshot: Snapshot, provider: ProviderCode, accountId?: string): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* A suppression document as read back from disk.
|
|
94
|
+
*
|
|
95
|
+
* `ok: false` is not "there were none". It means the suppression data could not
|
|
96
|
+
* be believed, and since a suppression is the only thing standing between a
|
|
97
|
+
* stale row and a surface, a suppression list that cannot be read makes every
|
|
98
|
+
* row in that document unknown. That is the fail closed direction: the cost of
|
|
99
|
+
* being wrong here is showing a number that is no longer true, and the cost of
|
|
100
|
+
* being right is one refresh cycle of honest unknown.
|
|
101
|
+
*/
|
|
102
|
+
export type SuppressionReadResult = {
|
|
103
|
+
ok: true;
|
|
104
|
+
suppressions: CacheSuppression[];
|
|
105
|
+
} | {
|
|
106
|
+
ok: false;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Read the suppression list out of a cache document.
|
|
110
|
+
*
|
|
111
|
+
* Absent is the ordinary case and reads as an empty list: a document written
|
|
112
|
+
* before suppressions existed has none, which is true. Anything present and
|
|
113
|
+
* unreadable is refused whole rather than partially salvaged, because a
|
|
114
|
+
* half read suppression list would silently un suppress whichever entries
|
|
115
|
+
* failed to parse, which is exactly the value it exists to prevent.
|
|
116
|
+
*/
|
|
117
|
+
export declare function readSuppressions(value: unknown): SuppressionReadResult;
|
|
118
|
+
/**
|
|
119
|
+
* Fold one collection report into the cache.
|
|
120
|
+
*
|
|
121
|
+
* Four rules, one per kind of outcome, and no fifth:
|
|
122
|
+
*
|
|
123
|
+
* 1. A successful report replaces every row for its identity and clears that
|
|
124
|
+
* identity's suppression. A provider that has started making sense again
|
|
125
|
+
* is trusted again, and only a real parse can say that.
|
|
126
|
+
* 2. A drift report removes those rows and records the suppression, replacing
|
|
127
|
+
* any earlier one, so the instant always reflects the latest drift.
|
|
128
|
+
* 3. Every other failure changes nothing at all. The rows we hold stay, and
|
|
129
|
+
* age out through the ordinary freshness rule.
|
|
130
|
+
* 4. A report whose instant cannot be read is not applied. An unreadable
|
|
131
|
+
* instant cannot be compared with a row's observation, so it could neither
|
|
132
|
+
* suppress honestly nor be cleared honestly.
|
|
133
|
+
*/
|
|
134
|
+
export declare function applyCollectionReport(state: CacheState, report: CollectionReport): CacheState;
|
|
135
|
+
/**
|
|
136
|
+
* The rows a surface may actually see.
|
|
137
|
+
*
|
|
138
|
+
* A row survives only when nothing suppresses its identity, or when it was
|
|
139
|
+
* observed strictly AFTER the suppression that does. Strictly after, not at or
|
|
140
|
+
* after: a row observed in the same millisecond as the drift that suppressed it
|
|
141
|
+
* is the very row the drift was about.
|
|
142
|
+
*
|
|
143
|
+
* Every read goes through here, which is what makes drift instant everywhere.
|
|
144
|
+
* `buildAdvice` is handed the result of this function and never the raw rows,
|
|
145
|
+
* so a suppressed provider is reported unknown by the statusline, the agent
|
|
146
|
+
* context, the dashboard and the tray in the same tick.
|
|
147
|
+
*/
|
|
148
|
+
export declare function visibleSnapshots(state: CacheState): Snapshot[];
|
|
149
|
+
/** Identity, exported for the cache writer that has to key rows the same way. */
|
|
150
|
+
export { snapshotIdentity };
|
|
151
|
+
//# sourceMappingURL=collection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,gBAAgB,EAAqB,MAAM,YAAY,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGzD;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,0BAA0B,6FAO7B,CAAC;AAEX,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAElF,MAAM,MAAM,gBAAgB,GACxB;IACE,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;CAChC,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC;AAEN;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,KAAoB,CAAC;AAExD,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC/B,YAAY,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,YAAY,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAER;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,YAAY,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAMT;AAWD;;;;;;;;;GASG;AACH,MAAM,MAAM,qBAAqB,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,gBAAgB,EAAE,CAAA;CAAE,GAC9C;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC;AASlB;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,qBAAqB,CA2BtE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,gBAAgB,GACvB,UAAU,CAqCZ;AAeD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ,EAAE,CAe9D;AAED,iFAAiF;AACjF,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { mergeSnapshots, snapshotIdentity, MAX_CACHE_ENTRIES } from "./merge.js";
|
|
2
|
+
import { PROVIDER_CODES, ACCOUNT_ID_PATTERN } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* What one collection run achieved, and what the cache does about it.
|
|
5
|
+
*
|
|
6
|
+
* The product's whole honesty problem in one file. A provider answering with a
|
|
7
|
+
* 200 is not a reading, and a provider answering with a well formed body whose
|
|
8
|
+
* meaning has changed is worse than a provider answering with nothing: the
|
|
9
|
+
* first is visibly unknown, the second is confidently wrong. So a run reports
|
|
10
|
+
* what it achieved rather than what it received, and drift is a first class
|
|
11
|
+
* outcome with a first class consequence.
|
|
12
|
+
*
|
|
13
|
+
* That consequence is a SUPPRESSION. Removing the affected rows is not enough
|
|
14
|
+
* on its own, because a cache is shared: the desktop, the command line tool,
|
|
15
|
+
* the adapters and the dashboard all read it, and any one of them could write a
|
|
16
|
+
* stale row back before the others noticed. A suppression is a standing
|
|
17
|
+
* instruction in the document itself, so every reader on the machine reaches
|
|
18
|
+
* the same unknown at the same instant, and a row that predates it can never
|
|
19
|
+
* become visible again.
|
|
20
|
+
*
|
|
21
|
+
* Everything here is pure: no clock, no file system, no network. The caller
|
|
22
|
+
* supplies the instant, so a whole history can be replayed and land on the same
|
|
23
|
+
* answer every time.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Why a collection run produced nothing usable.
|
|
27
|
+
*
|
|
28
|
+
* `drift` is separated from every other reason on purpose, and is the only one
|
|
29
|
+
* that suppresses. The rest are ordinary interruptions: the provider was busy,
|
|
30
|
+
* the machine was offline, a credential lapsed. None of those say anything
|
|
31
|
+
* about whether the reading we already hold is still true, so none of them
|
|
32
|
+
* throws it away; the reading ages out through the ordinary freshness rule
|
|
33
|
+
* instead. Drift is the one that says the previous reading's MEANING is in
|
|
34
|
+
* doubt, and that cannot be waited out.
|
|
35
|
+
*/
|
|
36
|
+
export const COLLECTION_FAILURE_REASONS = [
|
|
37
|
+
"drift",
|
|
38
|
+
"network",
|
|
39
|
+
"authentication",
|
|
40
|
+
"rate_limited",
|
|
41
|
+
"remote_error",
|
|
42
|
+
"local_io"
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* How many suppressions one cache document may carry.
|
|
46
|
+
*
|
|
47
|
+
* The same order of magnitude as the row bound, because there is at most one
|
|
48
|
+
* suppression per identity and identities are what rows are keyed by. A
|
|
49
|
+
* document claiming more than this is not a cache with a lot of drift, it is a
|
|
50
|
+
* document somebody wrote by hand.
|
|
51
|
+
*/
|
|
52
|
+
export const MAX_CACHE_SUPPRESSIONS = MAX_CACHE_ENTRIES;
|
|
53
|
+
/**
|
|
54
|
+
* The one place provider and account identity is decided.
|
|
55
|
+
*
|
|
56
|
+
* A row, a report and a suppression all have to agree on what "the same thing"
|
|
57
|
+
* means, and the way that goes wrong is three near identical comparisons in
|
|
58
|
+
* three files, one of which forgets that an absent account is not the account
|
|
59
|
+
* called "default". So there is one function, everything calls it, and an
|
|
60
|
+
* absent account keys exactly as it always did.
|
|
61
|
+
*/
|
|
62
|
+
export function collectionIdentity(provider, accountId) {
|
|
63
|
+
return accountId === undefined ? provider : provider + " " + accountId;
|
|
64
|
+
}
|
|
65
|
+
/** Whether this snapshot belongs to this provider and account. */
|
|
66
|
+
export function snapshotBelongsTo(snapshot, provider, accountId) {
|
|
67
|
+
return (snapshot.provider === provider &&
|
|
68
|
+
collectionIdentity(provider, snapshot.accountId) ===
|
|
69
|
+
collectionIdentity(provider, accountId));
|
|
70
|
+
}
|
|
71
|
+
function parseInstant(value) {
|
|
72
|
+
const parsed = Date.parse(value);
|
|
73
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
74
|
+
}
|
|
75
|
+
function isRecord(value) {
|
|
76
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
77
|
+
}
|
|
78
|
+
function isProviderCode(value) {
|
|
79
|
+
return (typeof value === "string" &&
|
|
80
|
+
PROVIDER_CODES.includes(value));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Read the suppression list out of a cache document.
|
|
84
|
+
*
|
|
85
|
+
* Absent is the ordinary case and reads as an empty list: a document written
|
|
86
|
+
* before suppressions existed has none, which is true. Anything present and
|
|
87
|
+
* unreadable is refused whole rather than partially salvaged, because a
|
|
88
|
+
* half read suppression list would silently un suppress whichever entries
|
|
89
|
+
* failed to parse, which is exactly the value it exists to prevent.
|
|
90
|
+
*/
|
|
91
|
+
export function readSuppressions(value) {
|
|
92
|
+
if (value === undefined || value === null)
|
|
93
|
+
return { ok: true, suppressions: [] };
|
|
94
|
+
if (!Array.isArray(value))
|
|
95
|
+
return { ok: false };
|
|
96
|
+
if (value.length > MAX_CACHE_SUPPRESSIONS)
|
|
97
|
+
return { ok: false };
|
|
98
|
+
const suppressions = [];
|
|
99
|
+
for (const entry of value) {
|
|
100
|
+
if (!isRecord(entry))
|
|
101
|
+
return { ok: false };
|
|
102
|
+
if (!isProviderCode(entry["provider"]))
|
|
103
|
+
return { ok: false };
|
|
104
|
+
if (entry["reason"] !== "drift")
|
|
105
|
+
return { ok: false };
|
|
106
|
+
const suppressedAt = entry["suppressedAt"];
|
|
107
|
+
if (typeof suppressedAt !== "string" || parseInstant(suppressedAt) === null) {
|
|
108
|
+
return { ok: false };
|
|
109
|
+
}
|
|
110
|
+
const accountId = entry["accountId"];
|
|
111
|
+
if (accountId !== undefined) {
|
|
112
|
+
if (typeof accountId !== "string" || !ACCOUNT_ID_PATTERN.test(accountId)) {
|
|
113
|
+
return { ok: false };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
suppressions.push({
|
|
117
|
+
provider: entry["provider"],
|
|
118
|
+
reason: "drift",
|
|
119
|
+
suppressedAt,
|
|
120
|
+
...(accountId === undefined ? {} : { accountId })
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return { ok: true, suppressions };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Fold one collection report into the cache.
|
|
127
|
+
*
|
|
128
|
+
* Four rules, one per kind of outcome, and no fifth:
|
|
129
|
+
*
|
|
130
|
+
* 1. A successful report replaces every row for its identity and clears that
|
|
131
|
+
* identity's suppression. A provider that has started making sense again
|
|
132
|
+
* is trusted again, and only a real parse can say that.
|
|
133
|
+
* 2. A drift report removes those rows and records the suppression, replacing
|
|
134
|
+
* any earlier one, so the instant always reflects the latest drift.
|
|
135
|
+
* 3. Every other failure changes nothing at all. The rows we hold stay, and
|
|
136
|
+
* age out through the ordinary freshness rule.
|
|
137
|
+
* 4. A report whose instant cannot be read is not applied. An unreadable
|
|
138
|
+
* instant cannot be compared with a row's observation, so it could neither
|
|
139
|
+
* suppress honestly nor be cleared honestly.
|
|
140
|
+
*/
|
|
141
|
+
export function applyCollectionReport(state, report) {
|
|
142
|
+
if (parseInstant(report.observedAt) === null)
|
|
143
|
+
return state;
|
|
144
|
+
const belongs = (snapshot) => snapshotBelongsTo(snapshot, report.provider, report.accountId);
|
|
145
|
+
const suppressionMatches = (suppression) => suppression.provider === report.provider &&
|
|
146
|
+
collectionIdentity(report.provider, suppression.accountId) ===
|
|
147
|
+
collectionIdentity(report.provider, report.accountId);
|
|
148
|
+
if (report.ok) {
|
|
149
|
+
/* Rows for this identity are dropped before the merge rather than left for
|
|
150
|
+
it, because a successful run that returns FEWER meters than last time
|
|
151
|
+
must not leave the missing ones behind as fresh looking survivors. */
|
|
152
|
+
const kept = state.snapshots.filter((snapshot) => !belongs(snapshot));
|
|
153
|
+
return {
|
|
154
|
+
snapshots: mergeSnapshots(kept, report.snapshots),
|
|
155
|
+
suppressions: state.suppressions.filter((suppression) => !suppressionMatches(suppression))
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (report.reason !== "drift")
|
|
159
|
+
return state;
|
|
160
|
+
const suppression = {
|
|
161
|
+
provider: report.provider,
|
|
162
|
+
reason: "drift",
|
|
163
|
+
suppressedAt: report.observedAt,
|
|
164
|
+
...(report.accountId === undefined ? {} : { accountId: report.accountId })
|
|
165
|
+
};
|
|
166
|
+
const others = state.suppressions.filter((existing) => !suppressionMatches(existing));
|
|
167
|
+
return {
|
|
168
|
+
snapshots: state.snapshots.filter((snapshot) => !belongs(snapshot)),
|
|
169
|
+
/* Bounded by dropping the oldest, so a machine that drifts on every
|
|
170
|
+
provider for a month cannot grow the document without limit. */
|
|
171
|
+
suppressions: boundSuppressions([...others, suppression])
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function suppressedMilliseconds(suppression) {
|
|
175
|
+
return parseInstant(suppression.suppressedAt) ?? 0;
|
|
176
|
+
}
|
|
177
|
+
function boundSuppressions(suppressions) {
|
|
178
|
+
if (suppressions.length <= MAX_CACHE_SUPPRESSIONS)
|
|
179
|
+
return [...suppressions];
|
|
180
|
+
return [...suppressions]
|
|
181
|
+
.sort((left, right) => suppressedMilliseconds(right) - suppressedMilliseconds(left))
|
|
182
|
+
.slice(0, MAX_CACHE_SUPPRESSIONS);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The rows a surface may actually see.
|
|
186
|
+
*
|
|
187
|
+
* A row survives only when nothing suppresses its identity, or when it was
|
|
188
|
+
* observed strictly AFTER the suppression that does. Strictly after, not at or
|
|
189
|
+
* after: a row observed in the same millisecond as the drift that suppressed it
|
|
190
|
+
* is the very row the drift was about.
|
|
191
|
+
*
|
|
192
|
+
* Every read goes through here, which is what makes drift instant everywhere.
|
|
193
|
+
* `buildAdvice` is handed the result of this function and never the raw rows,
|
|
194
|
+
* so a suppressed provider is reported unknown by the statusline, the agent
|
|
195
|
+
* context, the dashboard and the tray in the same tick.
|
|
196
|
+
*/
|
|
197
|
+
export function visibleSnapshots(state) {
|
|
198
|
+
if (state.suppressions.length === 0)
|
|
199
|
+
return [...state.snapshots];
|
|
200
|
+
return state.snapshots.filter((snapshot) => {
|
|
201
|
+
const observed = parseInstant(snapshot.observedAt);
|
|
202
|
+
for (const suppression of state.suppressions) {
|
|
203
|
+
if (!snapshotBelongsTo(snapshot, suppression.provider, suppression.accountId)) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
/* A row whose own observation cannot be read cannot be proven newer than
|
|
207
|
+
the suppression, so it is not shown. */
|
|
208
|
+
if (observed === null)
|
|
209
|
+
return false;
|
|
210
|
+
if (observed <= suppressedMilliseconds(suppression))
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/** Identity, exported for the cache writer that has to key rows the same way. */
|
|
217
|
+
export { snapshotIdentity };
|
|
218
|
+
//# sourceMappingURL=collection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection.js","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The connection lifecycle, as one pure function and two human tables.
|
|
3
|
+
*
|
|
4
|
+
* A parser is not a connection. The product's central honesty problem is that
|
|
5
|
+
* an implemented parser has been able to look like a live account, so the state
|
|
6
|
+
* a connection is in has to be a value the whole product agrees on rather than
|
|
7
|
+
* a sentence each surface invents. Everything here is data and arithmetic: no
|
|
8
|
+
* clock, no network, no storage, so a surface can replay a connection's whole
|
|
9
|
+
* history and get the same answer every time.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CONNECTION_STATES: readonly ["NOT_CONFIGURED", "DETECTED", "NEEDS_AUTH", "READY_TO_ENABLE", "CONNECTING", "CONNECTED", "DEGRADED", "STALE", "AUTH_EXPIRED", "IMPORT_ONLY", "MANUAL", "UNSUPPORTED", "ERROR"];
|
|
12
|
+
export type ConnectionState = (typeof CONNECTION_STATES)[number];
|
|
13
|
+
/**
|
|
14
|
+
* How many consecutive network failures turn a degraded connection into a
|
|
15
|
+
* broken one. One timeout is a bad moment; three in a row is a fault worth
|
|
16
|
+
* showing a person.
|
|
17
|
+
*/
|
|
18
|
+
export declare const NETWORK_FAILURE_ERROR_THRESHOLD = 3;
|
|
19
|
+
export type ConnectionEvent =
|
|
20
|
+
/** A supported local tool was found on this machine. */
|
|
21
|
+
{
|
|
22
|
+
kind: "detected";
|
|
23
|
+
}
|
|
24
|
+
/** Setup established that this connection cannot proceed without a secret. */
|
|
25
|
+
| {
|
|
26
|
+
kind: "credential_required";
|
|
27
|
+
}
|
|
28
|
+
/** A secret was accepted into the operating system credential store. */
|
|
29
|
+
| {
|
|
30
|
+
kind: "credential_stored";
|
|
31
|
+
}
|
|
32
|
+
/** The user asked for this connection to start collecting. */
|
|
33
|
+
| {
|
|
34
|
+
kind: "enable_requested";
|
|
35
|
+
}
|
|
36
|
+
/** A remote read finished. `parsed` says whether we understood the body. */
|
|
37
|
+
| {
|
|
38
|
+
kind: "http_response";
|
|
39
|
+
status: number;
|
|
40
|
+
parsed: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** A local read finished, for readers that never speak HTTP. */
|
|
43
|
+
| {
|
|
44
|
+
kind: "local_read";
|
|
45
|
+
parsed: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** A read never reached the provider. `consecutive` counts failures in a row. */
|
|
48
|
+
| {
|
|
49
|
+
kind: "network_failure";
|
|
50
|
+
consecutive: number;
|
|
51
|
+
}
|
|
52
|
+
/** The newest observation aged past its own expiry. */
|
|
53
|
+
| {
|
|
54
|
+
kind: "expiry_passed";
|
|
55
|
+
}
|
|
56
|
+
/** The user removed this connection. */
|
|
57
|
+
| {
|
|
58
|
+
kind: "disconnected";
|
|
59
|
+
}
|
|
60
|
+
/** The product states this source can only be fed by an explicit import. */
|
|
61
|
+
| {
|
|
62
|
+
kind: "declared_import_only";
|
|
63
|
+
}
|
|
64
|
+
/** The product states this source is a user maintained plan. */
|
|
65
|
+
| {
|
|
66
|
+
kind: "declared_manual";
|
|
67
|
+
}
|
|
68
|
+
/** The product states no safe automatic source exists for this product. */
|
|
69
|
+
| {
|
|
70
|
+
kind: "declared_unsupported";
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* What the transition function cannot work out from the current state alone.
|
|
74
|
+
*
|
|
75
|
+
* The one authentication question is whether this connection has ever worked,
|
|
76
|
+
* and the current state cannot answer it. CONNECTED then a 404 lands in ERROR,
|
|
77
|
+
* and a 401 arriving there would read as "we never had a credential" when what
|
|
78
|
+
* actually happened is that a credential which used to work stopped, which is a
|
|
79
|
+
* different sentence and a different button. So the fact is carried on the
|
|
80
|
+
* connection record, where it belongs, and passed in. It is one way: nothing
|
|
81
|
+
* ever sets it back to false, because a connection that worked once always did.
|
|
82
|
+
*/
|
|
83
|
+
export interface ConnectionContext {
|
|
84
|
+
everConnected: boolean;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Advance a connection by one event.
|
|
88
|
+
*
|
|
89
|
+
* Total and pure: every state, event and context triple has an answer, and an
|
|
90
|
+
* event that makes no sense where it arrived leaves the state exactly as it was
|
|
91
|
+
* rather than inventing a transition. The context is required rather than
|
|
92
|
+
* defaulted, because a caller that forgets it would silently get the wrong
|
|
93
|
+
* authentication answer, and a compiler error is cheaper than that.
|
|
94
|
+
*/
|
|
95
|
+
export declare function nextConnectionState(state: ConnectionState, event: ConnectionEvent, context: ConnectionContext): ConnectionState;
|
|
96
|
+
/** One sentence per state, written for a person rather than for a log. */
|
|
97
|
+
export declare const connectionSentence: {
|
|
98
|
+
readonly NOT_CONFIGURED: "Not connected.";
|
|
99
|
+
readonly DETECTED: "Found on this machine but not collecting yet.";
|
|
100
|
+
readonly NEEDS_AUTH: "Waiting for a credential.";
|
|
101
|
+
readonly READY_TO_ENABLE: "Ready to start collecting.";
|
|
102
|
+
readonly CONNECTING: "Connecting.";
|
|
103
|
+
readonly CONNECTED: "Connected and collecting.";
|
|
104
|
+
readonly DEGRADED: "The provider is refusing reads for now.";
|
|
105
|
+
readonly STALE: "The last reading is older than it should be.";
|
|
106
|
+
readonly AUTH_EXPIRED: "The stored credential stopped working.";
|
|
107
|
+
readonly IMPORT_ONLY: "Reads only what you import. Nothing is collected automatically.";
|
|
108
|
+
readonly MANUAL: "Shows the plan you entered yourself.";
|
|
109
|
+
readonly UNSUPPORTED: "No safe automatic source exists for this product yet.";
|
|
110
|
+
readonly ERROR: "This connection failed and needs a look.";
|
|
111
|
+
};
|
|
112
|
+
/** The one thing to do next, in the words of the button that does it. */
|
|
113
|
+
export declare const connectionNextAction: {
|
|
114
|
+
readonly NOT_CONFIGURED: "Connect";
|
|
115
|
+
readonly DETECTED: "Enable local integration";
|
|
116
|
+
readonly NEEDS_AUTH: "Add credential";
|
|
117
|
+
readonly READY_TO_ENABLE: "Enable";
|
|
118
|
+
readonly CONNECTING: "Wait";
|
|
119
|
+
readonly CONNECTED: "Refresh now";
|
|
120
|
+
readonly DEGRADED: "Wait for the next retry";
|
|
121
|
+
readonly STALE: "Refresh now";
|
|
122
|
+
readonly AUTH_EXPIRED: "Reconnect";
|
|
123
|
+
readonly IMPORT_ONLY: "Import a payload";
|
|
124
|
+
readonly MANUAL: "Edit plan";
|
|
125
|
+
readonly UNSUPPORTED: "Add manual plan";
|
|
126
|
+
readonly ERROR: "View diagnostics";
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Why a connection is not simply working, in a closed vocabulary.
|
|
130
|
+
*
|
|
131
|
+
* A state says WHAT a connection is. A reason says why it got there, and it is
|
|
132
|
+
* the half a person actually needs: "stale" is a shrug, "stale because the
|
|
133
|
+
* stored credential stopped working" is something they can act on. Closed
|
|
134
|
+
* rather than free text, because a reason is rendered next to a button and a
|
|
135
|
+
* provider must never be able to write either one.
|
|
136
|
+
*/
|
|
137
|
+
export declare const CONNECTION_REASONS: readonly ["token_expired", "reading_expired", "tool_not_running", "provider_refusing", "shape_mismatch", "network_unreachable", "no_credential"];
|
|
138
|
+
export type ConnectionReason = (typeof CONNECTION_REASONS)[number];
|
|
139
|
+
/** One sentence per reason, written for a person rather than for a log. */
|
|
140
|
+
export declare const connectionReasonSentence: {
|
|
141
|
+
readonly token_expired: "The credential this connection uses stopped working.";
|
|
142
|
+
readonly reading_expired: "The newest reading is older than the window it describes.";
|
|
143
|
+
readonly tool_not_running: "The local tool has not written a reading yet.";
|
|
144
|
+
readonly provider_refusing: "The provider refused the last read.";
|
|
145
|
+
readonly shape_mismatch: "The answer did not match the shape this build reads.";
|
|
146
|
+
readonly network_unreachable: "The last read never reached the provider.";
|
|
147
|
+
readonly no_credential: "No credential has been stored for this connection.";
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* The connection as one value a surface can render without deciding anything.
|
|
151
|
+
*
|
|
152
|
+
* State, reason, and the one instruction that belongs to that pair. Every field
|
|
153
|
+
* is written by OpenLimiter and never by a provider, which is what keeps a
|
|
154
|
+
* payload from writing the sentence beside a button.
|
|
155
|
+
*/
|
|
156
|
+
export interface ConnectionStatus {
|
|
157
|
+
readonly state: ConnectionState;
|
|
158
|
+
readonly reason: ConnectionReason | null;
|
|
159
|
+
/** What the PERSON does next. Never something this app does on their behalf. */
|
|
160
|
+
readonly instruction: string;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The local tool a connection reads through, when one exists.
|
|
164
|
+
*
|
|
165
|
+
* Carried so an instruction can name it. A connection with no local tool, such
|
|
166
|
+
* as a remote read behind a key the person pasted, passes null and gets an
|
|
167
|
+
* instruction that names no tool rather than an invented one.
|
|
168
|
+
*/
|
|
169
|
+
export type ConnectionTool = string | null;
|
|
170
|
+
/**
|
|
171
|
+
* One connection status, from a state and an optional reason.
|
|
172
|
+
*
|
|
173
|
+
* Pure, total, and the only place an instruction is chosen. A reason wins the
|
|
174
|
+
* instruction whenever it has one, because it is the more specific fact; with
|
|
175
|
+
* no reason the state's own next action stands. Nothing here reads a clock, a
|
|
176
|
+
* network or a disk, so a surface and a test see the same answer.
|
|
177
|
+
*/
|
|
178
|
+
export declare function connectionStatus(state: ConnectionState, reason?: ConnectionReason | null, tool?: ConnectionTool): ConnectionStatus;
|
|
179
|
+
/** The seven providers on the Connect and See catalogue surface. */
|
|
180
|
+
export declare const CATALOGUE_PROVIDER_IDS: readonly ["claude", "openrouter", "codex", "antigravity", "opencode", "grok", "kimi"];
|
|
181
|
+
export type CatalogueProviderId = (typeof CATALOGUE_PROVIDER_IDS)[number];
|
|
182
|
+
export type CataloguePlatform = "windows" | "macos" | "linux";
|
|
183
|
+
export type CatalogueCapabilityMode = "automatic" | "manual" | "event_driven";
|
|
184
|
+
export type CatalogueCapabilityMaturity = "supported" | "experimental";
|
|
185
|
+
export type CatalogueCapabilityLabel = "Supported" | "Event driven" | "Experimental" | "Manual" | "Manual experimental";
|
|
186
|
+
export type CatalogueAuthMode = "existing_local_cli" | "api_key" | "manual";
|
|
187
|
+
export interface CataloguePlatformCapability {
|
|
188
|
+
mode: CatalogueCapabilityMode;
|
|
189
|
+
maturity: CatalogueCapabilityMaturity;
|
|
190
|
+
label: CatalogueCapabilityLabel;
|
|
191
|
+
}
|
|
192
|
+
export interface ProviderCatalogueEntry {
|
|
193
|
+
providerId: CatalogueProviderId;
|
|
194
|
+
displayName: string;
|
|
195
|
+
connectionState: ConnectionState;
|
|
196
|
+
capabilities: Readonly<Record<CataloguePlatform, CataloguePlatformCapability>>;
|
|
197
|
+
authMode: CatalogueAuthMode;
|
|
198
|
+
action: string;
|
|
199
|
+
}
|
|
200
|
+
export interface GeneratedProviderDocument {
|
|
201
|
+
providers?: unknown;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Join generated provider facts to the closed connection state model.
|
|
205
|
+
*
|
|
206
|
+
* The generated document supplies identity, display name, auth mode and the
|
|
207
|
+
* platform set. Product capability maturity is the frozen overlay above. No
|
|
208
|
+
* site or local application is inspected at runtime.
|
|
209
|
+
*/
|
|
210
|
+
export declare function queryProviderCatalogue(generated: GeneratedProviderDocument, states?: Readonly<Partial<Record<CatalogueProviderId, ConnectionState>>>): readonly ProviderCatalogueEntry[];
|
|
211
|
+
export interface PlannedProviderEntry {
|
|
212
|
+
specId: string;
|
|
213
|
+
displayName: string;
|
|
214
|
+
action: "Planned";
|
|
215
|
+
}
|
|
216
|
+
export type CatalogueRow = ({
|
|
217
|
+
availability: "connectable";
|
|
218
|
+
} & ProviderCatalogueEntry) | ({
|
|
219
|
+
availability: "planned";
|
|
220
|
+
} & PlannedProviderEntry);
|
|
221
|
+
/**
|
|
222
|
+
* The connections surface showing connectable providers and planned products.
|
|
223
|
+
*
|
|
224
|
+
* The product catalogue presents every supported or planned provider in document
|
|
225
|
+
* order after the connectable set. Planned rows carry no connection state because
|
|
226
|
+
* no background collector or credential exists for them.
|
|
227
|
+
*/
|
|
228
|
+
export declare function queryCatalogueRows(generated: GeneratedProviderDocument, states?: Readonly<Partial<Record<CatalogueProviderId, ConnectionState>>>): readonly CatalogueRow[];
|
|
229
|
+
//# sourceMappingURL=connection-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-state.d.ts","sourceRoot":"","sources":["../src/connection-state.ts"],"names":[],"mappings":"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"}
|