@trazum/core 1.65.0 → 1.67.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 (50) hide show
  1. package/dist/config-schema.d.ts +32 -1
  2. package/dist/config-schema.d.ts.map +1 -1
  3. package/dist/config-schema.js +55 -3
  4. package/dist/config-schema.js.map +1 -1
  5. package/dist/conform.d.ts +1 -1
  6. package/dist/conform.d.ts.map +1 -1
  7. package/dist/conform.js +13 -0
  8. package/dist/conform.js.map +1 -1
  9. package/dist/gateway.d.ts +32 -1
  10. package/dist/gateway.d.ts.map +1 -1
  11. package/dist/gateway.js +64 -3
  12. package/dist/gateway.js.map +1 -1
  13. package/dist/guard.d.ts +20 -0
  14. package/dist/guard.d.ts.map +1 -1
  15. package/dist/guard.js +41 -3
  16. package/dist/guard.js.map +1 -1
  17. package/dist/index.d.ts +8 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +4 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/judgement.d.ts +159 -0
  22. package/dist/judgement.d.ts.map +1 -0
  23. package/dist/judgement.js +188 -0
  24. package/dist/judgement.js.map +1 -0
  25. package/dist/position-report.d.ts +107 -0
  26. package/dist/position-report.d.ts.map +1 -0
  27. package/dist/position-report.js +180 -0
  28. package/dist/position-report.js.map +1 -0
  29. package/dist/position.d.ts +74 -0
  30. package/dist/position.d.ts.map +1 -0
  31. package/dist/position.js +93 -0
  32. package/dist/position.js.map +1 -0
  33. package/dist/schemas.d.ts.map +1 -1
  34. package/dist/schemas.js +9 -0
  35. package/dist/schemas.js.map +1 -1
  36. package/dist/session-cost.d.ts +2 -0
  37. package/dist/session-cost.d.ts.map +1 -1
  38. package/dist/session-cost.js +1 -1
  39. package/dist/session-cost.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/config-schema.ts +89 -3
  42. package/src/conform.ts +12 -0
  43. package/src/gateway.ts +108 -4
  44. package/src/guard.ts +73 -3
  45. package/src/index.ts +24 -1
  46. package/src/judgement.ts +346 -0
  47. package/src/position-report.ts +275 -0
  48. package/src/position.ts +126 -0
  49. package/src/schemas.ts +9 -0
  50. package/src/session-cost.ts +1 -1
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The measured side of the limits policy: what a usage log says each scope
3
+ * has already spent.
4
+ *
5
+ * `judgeLimits` judges; this measures. The split is the product's oldest
6
+ * rule — the judging function must be pure and instant, so whatever reads
7
+ * and prices records happens once, here, and the doors keep the result.
8
+ *
9
+ * **Absence is `null`, never zero.** A log with no clock cannot say what
10
+ * today has cost; a log that records no sessions cannot say what a session
11
+ * has spent. Both come back `null`, and `judgeLimits` turns a `null` into
12
+ * `cannot-tell` rather than an approval. The one deliberate asymmetry: a log
13
+ * that *does* record sessions and has never seen this one answers `0` — the
14
+ * history of a conversation that has not started is complete and empty,
15
+ * which is a measurement, not an absence.
16
+ *
17
+ * **Session keys never leave this module.** The index groups by them — that
18
+ * is the whole job — but the map lives in door memory, `positionAt` returns
19
+ * only dollar figures, and nothing here is serialised. The guarantee the
20
+ * usage module states (a session key is somebody's conversation and is never
21
+ * printed) holds through this door too.
22
+ */
23
+ import { costOf } from './session-cost.js';
24
+ const DAY_MS = 24 * 60 * 60 * 1000;
25
+ /**
26
+ * Prices and groups a parsed usage log, once.
27
+ *
28
+ * An unpriced record contributes nothing to any figure and is **counted**
29
+ * instead of dropped silently: a door that reports "measured $3" over a log
30
+ * with a thousand unpriced records is wrong by an amount nobody can see, in
31
+ * the flattering direction, and `unpriced` is how the door says so.
32
+ */
33
+ export function indexUsage(records, options) {
34
+ const { catalogue, on = new Date() } = options;
35
+ const dayFromMs = Math.floor(on.getTime() / DAY_MS) * DAY_MS;
36
+ const dayToMs = dayFromMs + DAY_MS;
37
+ const labels = new Map();
38
+ const sessions = new Map();
39
+ let labelsSeen = false;
40
+ let sessionsSeen = false;
41
+ let clocksSeen = false;
42
+ let dayUsd = 0;
43
+ let unpriced = 0;
44
+ for (const record of records) {
45
+ if (record.label !== null)
46
+ labelsSeen = true;
47
+ if (record.session !== null)
48
+ sessionsSeen = true;
49
+ if (record.ts !== null)
50
+ clocksSeen = true;
51
+ const usd = costOf(record, catalogue, on);
52
+ if (usd === null) {
53
+ unpriced += 1;
54
+ continue;
55
+ }
56
+ if (record.label !== null)
57
+ labels.set(record.label, (labels.get(record.label) ?? 0) + usd);
58
+ if (record.session !== null)
59
+ sessions.set(record.session, (sessions.get(record.session) ?? 0) + usd);
60
+ if (record.ts !== null && record.ts >= dayFromMs && record.ts < dayToMs)
61
+ dayUsd += usd;
62
+ }
63
+ return {
64
+ dayUsd: clocksSeen ? dayUsd : null,
65
+ dayWindow: clocksSeen ? { fromMs: dayFromMs, toMs: dayToMs } : null,
66
+ labelsSeen,
67
+ sessionsSeen,
68
+ labels,
69
+ sessions,
70
+ any: records.length > 0,
71
+ unpriced,
72
+ };
73
+ }
74
+ /**
75
+ * The measured position for one proposed call, from an index built once.
76
+ *
77
+ * A scope the log cannot see is `null`; a scope it can see and has nothing
78
+ * recorded for is `0`. The difference is the difference between "unknown"
79
+ * and "a conversation that has not started yet", and collapsing them is how
80
+ * a dead log approves a live spend.
81
+ */
82
+ export function positionAt(index, call) {
83
+ if (!index.any) {
84
+ return { dayUsd: null, sessionUsd: null, labelUsd: null };
85
+ }
86
+ return {
87
+ dayUsd: index.dayUsd,
88
+ dayWindow: index.dayWindow,
89
+ sessionUsd: call.session !== undefined && index.sessionsSeen ? (index.sessions.get(call.session) ?? 0) : null,
90
+ labelUsd: call.label !== undefined && index.labelsSeen ? (index.labels.get(call.label) ?? 0) : null,
91
+ };
92
+ }
93
+ //# sourceMappingURL=position.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"position.js","sourceRoot":"","sources":["../src/position.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA6CjD;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA2F1E,CAAC;AAEF,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE1E"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA6CjD;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoG1E,CAAC;AAEF,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE1E"}
package/dist/schemas.js CHANGED
@@ -136,6 +136,15 @@ export const CONTRACT_SCHEMAS = {
136
136
  missing: array,
137
137
  measured: objectOrNull,
138
138
  }),
139
+ position: doc('position', {
140
+ // The document's own signature: no other contract names its source.
141
+ source: { const: 'usage-log' },
142
+ month: object,
143
+ positions: array,
144
+ unmeasured: array,
145
+ cannotSay: array,
146
+ unpricedRecords: number,
147
+ }),
139
148
  };
140
149
  /** The schema for one named contract — the object `trazum schema` prints. */
141
150
  export function contractSchema(name) {
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
@@ -64,6 +64,8 @@ export interface SessionCostTracker {
64
64
  add(record: UsageRecord): void;
65
65
  finish(): SessionCostShape[];
66
66
  }
67
+ /** Every billed dollar of one call, at its own model's rates. */
68
+ export declare function costOf(record: UsageRecord, catalogue: PricingCatalogue, on: Date): number | null;
67
69
  export declare function createSessionCostTracker(options: SessionCostOptions): SessionCostTracker;
68
70
  /** The same measurement over a list, for a caller holding one already. */
69
71
  export declare function sessionCostShapes(records: readonly UsageRecord[], options: SessionCostOptions): SessionCostShape[];
@@ -1 +1 @@
1
- {"version":3,"file":"session-cost.d.ts","sourceRoot":"","sources":["../src/session-cost.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,EAAE,CAAC,EAAE,IAAI,CAAC;IACV;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/B,MAAM,IAAI,gBAAgB,EAAE,CAAC;CAC9B;AAgCD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,kBAAkB,GAAG,kBAAkB,CAwDxF;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,OAAO,EAAE,kBAAkB,GAC1B,gBAAgB,EAAE,CAIpB"}
1
+ {"version":3,"file":"session-cost.d.ts","sourceRoot":"","sources":["../src/session-cost.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,EAAE,CAAC,EAAE,IAAI,CAAC;IACV;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/B,MAAM,IAAI,gBAAgB,EAAE,CAAC;CAC9B;AAED,iEAAiE;AACjE,wBAAgB,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,gBAAgB,EAAE,EAAE,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAahG;AAgBD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,kBAAkB,GAAG,kBAAkB,CAwDxF;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,OAAO,EAAE,kBAAkB,GAC1B,gBAAgB,EAAE,CAIpB"}
@@ -1,7 +1,7 @@
1
1
  import { effectivePricing, multipliersFor } from './pricing.js';
2
2
  import { UNLABELLED } from './usage.js';
3
3
  /** Every billed dollar of one call, at its own model's rates. */
4
- function costOf(record, catalogue, on) {
4
+ export function costOf(record, catalogue, on) {
5
5
  const model = catalogue.byId.get(record.model);
6
6
  if (!model)
7
7
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"session-cost.js","sourceRoot":"","sources":["../src/session-cost.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-cost.js","sourceRoot":"","sources":["../src/session-cost.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trazum/core",
3
- "version": "1.65.0",
3
+ "version": "1.67.0",
4
4
  "description": "Trazum core: priced advisories for LLM prompts (caching, model tier, batching, schemas), plus deterministic trimming, token counting and pricing.",
5
5
  "license": "MIT",
6
6
  "author": "David Mu\u00f1oz Rey",
@@ -137,6 +137,34 @@ export interface SpendConfig {
137
137
  substitute?: Record<string, { to: string; reason: string }>;
138
138
  }
139
139
 
140
+ /**
141
+ * The enforcement policy: USD ceilings judged at call time, at whichever door
142
+ * the call arrives — the gateway's 402, `serve`'s cost answer, `spend_guard`
143
+ * over MCP.
144
+ *
145
+ * A separate block from `spend`, and the separation is the point. `spend`
146
+ * gates *reports*: it is read after the fact, over a log or a store, and a
147
+ * crossing fails a check. This is read *before a call is made*, to refuse it.
148
+ * Same units, different moments — and the 1.62 arc's lesson is that two
149
+ * surfaces reading "the same" value from different slices of config is how
150
+ * they come to disagree about how much is left. One block, stated once,
151
+ * enforced identically at every door.
152
+ *
153
+ * Every ceiling is a **positive** dollar amount. `spend` accepts zero because
154
+ * a zero report-budget is a gate somebody can waive on the record; a zero
155
+ * enforcement ceiling is a door that refuses everything, which is an outage
156
+ * dressed as a policy — if the intent is "stop all calls", turning the door
157
+ * off is the honest spelling.
158
+ */
159
+ export interface LimitsConfig {
160
+ /** Ceiling per UTC calendar day, in dollars, measured across the store. */
161
+ dayUsd?: number;
162
+ /** Ceiling per session — the unit an agent product actually blows up in. */
163
+ sessionUsd?: number;
164
+ /** Per-label ceilings, each judged against that label's own measured spend. */
165
+ byLabel?: Record<string, number>;
166
+ }
167
+
140
168
  export interface TrazumConfig {
141
169
  level?: RuleLevel;
142
170
  locale?: Locale;
@@ -182,6 +210,7 @@ export interface TrazumConfig {
182
210
  */
183
211
  sources?: Record<string, string[]>;
184
212
  spend?: SpendConfig;
213
+ limits?: LimitsConfig;
185
214
  /**
186
215
  * Findings as policy: a gate failure the team has looked at and decided to
187
216
  * live with, on the record, for a bounded time.
@@ -298,6 +327,7 @@ export const CONFIG_KEYS = [
298
327
  'budgets',
299
328
  'labels',
300
329
  'spend',
330
+ 'limits',
301
331
  'sources',
302
332
  'store',
303
333
  'waive',
@@ -315,6 +345,8 @@ export const CONFIG_BASELINE_KEYS = ['path', 'maxGrowthTokens', 'maxGrowthPct']
315
345
 
316
346
  export const CONFIG_SPEND_KEYS = ['maxUsd', 'monthlyUsd', 'maxDayUsd', 'maxSessionUsd', 'maxCacheLossUsd', 'byLabel', 'bySource', 'substitute'] as const;
317
347
 
348
+ export const CONFIG_LIMITS_KEYS = ['dayUsd', 'sessionUsd', 'byLabel'] as const;
349
+
318
350
  export const CONFIG_WAIVE_KEYS = ['gate', 'reason', 'until'] as const;
319
351
 
320
352
  export const CONFIG_STORE_KEYS = ['keepDays'] as const;
@@ -590,6 +622,53 @@ function parseSpend(raw: unknown, source: string): SpendConfig {
590
622
  return spend;
591
623
  }
592
624
 
625
+ /**
626
+ * Validates the `limits` block.
627
+ *
628
+ * Every ceiling must be a **positive** finite number of dollars — stricter
629
+ * than `spend`, deliberately. A report-budget of zero is a gate somebody can
630
+ * waive on the record; an enforcement ceiling of zero refuses every call at
631
+ * that door, which is an outage dressed as a policy, so the error says what
632
+ * to write instead. Unknown keys are named rather than ignored, because a
633
+ * misspelled ceiling is a policy that is never enforced while CI stays green.
634
+ */
635
+ function requirePositiveUsd(value: unknown, label: string, source: string): number {
636
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
637
+ throw new ConfigError(
638
+ `"${label}" must be a positive number of dollars. A ceiling of 0 would refuse every call at this door — if that is the intent, turn the door off rather than writing an outage as a policy.`,
639
+ source,
640
+ );
641
+ }
642
+ return value;
643
+ }
644
+
645
+ export function parseLimits(raw: unknown, source: string): LimitsConfig {
646
+ if (!isPlainObject(raw)) throw new ConfigError('"limits" must be an object', source);
647
+ rejectUnknownKeys(raw, CONFIG_LIMITS_KEYS, source, 'limits.');
648
+
649
+ const limits: LimitsConfig = {};
650
+ if (raw.dayUsd !== undefined) {
651
+ limits.dayUsd = requirePositiveUsd(raw.dayUsd, 'limits.dayUsd', source);
652
+ }
653
+ if (raw.sessionUsd !== undefined) {
654
+ limits.sessionUsd = requirePositiveUsd(raw.sessionUsd, 'limits.sessionUsd', source);
655
+ }
656
+ if (raw.byLabel !== undefined) {
657
+ if (!isPlainObject(raw.byLabel)) {
658
+ throw new ConfigError('"limits.byLabel" must be an object', source);
659
+ }
660
+ const byLabel: Record<string, number> = {};
661
+ for (const [label, value] of Object.entries(raw.byLabel)) {
662
+ if (label.trim().length === 0) {
663
+ throw new ConfigError('"limits.byLabel" has an empty label', source);
664
+ }
665
+ byLabel[label] = requirePositiveUsd(value, `limits.byLabel["${label}"]`, source);
666
+ }
667
+ limits.byLabel = byLabel;
668
+ }
669
+ return limits;
670
+ }
671
+
593
672
  /**
594
673
  * Validates the `sources` block: named services, each a non-empty list of
595
674
  * glob patterns. Patterns are not checked against a filesystem here — a
@@ -869,7 +948,7 @@ function parseStore(raw: unknown, source: string): { keepDays?: number } {
869
948
  return out;
870
949
  }
871
950
 
872
- function parseWaive(raw: unknown, source: string): WaiveEntry[] {
951
+ export function parseWaive(raw: unknown, source: string): WaiveEntry[] {
873
952
  if (!Array.isArray(raw)) throw new ConfigError('"waive" must be an array', source);
874
953
  return raw.map((entry, index) => {
875
954
  const at = `waive[${index}]`;
@@ -882,10 +961,16 @@ function parseWaive(raw: unknown, source: string): WaiveEntry[] {
882
961
  }
883
962
  const known =
884
963
  (WAIVABLE_GATES as readonly string[]).includes(gate) ||
885
- (gate.startsWith('byLabel:') && gate.length > 'byLabel:'.length);
964
+ (gate.startsWith('byLabel:') && gate.length > 'byLabel:'.length) ||
965
+ // The limits policy's ceilings, waivable since the 1.66 arc: silencing
966
+ // a limit leaves a record, and the doors carry the waiver in every
967
+ // judgement rather than refusing. The forms mirror the config keys.
968
+ gate === 'limits.dayUsd' ||
969
+ gate === 'limits.sessionUsd' ||
970
+ (gate.startsWith('limits.byLabel:') && gate.length > 'limits.byLabel:'.length);
886
971
  if (!known) {
887
972
  throw new ConfigError(
888
- `"${at}.gate" names no gate: "${gate}". Waivable: ${WAIVABLE_GATES.join(', ')}, or byLabel:<label>.`,
973
+ `"${at}.gate" names no gate: "${gate}". Waivable: ${WAIVABLE_GATES.join(', ')}, byLabel:<label>, limits.dayUsd, limits.sessionUsd, or limits.byLabel:<label>.`,
889
974
  source,
890
975
  );
891
976
  }
@@ -1056,6 +1141,7 @@ export function parseConfig(raw: string, source = CONFIG_FILENAME): TrazumConfig
1056
1141
  if (document.budgets !== undefined) config.budgets = parseBudgets(document.budgets, source);
1057
1142
  if (document.labels !== undefined) config.labels = parseLabels(document.labels, source);
1058
1143
  if (document.spend !== undefined) config.spend = parseSpend(document.spend, source);
1144
+ if (document.limits !== undefined) config.limits = parseLimits(document.limits, source);
1059
1145
  if (document.sources !== undefined) config.sources = parseSources(document.sources, source);
1060
1146
  if (document.store !== undefined) config.store = parseStore(document.store, source);
1061
1147
  if (document.waive !== undefined) config.waive = parseWaive(document.waive, source);
package/src/conform.ts CHANGED
@@ -53,6 +53,7 @@ export const CONTRACT_NAMES = [
53
53
  'gateway-refusal',
54
54
  'bench',
55
55
  'prompt-draft',
56
+ 'position',
56
57
  ] as const;
57
58
 
58
59
  export type ContractName = (typeof CONTRACT_NAMES)[number];
@@ -385,6 +386,14 @@ const DOCUMENT_RULES: Record<Exclude<ContractName, 'usage-log'>, FieldRule[]> =
385
386
  rule('justified', 'an array — one entry per key written, with the observation behind it', isArray),
386
387
  rule('declined', 'an array — one entry per key left out, each with a typed why', isArray),
387
388
  ],
389
+ position: [
390
+ rule('source', 'the string "usage-log" — where every figure was measured from', (v) => v === 'usage-log'),
391
+ rule('month', 'the period object — id, fromMs, toMs, days', isObject),
392
+ rule('positions', 'an array — one standing per measurable ceiling', isArray),
393
+ rule('unmeasured', 'an array — configured ceilings this log cannot answer for, with reasons', isArray),
394
+ rule('cannotSay', 'an array of sentences — what is deliberately not answered', isArray),
395
+ rule('unpricedRecords', 'a count of records the catalogue cannot price', isNumber),
396
+ ],
388
397
  pulse: [
389
398
  rule('beats', 'an array — one per kind', isArray),
390
399
  rule('nowMs', 'the instant the ages were taken against', isNumber),
@@ -530,6 +539,9 @@ function contractOf(doc: Record<string, unknown>): Exclude<ContractName, 'usage-
530
539
  if (isObject(doc.config) && Array.isArray(doc.justified) && Array.isArray(doc.declined)) {
531
540
  return 'first-run';
532
541
  }
542
+ // Before the pulse and everything position-shaped: `source: "usage-log"`
543
+ // is this document's own signature — no other contract carries it.
544
+ if (doc.source === 'usage-log' && Array.isArray(doc.positions)) return 'position';
533
545
  if (Array.isArray(doc.beats) && typeof doc.nowMs === 'number') return 'pulse';
534
546
  if (Array.isArray(doc.rules) && typeof doc.floor === 'number') return 'rule-yield';
535
547
  if (isObject(doc.error) && typeof doc.reason === 'string' && Array.isArray(doc.alternatives)) {
package/src/gateway.ts CHANGED
@@ -51,6 +51,9 @@ import type { PlanAssumption } from './plan.js';
51
51
  import { effectivePricing, multipliersFor } from './pricing.js';
52
52
  import type { PricingCatalogue } from './pricing.js';
53
53
  import type { ModelPricing } from './types.js';
54
+ import { judgeLimits, limitSentence, unjudgedSentence } from './judgement.js';
55
+ import type { MeasuredPosition, PolicyJudgement } from './judgement.js';
56
+ import type { LimitsConfig, WaiveEntry } from './config-schema.js';
54
57
 
55
58
  /**
56
59
  * What the operator has decided happens when the gateway cannot judge.
@@ -80,6 +83,12 @@ export interface GatewayCall {
80
83
  maxOutputTokens: number | null;
81
84
  /** The workload, when the caller labelled it. */
82
85
  label: string | null;
86
+ /**
87
+ * The conversation, when the caller identified it — `metadata.trazum_session`,
88
+ * the same seam as the label. Used to judge the per-session ceiling and
89
+ * never recorded, printed, or forwarded anywhere by this module.
90
+ */
91
+ session: string | null;
83
92
  }
84
93
 
85
94
  /** Where the budget stands, as the gateway was told at the last refresh. */
@@ -97,10 +106,17 @@ export type RefuseReason =
97
106
  | 'budget-exhausted'
98
107
  /** This call would take it past, on an estimate of this call. */
99
108
  | 'call-would-cross'
109
+ /** A ceiling in the `limits` policy is over — the judgement names which. */
110
+ | 'limit-over'
100
111
  /** Cannot tell, and the operator chose fail-closed. */
101
112
  | 'cannot-tell-and-closed';
102
113
 
103
- export type CannotTellCause = 'no-budget' | 'nothing-measured' | 'model-unpriced';
114
+ export type CannotTellCause =
115
+ | 'no-budget'
116
+ | 'nothing-measured'
117
+ | 'model-unpriced'
118
+ /** A ceiling in the `limits` policy could not be judged — see `policy`. */
119
+ | 'limit-unjudged';
104
120
 
105
121
  /** A cheaper way to make the same call, named on a refusal. */
106
122
  export interface GatewayAlternative {
@@ -124,6 +140,8 @@ export type GatewayDecision =
124
140
  estimatedUsd: number | null;
125
141
  /** Present when the gateway could not judge and the operator fails open. */
126
142
  unjudged: CannotTellCause | null;
143
+ /** The limits policy, judged for this call — same judge as every door. */
144
+ policy: PolicyJudgement;
127
145
  }
128
146
  | {
129
147
  kind: 'refuse';
@@ -137,6 +155,8 @@ export type GatewayDecision =
137
155
  /** A refusal never arrives bare. Dearest saving first; may be empty. */
138
156
  alternatives: GatewayAlternative[];
139
157
  because: string;
158
+ /** The limits policy, judged for this call — same judge as every door. */
159
+ policy: PolicyJudgement;
140
160
  }
141
161
  | {
142
162
  /**
@@ -154,6 +174,8 @@ export type GatewayDecision =
154
174
  configuredReason: string;
155
175
  estimatedUsd: number | null;
156
176
  markedInStore: true;
177
+ /** The limits policy, judged for this call — same judge as every door. */
178
+ policy: PolicyJudgement;
157
179
  };
158
180
 
159
181
  export interface GatewayPolicy {
@@ -172,6 +194,19 @@ export interface GatewayOptions {
172
194
  catalogue: PricingCatalogue;
173
195
  policy: GatewayPolicy;
174
196
  on?: Date;
197
+ /**
198
+ * The `limits` block and the measured position it is judged against —
199
+ * chapter three of the 1.66 arc. Both optional: a gateway with no limits
200
+ * config behaves exactly as before, and the judgement is `no-policy`.
201
+ *
202
+ * The judging is `judgeLimits`, verbatim — the same function `serve` and
203
+ * `spend_guard` call, which is the whole point: three doors, one judge,
204
+ * no door doing its own arithmetic.
205
+ */
206
+ limits?: LimitsConfig;
207
+ position?: MeasuredPosition;
208
+ /** The config's `waive` list — a silenced limit forwards, on the record. */
209
+ waivers?: readonly WaiveEntry[];
175
210
  }
176
211
 
177
212
  /** What this call costs at a model's rates, or null when it cannot be priced. */
@@ -258,6 +293,63 @@ export function gatewayDecision(
258
293
  const model = catalogue.byId.get(call.model);
259
294
  const estimatedUsd = priceCall(model, call, on);
260
295
 
296
+ /**
297
+ * The limits policy, judged first and by the shared judge — never by
298
+ * arithmetic of this module's own. An `over` here refuses regardless of
299
+ * where the monthly budget stands: a session ceiling exists precisely for
300
+ * the call that is fine by the month and ruinous by the conversation.
301
+ */
302
+ const judged = judgeLimits(
303
+ options.limits,
304
+ options.position ?? { dayUsd: null, sessionUsd: null, labelUsd: null },
305
+ {
306
+ model: call.model,
307
+ inputTokens: call.inputTokens ?? undefined,
308
+ outputTokens: call.maxOutputTokens ?? undefined,
309
+ label: call.label ?? undefined,
310
+ session: call.session ?? undefined,
311
+ },
312
+ { catalogue, on, ...(options.waivers === undefined ? {} : { waivers: options.waivers }) },
313
+ );
314
+
315
+ if (judged.verdict === 'over') {
316
+ // The unwaived crossing — a waived `over` is present in the judgement
317
+ // and deliberately not the one a refusal is written about.
318
+ const over = judged.judgements.find((entry) => entry.verdict === 'over' && entry.waived === null)!;
319
+ /*
320
+ A configured substitution is deliberately NOT consulted here. The
321
+ operator wrote that rule for the budget; rerouting a call because a
322
+ *session* ceiling crossed would spend more money in the very unit that
323
+ is over, under a rule written about something else.
324
+ */
325
+ return {
326
+ kind: 'refuse',
327
+ reason: 'limit-over',
328
+ cause: null,
329
+ restsOn: over.restsOn,
330
+ standing,
331
+ estimatedUsd,
332
+ alternatives: model === undefined ? [] : alternativesFor(model, call, catalogue, on),
333
+ because: limitSentence(over),
334
+ policy: judged,
335
+ };
336
+ }
337
+
338
+ const limitUnjudged = judged.verdict === 'cannot-tell' && judged.reason !== 'no-policy';
339
+ if (limitUnjudged && policy.onCannotTell === 'fail-closed') {
340
+ return {
341
+ kind: 'refuse',
342
+ reason: 'cannot-tell-and-closed',
343
+ cause: 'limit-unjudged',
344
+ restsOn: null,
345
+ standing,
346
+ estimatedUsd,
347
+ alternatives: [],
348
+ because: `${unjudgedSentence(judged)} This gateway is configured to fail closed.`,
349
+ policy: judged,
350
+ };
351
+ }
352
+
261
353
  const cause = whyCannotTell(standing, estimatedUsd);
262
354
  if (cause !== null) {
263
355
  /**
@@ -267,7 +359,7 @@ export function gatewayDecision(
267
359
  * nothing to do with the caller's request.
268
360
  */
269
361
  if (policy.onCannotTell === 'fail-open') {
270
- return { kind: 'forward', estimatedUsd, unjudged: cause };
362
+ return { kind: 'forward', estimatedUsd, unjudged: cause, policy: judged };
271
363
  }
272
364
  return {
273
365
  kind: 'refuse',
@@ -278,6 +370,7 @@ export function gatewayDecision(
278
370
  estimatedUsd,
279
371
  alternatives: [],
280
372
  because: becauseCannotTell(cause),
373
+ policy: judged,
281
374
  };
282
375
  }
283
376
 
@@ -285,13 +378,21 @@ export function gatewayDecision(
285
378
  // it — narrowed here rather than asserted, so a change to `whyCannotTell`
286
379
  // that stopped guaranteeing them fails the build instead of the request.
287
380
  if (standing === null || estimatedUsd === null || model === undefined) {
288
- return { kind: 'forward', estimatedUsd, unjudged: 'no-budget' };
381
+ return {
382
+ kind: 'forward',
383
+ estimatedUsd,
384
+ unjudged: limitUnjudged ? 'limit-unjudged' : 'no-budget',
385
+ policy: judged,
386
+ };
289
387
  }
290
388
 
291
389
  const already = standing.consumedUsd > standing.limitUsd;
292
390
  const wouldCross = standing.consumedUsd + estimatedUsd > standing.limitUsd;
293
391
  if (!already && !wouldCross) {
294
- return { kind: 'forward', estimatedUsd, unjudged: null };
392
+ // The monthly budget holds. A limits ceiling nobody could judge still
393
+ // rides along as `unjudged` under fail-open, so the forward says what it
394
+ // did not check rather than implying everything was.
395
+ return { kind: 'forward', estimatedUsd, unjudged: limitUnjudged ? 'limit-unjudged' : null, policy: judged };
295
396
  }
296
397
 
297
398
  const configured = policy.substitute?.[call.model];
@@ -303,6 +404,7 @@ export function gatewayDecision(
303
404
  configuredReason: configured.reason,
304
405
  estimatedUsd: priceCall(target, call, on),
305
406
  markedInStore: true,
407
+ policy: judged,
306
408
  };
307
409
  }
308
410
 
@@ -319,9 +421,11 @@ export function gatewayDecision(
319
421
  because: already
320
422
  ? 'The budget for this period is already spent, measured.'
321
423
  : 'This call would take the budget past its limit, on an estimate of this call.',
424
+ policy: judged,
322
425
  };
323
426
  }
324
427
 
428
+
325
429
  function becauseCannotTell(cause: CannotTellCause): string {
326
430
  return cause === 'no-budget'
327
431
  ? 'No budget is configured, so there is nothing to judge this against, and this gateway is configured to fail closed.'