@cat-factory/orchestration 0.194.0 → 0.195.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/container/dependencies.d.ts +17 -1
- package/dist/container/dependencies.d.ts.map +1 -1
- package/dist/container/module-shapes.d.ts +5 -0
- package/dist/container/module-shapes.d.ts.map +1 -1
- package/dist/container/platform-modules.d.ts.map +1 -1
- package/dist/container/platform-modules.js +14 -1
- package/dist/container/platform-modules.js.map +1 -1
- package/dist/container.d.ts +20 -2
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +14 -0
- package/dist/container.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/debug/RunDebugService.d.ts +27 -1
- package/dist/modules/debug/RunDebugService.d.ts.map +1 -1
- package/dist/modules/debug/RunDebugService.js +24 -2
- package/dist/modules/debug/RunDebugService.js.map +1 -1
- package/dist/modules/debug/debug.logic.d.ts +3 -3
- package/dist/modules/debug/debug.logic.d.ts.map +1 -1
- package/dist/modules/debug/debug.logic.js +4 -1
- package/dist/modules/debug/debug.logic.js.map +1 -1
- package/dist/modules/execution/ExecutionService.d.ts.map +1 -1
- package/dist/modules/execution/ExecutionService.js +7 -0
- package/dist/modules/execution/ExecutionService.js.map +1 -1
- package/dist/modules/execution/ExecutionServiceDependencies.d.ts +6 -0
- package/dist/modules/execution/ExecutionServiceDependencies.d.ts.map +1 -1
- package/dist/modules/execution/MergeResolver.d.ts +14 -2
- package/dist/modules/execution/MergeResolver.d.ts.map +1 -1
- package/dist/modules/execution/MergeResolver.js +83 -30
- package/dist/modules/execution/MergeResolver.js.map +1 -1
- package/dist/modules/execution/PrVerificationReportController.d.ts +23 -0
- package/dist/modules/execution/PrVerificationReportController.d.ts.map +1 -1
- package/dist/modules/execution/PrVerificationReportController.js +18 -0
- package/dist/modules/execution/PrVerificationReportController.js.map +1 -1
- package/dist/modules/execution/RunLifecycleController.d.ts +14 -1
- package/dist/modules/execution/RunLifecycleController.d.ts.map +1 -1
- package/dist/modules/execution/RunLifecycleController.js +20 -2
- package/dist/modules/execution/RunLifecycleController.js.map +1 -1
- package/dist/modules/execution/RunStateMachine.d.ts.map +1 -1
- package/dist/modules/execution/RunStateMachine.js +27 -14
- package/dist/modules/execution/RunStateMachine.js.map +1 -1
- package/dist/modules/execution/StepDecisionController.d.ts +20 -0
- package/dist/modules/execution/StepDecisionController.d.ts.map +1 -1
- package/dist/modules/execution/StepDecisionController.js +30 -0
- package/dist/modules/execution/StepDecisionController.js.map +1 -1
- package/dist/modules/execution/policy-types.d.ts +14 -1
- package/dist/modules/execution/policy-types.d.ts.map +1 -1
- package/dist/modules/execution/prReport.commands.d.ts +54 -0
- package/dist/modules/execution/prReport.commands.d.ts.map +1 -0
- package/dist/modules/execution/prReport.commands.js +316 -0
- package/dist/modules/execution/prReport.commands.js.map +1 -0
- package/dist/modules/execution/prReport.environments.d.ts +11 -0
- package/dist/modules/execution/prReport.environments.d.ts.map +1 -1
- package/dist/modules/execution/prReport.environments.js +9 -1
- package/dist/modules/execution/prReport.environments.js.map +1 -1
- package/dist/modules/execution/prReport.logic.d.ts.map +1 -1
- package/dist/modules/execution/prReport.logic.js +23 -2
- package/dist/modules/execution/prReport.logic.js.map +1 -1
- package/dist/modules/execution/reproductionProof.logic.js +1 -1
- package/dist/modules/execution/reproductionProof.logic.js.map +1 -1
- package/dist/modules/execution/retry.logic.d.ts +3 -2
- package/dist/modules/execution/retry.logic.d.ts.map +1 -1
- package/dist/modules/execution/retry.logic.js +14 -2
- package/dist/modules/execution/retry.logic.js.map +1 -1
- package/dist/modules/execution/runMode.logic.d.ts +78 -0
- package/dist/modules/execution/runMode.logic.d.ts.map +1 -0
- package/dist/modules/execution/runMode.logic.js +78 -0
- package/dist/modules/execution/runMode.logic.js.map +1 -0
- package/dist/modules/execution/runStartOptions.d.ts +16 -1
- package/dist/modules/execution/runStartOptions.d.ts.map +1 -1
- package/dist/modules/merge/RiskPolicyService.d.ts.map +1 -1
- package/dist/modules/merge/RiskPolicyService.js +4 -0
- package/dist/modules/merge/RiskPolicyService.js.map +1 -1
- package/dist/modules/observability/LlmObservabilityService.d.ts +35 -4
- package/dist/modules/observability/LlmObservabilityService.d.ts.map +1 -1
- package/dist/modules/observability/LlmObservabilityService.js +43 -7
- package/dist/modules/observability/LlmObservabilityService.js.map +1 -1
- package/dist/modules/observability/observability.logic.d.ts +12 -2
- package/dist/modules/observability/observability.logic.d.ts.map +1 -1
- package/dist/modules/observability/observability.logic.js +31 -2
- package/dist/modules/observability/observability.logic.js.map +1 -1
- package/dist/modules/tutorial/TutorialProgressService.d.ts +51 -0
- package/dist/modules/tutorial/TutorialProgressService.d.ts.map +1 -0
- package/dist/modules/tutorial/TutorialProgressService.js +111 -0
- package/dist/modules/tutorial/TutorialProgressService.js.map +1 -0
- package/dist/modules/tutorial/TutorialTelemetryService.d.ts +47 -0
- package/dist/modules/tutorial/TutorialTelemetryService.d.ts.map +1 -0
- package/dist/modules/tutorial/TutorialTelemetryService.js +67 -0
- package/dist/modules/tutorial/TutorialTelemetryService.js.map +1 -0
- package/package.json +11 -11
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createStoreAgentContextGate, noopLogger, normalizeCallPhase, redactSecrets, runBestEffort, } from '@cat-factory/kernel';
|
|
1
|
+
import { createStoreAgentContextGate, noopLogger, normalizeCallPhase, priceRollupCells, redactSecrets, runBestEffort, } from '@cat-factory/kernel';
|
|
2
2
|
import { buildLlmMetricsExport, computeStoredPrompt } from './observability.logic.js';
|
|
3
3
|
import { buildRunTraceSpans } from './runTraceSpans.logic.js';
|
|
4
4
|
/**
|
|
@@ -45,7 +45,9 @@ export class LlmObservabilityService {
|
|
|
45
45
|
*/
|
|
46
46
|
bodiesEnabled;
|
|
47
47
|
log;
|
|
48
|
-
|
|
48
|
+
modelRates;
|
|
49
|
+
costCurrency;
|
|
50
|
+
constructor({ llmCallMetricRepository, idGenerator, clock, recordPrompts = true, traceSink, workspaceSettingsRepository, workspaceSettingsCache, logger, modelRates, costCurrency, }) {
|
|
49
51
|
this.repository = llmCallMetricRepository;
|
|
50
52
|
this.idGenerator = idGenerator;
|
|
51
53
|
this.clock = clock;
|
|
@@ -56,6 +58,15 @@ export class LlmObservabilityService {
|
|
|
56
58
|
cache: workspaceSettingsCache,
|
|
57
59
|
});
|
|
58
60
|
this.log = (logger ?? noopLogger).child({ service: 'llmObservability' });
|
|
61
|
+
this.modelRates = modelRates;
|
|
62
|
+
this.costCurrency = costCurrency;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The currency this service's rollup costs are denominated in, or null when nothing prices
|
|
66
|
+
* them. Read by the rollup consumers so the amount and its label come from ONE place.
|
|
67
|
+
*/
|
|
68
|
+
get rollupCurrency() {
|
|
69
|
+
return this.modelRates ? (this.costCurrency ?? null) : null;
|
|
59
70
|
}
|
|
60
71
|
/**
|
|
61
72
|
* Persist one metered call, assigning its id + timestamp and deriving the overhead.
|
|
@@ -204,9 +215,21 @@ export class LlmObservabilityService {
|
|
|
204
215
|
listByExecution(workspaceId, executionId, limit = DEFAULT_LIST_LIMIT) {
|
|
205
216
|
return this.repository.listByExecution(workspaceId, executionId, limit);
|
|
206
217
|
}
|
|
207
|
-
/**
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Per-`(agentKind, phase)` aggregates for a run, PRICED — the board step rollups and the
|
|
220
|
+
* per-phase burn breakdown.
|
|
221
|
+
*
|
|
222
|
+
* The store groups one grain finer (it also splits by `(provider, model)`), because cost is a
|
|
223
|
+
* function of the model and can only be computed while the model is still attached. This is
|
|
224
|
+
* the ONE place that fold happens, so the board rollup and the debug overview cannot report
|
|
225
|
+
* different money for the same run.
|
|
226
|
+
*/
|
|
227
|
+
async summarizeByExecution(workspaceId, executionId) {
|
|
228
|
+
const cells = await this.repository.summarizeByExecution(workspaceId, executionId);
|
|
229
|
+
// No rates wired ⇒ collapse the model dimension anyway, so every consumer sees the same
|
|
230
|
+
// `(agentKind, phase)` shape regardless of whether this deployment can price it. The cost
|
|
231
|
+
// stays null, which says "not priced here" rather than "cost nothing".
|
|
232
|
+
return priceRollupCells(cells, this.modelRates);
|
|
210
233
|
}
|
|
211
234
|
/**
|
|
212
235
|
* Build the LLM-friendly export for a run: a self-describing JSON bundle (totals +
|
|
@@ -214,8 +237,21 @@ export class LlmObservabilityService {
|
|
|
214
237
|
* model for analysis. Stamped with the service clock.
|
|
215
238
|
*/
|
|
216
239
|
async exportForExecution(workspaceId, executionId) {
|
|
217
|
-
|
|
218
|
-
|
|
240
|
+
// ONE row past the cap, so the bundle can SAY it is a slice instead of presenting the
|
|
241
|
+
// newest 1000 calls as the whole run. A separate COUNT would be a second query for one
|
|
242
|
+
// boolean, and inferring it from `calls.length === limit` guesses wrong on the run whose
|
|
243
|
+
// call count lands exactly on the cap.
|
|
244
|
+
const fetched = await this.listByExecution(workspaceId, executionId, DEFAULT_LIST_LIMIT + 1);
|
|
245
|
+
const truncated = fetched.length > DEFAULT_LIST_LIMIT;
|
|
246
|
+
const calls = truncated ? fetched.slice(0, DEFAULT_LIST_LIMIT) : fetched;
|
|
247
|
+
// Priced from the SAME table the rollups use. The export costs each call individually
|
|
248
|
+
// (it holds the rows), which is strictly finer than the rollup's per-cell arithmetic and
|
|
249
|
+
// agrees with it: both price a class at its own tier. A truncated bundle prices nothing:
|
|
250
|
+
// a slice's sum quoted as a run's cost is the failure the null rule exists to prevent.
|
|
251
|
+
return buildLlmMetricsExport(executionId, calls, this.clock.now(), {
|
|
252
|
+
rates: this.modelRates,
|
|
253
|
+
truncated,
|
|
254
|
+
});
|
|
219
255
|
}
|
|
220
256
|
}
|
|
221
257
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LlmObservabilityService.js","sourceRoot":"","sources":["../../../src/modules/observability/LlmObservabilityService.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,2BAA2B,EAC3B,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,aAAa,GACd,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"LlmObservabilityService.js","sourceRoot":"","sources":["../../../src/modules/observability/LlmObservabilityService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type LlmCallMetric } from '@cat-factory/kernel';
|
|
1
|
+
import { type LlmCallMetric, type LlmRateResolver } from '@cat-factory/kernel';
|
|
2
2
|
import type { LlmMetricsExport } from '@cat-factory/contracts';
|
|
3
3
|
export type LlmCallOutcome = 'ok' | 'warning' | 'error';
|
|
4
4
|
/** Whether a finish reason is a (non-fatal) warning — output truncated or filtered. */
|
|
@@ -57,11 +57,21 @@ export declare function computeStoredPrompt(fullPromptText: string, prev: Prompt
|
|
|
57
57
|
* the input order, with `promptText` set to the full array and `promptPrefixCount` 0.
|
|
58
58
|
*/
|
|
59
59
|
export declare function reconstructPrompts(calls: LlmCallMetric[]): LlmCallMetric[];
|
|
60
|
+
/** What the caller knows about the bundle that the stored calls alone cannot say. */
|
|
61
|
+
export interface LlmMetricsExportOptions {
|
|
62
|
+
/** Rates for pricing each call. Absent ⇒ every `costEstimate` is null (nothing prices them). */
|
|
63
|
+
rates?: LlmRateResolver;
|
|
64
|
+
/**
|
|
65
|
+
* The caller hit its row cap, so `storedCalls` is a SLICE of the run. Costs then decline to
|
|
66
|
+
* answer rather than reporting the slice's sum as the run's.
|
|
67
|
+
*/
|
|
68
|
+
truncated?: boolean;
|
|
69
|
+
}
|
|
60
70
|
/**
|
|
61
71
|
* Build the LLM-friendly export bundle for a run from its recorded calls: a
|
|
62
72
|
* self-describing JSON document (totals + per-agent insights + every call, with
|
|
63
73
|
* derived ratios precomputed) meant to be handed straight to a model for analysis.
|
|
64
74
|
* Pure so it is unit-testable; `generatedAt` is injected (no clock here).
|
|
65
75
|
*/
|
|
66
|
-
export declare function buildLlmMetricsExport(executionId: string, storedCalls: LlmCallMetric[], generatedAt: number): LlmMetricsExport;
|
|
76
|
+
export declare function buildLlmMetricsExport(executionId: string, storedCalls: LlmCallMetric[], generatedAt: number, options?: LlmMetricsExportOptions): LlmMetricsExport;
|
|
67
77
|
//# sourceMappingURL=observability.logic.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observability.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/observability/observability.logic.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"observability.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/observability/observability.logic.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,eAAe,EAErB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAoB,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAMhF,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvD,uFAAuF;AACvF,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAI1E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,cAAc,CAAC,GAAG,cAAc,CAI/F;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,oBAAoB,EAAE,MAAM,EAC5B,eAAe,EAAE,MAAM,GAAG,IAAI,GAC7B,MAAM,GAAG,IAAI,CAGf;AAED,+FAA+F;AAC/F,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5F;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,eAAe,EAAE,MAAM,EACvB,gBAAgB,EAAE,MAAM,EACxB,YAAY,EAAE,MAAM,GACnB,MAAM,GAAG,IAAI,CAIf;AAkBD,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAA;IACpB,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS/C;AAWD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,cAAc,GAAG,IAAI,GAC1B,YAAY,CAkBd;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAgC1E;AAED,qFAAqF;AACrF,MAAM,WAAW,uBAAuB;IACtC,gGAAgG;IAChG,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,aAAa,EAAE,EAC5B,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,uBAA4B,GACpC,gBAAgB,CAuElB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LLM_WARNING_FINISH_REASONS } from '@cat-factory/kernel';
|
|
1
|
+
import { LLM_WARNING_FINISH_REASONS, costOfTokenClasses, } from '@cat-factory/kernel';
|
|
2
2
|
/** Whether a finish reason is a (non-fatal) warning — output truncated or filtered. */
|
|
3
3
|
export function isWarningFinishReason(finishReason) {
|
|
4
4
|
return (finishReason != null && LLM_WARNING_FINISH_REASONS.includes(finishReason));
|
|
@@ -134,7 +134,8 @@ export function reconstructPrompts(calls) {
|
|
|
134
134
|
* derived ratios precomputed) meant to be handed straight to a model for analysis.
|
|
135
135
|
* Pure so it is unit-testable; `generatedAt` is injected (no clock here).
|
|
136
136
|
*/
|
|
137
|
-
export function buildLlmMetricsExport(executionId, storedCalls, generatedAt) {
|
|
137
|
+
export function buildLlmMetricsExport(executionId, storedCalls, generatedAt, options = {}) {
|
|
138
|
+
const { rates, truncated = false } = options;
|
|
138
139
|
// The export is a self-contained analysis bundle, so rebuild each call's full
|
|
139
140
|
// prompt from the stored deltas before assembling it.
|
|
140
141
|
const calls = reconstructPrompts(storedCalls);
|
|
@@ -172,6 +173,7 @@ export function buildLlmMetricsExport(executionId, storedCalls, generatedAt) {
|
|
|
172
173
|
transportOverheadRatio: transportOverheadRatio(upstreamMs, overheadMs),
|
|
173
174
|
errors: kindCalls.filter((c) => !c.ok).length,
|
|
174
175
|
warnings: kindCalls.filter((c) => c.ok && isWarningFinishReason(c.finishReason)).length,
|
|
176
|
+
costEstimate: costOfCalls(kindCalls, rates, truncated),
|
|
175
177
|
};
|
|
176
178
|
});
|
|
177
179
|
const upstreamMs = sum(calls, (c) => c.upstreamMs);
|
|
@@ -197,11 +199,38 @@ export function buildLlmMetricsExport(executionId, storedCalls, generatedAt) {
|
|
|
197
199
|
errors: calls.filter((c) => !c.ok).length,
|
|
198
200
|
warnings: calls.filter((c) => c.ok && isWarningFinishReason(c.finishReason)).length,
|
|
199
201
|
truncatedCalls: calls.filter((c) => c.finishReason === 'length').length,
|
|
202
|
+
costEstimate: costOfCalls(calls, rates, truncated),
|
|
200
203
|
},
|
|
201
204
|
insights,
|
|
202
205
|
calls,
|
|
206
|
+
truncated,
|
|
203
207
|
};
|
|
204
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Price a set of calls, EACH at its own model's rates and each input class at its own tier.
|
|
211
|
+
*
|
|
212
|
+
* Per call rather than per summed group because one agent kind's calls are routinely served by
|
|
213
|
+
* more than one model — a harness CLI answers some of its own turns with a cheaper one — so a
|
|
214
|
+
* group total priced at any single model's rate would be wrong for every group but the uniform
|
|
215
|
+
* ones. The arithmetic is kernel's `costOfTokenClasses`, the same one the rollup and the
|
|
216
|
+
* ledger use, so the export cannot come to disagree with the surfaces beside it.
|
|
217
|
+
*
|
|
218
|
+
* Returns null in three cases, all of them "this number would be a lie": nothing prices these
|
|
219
|
+
* calls, ANY call's model has no rate, or the call list is a TRUNCATED slice of the run. Each
|
|
220
|
+
* would otherwise produce a smaller number that still reads as a complete total.
|
|
221
|
+
*/
|
|
222
|
+
function costOfCalls(calls, rates, truncated) {
|
|
223
|
+
if (!rates || truncated)
|
|
224
|
+
return null;
|
|
225
|
+
let total = 0;
|
|
226
|
+
for (const call of calls) {
|
|
227
|
+
const r = rates(call.provider, call.model);
|
|
228
|
+
if (!r)
|
|
229
|
+
return null;
|
|
230
|
+
total += costOfTokenClasses(r, call);
|
|
231
|
+
}
|
|
232
|
+
return total;
|
|
233
|
+
}
|
|
205
234
|
function sum(items, pick) {
|
|
206
235
|
return items.reduce((acc, item) => acc + pick(item), 0);
|
|
207
236
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observability.logic.js","sourceRoot":"","sources":["../../../src/modules/observability/observability.logic.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"observability.logic.js","sourceRoot":"","sources":["../../../src/modules/observability/observability.logic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { TutorialProgress, UpdateTutorialProgressInput } from '@cat-factory/contracts';
|
|
2
|
+
import type { TutorialProgressRepository } from '@cat-factory/kernel';
|
|
3
|
+
export interface TutorialProgressServiceDependencies {
|
|
4
|
+
tutorialProgressRepository: TutorialProgressRepository;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Per-user tutorial progress: what the SPA's browser-persisted store mirrors to, so a person's
|
|
8
|
+
* walkthrough history follows them rather than their browser profile.
|
|
9
|
+
*
|
|
10
|
+
* Reads fall back to the defaults when a user has never saved a row, exactly like the sibling
|
|
11
|
+
* `UserSettingsService`. Writes are where the two differ, and the difference is the whole point.
|
|
12
|
+
*/
|
|
13
|
+
export declare class TutorialProgressService {
|
|
14
|
+
private readonly deps;
|
|
15
|
+
constructor(deps: TutorialProgressServiceDependencies);
|
|
16
|
+
get(userId: string): Promise<TutorialProgress>;
|
|
17
|
+
/**
|
|
18
|
+
* MERGE, never replace, and that is this service's only real design decision.
|
|
19
|
+
*
|
|
20
|
+
* Both id lists are grow-only sets: finishing a walkthrough and spending a contextual offer are
|
|
21
|
+
* facts that happened, and no client is ever right to un-say one. Two browsers signed in as the
|
|
22
|
+
* same person both hold a full local copy and both write it back, so a last-writer-wins replace
|
|
23
|
+
* silently drops whatever the other one had learned since they diverged — a completion vanishing
|
|
24
|
+
* from a laptop because the desktop wrote an older list.
|
|
25
|
+
*
|
|
26
|
+
* `decision` is the exception and takes the incoming value: it is a preference someone re-answers
|
|
27
|
+
* rather than an accumulating fact, so the newest answer is the right one. (An absent `decision`
|
|
28
|
+
* leaves the stored one alone; only an explicit `null` clears it, which is what a reset does.)
|
|
29
|
+
*
|
|
30
|
+
* **Why this load-apply-store is not rev-guarded, unlike every other one-JSON-blob row in the
|
|
31
|
+
* codebase** (the iterative-review stores carry a `rev` and a `compareAndSwap`). That rule is
|
|
32
|
+
* about rows where the row IS the data, and a lost update is therefore data LOSS. This row is a
|
|
33
|
+
* MIRROR: the browser-persisted store is authoritative, the SPA reads it and not this, and every
|
|
34
|
+
* push carries the client's whole local state rather than a delta. So two concurrent merges can
|
|
35
|
+
* still lose one writer's ids — a union is idempotent under RETRY, which is not the same as
|
|
36
|
+
* commutative under CONCURRENCY, and it would be wrong to claim otherwise — but the loss is
|
|
37
|
+
* repaired rather than permanent, because the RESPONSE is the merged row and the client
|
|
38
|
+
* reconciles against it, re-pushing when the answer is missing something it holds
|
|
39
|
+
* (`useTutorialServer.push`). That closure is what makes the missing guard a considered
|
|
40
|
+
* trade rather than the bug the rule exists to prevent; without it "the next write will fix it"
|
|
41
|
+
* would be a hope about a write that may never come.
|
|
42
|
+
*/
|
|
43
|
+
merge(userId: string, input: UpdateTutorialProgressInput): Promise<TutorialProgress>;
|
|
44
|
+
/**
|
|
45
|
+
* Forget everything: the "Reset progress" action, whose whole purpose is to restore the
|
|
46
|
+
* first-launch experience. Returns the defaults, so a caller renders the same state a user who
|
|
47
|
+
* has never touched the tutorial sees.
|
|
48
|
+
*/
|
|
49
|
+
reset(userId: string): Promise<TutorialProgress>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=TutorialProgressService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TutorialProgressService.d.ts","sourceRoot":"","sources":["../../../src/modules/tutorial/TutorialProgressService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAA;AAE3F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAA;AAErE,MAAM,WAAW,mCAAmC;IAClD,0BAA0B,EAAE,0BAA0B,CAAA;CACvD;AA8CD;;;;;;GAMG;AACH,qBAAa,uBAAuB;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,mCAAmC,EAAI;IAEpE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAEnD;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAazF;IAED;;;;OAIG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAGrD;CACF"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { DEFAULT_TUTORIAL_PROGRESS, MAX_TUTORIAL_TOUR_IDS } from '@cat-factory/contracts';
|
|
2
|
+
import { ValidationError } from '@cat-factory/kernel';
|
|
3
|
+
/**
|
|
4
|
+
* Union of two id lists, order-preserving and deduplicated: the existing ids keep their order and
|
|
5
|
+
* anything new is appended.
|
|
6
|
+
*
|
|
7
|
+
* Extracted rather than inlined twice because getting it wrong in one of the two sets would be
|
|
8
|
+
* invisible: both are grow-only sets, so a `replace` bug shows up only as a completion quietly
|
|
9
|
+
* disappearing, on a second device, some time later.
|
|
10
|
+
*/
|
|
11
|
+
function union(existing, incoming) {
|
|
12
|
+
if (incoming === undefined)
|
|
13
|
+
return [...existing];
|
|
14
|
+
const seen = new Set(existing);
|
|
15
|
+
const merged = [...existing];
|
|
16
|
+
for (const id of incoming) {
|
|
17
|
+
if (!seen.has(id)) {
|
|
18
|
+
seen.add(id);
|
|
19
|
+
merged.push(id);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return merged;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Refuse a merge whose RESULT would exceed the per-list ceiling.
|
|
26
|
+
*
|
|
27
|
+
* The wire schema bounds each REQUEST at {@link MAX_TUTORIAL_TOUR_IDS}, which bounds nothing about
|
|
28
|
+
* the stored row: a union of capped requests is uncapped, so N writes of N distinct ids grow the
|
|
29
|
+
* row without limit. That row is not cheap to grow, either — it rides EVERY workspace snapshot for
|
|
30
|
+
* this user, so it is paid on every board load, not just on the write.
|
|
31
|
+
*
|
|
32
|
+
* Refused rather than truncated, matching the schema's own promise. A real catalog is a few dozen
|
|
33
|
+
* ids, so a merge that crosses this is a client bug or abuse, and silently dropping the tail would
|
|
34
|
+
* make the first look like the second's absence.
|
|
35
|
+
*/
|
|
36
|
+
function assertWithinCap(field, merged) {
|
|
37
|
+
if (merged.length > MAX_TUTORIAL_TOUR_IDS) {
|
|
38
|
+
throw new ValidationError(`Too many tutorial tour ids in ${field}`, {
|
|
39
|
+
reason: 'tutorial_progress_too_large',
|
|
40
|
+
field,
|
|
41
|
+
limit: MAX_TUTORIAL_TOUR_IDS,
|
|
42
|
+
merged: merged.length,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Per-user tutorial progress: what the SPA's browser-persisted store mirrors to, so a person's
|
|
48
|
+
* walkthrough history follows them rather than their browser profile.
|
|
49
|
+
*
|
|
50
|
+
* Reads fall back to the defaults when a user has never saved a row, exactly like the sibling
|
|
51
|
+
* `UserSettingsService`. Writes are where the two differ, and the difference is the whole point.
|
|
52
|
+
*/
|
|
53
|
+
export class TutorialProgressService {
|
|
54
|
+
deps;
|
|
55
|
+
constructor(deps) {
|
|
56
|
+
this.deps = deps;
|
|
57
|
+
}
|
|
58
|
+
async get(userId) {
|
|
59
|
+
return (await this.deps.tutorialProgressRepository.get(userId)) ?? DEFAULT_TUTORIAL_PROGRESS;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* MERGE, never replace, and that is this service's only real design decision.
|
|
63
|
+
*
|
|
64
|
+
* Both id lists are grow-only sets: finishing a walkthrough and spending a contextual offer are
|
|
65
|
+
* facts that happened, and no client is ever right to un-say one. Two browsers signed in as the
|
|
66
|
+
* same person both hold a full local copy and both write it back, so a last-writer-wins replace
|
|
67
|
+
* silently drops whatever the other one had learned since they diverged — a completion vanishing
|
|
68
|
+
* from a laptop because the desktop wrote an older list.
|
|
69
|
+
*
|
|
70
|
+
* `decision` is the exception and takes the incoming value: it is a preference someone re-answers
|
|
71
|
+
* rather than an accumulating fact, so the newest answer is the right one. (An absent `decision`
|
|
72
|
+
* leaves the stored one alone; only an explicit `null` clears it, which is what a reset does.)
|
|
73
|
+
*
|
|
74
|
+
* **Why this load-apply-store is not rev-guarded, unlike every other one-JSON-blob row in the
|
|
75
|
+
* codebase** (the iterative-review stores carry a `rev` and a `compareAndSwap`). That rule is
|
|
76
|
+
* about rows where the row IS the data, and a lost update is therefore data LOSS. This row is a
|
|
77
|
+
* MIRROR: the browser-persisted store is authoritative, the SPA reads it and not this, and every
|
|
78
|
+
* push carries the client's whole local state rather than a delta. So two concurrent merges can
|
|
79
|
+
* still lose one writer's ids — a union is idempotent under RETRY, which is not the same as
|
|
80
|
+
* commutative under CONCURRENCY, and it would be wrong to claim otherwise — but the loss is
|
|
81
|
+
* repaired rather than permanent, because the RESPONSE is the merged row and the client
|
|
82
|
+
* reconciles against it, re-pushing when the answer is missing something it holds
|
|
83
|
+
* (`useTutorialServer.push`). That closure is what makes the missing guard a considered
|
|
84
|
+
* trade rather than the bug the rule exists to prevent; without it "the next write will fix it"
|
|
85
|
+
* would be a hope about a write that may never come.
|
|
86
|
+
*/
|
|
87
|
+
async merge(userId, input) {
|
|
88
|
+
const current = await this.get(userId);
|
|
89
|
+
const completedTourIds = union(current.completedTourIds, input.completedTourIds);
|
|
90
|
+
const nudgedTourIds = union(current.nudgedTourIds, input.nudgedTourIds);
|
|
91
|
+
assertWithinCap('completedTourIds', completedTourIds);
|
|
92
|
+
assertWithinCap('nudgedTourIds', nudgedTourIds);
|
|
93
|
+
const next = {
|
|
94
|
+
decision: 'decision' in input ? (input.decision ?? null) : current.decision,
|
|
95
|
+
completedTourIds,
|
|
96
|
+
nudgedTourIds,
|
|
97
|
+
};
|
|
98
|
+
await this.deps.tutorialProgressRepository.upsert(userId, next);
|
|
99
|
+
return next;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Forget everything: the "Reset progress" action, whose whole purpose is to restore the
|
|
103
|
+
* first-launch experience. Returns the defaults, so a caller renders the same state a user who
|
|
104
|
+
* has never touched the tutorial sees.
|
|
105
|
+
*/
|
|
106
|
+
async reset(userId) {
|
|
107
|
+
await this.deps.tutorialProgressRepository.remove(userId);
|
|
108
|
+
return DEFAULT_TUTORIAL_PROGRESS;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=TutorialProgressService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TutorialProgressService.js","sourceRoot":"","sources":["../../../src/modules/tutorial/TutorialProgressService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAEzF,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAOrD;;;;;;;GAOG;AACH,SAAS,KAAK,CAAC,QAA2B,EAAE,QAAuC;IACjF,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAA;IAChD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC9B,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAA;IAC5B,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACZ,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACjB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,eAAe,CAAC,KAA2C,EAAE,MAAgB;IACpF,IAAI,MAAM,CAAC,MAAM,GAAG,qBAAqB,EAAE,CAAC;QAC1C,MAAM,IAAI,eAAe,CAAC,iCAAiC,KAAK,EAAE,EAAE;YAClE,MAAM,EAAE,6BAA6B;YACrC,KAAK;YACL,KAAK,EAAE,qBAAqB;YAC5B,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC,CAAA;IACJ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,uBAAuB;IACL,IAAI;IAAjC,YAA6B,IAAyC;oBAAzC,IAAI;IAAwC,CAAC;IAE1E,KAAK,CAAC,GAAG,CAAC,MAAc;QACtB,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,0BAA0B,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,yBAAyB,CAAA;IAC9F,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,KAAkC;QAC5D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACtC,MAAM,gBAAgB,GAAG,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAA;QAChF,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,aAAa,CAAC,CAAA;QACvE,eAAe,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,CAAA;QACrD,eAAe,CAAC,eAAe,EAAE,aAAa,CAAC,CAAA;QAC/C,MAAM,IAAI,GAAqB;YAC7B,QAAQ,EAAE,UAAU,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ;YAC3E,gBAAgB;YAChB,aAAa;SACd,CAAA;QACD,MAAM,IAAI,CAAC,IAAI,CAAC,0BAA0B,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;QAC/D,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,MAAc;QACxB,MAAM,IAAI,CAAC,IAAI,CAAC,0BAA0B,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACzD,OAAO,yBAAyB,CAAA;IAClC,CAAC;CACF"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { TutorialEvent } from '@cat-factory/contracts';
|
|
2
|
+
import type { Logger, OperationalMetrics } from '@cat-factory/kernel';
|
|
3
|
+
/**
|
|
4
|
+
* How many DISTINCT tour ids one process (one isolate, on the Worker) will report as its own
|
|
5
|
+
* dimension value before folding the rest onto {@link OTHER_TOUR}.
|
|
6
|
+
*
|
|
7
|
+
* Sized well above any real catalog — the built-ins ship 13, and a deployment contributing its own
|
|
8
|
+
* is still counted in tens — so a healthy deployment never reaches it and an overflow is a genuine
|
|
9
|
+
* signal rather than a routine truncation.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MAX_DISTINCT_TOURS = 64;
|
|
12
|
+
/** The bucket every id past the cap is counted under. Visible in the data, never dropped. */
|
|
13
|
+
export declare const OTHER_TOUR = "other";
|
|
14
|
+
export interface TutorialTelemetryServiceDependencies {
|
|
15
|
+
metrics: OperationalMetrics;
|
|
16
|
+
logger?: Logger;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Counts in-app tutorial funnel events, with the one thing a browser-supplied metric dimension
|
|
20
|
+
* cannot be trusted to be: BOUNDED.
|
|
21
|
+
*
|
|
22
|
+
* Every distinct dimension value is its own time series in the operator's backend, so an
|
|
23
|
+
* unbounded one costs them money and eventually gets the whole series dropped. The wire schema
|
|
24
|
+
* already constrains an id's SHAPE, which stops junk but not volume: a buggy or hostile client can
|
|
25
|
+
* still emit an unlimited number of well-formed ids. So this holds the set it has already reported
|
|
26
|
+
* and folds everything past {@link MAX_DISTINCT_TOURS} onto {@link OTHER_TOUR}.
|
|
27
|
+
*
|
|
28
|
+
* The overflow is REPORTED, not silently absorbed, in both the ways that matter: the `other` series
|
|
29
|
+
* exists in the data (so a reader can see that something was folded rather than concluding the tail
|
|
30
|
+
* was never sent), and the first overflow logs once, naming the cap. A cap nobody can see reads
|
|
31
|
+
* exactly like complete coverage.
|
|
32
|
+
*
|
|
33
|
+
* Per process rather than shared, matching the collector it feeds: a counter is accumulated and
|
|
34
|
+
* flushed per process on Node and per ISOLATE on the Worker, so a cap held anywhere else would be
|
|
35
|
+
* a different scope from the thing it is protecting.
|
|
36
|
+
*/
|
|
37
|
+
export declare class TutorialTelemetryService {
|
|
38
|
+
private readonly deps;
|
|
39
|
+
private readonly log;
|
|
40
|
+
private readonly seen;
|
|
41
|
+
private overflowReported;
|
|
42
|
+
constructor(deps: TutorialTelemetryServiceDependencies);
|
|
43
|
+
record(event: TutorialEvent, tourId: string): void;
|
|
44
|
+
/** The bounded dimension value for a tour id: itself, or the overflow bucket. */
|
|
45
|
+
private dimension;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=TutorialTelemetryService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TutorialTelemetryService.d.ts","sourceRoot":"","sources":["../../../src/modules/tutorial/TutorialTelemetryService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAsB,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAGzF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,KAAK,CAAA;AAEpC,6FAA6F;AAC7F,eAAO,MAAM,UAAU,UAAU,CAAA;AAQjC,MAAM,WAAW,oCAAoC;IACnD,OAAO,EAAE,kBAAkB,CAAA;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,wBAAwB;IAKvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoB;IACzC,OAAO,CAAC,gBAAgB,CAAQ;IAEhC,YAA6B,IAAI,EAAE,oCAAoC,EAEtE;IAED,MAAM,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAEjD;IAED,iFAAiF;IACjF,OAAO,CAAC,SAAS;CAelB"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { noopLogger } from '@cat-factory/kernel';
|
|
2
|
+
/**
|
|
3
|
+
* How many DISTINCT tour ids one process (one isolate, on the Worker) will report as its own
|
|
4
|
+
* dimension value before folding the rest onto {@link OTHER_TOUR}.
|
|
5
|
+
*
|
|
6
|
+
* Sized well above any real catalog — the built-ins ship 13, and a deployment contributing its own
|
|
7
|
+
* is still counted in tens — so a healthy deployment never reaches it and an overflow is a genuine
|
|
8
|
+
* signal rather than a routine truncation.
|
|
9
|
+
*/
|
|
10
|
+
export const MAX_DISTINCT_TOURS = 64;
|
|
11
|
+
/** The bucket every id past the cap is counted under. Visible in the data, never dropped. */
|
|
12
|
+
export const OTHER_TOUR = 'other';
|
|
13
|
+
const COUNTER = {
|
|
14
|
+
started: 'tutorial.tour_started',
|
|
15
|
+
completed: 'tutorial.tour_completed',
|
|
16
|
+
abandoned: 'tutorial.tour_abandoned',
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Counts in-app tutorial funnel events, with the one thing a browser-supplied metric dimension
|
|
20
|
+
* cannot be trusted to be: BOUNDED.
|
|
21
|
+
*
|
|
22
|
+
* Every distinct dimension value is its own time series in the operator's backend, so an
|
|
23
|
+
* unbounded one costs them money and eventually gets the whole series dropped. The wire schema
|
|
24
|
+
* already constrains an id's SHAPE, which stops junk but not volume: a buggy or hostile client can
|
|
25
|
+
* still emit an unlimited number of well-formed ids. So this holds the set it has already reported
|
|
26
|
+
* and folds everything past {@link MAX_DISTINCT_TOURS} onto {@link OTHER_TOUR}.
|
|
27
|
+
*
|
|
28
|
+
* The overflow is REPORTED, not silently absorbed, in both the ways that matter: the `other` series
|
|
29
|
+
* exists in the data (so a reader can see that something was folded rather than concluding the tail
|
|
30
|
+
* was never sent), and the first overflow logs once, naming the cap. A cap nobody can see reads
|
|
31
|
+
* exactly like complete coverage.
|
|
32
|
+
*
|
|
33
|
+
* Per process rather than shared, matching the collector it feeds: a counter is accumulated and
|
|
34
|
+
* flushed per process on Node and per ISOLATE on the Worker, so a cap held anywhere else would be
|
|
35
|
+
* a different scope from the thing it is protecting.
|
|
36
|
+
*/
|
|
37
|
+
export class TutorialTelemetryService {
|
|
38
|
+
deps;
|
|
39
|
+
log;
|
|
40
|
+
seen = new Set();
|
|
41
|
+
overflowReported = false;
|
|
42
|
+
constructor(deps) {
|
|
43
|
+
this.deps = deps;
|
|
44
|
+
this.log = deps.logger ?? noopLogger;
|
|
45
|
+
}
|
|
46
|
+
record(event, tourId) {
|
|
47
|
+
this.deps.metrics.increment(COUNTER[event], { tour: this.dimension(tourId) });
|
|
48
|
+
}
|
|
49
|
+
/** The bounded dimension value for a tour id: itself, or the overflow bucket. */
|
|
50
|
+
dimension(tourId) {
|
|
51
|
+
if (this.seen.has(tourId))
|
|
52
|
+
return tourId;
|
|
53
|
+
if (this.seen.size < MAX_DISTINCT_TOURS) {
|
|
54
|
+
this.seen.add(tourId);
|
|
55
|
+
return tourId;
|
|
56
|
+
}
|
|
57
|
+
if (!this.overflowReported) {
|
|
58
|
+
this.overflowReported = true;
|
|
59
|
+
this.log.warn('Tutorial event dimension cap reached; further tours counted as "other"', {
|
|
60
|
+
limit: MAX_DISTINCT_TOURS,
|
|
61
|
+
tourId,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return OTHER_TOUR;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=TutorialTelemetryService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TutorialTelemetryService.js","sourceRoot":"","sources":["../../../src/modules/tutorial/TutorialTelemetryService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAEhD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAA;AAEpC,6FAA6F;AAC7F,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAA;AAEjC,MAAM,OAAO,GAA8C;IACzD,OAAO,EAAE,uBAAuB;IAChC,SAAS,EAAE,yBAAyB;IACpC,SAAS,EAAE,yBAAyB;CACrC,CAAA;AAOD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,wBAAwB;IAKN,IAAI;IAJhB,GAAG,CAAQ;IACX,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IACjC,gBAAgB,GAAG,KAAK,CAAA;IAEhC,YAA6B,IAA0C;oBAA1C,IAAI;QAC/B,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,UAAU,CAAA;IACtC,CAAC;IAED,MAAM,CAAC,KAAoB,EAAE,MAAc;QACzC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC/E,CAAC;IAED,iFAAiF;IACzE,SAAS,CAAC,MAAc;QAC9B,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAA;QACxC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,kBAAkB,EAAE,CAAC;YACxC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACrB,OAAO,MAAM,CAAA;QACf,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;YAC5B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,wEAAwE,EAAE;gBACtF,KAAK,EAAE,kBAAkB;gBACzB,MAAM;aACP,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,UAAU,CAAA;IACnB,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/orchestration",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.195.0",
|
|
4
4
|
"description": "Delivery-workflow engine for the Agent Architecture Board (execution, bootstrap, pipelines, board, boardScan, requirements, and composition root).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,20 +25,20 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"ai": "^7.0.47",
|
|
28
|
-
"@cat-factory/agents": "0.
|
|
29
|
-
"@cat-factory/caching": "0.14.
|
|
30
|
-
"@cat-factory/contracts": "0.
|
|
31
|
-
"@cat-factory/integrations": "0.122.
|
|
32
|
-
"@cat-factory/kernel": "0.
|
|
33
|
-
"@cat-factory/prompt-fragments": "0.15.
|
|
34
|
-
"@cat-factory/sandbox": "0.11.
|
|
35
|
-
"@cat-factory/spend": "0.
|
|
36
|
-
"@cat-factory/workspaces": "0.21.
|
|
28
|
+
"@cat-factory/agents": "0.108.0",
|
|
29
|
+
"@cat-factory/caching": "0.14.2",
|
|
30
|
+
"@cat-factory/contracts": "0.224.0",
|
|
31
|
+
"@cat-factory/integrations": "0.122.1",
|
|
32
|
+
"@cat-factory/kernel": "0.226.0",
|
|
33
|
+
"@cat-factory/prompt-fragments": "0.15.49",
|
|
34
|
+
"@cat-factory/sandbox": "0.11.43",
|
|
35
|
+
"@cat-factory/spend": "0.14.0",
|
|
36
|
+
"@cat-factory/workspaces": "0.21.36"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"typescript": "7.0.2",
|
|
40
40
|
"vitest": "^4.1.10",
|
|
41
|
-
"@cat-factory/sandbox-fixtures": "0.7.
|
|
41
|
+
"@cat-factory/sandbox-fixtures": "0.7.257"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc -b tsconfig.build.json",
|