@pipeline-builder/api-core 5.2.3 → 5.2.4

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.
Files changed (81) hide show
  1. package/lib/constants/ai-providers.d.ts +3 -22
  2. package/lib/constants/ai-providers.js +6 -56
  3. package/lib/errors/app-errors.d.ts +14 -0
  4. package/lib/errors/app-errors.js +21 -1
  5. package/lib/helpers/sse-helpers.d.ts +33 -3
  6. package/lib/helpers/sse-helpers.js +91 -19
  7. package/lib/middleware/auth.js +6 -3
  8. package/lib/middleware/deferred-quota-check.d.ts +5 -0
  9. package/lib/middleware/deferred-quota-check.js +33 -0
  10. package/lib/middleware/ecosystem-guard.d.ts +3 -3
  11. package/lib/middleware/ecosystem-guard.js +5 -5
  12. package/lib/middleware/index.d.ts +5 -2
  13. package/lib/middleware/index.js +6 -3
  14. package/lib/middleware/on-behalf.d.ts +37 -0
  15. package/lib/middleware/on-behalf.js +143 -0
  16. package/lib/middleware/permission-gates.d.ts +11 -0
  17. package/lib/middleware/permission-gates.js +7 -1
  18. package/lib/middleware/quota-exempt-routes.d.ts +36 -0
  19. package/lib/middleware/quota-exempt-routes.js +47 -0
  20. package/lib/middleware/route-table.d.ts +104 -0
  21. package/lib/middleware/route-table.js +72 -2
  22. package/lib/middleware/service-tokens.d.ts +8 -0
  23. package/lib/middleware/service-tokens.js +2 -1
  24. package/lib/middleware/step-up.js +11 -1
  25. package/lib/services/ai-call-weight.d.ts +24 -0
  26. package/lib/services/ai-call-weight.js +77 -0
  27. package/lib/services/audit-context.d.ts +105 -0
  28. package/lib/services/audit-context.js +134 -0
  29. package/lib/services/index.d.ts +7 -2
  30. package/lib/services/index.js +7 -2
  31. package/lib/services/org-ai-client.d.ts +61 -0
  32. package/lib/services/org-ai-client.js +112 -0
  33. package/lib/services/quota-increment-batcher.d.ts +81 -0
  34. package/lib/services/quota-increment-batcher.js +166 -0
  35. package/lib/services/quota.d.ts +158 -14
  36. package/lib/services/quota.js +252 -22
  37. package/lib/services/remote-audit-client.d.ts +44 -1
  38. package/lib/services/remote-audit-client.js +106 -20
  39. package/lib/services/version-vuln-policy.d.ts +174 -0
  40. package/lib/services/version-vuln-policy.js +242 -0
  41. package/lib/types/ai-catalog.d.ts +63 -0
  42. package/lib/types/ai-catalog.js +82 -0
  43. package/lib/types/ask-proposals.js +6 -6
  44. package/lib/types/build-catalog.d.ts +154 -0
  45. package/lib/types/build-catalog.js +158 -0
  46. package/lib/types/common.d.ts +37 -3
  47. package/lib/types/common.js +20 -2
  48. package/lib/types/ecosystem-notifications.d.ts +5 -5
  49. package/lib/types/ecosystem-notifications.js +9 -3
  50. package/lib/types/error-codes.d.ts +13 -6
  51. package/lib/types/error-codes.js +18 -8
  52. package/lib/types/feature-flags.d.ts +6 -4
  53. package/lib/types/feature-flags.js +11 -6
  54. package/lib/types/index.d.ts +6 -4
  55. package/lib/types/index.js +12 -5
  56. package/lib/types/metadata-keys.d.ts +6 -0
  57. package/lib/types/metadata-keys.js +4 -1
  58. package/lib/types/permissions.d.ts +344 -3
  59. package/lib/types/permissions.js +596 -21
  60. package/lib/types/pipeline.d.ts +39 -0
  61. package/lib/types/pipeline.js +57 -1
  62. package/lib/types/plugin-health.d.ts +0 -2
  63. package/lib/types/plugin-health.js +1 -11
  64. package/lib/types/plugin-scan.d.ts +192 -42
  65. package/lib/types/plugin-scan.js +159 -43
  66. package/lib/types/quota-catalog.d.ts +247 -0
  67. package/lib/types/quota-catalog.js +122 -0
  68. package/lib/types/quota-tiers.d.ts +118 -0
  69. package/lib/types/quota-tiers.js +153 -2
  70. package/lib/types/remote-audit-actions.d.ts +1 -1
  71. package/lib/types/remote-audit-actions.js +131 -1
  72. package/lib/types/wire-vocabulary.js +8 -1
  73. package/lib/utils/index.d.ts +1 -1
  74. package/lib/utils/index.js +2 -2
  75. package/lib/utils/response.d.ts +35 -2
  76. package/lib/utils/response.js +42 -3
  77. package/lib/validation/ai-schemas.d.ts +27 -0
  78. package/lib/validation/ai-schemas.js +31 -5
  79. package/lib/validation/plugin-schemas.d.ts +15 -0
  80. package/lib/validation/plugin-schemas.js +28 -1
  81. package/package.json +31 -2
@@ -0,0 +1,247 @@
1
+ /**
2
+ * The quota-type display catalog, as DATA ONLY — what a quota is CALLED and how
3
+ * its number is RENDERED, for every `QuotaType`.
4
+ *
5
+ * Dependency-free and env-free so the browser imports it directly
6
+ * (`@pipeline-builder/api-core/quota-catalog`). Its only import is a type, which
7
+ * is erased at compile time, so nothing of the server graph reaches the bundle.
8
+ *
9
+ * WHY IT EXISTS. The same facts were spelled in six places: this package's
10
+ * `QUOTA_TYPE_LABELS`, and five independent maps in the frontend — the pressure
11
+ * banner's labels, the quotas page's `QUOTA_META`, the org home's `QUOTA_LABELS`,
12
+ * the billing usage card's `QUOTA_LABELS` (with its own byte flag), and the
13
+ * add-on grid's `PACK_UNITS`. Each knew a subset, each could drift, and adding a
14
+ * quota type meant finding all six. `PACK_UNITS` had exactly one member, so the
15
+ * add-on grid rendered a GB step for storage and a bare pack count for
16
+ * everything else — not a decision anyone revisited, just the one entry somebody
17
+ * needed at the time.
18
+ *
19
+ * Add a quota type here and every surface renders it: `Record<QuotaType, …>`
20
+ * makes the compiler demand the entry, so none of the six can be forgotten
21
+ * again.
22
+ *
23
+ * EVERY display fact lives here, including the plural label. A first draft kept
24
+ * plurals in a frontend helper with a `Record<string, string>` and a fallback to
25
+ * the singular — which would have rendered a new quota type's plural wrongly and
26
+ * silently, reproducing the exact partial-map trap this file removes. A fact
27
+ * split across two packages is a fact that drifts.
28
+ */
29
+ import type { QuotaType } from './common.js';
30
+ /** How a quota's usage is learned: incremented as it happens, or read on demand. */
31
+ export type QuotaObservation =
32
+ /** The platform increments a counter as the work happens. */
33
+ 'metered'
34
+ /** Nothing increments: the figure is read from the system of record. */
35
+ | 'measured';
36
+ /** Whether a quota refills on the period or persists until something changes. */
37
+ export type QuotaAccrual =
38
+ /** A per-period rate: reserved on use, zeroed at the period boundary. */
39
+ 'flow'
40
+ /** A level that stays until the tenant frees it. No reset, no forecast. */
41
+ | 'stock';
42
+ /** How a raw quota number should be rendered. */
43
+ export type QuotaRender =
44
+ /** A plain integer — "4 / 10". */
45
+ 'count'
46
+ /** A byte count — render with `formatBytes` ("18.4 GB"), never raw. */
47
+ | 'bytes';
48
+ /** What a surface needs to know to show one quota type. */
49
+ export interface QuotaTypeMeta {
50
+ /**
51
+ * SINGULAR human label — the one spelling every 429 body, notification and
52
+ * dashboard uses ("API call quota reached …").
53
+ */
54
+ label: string;
55
+ /**
56
+ * PLURAL label, for surfaces that list dimensions rather than address one
57
+ * ("API calls" on a usage card, against "API call quota reached" in a 429).
58
+ *
59
+ * REQUIRED, and here rather than in the frontend, because the first draft put
60
+ * it in a `Record<string, string>` helper that fell back to the singular when
61
+ * a type was missing — so a new quota type would have rendered "Build minute"
62
+ * in plural contexts, wrongly and silently. That is the same partial-map trap
63
+ * this catalog exists to remove, and `Record<QuotaType, …>` closes it only if
64
+ * every display fact lives here.
65
+ */
66
+ labelPlural: string;
67
+ /** How to render a raw value for this type. */
68
+ render: QuotaRender;
69
+ /**
70
+ * Whether this quota REFILLS on the period, or persists until the tenant
71
+ * changes something.
72
+ *
73
+ * This is the single most load-bearing semantic difference between quota
74
+ * types and nothing could read it before: a surface had to know, by hand,
75
+ * that "resets in 2 days" is true of `apiCalls` and false of `storageBytes`.
76
+ * Getting it wrong is not cosmetic — the quotas page told an organization its
77
+ * registry storage would reset, which is the opposite of the one thing it
78
+ * needs to understand about storage.
79
+ *
80
+ * - `flow` — a per-period rate, reserved on create/build and zeroed at the
81
+ * boundary. Hitting it is temporary and the account self-heals. The period is
82
+ * PER TYPE rather than global: `QUOTA_RESET_DAYS` (3 days) for every flow but
83
+ * `cpuCredits`, which is monthly. Read it through `quotaPeriodDays` and never
84
+ * assume the global — a forecast or a "resets in N days" line computed
85
+ * against the wrong window is wrong by a factor of ten for credits.
86
+ * - `stock` — a level, not a rate. It stays where it is until bytes are freed
87
+ * or rows are deleted, so a reset date, a "resets in N days" line and a
88
+ * burn-rate forecast are all meaningless for it. `storageBytes` is measured;
89
+ * the resource-count caps are counted at create time and decremented on
90
+ * delete; `listings` counts active publications.
91
+ *
92
+ * The {@link QuotaTypeMeta.description} wording follows from this ("this
93
+ * period" against "currently"), and a test holds the two in agreement.
94
+ */
95
+ accrual: QuotaAccrual;
96
+ /**
97
+ * How usage is OBSERVED — which is a different question from whether the
98
+ * quota refills, and the two do not line up.
99
+ *
100
+ * - `metered` — the platform INCREMENTS a counter as the work happens. Every
101
+ * quota but one, including the create-time counts that are stocks
102
+ * (`dashboards`, `listings`, …).
103
+ * - `measured` — nothing increments anything; the figure is READ from the
104
+ * system of record on demand. `storageBytes` only: the registry walks its
105
+ * own catalog, which is why it is "the one quota nothing increments".
106
+ *
107
+ * It decides which code path may legitimately notify about a quota. A storage
108
+ * notice arriving from the increment path could only be a bug, and a
109
+ * `listings` notice arriving from a sweep likewise — conflating this with
110
+ * {@link accrual} would have silenced every create-time count quota, because
111
+ * those are stocks that are nonetheless metered.
112
+ */
113
+ observed: QuotaObservation;
114
+ /**
115
+ * What this counter actually counts, in one short phrase — the answer to "am I
116
+ * spending this one?".
117
+ *
118
+ * REQUIRED, and a display FACT rather than page copy, which is the call a
119
+ * first pass got wrong by judging the field's name instead of its content. It
120
+ * had been left in a frontend `Record<DisplayedQuotaType, string>` on the
121
+ * reasoning that descriptions are a page's own voice. They are not: each of
122
+ * these answers a question whose answer is the same on every surface, and two
123
+ * surfaces disagreeing is a bug, not a difference in tone. Two of the five
124
+ * local copies had already drifted — `automationCalls` omitted personal
125
+ * access keys (so a PAT holder read the page and concluded they were spending
126
+ * `apiCalls`), and `plugins`/`pipelines` were phrased as stocks.
127
+ *
128
+ * The test is whether `docs/quotas.md` and this string can legitimately
129
+ * differ. For these, no. For a tier's positioning line ("Starter tier") or the
130
+ * quotas page's pooling explanation, yes — those stay in the page, exactly as
131
+ * `TIER_META` already splits a tier's label from its sales copy.
132
+ *
133
+ * Phrasing follows the SEMANTICS in {@link QuotaType}'s own documentation, not
134
+ * a house style: "this period" for the per-period flows, "currently" for the
135
+ * create-time counts that decrement on delete, and neither for the measured
136
+ * storage total.
137
+ */
138
+ description: string;
139
+ /**
140
+ * The unit an ADD-ON PACK of this quota is typed in, and the divisor from the
141
+ * raw grant to that unit — so a +10 GB storage pack offers a GB step rather
142
+ * than a pack count.
143
+ *
144
+ * Absent means "type pack counts", which is right where the quota has no
145
+ * natural unit: "1 pack of API calls" reads better than "25000 calls". The
146
+ * step itself is always derived from the bundle's own `grants`, never from a
147
+ * constant here, because a grant is env-overridable.
148
+ */
149
+ packUnit?: {
150
+ name: string;
151
+ per: number;
152
+ };
153
+ }
154
+ /**
155
+ * Display metadata for every quota type. Labels are the canonical ones — the
156
+ * strings here are user-facing and are relied on by 429 bodies and notices, so
157
+ * they change only deliberately.
158
+ */
159
+ export declare const QUOTA_CATALOG: Readonly<Record<QuotaType, QuotaTypeMeta>>;
160
+ /**
161
+ * Consumables that are NOT quota types but render beside them — same label,
162
+ * same bar, same card.
163
+ *
164
+ * `seats` is the only one: it is a STOCK (how many people may be on the
165
+ * account) rather than a per-period flow, so it is deliberately outside
166
+ * `QuotaType` and is sourced separately from the quota rollup. But the billing
167
+ * usage card draws it in the same list, and before this entry existed that one
168
+ * row carried its own inline "Seats" label while the five beside it came from
169
+ * the catalog — the precise split-fact drift this file exists to remove.
170
+ *
171
+ * Keep this for things rendered AS a consumable. It is not a back door for
172
+ * quota types: a real quota belongs in {@link QUOTA_CATALOG}, where
173
+ * `Record<QuotaType, …>` forces the entry.
174
+ */
175
+ export declare const NON_QUOTA_CONSUMABLE_CATALOG: Readonly<Record<string, QuotaTypeMeta>>;
176
+ /**
177
+ * This type's singular label, or the raw key for a value outside the catalog.
178
+ *
179
+ * The accessors take `string` because the quota type often arrives from the wire
180
+ * (a 429 body, a quota response) where an unknown value must render as something
181
+ * rather than crash a page. The `QuotaType` overload is for internal callers: it
182
+ * makes a typo a compile error instead of a key rendered as a label.
183
+ */
184
+ export declare function quotaLabel(type: QuotaType): string;
185
+ export declare function quotaLabel(type: string): string;
186
+ /** This type's PLURAL label, or the raw key for a value outside the catalog. */
187
+ export declare function quotaLabelPlural(type: QuotaType): string;
188
+ export declare function quotaLabelPlural(type: string): string;
189
+ /**
190
+ * How to render this type's values. `count` for anything the catalog does not
191
+ * know, so an unfamiliar wire value renders as a plain number.
192
+ *
193
+ * Read THIS rather than asking "is it bytes?". A boolean helper degrades
194
+ * silently the moment {@link QuotaRender} grows a third member — a new mode
195
+ * would simply render as a count, with nothing failing — whereas a caller that
196
+ * switches on this value exhaustively stops compiling until it handles the new
197
+ * mode. That is the whole point of the catalog, applied to its own accessors.
198
+ */
199
+ export declare function quotaRender(type: QuotaType): QuotaRender;
200
+ export declare function quotaRender(type: string): QuotaRender;
201
+ /**
202
+ * A byte count as a human amount — `1.5 GB`, `512 MB`, `0 B`.
203
+ *
204
+ * The ONE implementation: the frontend's `formatBytes` delegates here rather
205
+ * than keeping its own copy, so a storage figure reads identically in a quota
206
+ * notice, a 403 explanation and the dashboard. Binary units (1024), one decimal
207
+ * below 10 and rounded above, which is what the dashboard already rendered —
208
+ * this moved the algorithm, it did not change it.
209
+ */
210
+ export declare function formatByteAmount(bytes: number): string;
211
+ /**
212
+ * A raw quota value as the amount a HUMAN should read, per this type's
213
+ * {@link QuotaRender} — bytes as `25 GB`, counts grouped as `60,000`.
214
+ *
215
+ * For server-side copy (quota notices, audit reasons, 403 explanations), which
216
+ * is why it is here rather than in the frontend: a storage notice that
217
+ * interpolated the raw number would read "used all 26843545600 of its registry
218
+ * storage". The frontend has its own thin wrapper because it renders an
219
+ * unlimited (-1) limit as `∞`, which is a display choice no server copy makes
220
+ * (unlimited orgs are skipped before any copy is generated).
221
+ */
222
+ export declare function formatQuotaAmount(type: string, value: number): string;
223
+ /**
224
+ * How this quota's usage is observed: `metered` (incremented) or `measured`
225
+ * (read on demand). Unknown wire values read as `metered`, matching every
226
+ * quota but storage.
227
+ */
228
+ export declare function quotaObservation(type: QuotaType): QuotaObservation;
229
+ export declare function quotaObservation(type: string): QuotaObservation;
230
+ /**
231
+ * Whether this quota refills on the period (`flow`) or persists (`stock`).
232
+ * Unknown wire values are treated as `flow`, matching every metered quota.
233
+ */
234
+ export declare function quotaAccrual(type: QuotaType): QuotaAccrual;
235
+ export declare function quotaAccrual(type: string): QuotaAccrual;
236
+ /** What this type counts, or the empty string for a value outside the catalog. */
237
+ export declare function quotaDescription(type: QuotaType): string;
238
+ export declare function quotaDescription(type: string): string;
239
+ /** The pack unit for this type, or `null` to type pack counts instead. */
240
+ export declare function quotaPackUnit(type: QuotaType): {
241
+ name: string;
242
+ per: number;
243
+ } | null;
244
+ export declare function quotaPackUnit(type: string): {
245
+ name: string;
246
+ per: number;
247
+ } | null;
@@ -0,0 +1,122 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ const GB = 1024 ** 3;
4
+ /**
5
+ * Display metadata for every quota type. Labels are the canonical ones — the
6
+ * strings here are user-facing and are relied on by 429 bodies and notices, so
7
+ * they change only deliberately.
8
+ */
9
+ export const QUOTA_CATALOG = {
10
+ // Per-period FLOWS — a creation rate, not a stock. Deleting the entity does
11
+ // not give the slot back (`decrement-on-delete` was considered and rejected),
12
+ // so the wording says "this period" to stop the stock reading.
13
+ plugins: { label: 'Plugin build', labelPlural: 'Plugins', render: 'count', accrual: 'flow', observed: 'metered', description: 'Plugin versions deployed this period' },
14
+ pipelines: { label: 'Pipeline creation', labelPlural: 'Pipelines', render: 'count', accrual: 'flow', observed: 'metered', description: 'Pipelines created this period' },
15
+ // The call budgets. WHICH ONE a request spends is decided by
16
+ // `meteredQuotaTypeFor`, never by the route: a service-account key, a PERSONAL
17
+ // ACCESS key (`token_use: 'api_key'`) or anything minted while holding one
18
+ // (`parentKeyId`) spends `automationCalls` INSTEAD of `apiCalls`.
19
+ apiCalls: { label: 'API call', labelPlural: 'API calls', render: 'count', accrual: 'flow', observed: 'metered', description: 'Requests made by people in browser sessions this period' },
20
+ automationCalls: { label: 'Automation call', labelPlural: 'Automation calls', render: 'count', accrual: 'flow', observed: 'metered', description: 'Requests made with service-account or personal access keys this period' },
21
+ aiCalls: { label: 'AI call', labelPlural: 'AI calls', render: 'count', accrual: 'flow', observed: 'metered', description: 'AI generation invocations this period' },
22
+ // The one flow quota on a period of its OWN — a MONTH, where every other flow
23
+ // rolls on QUOTA_RESET_DAYS (3 days). Rendered as a COUNT and never as a
24
+ // duration: the unit is an abstract credit precisely so it cannot be misread as
25
+ // wall-clock ("11,250 build minutes" reads as 187 hours when it buys 125), and
26
+ // formatting it back into "187h 30m" would undo that. The wall-clock conversion
27
+ // belongs in the pre-build cost hint, where it explains rather than misleads.
28
+ cpuCredits: { label: 'CPU credit', labelPlural: 'CPU credits', render: 'count', accrual: 'flow', observed: 'metered', description: 'vCPU-minutes of build compute used this month', packUnit: { name: 'credits', per: 1 } },
29
+ // A MEASURED total, recomputed on demand rather than counted — the one quota
30
+ // that is neither a period flow nor a create-time count.
31
+ storageBytes: { label: 'Registry storage', labelPlural: 'Registry storage', render: 'bytes', accrual: 'stock', observed: 'measured', description: 'Registry storage currently in use', packUnit: { name: 'GB', per: GB } },
32
+ // Resource-count caps: counted at create time and DECREMENTED on delete, so
33
+ // these really are stocks and "this period" would be wrong.
34
+ dashboards: { label: 'Dashboard', labelPlural: 'Dashboards', render: 'count', accrual: 'stock', observed: 'metered', description: 'Dashboards currently defined' },
35
+ alertRules: { label: 'Alert rule', labelPlural: 'Alert rules', render: 'count', accrual: 'stock', observed: 'metered', description: 'Alert rules currently defined' },
36
+ alertDestinations: { label: 'Alert destination', labelPlural: 'Alert destinations', render: 'count', accrual: 'stock', observed: 'metered', description: 'Alert destinations currently defined' },
37
+ idpConfigs: { label: 'IdP configuration', labelPlural: 'IdP configurations', render: 'count', accrual: 'stock', observed: 'metered', description: 'Identity-provider configurations currently defined' },
38
+ listings: { label: 'Plugin listing', labelPlural: 'Plugin listings', render: 'count', accrual: 'stock', observed: 'metered', description: 'Active plugin listings you publish' },
39
+ };
40
+ /**
41
+ * Consumables that are NOT quota types but render beside them — same label,
42
+ * same bar, same card.
43
+ *
44
+ * `seats` is the only one: it is a STOCK (how many people may be on the
45
+ * account) rather than a per-period flow, so it is deliberately outside
46
+ * `QuotaType` and is sourced separately from the quota rollup. But the billing
47
+ * usage card draws it in the same list, and before this entry existed that one
48
+ * row carried its own inline "Seats" label while the five beside it came from
49
+ * the catalog — the precise split-fact drift this file exists to remove.
50
+ *
51
+ * Keep this for things rendered AS a consumable. It is not a back door for
52
+ * quota types: a real quota belongs in {@link QUOTA_CATALOG}, where
53
+ * `Record<QuotaType, …>` forces the entry.
54
+ */
55
+ export const NON_QUOTA_CONSUMABLE_CATALOG = {
56
+ seats: { label: 'Seat', labelPlural: 'Seats', render: 'count', accrual: 'stock', observed: 'measured', description: 'Members this account may have' },
57
+ };
58
+ /**
59
+ * Every described consumable, widened for lookups by a value that came off the
60
+ * wire.
61
+ */
62
+ const byKey = {
63
+ ...NON_QUOTA_CONSUMABLE_CATALOG,
64
+ ...QUOTA_CATALOG,
65
+ };
66
+ export function quotaLabel(type) {
67
+ return byKey[type]?.label ?? type;
68
+ }
69
+ export function quotaLabelPlural(type) {
70
+ return byKey[type]?.labelPlural ?? type;
71
+ }
72
+ export function quotaRender(type) {
73
+ return byKey[type]?.render ?? 'count';
74
+ }
75
+ const BYTE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
76
+ /**
77
+ * A byte count as a human amount — `1.5 GB`, `512 MB`, `0 B`.
78
+ *
79
+ * The ONE implementation: the frontend's `formatBytes` delegates here rather
80
+ * than keeping its own copy, so a storage figure reads identically in a quota
81
+ * notice, a 403 explanation and the dashboard. Binary units (1024), one decimal
82
+ * below 10 and rounded above, which is what the dashboard already rendered —
83
+ * this moved the algorithm, it did not change it.
84
+ */
85
+ export function formatByteAmount(bytes) {
86
+ if (!Number.isFinite(bytes) || bytes < 0)
87
+ return '0 B';
88
+ let value = bytes;
89
+ let i = 0;
90
+ while (value >= 1024 && i < BYTE_UNITS.length - 1) {
91
+ value /= 1024;
92
+ i++;
93
+ }
94
+ return value < 10 ? `${value.toFixed(1)} ${BYTE_UNITS[i]}` : `${Math.round(value)} ${BYTE_UNITS[i]}`;
95
+ }
96
+ /**
97
+ * A raw quota value as the amount a HUMAN should read, per this type's
98
+ * {@link QuotaRender} — bytes as `25 GB`, counts grouped as `60,000`.
99
+ *
100
+ * For server-side copy (quota notices, audit reasons, 403 explanations), which
101
+ * is why it is here rather than in the frontend: a storage notice that
102
+ * interpolated the raw number would read "used all 26843545600 of its registry
103
+ * storage". The frontend has its own thin wrapper because it renders an
104
+ * unlimited (-1) limit as `∞`, which is a display choice no server copy makes
105
+ * (unlimited orgs are skipped before any copy is generated).
106
+ */
107
+ export function formatQuotaAmount(type, value) {
108
+ return quotaRender(type) === 'bytes' ? formatByteAmount(value) : value.toLocaleString('en-US');
109
+ }
110
+ export function quotaObservation(type) {
111
+ return byKey[type]?.observed ?? 'metered';
112
+ }
113
+ export function quotaAccrual(type) {
114
+ return byKey[type]?.accrual ?? 'flow';
115
+ }
116
+ export function quotaDescription(type) {
117
+ return byKey[type]?.description ?? '';
118
+ }
119
+ export function quotaPackUnit(type) {
120
+ return byKey[type]?.packUnit ?? null;
121
+ }
122
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVvdGEtY2F0YWxvZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90eXBlcy9xdW90YS1jYXRhbG9nLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUE4SnRDLE1BQU0sRUFBRSxHQUFHLElBQUksSUFBSSxDQUFDLENBQUM7QUFFckI7Ozs7R0FJRztBQUNILE1BQU0sQ0FBQyxNQUFNLGFBQWEsR0FBK0M7SUFDdkUsNEVBQTRFO0lBQzVFLDhFQUE4RTtJQUM5RSwrREFBK0Q7SUFDL0QsT0FBTyxFQUFFLEVBQUUsS0FBSyxFQUFFLGNBQWMsRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLFdBQVcsRUFBRSxzQ0FBc0MsRUFBRTtJQUN0SyxTQUFTLEVBQUUsRUFBRSxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsK0JBQStCLEVBQUU7SUFDeEssNkRBQTZEO0lBQzdELCtFQUErRTtJQUMvRSwyRUFBMkU7SUFDM0Usa0VBQWtFO0lBQ2xFLFFBQVEsRUFBRSxFQUFFLEtBQUssRUFBRSxVQUFVLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUseURBQXlELEVBQUU7SUFDeEwsZUFBZSxFQUFFLEVBQUUsS0FBSyxFQUFFLGlCQUFpQixFQUFFLFdBQVcsRUFBRSxrQkFBa0IsRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsd0VBQXdF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QyxJQUFJLENBQUMsS0FBSyxPQUFPLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsY0FBYyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0FBQ2pHLENBQUM7QUFTRCxNQUFNLFVBQVUsZ0JBQWdCLENBQUMsSUFBWTtJQUMzQyxPQUFPLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxRQUFRLElBQUksU0FBUyxDQUFDO0FBQzVDLENBQUM7QUFRRCxNQUFNLFVBQVUsWUFBWSxDQUFDLElBQVk7SUFDdkMsT0FBTyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxJQUFJLE1BQU0sQ0FBQztBQUN4QyxDQUFDO0FBS0QsTUFBTSxVQUFVLGdCQUFnQixDQUFDLElBQVk7SUFDM0MsT0FBTyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUUsV0FBVyxJQUFJLEVBQUUsQ0FBQztBQUN4QyxDQUFDO0FBS0QsTUFBTSxVQUFVLGFBQWEsQ0FBQyxJQUFZO0lBQ3hDLE9BQU8sS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLFFBQVEsSUFBSSxJQUFJLENBQUM7QUFDdkMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFRoZSBxdW90YS10eXBlIGRpc3BsYXkgY2F0YWxvZywgYXMgREFUQSBPTkxZIOKAlCB3aGF0IGEgcXVvdGEgaXMgQ0FMTEVEIGFuZCBob3dcbiAqIGl0cyBudW1iZXIgaXMgUkVOREVSRUQsIGZvciBldmVyeSBgUXVvdGFUeXBlYC5cbiAqXG4gKiBEZXBlbmRlbmN5LWZyZWUgYW5kIGVudi1mcmVlIHNvIHRoZSBicm93c2VyIGltcG9ydHMgaXQgZGlyZWN0bHlcbiAqIChgQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUvcXVvdGEtY2F0YWxvZ2ApLiBJdHMgb25seSBpbXBvcnQgaXMgYSB0eXBlLCB3aGljaFxuICogaXMgZXJhc2VkIGF0IGNvbXBpbGUgdGltZSwgc28gbm90aGluZyBvZiB0aGUgc2VydmVyIGdyYXBoIHJlYWNoZXMgdGhlIGJ1bmRsZS5cbiAqXG4gKiBXSFkgSVQgRVhJU1RTLiBUaGUgc2FtZSBmYWN0cyB3ZXJlIHNwZWxsZWQgaW4gc2l4IHBsYWNlczogdGhpcyBwYWNrYWdlJ3NcbiAqIGBRVU9UQV9UWVBFX0xBQkVMU2AsIGFuZCBmaXZlIGluZGVwZW5kZW50IG1hcHMgaW4gdGhlIGZyb250ZW5kIOKAlCB0aGUgcHJlc3N1cmVcbiAqIGJhbm5lcidzIGxhYmVscywgdGhlIHF1b3RhcyBwYWdlJ3MgYFFVT1RBX01FVEFgLCB0aGUgb3JnIGhvbWUncyBgUVVPVEFfTEFCRUxTYCxcbiAqIHRoZSBiaWxsaW5nIHVzYWdlIGNhcmQncyBgUVVPVEFfTEFCRUxTYCAod2l0aCBpdHMgb3duIGJ5dGUgZmxhZyksIGFuZCB0aGVcbiAqIGFkZC1vbiBncmlkJ3MgYFBBQ0tfVU5JVFNgLiBFYWNoIGtuZXcgYSBzdWJzZXQsIGVhY2ggY291bGQgZHJpZnQsIGFuZCBhZGRpbmcgYVxuICogcXVvdGEgdHlwZSBtZWFudCBmaW5kaW5nIGFsbCBzaXguIGBQQUNLX1VOSVRTYCBoYWQgZXhhY3RseSBvbmUgbWVtYmVyLCBzbyB0aGVcbiAqIGFkZC1vbiBncmlkIHJlbmRlcmVkIGEgR0Igc3RlcCBmb3Igc3RvcmFnZSBhbmQgYSBiYXJlIHBhY2sgY291bnQgZm9yXG4gKiBldmVyeXRoaW5nIGVsc2Ug4oCUIG5vdCBhIGRlY2lzaW9uIGFueW9uZSByZXZpc2l0ZWQsIGp1c3QgdGhlIG9uZSBlbnRyeSBzb21lYm9keVxuICogbmVlZGVkIGF0IHRoZSB0aW1lLlxuICpcbiAqIEFkZCBhIHF1b3RhIHR5cGUgaGVyZSBhbmQgZXZlcnkgc3VyZmFjZSByZW5kZXJzIGl0OiBgUmVjb3JkPFF1b3RhVHlwZSwg4oCmPmBcbiAqIG1ha2VzIHRoZSBjb21waWxlciBkZW1hbmQgdGhlIGVudHJ5LCBzbyBub25lIG9mIHRoZSBzaXggY2FuIGJlIGZvcmdvdHRlblxuICogYWdhaW4uXG4gKlxuICogRVZFUlkgZGlzcGxheSBmYWN0IGxpdmVzIGhlcmUsIGluY2x1ZGluZyB0aGUgcGx1cmFsIGxhYmVsLiBBIGZpcnN0IGRyYWZ0IGtlcHRcbiAqIHBsdXJhbHMgaW4gYSBmcm9udGVuZCBoZWxwZXIgd2l0aCBhIGBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+YCBhbmQgYSBmYWxsYmFjayB0b1xuICogdGhlIHNpbmd1bGFyIOKAlCB3aGljaCB3b3VsZCBoYXZlIHJlbmRlcmVkIGEgbmV3IHF1b3RhIHR5cGUncyBwbHVyYWwgd3JvbmdseSBhbmRcbiAqIHNpbGVudGx5LCByZXByb2R1Y2luZyB0aGUgZXhhY3QgcGFydGlhbC1tYXAgdHJhcCB0aGlzIGZpbGUgcmVtb3Zlcy4gQSBmYWN0XG4gKiBzcGxpdCBhY3Jvc3MgdHdvIHBhY2thZ2VzIGlzIGEgZmFjdCB0aGF0IGRyaWZ0cy5cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IFF1b3RhVHlwZSB9IGZyb20gJy4vY29tbW9uLmpzJztcblxuLyoqIEhvdyBhIHF1b3RhJ3MgdXNhZ2UgaXMgbGVhcm5lZDogaW5jcmVtZW50ZWQgYXMgaXQgaGFwcGVucywgb3IgcmVhZCBvbiBkZW1hbmQuICovXG5leHBvcnQgdHlwZSBRdW90YU9ic2VydmF0aW9uID1cbiAgLyoqIFRoZSBwbGF0Zm9ybSBpbmNyZW1lbnRzIGEgY291bnRlciBhcyB0aGUgd29yayBoYXBwZW5zLiAqL1xuICB8ICdtZXRlcmVkJ1xuICAvKiogTm90aGluZyBpbmNyZW1lbnRzOiB0aGUgZmlndXJlIGlzIHJlYWQgZnJvbSB0aGUgc3lzdGVtIG9mIHJlY29yZC4gKi9cbiAgfCAnbWVhc3VyZWQnO1xuXG4vKiogV2hldGhlciBhIHF1b3RhIHJlZmlsbHMgb24gdGhlIHBlcmlvZCBvciBwZXJzaXN0cyB1bnRpbCBzb21ldGhpbmcgY2hhbmdlcy4gKi9cbmV4cG9ydCB0eXBlIFF1b3RhQWNjcnVhbCA9XG4gIC8qKiBBIHBlci1wZXJpb2QgcmF0ZTogcmVzZXJ2ZWQgb24gdXNlLCB6ZXJvZWQgYXQgdGhlIHBlcmlvZCBib3VuZGFyeS4gKi9cbiAgfCAnZmxvdydcbiAgLyoqIEEgbGV2ZWwgdGhhdCBzdGF5cyB1bnRpbCB0aGUgdGVuYW50IGZyZWVzIGl0LiBObyByZXNldCwgbm8gZm9yZWNhc3QuICovXG4gIHwgJ3N0b2NrJztcblxuLyoqIEhvdyBhIHJhdyBxdW90YSBudW1iZXIgc2hvdWxkIGJlIHJlbmRlcmVkLiAqL1xuZXhwb3J0IHR5cGUgUXVvdGFSZW5kZXIgPVxuICAvKiogQSBwbGFpbiBpbnRlZ2VyIOKAlCBcIjQgLyAxMFwiLiAqL1xuICB8ICdjb3VudCdcbiAgLyoqIEEgYnl0ZSBjb3VudCDigJQgcmVuZGVyIHdpdGggYGZvcm1hdEJ5dGVzYCAoXCIxOC40IEdCXCIpLCBuZXZlciByYXcuICovXG4gIHwgJ2J5dGVzJztcblxuLyoqIFdoYXQgYSBzdXJmYWNlIG5lZWRzIHRvIGtub3cgdG8gc2hvdyBvbmUgcXVvdGEgdHlwZS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUXVvdGFUeXBlTWV0YSB7XG4gIC8qKlxuICAgKiBTSU5HVUxBUiBodW1hbiBsYWJlbCDigJQgdGhlIG9uZSBzcGVsbGluZyBldmVyeSA0MjkgYm9keSwgbm90aWZpY2F0aW9uIGFuZFxuICAgKiBkYXNoYm9hcmQgdXNlcyAoXCJBUEkgY2FsbCBxdW90YSByZWFjaGVkIOKAplwiKS5cbiAgICovXG4gIGxhYmVsOiBzdHJpbmc7XG4gIC8qKlxuICAgKiBQTFVSQUwgbGFiZWwsIGZvciBzdXJmYWNlcyB0aGF0IGxpc3QgZGltZW5zaW9ucyByYXRoZXIgdGhhbiBhZGRyZXNzIG9uZVxuICAgKiAoXCJBUEkgY2FsbHNcIiBvbiBhIHVzYWdlIGNhcmQsIGFnYWluc3QgXCJBUEkgY2FsbCBxdW90YSByZWFjaGVkXCIgaW4gYSA0MjkpLlxuICAgKlxuICAgKiBSRVFVSVJFRCwgYW5kIGhlcmUgcmF0aGVyIHRoYW4gaW4gdGhlIGZyb250ZW5kLCBiZWNhdXNlIHRoZSBmaXJzdCBkcmFmdCBwdXRcbiAgICogaXQgaW4gYSBgUmVjb3JkPHN0cmluZywgc3RyaW5nPmAgaGVscGVyIHRoYXQgZmVsbCBiYWNrIHRvIHRoZSBzaW5ndWxhciB3aGVuXG4gICAqIGEgdHlwZSB3YXMgbWlzc2luZyDigJQgc28gYSBuZXcgcXVvdGEgdHlwZSB3b3VsZCBoYXZlIHJlbmRlcmVkIFwiQnVpbGQgbWludXRlXCJcbiAgICogaW4gcGx1cmFsIGNvbnRleHRzLCB3cm9uZ2x5IGFuZCBzaWxlbnRseS4gVGhhdCBpcyB0aGUgc2FtZSBwYXJ0aWFsLW1hcCB0cmFwXG4gICAqIHRoaXMgY2F0YWxvZyBleGlzdHMgdG8gcmVtb3ZlLCBhbmQgYFJlY29yZDxRdW90YVR5cGUsIOKApj5gIGNsb3NlcyBpdCBvbmx5IGlmXG4gICAqIGV2ZXJ5IGRpc3BsYXkgZmFjdCBsaXZlcyBoZXJlLlxuICAgKi9cbiAgbGFiZWxQbHVyYWw6IHN0cmluZztcbiAgLyoqIEhvdyB0byByZW5kZXIgYSByYXcgdmFsdWUgZm9yIHRoaXMgdHlwZS4gKi9cbiAgcmVuZGVyOiBRdW90YVJlbmRlcjtcbiAgLyoqXG4gICAqIFdoZXRoZXIgdGhpcyBxdW90YSBSRUZJTExTIG9uIHRoZSBwZXJpb2QsIG9yIHBlcnNpc3RzIHVudGlsIHRoZSB0ZW5hbnRcbiAgICogY2hhbmdlcyBzb21ldGhpbmcuXG4gICAqXG4gICAqIFRoaXMgaXMgdGhlIHNpbmdsZSBtb3N0IGxvYWQtYmVhcmluZyBzZW1hbnRpYyBkaWZmZXJlbmNlIGJldHdlZW4gcXVvdGFcbiAgICogdHlwZXMgYW5kIG5vdGhpbmcgY291bGQgcmVhZCBpdCBiZWZvcmU6IGEgc3VyZmFjZSBoYWQgdG8ga25vdywgYnkgaGFuZCxcbiAgICogdGhhdCBcInJlc2V0cyBpbiAyIGRheXNcIiBpcyB0cnVlIG9mIGBhcGlDYWxsc2AgYW5kIGZhbHNlIG9mIGBzdG9yYWdlQnl0ZXNgLlxuICAgKiBHZXR0aW5nIGl0IHdyb25nIGlzIG5vdCBjb3NtZXRpYyDigJQgdGhlIHF1b3RhcyBwYWdlIHRvbGQgYW4gb3JnYW5pemF0aW9uIGl0c1xuICAgKiByZWdpc3RyeSBzdG9yYWdlIHdvdWxkIHJlc2V0LCB3aGljaCBpcyB0aGUgb3Bwb3NpdGUgb2YgdGhlIG9uZSB0aGluZyBpdFxuICAgKiBuZWVkcyB0byB1bmRlcnN0YW5kIGFib3V0IHN0b3JhZ2UuXG4gICAqXG4gICAqIC0gYGZsb3dgIOKAlCBhIHBlci1wZXJpb2QgcmF0ZSwgcmVzZXJ2ZWQgb24gY3JlYXRlL2J1aWxkIGFuZCB6ZXJvZWQgYXQgdGhlXG4gICAqICAgYm91bmRhcnkuIEhpdHRpbmcgaXQgaXMgdGVtcG9yYXJ5IGFuZCB0aGUgYWNjb3VudCBzZWxmLWhlYWxzLiBUaGUgcGVyaW9kIGlzXG4gICAqICAgUEVSIFRZUEUgcmF0aGVyIHRoYW4gZ2xvYmFsOiBgUVVPVEFfUkVTRVRfREFZU2AgKDMgZGF5cykgZm9yIGV2ZXJ5IGZsb3cgYnV0XG4gICAqICAgYGNwdUNyZWRpdHNgLCB3aGljaCBpcyBtb250aGx5LiBSZWFkIGl0IHRocm91Z2ggYHF1b3RhUGVyaW9kRGF5c2AgYW5kIG5ldmVyXG4gICAqICAgYXNzdW1lIHRoZSBnbG9iYWwg4oCUIGEgZm9yZWNhc3Qgb3IgYSBcInJlc2V0cyBpbiBOIGRheXNcIiBsaW5lIGNvbXB1dGVkXG4gICAqICAgYWdhaW5zdCB0aGUgd3Jvbmcgd2luZG93IGlzIHdyb25nIGJ5IGEgZmFjdG9yIG9mIHRlbiBmb3IgY3JlZGl0cy5cbiAgICogLSBgc3RvY2tgIOKAlCBhIGxldmVsLCBub3QgYSByYXRlLiBJdCBzdGF5cyB3aGVyZSBpdCBpcyB1bnRpbCBieXRlcyBhcmUgZnJlZWRcbiAgICogICBvciByb3dzIGFyZSBkZWxldGVkLCBzbyBhIHJlc2V0IGRhdGUsIGEgXCJyZXNldHMgaW4gTiBkYXlzXCIgbGluZSBhbmQgYVxuICAgKiAgIGJ1cm4tcmF0ZSBmb3JlY2FzdCBhcmUgYWxsIG1lYW5pbmdsZXNzIGZvciBpdC4gYHN0b3JhZ2VCeXRlc2AgaXMgbWVhc3VyZWQ7XG4gICAqICAgdGhlIHJlc291cmNlLWNvdW50IGNhcHMgYXJlIGNvdW50ZWQgYXQgY3JlYXRlIHRpbWUgYW5kIGRlY3JlbWVudGVkIG9uXG4gICAqICAgZGVsZXRlOyBgbGlzdGluZ3NgIGNvdW50cyBhY3RpdmUgcHVibGljYXRpb25zLlxuICAgKlxuICAgKiBUaGUge0BsaW5rIFF1b3RhVHlwZU1ldGEuZGVzY3JpcHRpb259IHdvcmRpbmcgZm9sbG93cyBmcm9tIHRoaXMgKFwidGhpc1xuICAgKiBwZXJpb2RcIiBhZ2FpbnN0IFwiY3VycmVudGx5XCIpLCBhbmQgYSB0ZXN0IGhvbGRzIHRoZSB0d28gaW4gYWdyZWVtZW50LlxuICAgKi9cbiAgYWNjcnVhbDogUXVvdGFBY2NydWFsO1xuICAvKipcbiAgICogSG93IHVzYWdlIGlzIE9CU0VSVkVEIOKAlCB3aGljaCBpcyBhIGRpZmZlcmVudCBxdWVzdGlvbiBmcm9tIHdoZXRoZXIgdGhlXG4gICAqIHF1b3RhIHJlZmlsbHMsIGFuZCB0aGUgdHdvIGRvIG5vdCBsaW5lIHVwLlxuICAgKlxuICAgKiAtIGBtZXRlcmVkYCDigJQgdGhlIHBsYXRmb3JtIElOQ1JFTUVOVFMgYSBjb3VudGVyIGFzIHRoZSB3b3JrIGhhcHBlbnMuIEV2ZXJ5XG4gICAqICAgcXVvdGEgYnV0IG9uZSwgaW5jbHVkaW5nIHRoZSBjcmVhdGUtdGltZSBjb3VudHMgdGhhdCBhcmUgc3RvY2tzXG4gICAqICAgKGBkYXNoYm9hcmRzYCwgYGxpc3RpbmdzYCwg4oCmKS5cbiAgICogLSBgbWVhc3VyZWRgIOKAlCBub3RoaW5nIGluY3JlbWVudHMgYW55dGhpbmc7IHRoZSBmaWd1cmUgaXMgUkVBRCBmcm9tIHRoZVxuICAgKiAgIHN5c3RlbSBvZiByZWNvcmQgb24gZGVtYW5kLiBgc3RvcmFnZUJ5dGVzYCBvbmx5OiB0aGUgcmVnaXN0cnkgd2Fsa3MgaXRzXG4gICAqICAgb3duIGNhdGFsb2csIHdoaWNoIGlzIHdoeSBpdCBpcyBcInRoZSBvbmUgcXVvdGEgbm90aGluZyBpbmNyZW1lbnRzXCIuXG4gICAqXG4gICAqIEl0IGRlY2lkZXMgd2hpY2ggY29kZSBwYXRoIG1heSBsZWdpdGltYXRlbHkgbm90aWZ5IGFib3V0IGEgcXVvdGEuIEEgc3RvcmFnZVxuICAgKiBub3RpY2UgYXJyaXZpbmcgZnJvbSB0aGUgaW5jcmVtZW50IHBhdGggY291bGQgb25seSBiZSBhIGJ1ZywgYW5kIGFcbiAgICogYGxpc3RpbmdzYCBub3RpY2UgYXJyaXZpbmcgZnJvbSBhIHN3ZWVwIGxpa2V3aXNlIOKAlCBjb25mbGF0aW5nIHRoaXMgd2l0aFxuICAgKiB7QGxpbmsgYWNjcnVhbH0gd291bGQgaGF2ZSBzaWxlbmNlZCBldmVyeSBjcmVhdGUtdGltZSBjb3VudCBxdW90YSwgYmVjYXVzZVxuICAgKiB0aG9zZSBhcmUgc3RvY2tzIHRoYXQgYXJlIG5vbmV0aGVsZXNzIG1ldGVyZWQuXG4gICAqL1xuICBvYnNlcnZlZDogUXVvdGFPYnNlcnZhdGlvbjtcbiAgLyoqXG4gICAqIFdoYXQgdGhpcyBjb3VudGVyIGFjdHVhbGx5IGNvdW50cywgaW4gb25lIHNob3J0IHBocmFzZSDigJQgdGhlIGFuc3dlciB0byBcImFtIElcbiAgICogc3BlbmRpbmcgdGhpcyBvbmU/XCIuXG4gICAqXG4gICAqIFJFUVVJUkVELCBhbmQgYSBkaXNwbGF5IEZBQ1QgcmF0aGVyIHRoYW4gcGFnZSBjb3B5LCB3aGljaCBpcyB0aGUgY2FsbCBhXG4gICAqIGZpcnN0IHBhc3MgZ290IHdyb25nIGJ5IGp1ZGdpbmcgdGhlIGZpZWxkJ3MgbmFtZSBpbnN0ZWFkIG9mIGl0cyBjb250ZW50LiBJdFxuICAgKiBoYWQgYmVlbiBsZWZ0IGluIGEgZnJvbnRlbmQgYFJlY29yZDxEaXNwbGF5ZWRRdW90YVR5cGUsIHN0cmluZz5gIG9uIHRoZVxuICAgKiByZWFzb25pbmcgdGhhdCBkZXNjcmlwdGlvbnMgYXJlIGEgcGFnZSdzIG93biB2b2ljZS4gVGhleSBhcmUgbm90OiBlYWNoIG9mXG4gICAqIHRoZXNlIGFuc3dlcnMgYSBxdWVzdGlvbiB3aG9zZSBhbnN3ZXIgaXMgdGhlIHNhbWUgb24gZXZlcnkgc3VyZmFjZSwgYW5kIHR3b1xuICAgKiBzdXJmYWNlcyBkaXNhZ3JlZWluZyBpcyBhIGJ1Zywgbm90IGEgZGlmZmVyZW5jZSBpbiB0b25lLiBUd28gb2YgdGhlIGZpdmVcbiAgICogbG9jYWwgY29waWVzIGhhZCBhbHJlYWR5IGRyaWZ0ZWQg4oCUIGBhdXRvbWF0aW9uQ2FsbHNgIG9taXR0ZWQgcGVyc29uYWxcbiAgICogYWNjZXNzIGtleXMgKHNvIGEgUEFUIGhvbGRlciByZWFkIHRoZSBwYWdlIGFuZCBjb25jbHVkZWQgdGhleSB3ZXJlIHNwZW5kaW5nXG4gICAqIGBhcGlDYWxsc2ApLCBhbmQgYHBsdWdpbnNgL2BwaXBlbGluZXNgIHdlcmUgcGhyYXNlZCBhcyBzdG9ja3MuXG4gICAqXG4gICAqIFRoZSB0ZXN0IGlzIHdoZXRoZXIgYGRvY3MvcXVvdGFzLm1kYCBhbmQgdGhpcyBzdHJpbmcgY2FuIGxlZ2l0aW1hdGVseVxuICAgKiBkaWZmZXIuIEZvciB0aGVzZSwgbm8uIEZvciBhIHRpZXIncyBwb3NpdGlvbmluZyBsaW5lIChcIlN0YXJ0ZXIgdGllclwiKSBvciB0aGVcbiAgICogcXVvdGFzIHBhZ2UncyBwb29saW5nIGV4cGxhbmF0aW9uLCB5ZXMg4oCUIHRob3NlIHN0YXkgaW4gdGhlIHBhZ2UsIGV4YWN0bHkgYXNcbiAgICogYFRJRVJfTUVUQWAgYWxyZWFkeSBzcGxpdHMgYSB0aWVyJ3MgbGFiZWwgZnJvbSBpdHMgc2FsZXMgY29weS5cbiAgICpcbiAgICogUGhyYXNpbmcgZm9sbG93cyB0aGUgU0VNQU5USUNTIGluIHtAbGluayBRdW90YVR5cGV9J3Mgb3duIGRvY3VtZW50YXRpb24sIG5vdFxuICAgKiBhIGhvdXNlIHN0eWxlOiBcInRoaXMgcGVyaW9kXCIgZm9yIHRoZSBwZXItcGVyaW9kIGZsb3dzLCBcImN1cnJlbnRseVwiIGZvciB0aGVcbiAgICogY3JlYXRlLXRpbWUgY291bnRzIHRoYXQgZGVjcmVtZW50IG9uIGRlbGV0ZSwgYW5kIG5laXRoZXIgZm9yIHRoZSBtZWFzdXJlZFxuICAgKiBzdG9yYWdlIHRvdGFsLlxuICAgKi9cbiAgZGVzY3JpcHRpb246IHN0cmluZztcbiAgLyoqXG4gICAqIFRoZSB1bml0IGFuIEFERC1PTiBQQUNLIG9mIHRoaXMgcXVvdGEgaXMgdHlwZWQgaW4sIGFuZCB0aGUgZGl2aXNvciBmcm9tIHRoZVxuICAgKiByYXcgZ3JhbnQgdG8gdGhhdCB1bml0IOKAlCBzbyBhICsxMCBHQiBzdG9yYWdlIHBhY2sgb2ZmZXJzIGEgR0Igc3RlcCByYXRoZXJcbiAgICogdGhhbiBhIHBhY2sgY291bnQuXG4gICAqXG4gICAqIEFic2VudCBtZWFucyBcInR5cGUgcGFjayBjb3VudHNcIiwgd2hpY2ggaXMgcmlnaHQgd2hlcmUgdGhlIHF1b3RhIGhhcyBub1xuICAgKiBuYXR1cmFsIHVuaXQ6IFwiMSBwYWNrIG9mIEFQSSBjYWxsc1wiIHJlYWRzIGJldHRlciB0aGFuIFwiMjUwMDAgY2FsbHNcIi4gVGhlXG4gICAqIHN0ZXAgaXRzZWxmIGlzIGFsd2F5cyBkZXJpdmVkIGZyb20gdGhlIGJ1bmRsZSdzIG93biBgZ3JhbnRzYCwgbmV2ZXIgZnJvbSBhXG4gICAqIGNvbnN0YW50IGhlcmUsIGJlY2F1c2UgYSBncmFudCBpcyBlbnYtb3ZlcnJpZGFibGUuXG4gICAqL1xuICBwYWNrVW5pdD86IHsgbmFtZTogc3RyaW5nOyBwZXI6IG51bWJlciB9O1xufVxuXG5jb25zdCBHQiA9IDEwMjQgKiogMztcblxuLyoqXG4gKiBEaXNwbGF5IG1ldGFkYXRhIGZvciBldmVyeSBxdW90YSB0eXBlLiBMYWJlbHMgYXJlIHRoZSBjYW5vbmljYWwgb25lcyDigJQgdGhlXG4gKiBzdHJpbmdzIGhlcmUgYXJlIHVzZXItZmFjaW5nIGFuZCBhcmUgcmVsaWVkIG9uIGJ5IDQyOSBib2RpZXMgYW5kIG5vdGljZXMsIHNvXG4gKiB0aGV5IGNoYW5nZSBvbmx5IGRlbGliZXJhdGVseS5cbiAqL1xuZXhwb3J0IGNvbnN0IFFVT1RBX0NBVEFMT0c6IFJlYWRvbmx5PFJlY29yZDxRdW90YVR5cGUsIFF1b3RhVHlwZU1ldGE+PiA9IHtcbiAgLy8gUGVyLXBlcmlvZCBGTE9XUyDigJQgYSBjcmVhdGlvbiByYXRlLCBub3QgYSBzdG9jay4gRGVsZXRpbmcgdGhlIGVudGl0eSBkb2VzXG4gIC8vIG5vdCBnaXZlIHRoZSBzbG90IGJhY2sgKGBkZWNyZW1lbnQtb24tZGVsZXRlYCB3YXMgY29uc2lkZXJlZCBhbmQgcmVqZWN0ZWQpLFxuICAvLyBzbyB0aGUgd29yZGluZyBzYXlzIFwidGhpcyBwZXJpb2RcIiB0byBzdG9wIHRoZSBzdG9jayByZWFkaW5nLlxuICBwbHVnaW5zOiB7IGxhYmVsOiAnUGx1Z2luIGJ1aWxkJywgbGFiZWxQbHVyYWw6ICdQbHVnaW5zJywgcmVuZGVyOiAnY291bnQnLCBhY2NydWFsOiAnZmxvdycsIG9ic2VydmVkOiAnbWV0ZXJlZCcsIGRlc2NyaXB0aW9uOiAnUGx1Z2luIHZlcnNpb25zIGRlcGxveWVkIHRoaXMgcGVyaW9kJyB9LFxuICBwaXBlbGluZXM6IHsgbGFiZWw6ICdQaXBlbGluZSBjcmVhdGlvbicsIGxhYmVsUGx1cmFsOiAnUGlwZWxpbmVzJywgcmVuZGVyOiAnY291bnQnLCBhY2NydWFsOiAnZmxvdycsIG9ic2VydmVkOiAnbWV0ZXJlZCcsIGRlc2NyaXB0aW9uOiAnUGlwZWxpbmVzIGNyZWF0ZWQgdGhpcyBwZXJpb2QnIH0sXG4gIC8vIFRoZSBjYWxsIGJ1ZGdldHMuIFdISUNIIE9ORSBhIHJlcXVlc3Qgc3BlbmRzIGlzIGRlY2lkZWQgYnlcbiAgLy8gYG1ldGVyZWRRdW90YVR5cGVGb3JgLCBuZXZlciBieSB0aGUgcm91dGU6IGEgc2VydmljZS1hY2NvdW50IGtleSwgYSBQRVJTT05BTFxuICAvLyBBQ0NFU1Mga2V5IChgdG9rZW5fdXNlOiAnYXBpX2tleSdgKSBvciBhbnl0aGluZyBtaW50ZWQgd2hpbGUgaG9sZGluZyBvbmVcbiAgLy8gKGBwYXJlbnRLZXlJZGApIHNwZW5kcyBgYXV0b21hdGlvbkNhbGxzYCBJTlNURUFEIG9mIGBhcGlDYWxsc2AuXG4gIGFwaUNhbGxzOiB7IGxhYmVsOiAnQVBJIGNhbGwnLCBsYWJlbFBsdXJhbDogJ0FQSSBjYWxscycsIHJlbmRlcjogJ2NvdW50JywgYWNjcnVhbDogJ2Zsb3cnLCBvYnNlcnZlZDogJ21ldGVyZWQnLCBkZXNjcmlwdGlvbjogJ1JlcXVlc3RzIG1hZGUgYnkgcGVvcGxlIGluIGJyb3dzZXIgc2Vzc2lvbnMgdGhpcyBwZXJpb2QnIH0sXG4gIGF1dG9tYXRpb25DYWxsczogeyBsYWJlbDogJ0F1dG9tYXRpb24gY2FsbCcsIGxhYmVsUGx1cmFsOiAnQXV0b21hdGlvbiBjYWxscycsIHJlbmRlcjogJ2NvdW50JywgYWNjcnVhbDogJ2Zsb3cnLCBvYnNlcnZlZDogJ21ldGVyZWQnLCBkZXNjcmlwdGlvbjogJ1JlcXVlc3RzIG1hZGUgd2l0aCBzZXJ2aWNlLWFjY291bnQgb3IgcGVyc29uYWwgYWNjZXNzIGtleXMgdGhpcyBwZXJpb2QnIH0sXG4gIGFpQ2FsbHM6IHsgbGFiZWw6ICdBSSBjYWxsJywgbGFiZWxQbHVyYWw6ICdBSSBjYWxscycsIHJlbmRlcjogJ2NvdW50JywgYWNjcnVhbDogJ2Zsb3cnLCBvYnNlcnZlZDogJ21ldGVyZWQnLCBkZXNjcmlwdGlvbjogJ0FJIGdlbmVyYXRpb24gaW52b2NhdGlvbnMgdGhpcyBwZXJpb2QnIH0sXG4gIC8vIFRoZSBvbmUgZmxvdyBxdW90YSBvbiBhIHBlcmlvZCBvZiBpdHMgT1dOIOKAlCBhIE1PTlRILCB3aGVyZSBldmVyeSBvdGhlciBmbG93XG4gIC8vIHJvbGxzIG9uIFFVT1RBX1JFU0VUX0RBWVMgKDMgZGF5cykuIFJlbmRlcmVkIGFzIGEgQ09VTlQgYW5kIG5ldmVyIGFzIGFcbiAgLy8gZHVyYXRpb246IHRoZSB1bml0IGlzIGFuIGFic3RyYWN0IGNyZWRpdCBwcmVjaXNlbHkgc28gaXQgY2Fubm90IGJlIG1pc3JlYWQgYXNcbiAgLy8gd2FsbC1jbG9jayAoXCIxMSwyNTAgYnVpbGQgbWludXRlc1wiIHJlYWRzIGFzIDE4NyBob3VycyB3aGVuIGl0IGJ1eXMgMTI1KSwgYW5kXG4gIC8vIGZvcm1hdHRpbmcgaXQgYmFjayBpbnRvIFwiMTg3aCAzMG1cIiB3b3VsZCB1bmRvIHRoYXQuIFRoZSB3YWxsLWNsb2NrIGNvbnZlcnNpb25cbiAgLy8gYmVsb25ncyBpbiB0aGUgcHJlLWJ1aWxkIGNvc3QgaGludCwgd2hlcmUgaXQgZXhwbGFpbnMgcmF0aGVyIHRoYW4gbWlzbGVhZHMuXG4gIGNwdUNyZWRpdHM6IHsgbGFiZWw6ICdDUFUgY3JlZGl0JywgbGFiZWxQbHVyYWw6ICdDUFUgY3JlZGl0cycsIHJlbmRlcjogJ2NvdW50JywgYWNjcnVhbDogJ2Zsb3cnLCBvYnNlcnZlZDogJ21ldGVyZWQnLCBkZXNjcmlwdGlvbjogJ3ZDUFUtbWludXRlcyBvZiBidWlsZCBjb21wdXRlIHVzZWQgdGhpcyBtb250aCcsIHBhY2tVbml0OiB7IG5hbWU6ICdjcmVkaXRzJywgcGVyOiAxIH0gfSxcbiAgLy8gQSBNRUFTVVJFRCB0b3RhbCwgcmVjb21wdXRlZCBvbiBkZW1hbmQgcmF0aGVyIHRoYW4gY291bnRlZCDigJQgdGhlIG9uZSBxdW90YVxuICAvLyB0aGF0IGlzIG5laXRoZXIgYSBwZXJpb2QgZmxvdyBub3IgYSBjcmVhdGUtdGltZSBjb3VudC5cbiAgc3RvcmFnZUJ5dGVzOiB7IGxhYmVsOiAnUmVnaXN0cnkgc3RvcmFnZScsIGxhYmVsUGx1cmFsOiAnUmVnaXN0cnkgc3RvcmFnZScsIHJlbmRlcjogJ2J5dGVzJywgYWNjcnVhbDogJ3N0b2NrJywgb2JzZXJ2ZWQ6ICdtZWFzdXJlZCcsIGRlc2NyaXB0aW9uOiAnUmVnaXN0cnkgc3RvcmFnZSBjdXJyZW50bHkgaW4gdXNlJywgcGFja1VuaXQ6IHsgbmFtZTogJ0dCJywgcGVyOiBHQiB9IH0sXG4gIC8vIFJlc291cmNlLWNvdW50IGNhcHM6IGNvdW50ZWQgYXQgY3JlYXRlIHRpbWUgYW5kIERFQ1JFTUVOVEVEIG9uIGRlbGV0ZSwgc29cbiAgLy8gdGhlc2UgcmVhbGx5IGFyZSBzdG9ja3MgYW5kIFwidGhpcyBwZXJpb2RcIiB3b3VsZCBiZSB3cm9uZy5cbiAgZGFzaGJvYXJkczogeyBsYWJlbDogJ0Rhc2hib2FyZCcsIGxhYmVsUGx1cmFsOiAnRGFzaGJvYXJkcycsIHJlbmRlcjogJ2NvdW50JywgYWNjcnVhbDogJ3N0b2NrJywgb2JzZXJ2ZWQ6ICdtZXRlcmVkJywgZGVzY3JpcHRpb246ICdEYXNoYm9hcmRzIGN1cnJlbnRseSBkZWZpbmVkJyB9LFxuICBhbGVydFJ1bGVzOiB7IGxhYmVsOiAnQWxlcnQgcnVsZScsIGxhYmVsUGx1cmFsOiAnQWxlcnQgcnVsZXMnLCByZW5kZXI6ICdjb3VudCcsIGFjY3J1YWw6ICdzdG9jaycsIG9ic2VydmVkOiAnbWV0ZXJlZCcsIGRlc2NyaXB0aW9uOiAnQWxlcnQgcnVsZXMgY3VycmVudGx5IGRlZmluZWQnIH0sXG4gIGFsZXJ0RGVzdGluYXRpb25zOiB7IGxhYmVsOiAnQWxlcnQgZGVzdGluYXRpb24nLCBsYWJlbFBsdXJhbDogJ0FsZXJ0IGRlc3RpbmF0aW9ucycsIHJlbmRlcjogJ2NvdW50JywgYWNjcnVhbDogJ3N0b2NrJywgb2JzZXJ2ZWQ6ICdtZXRlcmVkJywgZGVzY3JpcHRpb246ICdBbGVydCBkZXN0aW5hdGlvbnMgY3VycmVudGx5IGRlZmluZWQnIH0sXG4gIGlkcENvbmZpZ3M6IHsgbGFiZWw6ICdJZFAgY29uZmlndXJhdGlvbicsIGxhYmVsUGx1cmFsOiAnSWRQIGNvbmZpZ3VyYXRpb25zJywgcmVuZGVyOiAnY291bnQnLCBhY2NydWFsOiAnc3RvY2snLCBvYnNlcnZlZDogJ21ldGVyZWQnLCBkZXNjcmlwdGlvbjogJ0lkZW50aXR5LXByb3ZpZGVyIGNvbmZpZ3VyYXRpb25zIGN1cnJlbnRseSBkZWZpbmVkJyB9LFxuICBsaXN0aW5nczogeyBsYWJlbDogJ1BsdWdpbiBsaXN0aW5nJywgbGFiZWxQbHVyYWw6ICdQbHVnaW4gbGlzdGluZ3MnLCByZW5kZXI6ICdjb3VudCcsIGFjY3J1YWw6ICdzdG9jaycsIG9ic2VydmVkOiAnbWV0ZXJlZCcsIGRlc2NyaXB0aW9uOiAnQWN0aXZlIHBsdWdpbiBsaXN0aW5ncyB5b3UgcHVibGlzaCcgfSxcbn07XG5cbi8qKlxuICogQ29uc3VtYWJsZXMgdGhhdCBhcmUgTk9UIHF1b3RhIHR5cGVzIGJ1dCByZW5kZXIgYmVzaWRlIHRoZW0g4oCUIHNhbWUgbGFiZWwsXG4gKiBzYW1lIGJhciwgc2FtZSBjYXJkLlxuICpcbiAqIGBzZWF0c2AgaXMgdGhlIG9ubHkgb25lOiBpdCBpcyBhIFNUT0NLIChob3cgbWFueSBwZW9wbGUgbWF5IGJlIG9uIHRoZVxuICogYWNjb3VudCkgcmF0aGVyIHRoYW4gYSBwZXItcGVyaW9kIGZsb3csIHNvIGl0IGlzIGRlbGliZXJhdGVseSBvdXRzaWRlXG4gKiBgUXVvdGFUeXBlYCBhbmQgaXMgc291cmNlZCBzZXBhcmF0ZWx5IGZyb20gdGhlIHF1b3RhIHJvbGx1cC4gQnV0IHRoZSBiaWxsaW5nXG4gKiB1c2FnZSBjYXJkIGRyYXdzIGl0IGluIHRoZSBzYW1lIGxpc3QsIGFuZCBiZWZvcmUgdGhpcyBlbnRyeSBleGlzdGVkIHRoYXQgb25lXG4gKiByb3cgY2FycmllZCBpdHMgb3duIGlubGluZSBcIlNlYXRzXCIgbGFiZWwgd2hpbGUgdGhlIGZpdmUgYmVzaWRlIGl0IGNhbWUgZnJvbVxuICogdGhlIGNhdGFsb2cg4oCUIHRoZSBwcmVjaXNlIHNwbGl0LWZhY3QgZHJpZnQgdGhpcyBmaWxlIGV4aXN0cyB0byByZW1vdmUuXG4gKlxuICogS2VlcCB0aGlzIGZvciB0aGluZ3MgcmVuZGVyZWQgQVMgYSBjb25zdW1hYmxlLiBJdCBpcyBub3QgYSBiYWNrIGRvb3IgZm9yXG4gKiBxdW90YSB0eXBlczogYSByZWFsIHF1b3RhIGJlbG9uZ3MgaW4ge0BsaW5rIFFVT1RBX0NBVEFMT0d9LCB3aGVyZVxuICogYFJlY29yZDxRdW90YVR5cGUsIOKApj5gIGZvcmNlcyB0aGUgZW50cnkuXG4gKi9cbmV4cG9ydCBjb25zdCBOT05fUVVPVEFfQ09OU1VNQUJMRV9DQVRBTE9HOiBSZWFkb25seTxSZWNvcmQ8c3RyaW5nLCBRdW90YVR5cGVNZXRhPj4gPSB7XG4gIHNlYXRzOiB7IGxhYmVsOiAnU2VhdCcsIGxhYmVsUGx1cmFsOiAnU2VhdHMnLCByZW5kZXI6ICdjb3VudCcsIGFjY3J1YWw6ICdzdG9jaycsIG9ic2VydmVkOiAnbWVhc3VyZWQnLCBkZXNjcmlwdGlvbjogJ01lbWJlcnMgdGhpcyBhY2NvdW50IG1heSBoYXZlJyB9LFxufTtcblxuLyoqXG4gKiBFdmVyeSBkZXNjcmliZWQgY29uc3VtYWJsZSwgd2lkZW5lZCBmb3IgbG9va3VwcyBieSBhIHZhbHVlIHRoYXQgY2FtZSBvZmYgdGhlXG4gKiB3aXJlLlxuICovXG5jb25zdCBieUtleTogUmVjb3JkPHN0cmluZywgUXVvdGFUeXBlTWV0YSB8IHVuZGVmaW5lZD4gPSB7XG4gIC4uLk5PTl9RVU9UQV9DT05TVU1BQkxFX0NBVEFMT0csXG4gIC4uLlFVT1RBX0NBVEFMT0csXG59O1xuXG4vKipcbiAqIFRoaXMgdHlwZSdzIHNpbmd1bGFyIGxhYmVsLCBvciB0aGUgcmF3IGtleSBmb3IgYSB2YWx1ZSBvdXRzaWRlIHRoZSBjYXRhbG9nLlxuICpcbiAqIFRoZSBhY2Nlc3NvcnMgdGFrZSBgc3RyaW5nYCBiZWNhdXNlIHRoZSBxdW90YSB0eXBlIG9mdGVuIGFycml2ZXMgZnJvbSB0aGUgd2lyZVxuICogKGEgNDI5IGJvZHksIGEgcXVvdGEgcmVzcG9uc2UpIHdoZXJlIGFuIHVua25vd24gdmFsdWUgbXVzdCByZW5kZXIgYXMgc29tZXRoaW5nXG4gKiByYXRoZXIgdGhhbiBjcmFzaCBhIHBhZ2UuIFRoZSBgUXVvdGFUeXBlYCBvdmVybG9hZCBpcyBmb3IgaW50ZXJuYWwgY2FsbGVyczogaXRcbiAqIG1ha2VzIGEgdHlwbyBhIGNvbXBpbGUgZXJyb3IgaW5zdGVhZCBvZiBhIGtleSByZW5kZXJlZCBhcyBhIGxhYmVsLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcXVvdGFMYWJlbCh0eXBlOiBRdW90YVR5cGUpOiBzdHJpbmc7XG5leHBvcnQgZnVuY3Rpb24gcXVvdGFMYWJlbCh0eXBlOiBzdHJpbmcpOiBzdHJpbmc7XG5leHBvcnQgZnVuY3Rpb24gcXVvdGFMYWJlbCh0eXBlOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gYnlLZXlbdHlwZV0/LmxhYmVsID8/IHR5cGU7XG59XG5cbi8qKiBUaGlzIHR5cGUncyBQTFVSQUwgbGFiZWwsIG9yIHRoZSByYXcga2V5IGZvciBhIHZhbHVlIG91dHNpZGUgdGhlIGNhdGFsb2cuICovXG5leHBvcnQgZnVuY3Rpb24gcXVvdGFMYWJlbFBsdXJhbCh0eXBlOiBRdW90YVR5cGUpOiBzdHJpbmc7XG5leHBvcnQgZnVuY3Rpb24gcXVvdGFMYWJlbFBsdXJhbCh0eXBlOiBzdHJpbmcpOiBzdHJpbmc7XG5leHBvcnQgZnVuY3Rpb24gcXVvdGFMYWJlbFBsdXJhbCh0eXBlOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gYnlLZXlbdHlwZV0/LmxhYmVsUGx1cmFsID8/IHR5cGU7XG59XG5cbi8qKlxuICogSG93IHRvIHJlbmRlciB0aGlzIHR5cGUncyB2YWx1ZXMuIGBjb3VudGAgZm9yIGFueXRoaW5nIHRoZSBjYXRhbG9nIGRvZXMgbm90XG4gKiBrbm93LCBzbyBhbiB1bmZhbWlsaWFyIHdpcmUgdmFsdWUgcmVuZGVycyBhcyBhIHBsYWluIG51bWJlci5cbiAqXG4gKiBSZWFkIFRISVMgcmF0aGVyIHRoYW4gYXNraW5nIFwiaXMgaXQgYnl0ZXM/XCIuIEEgYm9vbGVhbiBoZWxwZXIgZGVncmFkZXNcbiAqIHNpbGVudGx5IHRoZSBtb21lbnQge0BsaW5rIFF1b3RhUmVuZGVyfSBncm93cyBhIHRoaXJkIG1lbWJlciDigJQgYSBuZXcgbW9kZVxuICogd291bGQgc2ltcGx5IHJlbmRlciBhcyBhIGNvdW50LCB3aXRoIG5vdGhpbmcgZmFpbGluZyDigJQgd2hlcmVhcyBhIGNhbGxlciB0aGF0XG4gKiBzd2l0Y2hlcyBvbiB0aGlzIHZhbHVlIGV4aGF1c3RpdmVseSBzdG9wcyBjb21waWxpbmcgdW50aWwgaXQgaGFuZGxlcyB0aGUgbmV3XG4gKiBtb2RlLiBUaGF0IGlzIHRoZSB3aG9sZSBwb2ludCBvZiB0aGUgY2F0YWxvZywgYXBwbGllZCB0byBpdHMgb3duIGFjY2Vzc29ycy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHF1b3RhUmVuZGVyKHR5cGU6IFF1b3RhVHlwZSk6IFF1b3RhUmVuZGVyO1xuZXhwb3J0IGZ1bmN0aW9uIHF1b3RhUmVuZGVyKHR5cGU6IHN0cmluZyk6IFF1b3RhUmVuZGVyO1xuZXhwb3J0IGZ1bmN0aW9uIHF1b3RhUmVuZGVyKHR5cGU6IHN0cmluZyk6IFF1b3RhUmVuZGVyIHtcbiAgcmV0dXJuIGJ5S2V5W3R5cGVdPy5yZW5kZXIgPz8gJ2NvdW50Jztcbn1cblxuY29uc3QgQllURV9VTklUUyA9IFsnQicsICdLQicsICdNQicsICdHQicsICdUQiddIGFzIGNvbnN0O1xuXG4vKipcbiAqIEEgYnl0ZSBjb3VudCBhcyBhIGh1bWFuIGFtb3VudCDigJQgYDEuNSBHQmAsIGA1MTIgTUJgLCBgMCBCYC5cbiAqXG4gKiBUaGUgT05FIGltcGxlbWVudGF0aW9uOiB0aGUgZnJvbnRlbmQncyBgZm9ybWF0Qnl0ZXNgIGRlbGVnYXRlcyBoZXJlIHJhdGhlclxuICogdGhhbiBrZWVwaW5nIGl0cyBvd24gY29weSwgc28gYSBzdG9yYWdlIGZpZ3VyZSByZWFkcyBpZGVudGljYWxseSBpbiBhIHF1b3RhXG4gKiBub3RpY2UsIGEgNDAzIGV4cGxhbmF0aW9uIGFuZCB0aGUgZGFzaGJvYXJkLiBCaW5hcnkgdW5pdHMgKDEwMjQpLCBvbmUgZGVjaW1hbFxuICogYmVsb3cgMTAgYW5kIHJvdW5kZWQgYWJvdmUsIHdoaWNoIGlzIHdoYXQgdGhlIGRhc2hib2FyZCBhbHJlYWR5IHJlbmRlcmVkIOKAlFxuICogdGhpcyBtb3ZlZCB0aGUgYWxnb3JpdGhtLCBpdCBkaWQgbm90IGNoYW5nZSBpdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdEJ5dGVBbW91bnQoYnl0ZXM6IG51bWJlcik6IHN0cmluZyB7XG4gIGlmICghTnVtYmVyLmlzRmluaXRlKGJ5dGVzKSB8fCBieXRlcyA8IDApIHJldHVybiAnMCBCJztcbiAgbGV0IHZhbHVlID0gYnl0ZXM7XG4gIGxldCBpID0gMDtcbiAgd2hpbGUgKHZhbHVlID49IDEwMjQgJiYgaSA8IEJZVEVfVU5JVFMubGVuZ3RoIC0gMSkge1xuICAgIHZhbHVlIC89IDEwMjQ7XG4gICAgaSsrO1xuICB9XG4gIHJldHVybiB2YWx1ZSA8IDEwID8gYCR7dmFsdWUudG9GaXhlZCgxKX0gJHtCWVRFX1VOSVRTW2ldfWAgOiBgJHtNYXRoLnJvdW5kKHZhbHVlKX0gJHtCWVRFX1VOSVRTW2ldfWA7XG59XG5cbi8qKlxuICogQSByYXcgcXVvdGEgdmFsdWUgYXMgdGhlIGFtb3VudCBhIEhVTUFOIHNob3VsZCByZWFkLCBwZXIgdGhpcyB0eXBlJ3NcbiAqIHtAbGluayBRdW90YVJlbmRlcn0g4oCUIGJ5dGVzIGFzIGAyNSBHQmAsIGNvdW50cyBncm91cGVkIGFzIGA2MCwwMDBgLlxuICpcbiAqIEZvciBzZXJ2ZXItc2lkZSBjb3B5IChxdW90YSBub3RpY2VzLCBhdWRpdCByZWFzb25zLCA0MDMgZXhwbGFuYXRpb25zKSwgd2hpY2hcbiAqIGlzIHdoeSBpdCBpcyBoZXJlIHJhdGhlciB0aGFuIGluIHRoZSBmcm9udGVuZDogYSBzdG9yYWdlIG5vdGljZSB0aGF0XG4gKiBpbnRlcnBvbGF0ZWQgdGhlIHJhdyBudW1iZXIgd291bGQgcmVhZCBcInVzZWQgYWxsIDI2ODQzNTQ1NjAwIG9mIGl0cyByZWdpc3RyeVxuICogc3RvcmFnZVwiLiBUaGUgZnJvbnRlbmQgaGFzIGl0cyBvd24gdGhpbiB3cmFwcGVyIGJlY2F1c2UgaXQgcmVuZGVycyBhblxuICogdW5saW1pdGVkICgtMSkgbGltaXQgYXMgYOKInmAsIHdoaWNoIGlzIGEgZGlzcGxheSBjaG9pY2Ugbm8gc2VydmVyIGNvcHkgbWFrZXNcbiAqICh1bmxpbWl0ZWQgb3JncyBhcmUgc2tpcHBlZCBiZWZvcmUgYW55IGNvcHkgaXMgZ2VuZXJhdGVkKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdFF1b3RhQW1vdW50KHR5cGU6IHN0cmluZywgdmFsdWU6IG51bWJlcik6IHN0cmluZyB7XG4gIHJldHVybiBxdW90YVJlbmRlcih0eXBlKSA9PT0gJ2J5dGVzJyA/IGZvcm1hdEJ5dGVBbW91bnQodmFsdWUpIDogdmFsdWUudG9Mb2NhbGVTdHJpbmcoJ2VuLVVTJyk7XG59XG5cbi8qKlxuICogSG93IHRoaXMgcXVvdGEncyB1c2FnZSBpcyBvYnNlcnZlZDogYG1ldGVyZWRgIChpbmNyZW1lbnRlZCkgb3IgYG1lYXN1cmVkYFxuICogKHJlYWQgb24gZGVtYW5kKS4gVW5rbm93biB3aXJlIHZhbHVlcyByZWFkIGFzIGBtZXRlcmVkYCwgbWF0Y2hpbmcgZXZlcnlcbiAqIHF1b3RhIGJ1dCBzdG9yYWdlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcXVvdGFPYnNlcnZhdGlvbih0eXBlOiBRdW90YVR5cGUpOiBRdW90YU9ic2VydmF0aW9uO1xuZXhwb3J0IGZ1bmN0aW9uIHF1b3RhT2JzZXJ2YXRpb24odHlwZTogc3RyaW5nKTogUXVvdGFPYnNlcnZhdGlvbjtcbmV4cG9ydCBmdW5jdGlvbiBxdW90YU9ic2VydmF0aW9uKHR5cGU6IHN0cmluZyk6IFF1b3RhT2JzZXJ2YXRpb24ge1xuICByZXR1cm4gYnlLZXlbdHlwZV0/Lm9ic2VydmVkID8/ICdtZXRlcmVkJztcbn1cblxuLyoqXG4gKiBXaGV0aGVyIHRoaXMgcXVvdGEgcmVmaWxscyBvbiB0aGUgcGVyaW9kIChgZmxvd2ApIG9yIHBlcnNpc3RzIChgc3RvY2tgKS5cbiAqIFVua25vd24gd2lyZSB2YWx1ZXMgYXJlIHRyZWF0ZWQgYXMgYGZsb3dgLCBtYXRjaGluZyBldmVyeSBtZXRlcmVkIHF1b3RhLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcXVvdGFBY2NydWFsKHR5cGU6IFF1b3RhVHlwZSk6IFF1b3RhQWNjcnVhbDtcbmV4cG9ydCBmdW5jdGlvbiBxdW90YUFjY3J1YWwodHlwZTogc3RyaW5nKTogUXVvdGFBY2NydWFsO1xuZXhwb3J0IGZ1bmN0aW9uIHF1b3RhQWNjcnVhbCh0eXBlOiBzdHJpbmcpOiBRdW90YUFjY3J1YWwge1xuICByZXR1cm4gYnlLZXlbdHlwZV0/LmFjY3J1YWwgPz8gJ2Zsb3cnO1xufVxuXG4vKiogV2hhdCB0aGlzIHR5cGUgY291bnRzLCBvciB0aGUgZW1wdHkgc3RyaW5nIGZvciBhIHZhbHVlIG91dHNpZGUgdGhlIGNhdGFsb2cuICovXG5leHBvcnQgZnVuY3Rpb24gcXVvdGFEZXNjcmlwdGlvbih0eXBlOiBRdW90YVR5cGUpOiBzdHJpbmc7XG5leHBvcnQgZnVuY3Rpb24gcXVvdGFEZXNjcmlwdGlvbih0eXBlOiBzdHJpbmcpOiBzdHJpbmc7XG5leHBvcnQgZnVuY3Rpb24gcXVvdGFEZXNjcmlwdGlvbih0eXBlOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gYnlLZXlbdHlwZV0/LmRlc2NyaXB0aW9uID8/ICcnO1xufVxuXG4vKiogVGhlIHBhY2sgdW5pdCBmb3IgdGhpcyB0eXBlLCBvciBgbnVsbGAgdG8gdHlwZSBwYWNrIGNvdW50cyBpbnN0ZWFkLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHF1b3RhUGFja1VuaXQodHlwZTogUXVvdGFUeXBlKTogeyBuYW1lOiBzdHJpbmc7IHBlcjogbnVtYmVyIH0gfCBudWxsO1xuZXhwb3J0IGZ1bmN0aW9uIHF1b3RhUGFja1VuaXQodHlwZTogc3RyaW5nKTogeyBuYW1lOiBzdHJpbmc7IHBlcjogbnVtYmVyIH0gfCBudWxsO1xuZXhwb3J0IGZ1bmN0aW9uIHF1b3RhUGFja1VuaXQodHlwZTogc3RyaW5nKTogeyBuYW1lOiBzdHJpbmc7IHBlcjogbnVtYmVyIH0gfCBudWxsIHtcbiAgcmV0dXJuIGJ5S2V5W3R5cGVdPy5wYWNrVW5pdCA/PyBudWxsO1xufVxuIl19
@@ -1,3 +1,4 @@
1
+ import type { QuotaType } from './common.js';
1
2
  /** Available quota tier identifiers.
2
3
  *
3
4
  * `unlimited` is a special tier: every quota is `-1` (uncapped) and it includes
@@ -10,6 +11,12 @@ export interface QuotaTierLimits {
10
11
  plugins: number;
11
12
  pipelines: number;
12
13
  apiCalls: number;
14
+ /**
15
+ * Metered API calls made by ORG SERVICE ACCOUNTS (automation), counted
16
+ * instead of `apiCalls` so pipelines can't starve the org's people. See
17
+ * {@link AUTOMATION_CALLS_SIZING} for how the defaults are derived.
18
+ */
19
+ automationCalls: number;
13
20
  aiCalls: number;
14
21
  /**
15
22
  * Aggregate registry storage cap in BYTES. Counted across every repo
@@ -18,6 +25,23 @@ export interface QuotaTierLimits {
18
25
  * `push` scope when the org's measured usage exceeds the limit.
19
26
  */
20
27
  storageBytes: number;
28
+ /**
29
+ * vCPU-MINUTES of plugin-build compute per MONTH — the one quota on a period
30
+ * of its own (every other flow quota rolls on `QUOTA_RESET_DAYS`, 3 days).
31
+ *
32
+ * Derived from the `plugins` cap rather than guessed: `plugins x 10` builds a
33
+ * month, at a flat 30 wall-minutes a build and 1.5 vCPU of buildkitd, so
34
+ * `credits = builds x 45`. The flat per-build figure is deliberate — scaling
35
+ * BOTH the build count and the per-build cost by tier made credits
36
+ * superlinear in tier on the strength of an unmeasured claim, and it made a
37
+ * pack's credit grant tier-dependent, which the catalogue's grant shape cannot
38
+ * express.
39
+ *
40
+ * This is the first dimension with genuine per-unit marginal cost: every other
41
+ * quota is a rate limit protecting shared infrastructure, this one is compute
42
+ * the platform pays for. See `docs/plans/cpu-credits.md`.
43
+ */
44
+ cpuCredits: number;
21
45
  /** Count-quotas on the user-editable feature tables added to close per-org
22
46
  * DoS via spam. -1 means unlimited. */
23
47
  dashboards: number;
@@ -86,6 +110,78 @@ export interface QuotaTierPreset {
86
110
  label: string;
87
111
  limits: QuotaTierLimits;
88
112
  }
113
+ /**
114
+ * How the `automationCalls` defaults are sized — written down so the next
115
+ * person to touch them argues with the arithmetic, not with the numbers.
116
+ *
117
+ * Every call an org's automation keys make in one pipeline RUN, itemized (the
118
+ * earlier sizing counted only the plugin lookups and hand-waved the rest as
119
+ * "×2"):
120
+ *
121
+ * plugin lookups 12 one per plugin step (self-mutating pipeline)
122
+ * synth 1 the synth step fetching the pipeline config
123
+ * runtime check 1 the pre-deploy runtime-check call
124
+ * security-report upload 1 the scan step posting its report
125
+ * registry register 1 pipeline-register stamping the deployed version
126
+ * reporting ingest batches 12 the event forwarder posts ~one batch per action
127
+ * --
128
+ * 28 calls per run
129
+ *
130
+ * Per default period (`QUOTA_RESET_DAYS`, 3 days) one active pipeline running
131
+ * hourly makes 28 × 24 × 3 = 2,016 calls. An org keeps far more pipelines alive
132
+ * than it CREATES in a period (`pipelines` is a creation rate), so active
133
+ * pipelines are taken as 4× the tier's `pipelines`. On top sits a per-ORG
134
+ * constant that does not scale with pipelines: the ingest-health heartbeat
135
+ * (≤ 1/minute → 1,440/day → 4,320/period). The sum gets 25% headroom (retries,
136
+ * registry pushes, ad-hoc CI scripts on keys) and rounds UP to the next 10,000:
137
+ *
138
+ * developer 2×4×2016 + 4320 = 20,448 × 1.25 = 25,560 → 30,000
139
+ * pro 5×4×2016 + 4320 = 44,640 × 1.25 = 55,800 → 60,000
140
+ * team 10×4×2016 + 4320 = 84,960 × 1.25 = 106,200 → 110,000
141
+ * business 20×4×2016 + 4320 = 165,600 × 1.25 = 207,000 → 210,000
142
+ * enterprise 30×4×2016 + 4320 = 246,240 × 1.25 = 307,800 → 310,000
143
+ *
144
+ * {@link automationCallsFor} computes exactly this, and the tier tests hold the
145
+ * table to it. Over the cap the 429 is SOFT for a band ({@link quotaGracePct}):
146
+ * a self-mutating pipeline that hard-stopped at 100% stayed stopped until the
147
+ * period reset, up to 3 days. The limits are PER PERIOD: an operator who
148
+ * lengthens `QUOTA_RESET_DAYS` should scale `QUOTA_TIER_<TIER>_AUTOMATION_CALLS`
149
+ * by the same factor.
150
+ */
151
+ export declare const AUTOMATION_CALLS_SIZING: {
152
+ readonly callsPerRun: {
153
+ readonly pluginLookups: 12;
154
+ readonly synth: 1;
155
+ readonly runtimeCheck: 1;
156
+ readonly securityReportUpload: 1;
157
+ readonly registryRegister: 1;
158
+ readonly ingestBatches: 12;
159
+ };
160
+ readonly runsPerDay: 24;
161
+ readonly activePipelinesPerCreation: 4;
162
+ /** Ingest-health heartbeats per org per day (≤ one a minute). */
163
+ readonly heartbeatsPerDay: 1440;
164
+ /** Multiplier on the itemized total. */
165
+ readonly headroom: 1.25;
166
+ /** Defaults round UP to a multiple of this. */
167
+ readonly roundTo: 10000;
168
+ };
169
+ /** The {@link AUTOMATION_CALLS_SIZING} formula for a tier creating `pipelines` per period. */
170
+ export declare function automationCallsFor(pipelines: number, periodDays?: number): number;
171
+ /** The soft band for `quotaType` in percent, or 0 for a type that stops hard. */
172
+ export declare function quotaGracePct(quotaType: string): number;
173
+ /**
174
+ * The limit enforcement actually binds on for `quotaType`: the nominal limit,
175
+ * plus the type's {@link quotaGracePct} band where it has one. Unlimited (-1)
176
+ * and zero ("none") limits are returned unchanged — a 0 cap is a deliberate
177
+ * refusal, not a budget to overrun.
178
+ *
179
+ * {@link QUOTA_GRACE_PCT} decides WHETHER a type has a band; `gracePct` only
180
+ * sizes it. So an explicit percentage passed for an unbanded type still yields
181
+ * the nominal limit, rather than quietly granting a band the type is not meant
182
+ * to have.
183
+ */
184
+ export declare function enforcedQuotaLimit(quotaType: string, limit: number, gracePct?: number): number;
89
185
  export declare const QUOTA_TIERS: Record<QuotaTier, QuotaTierPreset>;
90
186
  /** All valid tier names (incl. `unlimited`). Used for validation of a *stored*
91
187
  * tier — `unlimited` is a valid value even though it's never offered for sale. */
@@ -136,4 +232,26 @@ export declare function getTierLimits(tier: string): QuotaTierLimits;
136
232
  */
137
233
  export declare const QUOTA_RESET_DAYS: number;
138
234
  /** The next quota reset: local midnight `days` from `now`. */
235
+ /**
236
+ * The `cpuCredits` period, in days. A MONTH rather than the global three-day
237
+ * window, because build demand is bursty in a way API traffic is not: a full
238
+ * catalogue rebuild is a day's work that arrives on one day, and a three-day
239
+ * window refuses it while the account holds most of a month's budget unspent.
240
+ */
241
+ export declare const CPU_CREDITS_PERIOD_DAYS: number;
242
+ /**
243
+ * The usage period for ONE quota type, in days.
244
+ *
245
+ * Every flow quota but `cpuCredits` rolls on the global {@link QUOTA_RESET_DAYS}.
246
+ * Credits are monthly, which makes the period a PER-TYPE fact — so anything that
247
+ * computes a reset date, a "resets in N days" line or a burn-rate forecast has
248
+ * to ask for the type's own period rather than reading the global constant. A
249
+ * forecast against the wrong window is wrong by a factor of ten.
250
+ *
251
+ * Stocks (`storageBytes` and the resource counts) have no period at all; this
252
+ * returns the global window for them because callers gate on
253
+ * `quotaAccrual(type) === 'stock'` before using it, and returning a figure is
254
+ * less disruptive than making every caller handle a null. Check the accrual.
255
+ */
256
+ export declare function quotaPeriodDays(type: QuotaType): number;
139
257
  export declare function nextQuotaResetDate(days?: number, now?: Date): Date;