@qcobro/common 1.12.4 → 1.14.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.
Files changed (89) hide show
  1. package/dist/billing/evaluate.d.ts +36 -0
  2. package/dist/billing/evaluate.d.ts.map +1 -0
  3. package/dist/billing/evaluate.js +136 -0
  4. package/dist/billing/evaluate.js.map +1 -0
  5. package/dist/billing/index.d.ts +7 -0
  6. package/dist/billing/index.d.ts.map +1 -0
  7. package/dist/billing/index.js +7 -0
  8. package/dist/billing/index.js.map +1 -0
  9. package/dist/billing/ledger.d.ts +70 -0
  10. package/dist/billing/ledger.d.ts.map +1 -0
  11. package/dist/billing/ledger.js +59 -0
  12. package/dist/billing/ledger.js.map +1 -0
  13. package/dist/billing/money.d.ts +26 -0
  14. package/dist/billing/money.d.ts.map +1 -0
  15. package/dist/billing/money.js +58 -0
  16. package/dist/billing/money.js.map +1 -0
  17. package/dist/billing/pricing.d.ts +58 -0
  18. package/dist/billing/pricing.d.ts.map +1 -0
  19. package/dist/billing/pricing.js +68 -0
  20. package/dist/billing/pricing.js.map +1 -0
  21. package/dist/billing/proration.d.ts +8 -0
  22. package/dist/billing/proration.d.ts.map +1 -0
  23. package/dist/billing/proration.js +17 -0
  24. package/dist/billing/proration.js.map +1 -0
  25. package/dist/billing/rates.d.ts +108 -0
  26. package/dist/billing/rates.d.ts.map +1 -0
  27. package/dist/billing/rates.js +56 -0
  28. package/dist/billing/rates.js.map +1 -0
  29. package/dist/bin/engineEval.d.ts +67 -0
  30. package/dist/bin/engineEval.d.ts.map +1 -0
  31. package/dist/bin/engineEval.js +290 -0
  32. package/dist/bin/engineEval.js.map +1 -0
  33. package/dist/config.d.ts +141 -1
  34. package/dist/config.d.ts.map +1 -1
  35. package/dist/config.js +70 -2
  36. package/dist/config.js.map +1 -1
  37. package/dist/evaluation/evaluate.d.ts +4 -0
  38. package/dist/evaluation/evaluate.d.ts.map +1 -0
  39. package/dist/evaluation/evaluate.js +504 -0
  40. package/dist/evaluation/evaluate.js.map +1 -0
  41. package/dist/evaluation/index.d.ts +3 -0
  42. package/dist/evaluation/index.d.ts.map +1 -0
  43. package/dist/evaluation/index.js +3 -0
  44. package/dist/evaluation/index.js.map +1 -0
  45. package/dist/evaluation/scorecard.d.ts +84 -0
  46. package/dist/evaluation/scorecard.d.ts.map +1 -0
  47. package/dist/evaluation/scorecard.js +29 -0
  48. package/dist/evaluation/scorecard.js.map +1 -0
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +2 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/schemas/billing.d.ts +74 -0
  54. package/dist/schemas/billing.d.ts.map +1 -0
  55. package/dist/schemas/billing.js +68 -0
  56. package/dist/schemas/billing.js.map +1 -0
  57. package/dist/schemas/engineEvents.d.ts +407 -0
  58. package/dist/schemas/engineEvents.d.ts.map +1 -0
  59. package/dist/schemas/engineEvents.js +179 -0
  60. package/dist/schemas/engineEvents.js.map +1 -0
  61. package/dist/schemas/index.d.ts +2 -0
  62. package/dist/schemas/index.d.ts.map +1 -1
  63. package/dist/schemas/index.js +2 -0
  64. package/dist/schemas/index.js.map +1 -1
  65. package/dist/types/billing.d.ts +134 -0
  66. package/dist/types/billing.d.ts.map +1 -0
  67. package/dist/types/billing.js +8 -0
  68. package/dist/types/billing.js.map +1 -0
  69. package/dist/types/engine.d.ts +13 -2
  70. package/dist/types/engine.d.ts.map +1 -1
  71. package/dist/types/engine.js +12 -1
  72. package/dist/types/engine.js.map +1 -1
  73. package/dist/types/index.d.ts +1 -0
  74. package/dist/types/index.d.ts.map +1 -1
  75. package/dist/types/index.js +1 -0
  76. package/dist/types/index.js.map +1 -1
  77. package/dist/utils/index.d.ts +2 -1
  78. package/dist/utils/index.d.ts.map +1 -1
  79. package/dist/utils/index.js +2 -1
  80. package/dist/utils/index.js.map +1 -1
  81. package/dist/utils/pacing.d.ts +17 -0
  82. package/dist/utils/pacing.d.ts.map +1 -0
  83. package/dist/utils/pacing.js +18 -0
  84. package/dist/utils/pacing.js.map +1 -0
  85. package/dist/utils/time.d.ts +16 -0
  86. package/dist/utils/time.d.ts.map +1 -1
  87. package/dist/utils/time.js +17 -0
  88. package/dist/utils/time.js.map +1 -1
  89. package/package.json +4 -1
@@ -0,0 +1,108 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The billable-meter catalog and per-meter rate schemas.
4
+ *
5
+ * The catalog is closed-world: exactly seven meters, one per dispatchable
6
+ * channel/mode. Message meters bill per message; voice meters bill answered
7
+ * duration under a telecom increment pair. Each meter kind has its own strict
8
+ * schema so a rate object with fields from the wrong kind (e.g. `increments`
9
+ * on `sms`) fails validation instead of being silently ignored.
10
+ *
11
+ * The two WhatsApp voice meters are reserved: they must be priced in every
12
+ * plan, but no dispatch path produces them until Fonoster ships that transport.
13
+ */
14
+ export declare const MESSAGE_METERS: readonly ["sms", "email", "whatsappMessage"];
15
+ export declare const VOICE_METERS: readonly ["voicePrerecorded", "voiceAi", "whatsappVoicePrerecorded", "whatsappVoiceAi"];
16
+ export declare const BILLING_METERS: readonly ["sms", "email", "whatsappMessage", "voicePrerecorded", "voiceAi", "whatsappVoicePrerecorded", "whatsappVoiceAi"];
17
+ export type MessageMeter = (typeof MESSAGE_METERS)[number];
18
+ export type VoiceMeter = (typeof VOICE_METERS)[number];
19
+ export type BillingMeter = (typeof BILLING_METERS)[number];
20
+ export declare const billingMeterSchema: z.ZodEnum<{
21
+ sms: "sms";
22
+ email: "email";
23
+ whatsappMessage: "whatsappMessage";
24
+ voicePrerecorded: "voicePrerecorded";
25
+ voiceAi: "voiceAi";
26
+ whatsappVoicePrerecorded: "whatsappVoicePrerecorded";
27
+ whatsappVoiceAi: "whatsappVoiceAi";
28
+ }>;
29
+ /**
30
+ * Telecom billing-increment notation `"initial/subsequent"` in seconds — the
31
+ * industry standard (15/15 rounds every call up to 15s blocks; 60/6 bills a
32
+ * full first minute then 6s steps).
33
+ */
34
+ export declare const incrementNotationSchema: z.ZodString;
35
+ /** A message meter's rate: decimal currency units per message. */
36
+ export declare const messageRateSchema: z.ZodObject<{
37
+ perMessage: z.ZodNumber;
38
+ }, z.core.$strict>;
39
+ export type MessageRate = z.infer<typeof messageRateSchema>;
40
+ /** A voice meter's rate: decimal currency units per minute + increment pair. */
41
+ export declare const voiceRateSchema: z.ZodObject<{
42
+ perMinute: z.ZodNumber;
43
+ increments: z.ZodString;
44
+ }, z.core.$strict>;
45
+ export type VoiceRate = z.infer<typeof voiceRateSchema>;
46
+ /** A complete rate card: all seven meters, each with its kind's schema. */
47
+ export declare const ratesSchema: z.ZodObject<{
48
+ sms: z.ZodObject<{
49
+ perMessage: z.ZodNumber;
50
+ }, z.core.$strict>;
51
+ email: z.ZodObject<{
52
+ perMessage: z.ZodNumber;
53
+ }, z.core.$strict>;
54
+ whatsappMessage: z.ZodObject<{
55
+ perMessage: z.ZodNumber;
56
+ }, z.core.$strict>;
57
+ voicePrerecorded: z.ZodObject<{
58
+ perMinute: z.ZodNumber;
59
+ increments: z.ZodString;
60
+ }, z.core.$strict>;
61
+ voiceAi: z.ZodObject<{
62
+ perMinute: z.ZodNumber;
63
+ increments: z.ZodString;
64
+ }, z.core.$strict>;
65
+ whatsappVoicePrerecorded: z.ZodObject<{
66
+ perMinute: z.ZodNumber;
67
+ increments: z.ZodString;
68
+ }, z.core.$strict>;
69
+ whatsappVoiceAi: z.ZodObject<{
70
+ perMinute: z.ZodNumber;
71
+ increments: z.ZodString;
72
+ }, z.core.$strict>;
73
+ }, z.core.$strict>;
74
+ export type Rates = z.infer<typeof ratesSchema>;
75
+ /**
76
+ * Per-workspace enterprise overrides: any subset of the rate card, validated
77
+ * with the same per-meter schemas. Stored on workspace billing state (DB),
78
+ * never in configuration.
79
+ */
80
+ export declare const rateOverridesSchema: z.ZodObject<{
81
+ sms: z.ZodOptional<z.ZodObject<{
82
+ perMessage: z.ZodNumber;
83
+ }, z.core.$strict>>;
84
+ email: z.ZodOptional<z.ZodObject<{
85
+ perMessage: z.ZodNumber;
86
+ }, z.core.$strict>>;
87
+ whatsappMessage: z.ZodOptional<z.ZodObject<{
88
+ perMessage: z.ZodNumber;
89
+ }, z.core.$strict>>;
90
+ voicePrerecorded: z.ZodOptional<z.ZodObject<{
91
+ perMinute: z.ZodNumber;
92
+ increments: z.ZodString;
93
+ }, z.core.$strict>>;
94
+ voiceAi: z.ZodOptional<z.ZodObject<{
95
+ perMinute: z.ZodNumber;
96
+ increments: z.ZodString;
97
+ }, z.core.$strict>>;
98
+ whatsappVoicePrerecorded: z.ZodOptional<z.ZodObject<{
99
+ perMinute: z.ZodNumber;
100
+ increments: z.ZodString;
101
+ }, z.core.$strict>>;
102
+ whatsappVoiceAi: z.ZodOptional<z.ZodObject<{
103
+ perMinute: z.ZodNumber;
104
+ increments: z.ZodString;
105
+ }, z.core.$strict>>;
106
+ }, z.core.$strict>;
107
+ export type RateOverrides = z.infer<typeof rateOverridesSchema>;
108
+ //# sourceMappingURL=rates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rates.d.ts","sourceRoot":"","sources":["../../src/billing/rates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,cAAc,8CAA+C,CAAC;AAC3E,eAAO,MAAM,YAAY,yFAKf,CAAC;AACX,eAAO,MAAM,cAAc,4HAAgD,CAAC;AAE5E,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAC3D,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AACvD,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,eAAO,MAAM,kBAAkB;;;;;;;;EAAyB,CAAC;AAEzD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,aAKjC,CAAC;AAEJ,kEAAkE;AAClE,eAAO,MAAM,iBAAiB;;kBAE5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,gFAAgF;AAChF,eAAO,MAAM,eAAe;;;kBAG1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,2EAA2E;AAC3E,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;kBAQtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;kBAAwB,CAAC;AACzD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The billable-meter catalog and per-meter rate schemas.
4
+ *
5
+ * The catalog is closed-world: exactly seven meters, one per dispatchable
6
+ * channel/mode. Message meters bill per message; voice meters bill answered
7
+ * duration under a telecom increment pair. Each meter kind has its own strict
8
+ * schema so a rate object with fields from the wrong kind (e.g. `increments`
9
+ * on `sms`) fails validation instead of being silently ignored.
10
+ *
11
+ * The two WhatsApp voice meters are reserved: they must be priced in every
12
+ * plan, but no dispatch path produces them until Fonoster ships that transport.
13
+ */
14
+ export const MESSAGE_METERS = ["sms", "email", "whatsappMessage"];
15
+ export const VOICE_METERS = [
16
+ "voicePrerecorded",
17
+ "voiceAi",
18
+ "whatsappVoicePrerecorded",
19
+ "whatsappVoiceAi"
20
+ ];
21
+ export const BILLING_METERS = [...MESSAGE_METERS, ...VOICE_METERS];
22
+ export const billingMeterSchema = z.enum(BILLING_METERS);
23
+ /**
24
+ * Telecom billing-increment notation `"initial/subsequent"` in seconds — the
25
+ * industry standard (15/15 rounds every call up to 15s blocks; 60/6 bills a
26
+ * full first minute then 6s steps).
27
+ */
28
+ export const incrementNotationSchema = z
29
+ .string()
30
+ .regex(/^[1-9]\d*\/[1-9]\d*$/, 'increments must be "initial/subsequent" in positive whole seconds (e.g. "15/15")');
31
+ /** A message meter's rate: decimal currency units per message. */
32
+ export const messageRateSchema = z.strictObject({
33
+ perMessage: z.number().positive()
34
+ });
35
+ /** A voice meter's rate: decimal currency units per minute + increment pair. */
36
+ export const voiceRateSchema = z.strictObject({
37
+ perMinute: z.number().positive(),
38
+ increments: incrementNotationSchema
39
+ });
40
+ /** A complete rate card: all seven meters, each with its kind's schema. */
41
+ export const ratesSchema = z.strictObject({
42
+ sms: messageRateSchema,
43
+ email: messageRateSchema,
44
+ whatsappMessage: messageRateSchema,
45
+ voicePrerecorded: voiceRateSchema,
46
+ voiceAi: voiceRateSchema,
47
+ whatsappVoicePrerecorded: voiceRateSchema,
48
+ whatsappVoiceAi: voiceRateSchema
49
+ });
50
+ /**
51
+ * Per-workspace enterprise overrides: any subset of the rate card, validated
52
+ * with the same per-meter schemas. Stored on workspace billing state (DB),
53
+ * never in configuration.
54
+ */
55
+ export const rateOverridesSchema = ratesSchema.partial();
56
+ //# sourceMappingURL=rates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rates.js","sourceRoot":"","sources":["../../src/billing/rates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,iBAAiB,CAAU,CAAC;AAC3E,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,kBAAkB;IAClB,SAAS;IACT,0BAA0B;IAC1B,iBAAiB;CACT,CAAC;AACX,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAG,cAAc,EAAE,GAAG,YAAY,CAAU,CAAC;AAM5E,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACrC,MAAM,EAAE;KACR,KAAK,CACJ,sBAAsB,EACtB,kFAAkF,CACnF,CAAC;AAEJ,kEAAkE;AAClE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,YAAY,CAAC;IAC9C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAGH,gFAAgF;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,YAAY,CAAC;IAC5C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,UAAU,EAAE,uBAAuB;CACpC,CAAC,CAAC;AAGH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,YAAY,CAAC;IACxC,GAAG,EAAE,iBAAiB;IACtB,KAAK,EAAE,iBAAiB;IACxB,eAAe,EAAE,iBAAiB;IAClC,gBAAgB,EAAE,eAAe;IACjC,OAAO,EAAE,eAAe;IACxB,wBAAwB,EAAE,eAAe;IACzC,eAAe,EAAE,eAAe;CACjC,CAAC,CAAC;AAGH;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC"}
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ import { type EvaluationParameters, type Scorecard } from "../evaluation/scorecard.js";
3
+ /**
4
+ * `engine-eval` — the npx-runnable judge client (engine-scorecard capability,
5
+ * requirement "A deployment can be evaluated with npx"). Fetches the flight-
6
+ * recorder stream + engine parameters from a deployment's
7
+ * `GET /api/engine/events`, runs the pure `evaluate` from this same package
8
+ * locally, and prints the resulting scorecard. No repository checkout,
9
+ * database access, or config file is required — only the API URL and a
10
+ * workspace API key pair. All imports below are relative (this file ships
11
+ * inside `@qcobro/common`; it must not import its own package by name).
12
+ */
13
+ export declare const DEFAULT_URL = "https://api.qcobro.com";
14
+ export interface CliOptions {
15
+ url: string;
16
+ from?: string;
17
+ to?: string;
18
+ accessKeyId?: string;
19
+ accessKeySecret?: string;
20
+ latencyP95?: number;
21
+ maxErrorRate?: number;
22
+ livenessTicks?: number;
23
+ json: boolean;
24
+ help: boolean;
25
+ }
26
+ export declare const USAGE = "Usage: engine-eval [options]\n\nEvaluate a QCobro deployment's campaigns-engine event stream against the\nbuilt-in invariant catalog (safety, performance, liveness) and print a\nscorecard. Exits non-zero when the overall verdict is fail.\n\nOptions:\n --url <url> Deployment API base URL (default: https://api.qcobro.com)\n --from <iso> Range start (ISO datetime)\n --to <iso> Range end (ISO datetime)\n (omit both to evaluate today, operator's local timezone)\n --access-key-id <id> Workspace API key id (or QCOBRO_ACCESS_KEY_ID)\n --access-key-secret <secret> Workspace API key secret (or QCOBRO_ACCESS_KEY_SECRET)\n --latency-p95 <ms> Override PERF-2 threshold (dispatch latency p95, ms)\n --max-error-rate <0..1> Override PERF-3 threshold (max per-channel error rate)\n --liveness-ticks <n> Override LIVE-1 threshold (ticks to first attempt)\n --json Print the scorecard as JSON instead of text\n --help Show this help\n\nExit codes: 0 = pass, 1 = fail, 2 = usage/network/auth error.\n";
27
+ /** Raised for usage, network, or auth failures — mapped to exit code 2. */
28
+ export declare class CliError extends Error {
29
+ }
30
+ /** Parses argv into typed options. Throws `CliError` on malformed flags. */
31
+ export declare function parseCliArgs(argv: string[]): CliOptions;
32
+ /**
33
+ * Resolves the from/to range. When BOTH flags are omitted, defaults to today
34
+ * in the operator's local timezone (the machine running the CLI): local
35
+ * midnight through now. When either flag is given, both are passed through
36
+ * unchanged (no default is applied to just one side).
37
+ */
38
+ export declare function resolveRange(from: string | undefined, to: string | undefined, now: Date): {
39
+ from?: string;
40
+ to?: string;
41
+ };
42
+ /** Resolves the API key pair from flags, falling back to environment variables. */
43
+ export declare function resolveCredentials(opts: Pick<CliOptions, "accessKeyId" | "accessKeySecret">, env: Record<string, string | undefined>): {
44
+ accessKeyId: string;
45
+ accessKeySecret: string;
46
+ };
47
+ /** Builds only the threshold overrides the operator actually passed. */
48
+ export declare function buildThresholdOverrides(opts: Pick<CliOptions, "latencyP95" | "maxErrorRate" | "livenessTicks">): Partial<EvaluationParameters["thresholds"]>;
49
+ /**
50
+ * Renders rows as aligned columns: each column as wide as its widest cell,
51
+ * numeric-looking cells right-aligned, two spaces between columns. Width is
52
+ * computed from the data, so alignment holds for any campaign name or count.
53
+ */
54
+ export declare function alignColumns(rows: string[][], indent?: string): string[];
55
+ /** Renders the scorecard as a plain-ASCII, human-readable summary. */
56
+ export declare function formatScorecard(scorecard: Scorecard, ctx: {
57
+ url: string;
58
+ from?: string;
59
+ to?: string;
60
+ }): string;
61
+ /**
62
+ * Entry point. Exported (rather than only run inline) so it can be exercised
63
+ * directly if ever needed; the module also self-invokes when run via `node`
64
+ * or `npx` (see the guard at the bottom of the file).
65
+ */
66
+ export declare function main(argv?: string[], env?: Record<string, string | undefined>, now?: Date, stdout?: (s: string) => void, stderr?: (s: string) => void): Promise<number>;
67
+ //# sourceMappingURL=engineEval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engineEval.d.ts","sourceRoot":"","sources":["../../src/bin/engineEval.ts"],"names":[],"mappings":";AAGA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACf,MAAM,4BAA4B,CAAC;AAGpC;;;;;;;;;GASG;AAEH,eAAO,MAAM,WAAW,2BAA2B,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED,eAAO,MAAM,KAAK,opCAoBjB,CAAC;AAEF,2EAA2E;AAC3E,qBAAa,QAAS,SAAQ,KAAK;CAAG;AAwBtC,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAmBvD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,GAAG,EAAE,IAAI,GACR;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAMhC;AAED,mFAAmF;AACnF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,iBAAiB,CAAC,EACzD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAUlD;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,GAAG,cAAc,GAAG,eAAe,CAAC,GACtE,OAAO,CAAC,oBAAoB,CAAC,YAAY,CAAC,CAAC,CAM7C;AA6CD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,SAAO,GAAG,MAAM,EAAE,CAqBtE;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,EACpB,GAAG,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,MAAM,CA4ER;AAED;;;;GAIG;AACH,wBAAsB,IAAI,CACxB,IAAI,GAAE,MAAM,EAA0B,EACtC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,GAAG,GAAE,IAAiB,EACtB,MAAM,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAqC,EAC5D,MAAM,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAqC,GAC3D,OAAO,CAAC,MAAM,CAAC,CA8CjB"}
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from "node:util";
3
+ import { evaluate } from "../evaluation/evaluate.js";
4
+ import { evaluationParametersSchema } from "../evaluation/scorecard.js";
5
+ /**
6
+ * `engine-eval` — the npx-runnable judge client (engine-scorecard capability,
7
+ * requirement "A deployment can be evaluated with npx"). Fetches the flight-
8
+ * recorder stream + engine parameters from a deployment's
9
+ * `GET /api/engine/events`, runs the pure `evaluate` from this same package
10
+ * locally, and prints the resulting scorecard. No repository checkout,
11
+ * database access, or config file is required — only the API URL and a
12
+ * workspace API key pair. All imports below are relative (this file ships
13
+ * inside `@qcobro/common`; it must not import its own package by name).
14
+ */
15
+ export const DEFAULT_URL = "https://api.qcobro.com";
16
+ export const USAGE = `Usage: engine-eval [options]
17
+
18
+ Evaluate a QCobro deployment's campaigns-engine event stream against the
19
+ built-in invariant catalog (safety, performance, liveness) and print a
20
+ scorecard. Exits non-zero when the overall verdict is fail.
21
+
22
+ Options:
23
+ --url <url> Deployment API base URL (default: ${DEFAULT_URL})
24
+ --from <iso> Range start (ISO datetime)
25
+ --to <iso> Range end (ISO datetime)
26
+ (omit both to evaluate today, operator's local timezone)
27
+ --access-key-id <id> Workspace API key id (or QCOBRO_ACCESS_KEY_ID)
28
+ --access-key-secret <secret> Workspace API key secret (or QCOBRO_ACCESS_KEY_SECRET)
29
+ --latency-p95 <ms> Override PERF-2 threshold (dispatch latency p95, ms)
30
+ --max-error-rate <0..1> Override PERF-3 threshold (max per-channel error rate)
31
+ --liveness-ticks <n> Override LIVE-1 threshold (ticks to first attempt)
32
+ --json Print the scorecard as JSON instead of text
33
+ --help Show this help
34
+
35
+ Exit codes: 0 = pass, 1 = fail, 2 = usage/network/auth error.
36
+ `;
37
+ /** Raised for usage, network, or auth failures — mapped to exit code 2. */
38
+ export class CliError extends Error {
39
+ }
40
+ const OPTION_SPEC = {
41
+ url: { type: "string" },
42
+ from: { type: "string" },
43
+ to: { type: "string" },
44
+ "access-key-id": { type: "string" },
45
+ "access-key-secret": { type: "string" },
46
+ "latency-p95": { type: "string" },
47
+ "max-error-rate": { type: "string" },
48
+ "liveness-ticks": { type: "string" },
49
+ json: { type: "boolean", default: false },
50
+ help: { type: "boolean", default: false }
51
+ };
52
+ function parseNumberFlag(flag, raw) {
53
+ if (raw === undefined)
54
+ return undefined;
55
+ const value = Number(raw);
56
+ if (!Number.isFinite(value)) {
57
+ throw new CliError(`--${flag} must be a number, got "${raw}"`);
58
+ }
59
+ return value;
60
+ }
61
+ /** Parses argv into typed options. Throws `CliError` on malformed flags. */
62
+ export function parseCliArgs(argv) {
63
+ let values;
64
+ try {
65
+ ({ values } = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: false }));
66
+ }
67
+ catch (err) {
68
+ throw new CliError(`Invalid arguments: ${err instanceof Error ? err.message : String(err)}`);
69
+ }
70
+ return {
71
+ url: values.url ?? DEFAULT_URL,
72
+ from: values.from,
73
+ to: values.to,
74
+ accessKeyId: values["access-key-id"],
75
+ accessKeySecret: values["access-key-secret"],
76
+ latencyP95: parseNumberFlag("latency-p95", values["latency-p95"]),
77
+ maxErrorRate: parseNumberFlag("max-error-rate", values["max-error-rate"]),
78
+ livenessTicks: parseNumberFlag("liveness-ticks", values["liveness-ticks"]),
79
+ json: values.json ?? false,
80
+ help: values.help ?? false
81
+ };
82
+ }
83
+ /**
84
+ * Resolves the from/to range. When BOTH flags are omitted, defaults to today
85
+ * in the operator's local timezone (the machine running the CLI): local
86
+ * midnight through now. When either flag is given, both are passed through
87
+ * unchanged (no default is applied to just one side).
88
+ */
89
+ export function resolveRange(from, to, now) {
90
+ if (from === undefined && to === undefined) {
91
+ const localMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
92
+ return { from: localMidnight.toISOString(), to: now.toISOString() };
93
+ }
94
+ return { from, to };
95
+ }
96
+ /** Resolves the API key pair from flags, falling back to environment variables. */
97
+ export function resolveCredentials(opts, env) {
98
+ const accessKeyId = opts.accessKeyId ?? env.QCOBRO_ACCESS_KEY_ID;
99
+ const accessKeySecret = opts.accessKeySecret ?? env.QCOBRO_ACCESS_KEY_SECRET;
100
+ if (!accessKeyId || !accessKeySecret) {
101
+ throw new CliError("Missing API key credentials: pass --access-key-id/--access-key-secret or set " +
102
+ "QCOBRO_ACCESS_KEY_ID/QCOBRO_ACCESS_KEY_SECRET");
103
+ }
104
+ return { accessKeyId, accessKeySecret };
105
+ }
106
+ /** Builds only the threshold overrides the operator actually passed. */
107
+ export function buildThresholdOverrides(opts) {
108
+ const thresholds = {};
109
+ if (opts.latencyP95 !== undefined)
110
+ thresholds.dispatchLatencyP95Ms = opts.latencyP95;
111
+ if (opts.maxErrorRate !== undefined)
112
+ thresholds.maxErrorRate = opts.maxErrorRate;
113
+ if (opts.livenessTicks !== undefined)
114
+ thresholds.livenessTicks = opts.livenessTicks;
115
+ return thresholds;
116
+ }
117
+ async function fetchEvents(url, range, accessKeyId, accessKeySecret) {
118
+ let endpoint;
119
+ try {
120
+ endpoint = new URL("/api/engine/events", url);
121
+ }
122
+ catch {
123
+ throw new CliError(`Invalid --url "${url}"`);
124
+ }
125
+ if (range.from)
126
+ endpoint.searchParams.set("from", range.from);
127
+ if (range.to)
128
+ endpoint.searchParams.set("to", range.to);
129
+ const auth = Buffer.from(`${accessKeyId}:${accessKeySecret}`).toString("base64");
130
+ let res;
131
+ try {
132
+ res = await fetch(endpoint, { headers: { Authorization: `Basic ${auth}` } });
133
+ }
134
+ catch (err) {
135
+ throw new CliError(`Could not reach ${endpoint.origin}: ${err instanceof Error ? err.message : String(err)}`);
136
+ }
137
+ if (res.status === 401) {
138
+ throw new CliError("Authentication failed: invalid API key credentials");
139
+ }
140
+ if (!res.ok) {
141
+ throw new CliError(`Request failed: ${res.status} ${res.statusText}`);
142
+ }
143
+ return (await res.json());
144
+ }
145
+ /**
146
+ * Renders rows as aligned columns: each column as wide as its widest cell,
147
+ * numeric-looking cells right-aligned, two spaces between columns. Width is
148
+ * computed from the data, so alignment holds for any campaign name or count.
149
+ */
150
+ export function alignColumns(rows, indent = " ") {
151
+ const widths = [];
152
+ for (const row of rows) {
153
+ row.forEach((cell, i) => {
154
+ widths[i] = Math.max(widths[i] ?? 0, cell.length);
155
+ });
156
+ }
157
+ const numeric = /^[\d,.%]+$/;
158
+ return rows.map((row) => (indent +
159
+ row
160
+ .map((cell, i) => {
161
+ const w = widths[i];
162
+ // Last column flows free; numbers right-align under their header.
163
+ if (i === row.length - 1)
164
+ return cell;
165
+ return numeric.test(cell) ? cell.padStart(w) : cell.padEnd(w);
166
+ })
167
+ .join(" ")).trimEnd());
168
+ }
169
+ /** Renders the scorecard as a plain-ASCII, human-readable summary. */
170
+ export function formatScorecard(scorecard, ctx) {
171
+ const lines = [];
172
+ lines.push("QCobro engine-eval");
173
+ lines.push(`url: ${ctx.url}`);
174
+ lines.push(`range: ${ctx.from ?? "(unbounded)"} .. ${ctx.to ?? "(unbounded)"}`);
175
+ lines.push(`totals: events=${scorecard.totals.events} ticks=${scorecard.totals.ticks} ` +
176
+ `campaigns=${scorecard.totals.campaigns} accountsConsidered=${scorecard.totals.accountsConsidered}`);
177
+ if (scorecard.gaps.length === 0) {
178
+ lines.push("gaps: none");
179
+ }
180
+ else {
181
+ lines.push(`gaps: ${scorecard.gaps.length} tick(s) started but never completed`);
182
+ for (const g of scorecard.gaps)
183
+ lines.push(` - tick ${g.tickId} at ${g.at}`);
184
+ }
185
+ lines.push("");
186
+ lines.push(`VERDICT: ${scorecard.verdict.toUpperCase()}`);
187
+ lines.push("");
188
+ lines.push("INVARIANTS");
189
+ lines.push(...alignColumns(scorecard.invariants.map((inv) => [
190
+ `[${inv.verdict === "pass" ? "PASS" : "FAIL"}]`,
191
+ inv.id,
192
+ inv.name,
193
+ inv.scope,
194
+ inv.metric ?? ""
195
+ ])));
196
+ lines.push("");
197
+ lines.push("BY CAMPAIGN");
198
+ // Names come from the stream's campaign.evaluated events; streams recorded
199
+ // before names existed fall back to the id.
200
+ const campaignName = new Map(scorecard.campaigns.map((c) => [c.campaignId, c.name]));
201
+ const displayName = (id) => campaignName.get(id) ?? id;
202
+ if (scorecard.campaigns.length === 0) {
203
+ lines.push(" (no campaigns in range)");
204
+ }
205
+ else {
206
+ lines.push(...alignColumns([
207
+ ["campaign", "ticks", "considered", "dispatched", "failed", "suppressed", "violations"],
208
+ ...scorecard.campaigns.map((c) => [
209
+ c.name ?? c.campaignId,
210
+ String(c.ticksSeen),
211
+ String(c.considered),
212
+ String(c.dispatched),
213
+ String(c.failed),
214
+ String(c.suppressed),
215
+ Object.entries(c.violations)
216
+ .map(([id, count]) => `${id}:${count}`)
217
+ .join(" ") || "-"
218
+ ])
219
+ ]));
220
+ }
221
+ const violating = scorecard.invariants.filter((inv) => inv.violations.length > 0);
222
+ if (violating.length > 0) {
223
+ lines.push("");
224
+ lines.push("VIOLATIONS");
225
+ for (const inv of violating) {
226
+ lines.push(` ${inv.id} ${inv.name}`);
227
+ for (const v of inv.violations) {
228
+ const scope = [
229
+ v.campaignId ? `campaign "${displayName(v.campaignId)}"` : undefined,
230
+ v.portfolioAccountId ? `account ${v.portfolioAccountId}` : undefined
231
+ ]
232
+ .filter((s) => Boolean(s))
233
+ .join(", ");
234
+ lines.push(` - ${scope ? scope + ": " : ""}${v.detail} [events: ${v.eventIds.join(", ")}]`);
235
+ }
236
+ }
237
+ }
238
+ return lines.join("\n");
239
+ }
240
+ /**
241
+ * Entry point. Exported (rather than only run inline) so it can be exercised
242
+ * directly if ever needed; the module also self-invokes when run via `node`
243
+ * or `npx` (see the guard at the bottom of the file).
244
+ */
245
+ export async function main(argv = process.argv.slice(2), env = process.env, now = new Date(), stdout = (s) => process.stdout.write(s), stderr = (s) => process.stderr.write(s)) {
246
+ let opts;
247
+ try {
248
+ opts = parseCliArgs(argv);
249
+ }
250
+ catch (err) {
251
+ stderr(`engine-eval: ${err instanceof Error ? err.message : String(err)}\n\n${USAGE}`);
252
+ return 2;
253
+ }
254
+ if (opts.help) {
255
+ stdout(USAGE);
256
+ return 0;
257
+ }
258
+ try {
259
+ const { accessKeyId, accessKeySecret } = resolveCredentials(opts, env);
260
+ const range = resolveRange(opts.from, opts.to, now);
261
+ const { events, parameters, truncated } = await fetchEvents(opts.url, range, accessKeyId, accessKeySecret);
262
+ if (truncated) {
263
+ stderr("engine-eval: warning — the server truncated the event stream for this range; " +
264
+ "verdicts may miss violations. Narrow --from/--to and re-run.\n");
265
+ }
266
+ const evaluationParameters = evaluationParametersSchema.parse({
267
+ tickSeconds: parameters.tickSeconds,
268
+ ratesPerMinute: parameters.ratesPerMinute,
269
+ thresholds: buildThresholdOverrides(opts)
270
+ });
271
+ const scorecard = evaluate(events, evaluationParameters);
272
+ if (opts.json) {
273
+ stdout(JSON.stringify(scorecard, null, 2) + "\n");
274
+ }
275
+ else {
276
+ stdout(formatScorecard(scorecard, { url: opts.url, from: range.from, to: range.to }) + "\n");
277
+ }
278
+ return scorecard.verdict === "pass" ? 0 : 1;
279
+ }
280
+ catch (err) {
281
+ const message = err instanceof Error ? err.message : String(err);
282
+ stderr(`engine-eval: ${message}\n`);
283
+ return 2;
284
+ }
285
+ }
286
+ // Only run when this file is the process entry point (not when imported by tests).
287
+ if (import.meta.url === `file://${process.argv[1]}`) {
288
+ main().then((code) => process.exit(code));
289
+ }
290
+ //# sourceMappingURL=engineEval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engineEval.js","sourceRoot":"","sources":["../../src/bin/engineEval.ts"],"names":[],"mappings":"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"}