create-quorum-router 0.1.5 → 0.1.8

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/README.md CHANGED
@@ -12,10 +12,12 @@ npx --yes create-quorum-router@latest my-quorum-router-demo
12
12
  cd my-quorum-router-demo
13
13
  deno --version
14
14
  deno task smoke
15
+ deno task calibration:demo
15
16
  deno task intake
17
+ deno task supabase:status
16
18
  ```
17
19
 
18
- Current package version: `create-quorum-router@0.1.5`. Releases are published
20
+ Current package version: `create-quorum-router@0.1.8`. Releases are published
19
21
  from an immutable Git tag through GitHub Actions OIDC Trusted Publishing.
20
22
 
21
23
  ## What the generated project supports
@@ -23,6 +25,10 @@ from an immutable Git tag through GitHub Actions OIDC Trusted Publishing.
23
25
  `deno task smoke` is deterministic fixture-only and does not call a provider
24
26
  API.
25
27
 
28
+ `deno task calibration:demo` runs the bundled calibration-by-task API against
29
+ deterministic local observations. The report is advisory-only and is not
30
+ connected to routing weights, provider eligibility, or execution.
31
+
26
32
  `deno task intake` is the first real setup command. It detects local provider
27
33
  wrappers, checks OAuth/session status, runs safe list-only model inventory where
28
34
  possible, writes redacted local health artifacts under `out/`, and recommends
@@ -31,6 +37,7 @@ the next command.
31
37
  ```bash
32
38
  deno task check
33
39
  deno task smoke
40
+ deno task calibration:demo
34
41
  deno task intake
35
42
  deno task auth:status
36
43
  deno task auth:login
@@ -63,6 +70,19 @@ preferred public dogfood path.
63
70
  Do not commit `.env`, `router.config.local.json`, `.quorum-router/`, or `out/`.
64
71
  Do not paste tokens into chat/logs.
65
72
 
73
+ ## Optional user-owned Supabase audit
74
+
75
+ The generated project works without Supabase. Audit defaults to `disabled`.
76
+ Users who opt in apply the generated migration to a Supabase project they own,
77
+ choose `optional` or `required` in the non-secret feature config, inject a
78
+ project URL, publishable/anon key, and Supabase Auth session JWT at runtime, and
79
+ run `deno task supabase:status` before routing.
80
+
81
+ The runtime rejects service-role/admin credentials. Audit records exclude
82
+ prompts, model responses, credentials, client-supplied actor/org identity, and
83
+ client timestamps. Agent Bus, Realtime, state sync, and analytics dashboards are
84
+ not part of this integration.
85
+
66
86
  ## Runtime boundaries
67
87
 
68
88
  - Best Route/direct is production-ready best-answer routing.
@@ -71,7 +91,7 @@ Do not paste tokens into chat/logs.
71
91
  slice only when separately configured with signed policy and distinct
72
92
  approval.
73
93
  - The generated scaffold does not enable mutation by default.
74
- - No service-role runtime.
94
+ - No service-role/admin runtime credentials.
75
95
  - No live Supabase Agent Bus runtime writes.
76
96
  - Public launch requires the repository verification, package tarball, registry
77
97
  readback, and clean-room NPX scaffold checks to pass.
@@ -3,7 +3,7 @@ const fs = require("fs");
3
3
  const path = require("path");
4
4
  const { spawnSync } = require("child_process");
5
5
 
6
- const VERSION = "0.1.5";
6
+ const VERSION = "0.1.8";
7
7
  const SUPPORTED_TEMPLATES = new Set(["basic"]);
8
8
 
9
9
  function usage() {
@@ -56,13 +56,33 @@ function parseArgs(argv) {
56
56
  return options;
57
57
  }
58
58
 
59
- function copyRecursive(from, to) {
59
+ function rejectSymlink(destination) {
60
+ try {
61
+ if (fs.lstatSync(destination).isSymbolicLink()) {
62
+ throw new Error(`refusing to write through symlink: ${destination}`);
63
+ }
64
+ } catch (error) {
65
+ if (error && error.code === "ENOENT") return;
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ function copyRecursive(from, to, targetRoot) {
71
+ const relative = path.relative(targetRoot, to);
72
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
73
+ throw new Error(`refusing to write outside target directory: ${to}`);
74
+ }
75
+ rejectSymlink(to);
60
76
  const stat = fs.statSync(from);
61
77
  if (stat.isDirectory()) {
62
78
  fs.mkdirSync(to, { recursive: true });
63
79
  for (const entry of fs.readdirSync(from)) {
64
80
  const targetEntry = entry === "gitignore" ? ".gitignore" : entry;
65
- copyRecursive(path.join(from, entry), path.join(to, targetEntry));
81
+ copyRecursive(
82
+ path.join(from, entry),
83
+ path.join(to, targetEntry),
84
+ targetRoot,
85
+ );
66
86
  }
67
87
  return;
68
88
  }
@@ -97,6 +117,7 @@ function main() {
97
117
  }
98
118
 
99
119
  const targetDir = path.resolve(process.cwd(), parsed.dir);
120
+ rejectSymlink(targetDir);
100
121
  if (isNonEmptyDirectory(targetDir) && !parsed.force) {
101
122
  throw new Error(
102
123
  `refusing to overwrite non-empty directory: ${targetDir}\n` +
@@ -106,7 +127,7 @@ function main() {
106
127
 
107
128
  const templateDir = path.join(__dirname, "..", "templates", parsed.template);
108
129
  fs.mkdirSync(targetDir, { recursive: true });
109
- copyRecursive(templateDir, targetDir);
130
+ copyRecursive(templateDir, targetDir, targetDir);
110
131
  fs.mkdirSync(path.join(targetDir, "out"), { recursive: true });
111
132
  fs.writeFileSync(path.join(targetDir, "out", ".gitkeep"), "");
112
133
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-quorum-router",
3
- "version": "0.1.5",
3
+ "version": "0.1.8",
4
4
  "description": "Create a local QuorumRouter project.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  # QuorumRouter generated workspace
2
2
 
3
3
  This generated workspace contains the MIT-licensed QuorumRouter current release.
4
- npm latest targets v0.1.5.
4
+ npm latest targets v0.1.8.
5
5
 
6
6
  QuorumRouter is **MIT**. It is **open source**. Commercial and production use
7
7
  are permitted under the MIT License.
@@ -19,8 +19,10 @@ are permitted under the MIT License.
19
19
  - Conversation-only `agent_chat` is explicit opt-in.
20
20
  - SafeLoop-backed production repository execution is not enabled by this
21
21
  generated scaffold; it requires external signed policy and distinct approval.
22
- - No service-role runtime.
23
- - No live Supabase runtime writes.
22
+ - No service-role runtime; service-role/admin credentials are rejected even when
23
+ audit is disabled.
24
+ - BYO Supabase audit is disabled by default and writes only when explicitly set
25
+ to `optional` or `required`.
24
26
  - No live Supabase Agent Bus runtime writes.
25
27
  - Best Route/direct is the production-ready best-answer routing path.
26
28
  - `agent-chat` is read-only explicit opt-in only.
@@ -35,15 +37,22 @@ deno --version
35
37
 
36
38
  ```bash
37
39
  deno task smoke
40
+ deno task calibration:demo
38
41
  deno task intake
39
42
  deno task auth:status
40
43
  deno task models:list
41
44
  deno task health
45
+ deno task supabase:status
42
46
  ```
43
47
 
44
48
  `smoke` proves the local scaffold runs with deterministic fixtures only. It does
45
49
  **not** call a real provider API.
46
50
 
51
+ `calibration:demo` exercises the bundled calibration-by-task API with local
52
+ fixture observations. Calibration reports are advisory-only: the scaffold does
53
+ not use them to change routing weights, ranks, provider eligibility, quorum, or
54
+ execution.
55
+
47
56
  `intake` detects local provider wrappers, checks OAuth/session status, runs safe
48
57
  model inventory/list-only probes where possible, writes local health traces
49
58
  under `out/`, and recommends the next command.
@@ -51,6 +60,37 @@ under `out/`, and recommends the next command.
51
60
  In short: intake is the first real setup command before `route:once`,
52
61
  `best-route`, or read-only `agent-chat`.
53
62
 
63
+ ## Optional BYO Supabase audit
64
+
65
+ Supabase is not required. With no Supabase config or env, every existing task
66
+ keeps its prior behavior and `deno task supabase:status` exits successfully with
67
+ `state: disabled` without making a network request.
68
+
69
+ To persist route outcomes in a Supabase project you own:
70
+
71
+ 1. Create or choose your Supabase project.
72
+ 2. Apply **both** files under `supabase/migrations/` in filename order using an
73
+ admin context outside the router runtime. The later limits migration is
74
+ mandatory.
75
+ 3. Ensure the active Supabase Auth user session JWT has an authenticated `sub`.
76
+ 4. Copy `router.config.example.json` to `router.config.json` and set only
77
+ `features.supabase.audit.mode` to `optional` or `required`.
78
+ 5. Inject `QUORUM_ROUTER_SUPABASE_URL`, `QUORUM_ROUTER_SUPABASE_ANON_KEY`, and
79
+ `QUORUM_ROUTER_SUPABASE_SESSION_JWT` at runtime.
80
+ 6. Run `deno task supabase:status`, then run `route:once` or `best-route`.
81
+
82
+ `optional` preserves a successful route and prints a warning when audit delivery
83
+ fails. `required` withholds the route result and exits nonzero when audit
84
+ delivery fails. The RPC receives only the route decision and bounded metadata.
85
+ It never receives prompts, model responses, credentials, `org_id`, `actor_id`,
86
+ or `created_at`; the database derives both the actor and this single-user BYO
87
+ audit namespace from `auth.uid()` and owns the timestamp. There is no central or
88
+ shared database and no client tenant claim.
89
+
90
+ Runtime service-role/admin credentials are forbidden. Agent Bus, Realtime
91
+ wakeup, state sync, and an analytics dashboard are future features and are not
92
+ enabled by this audit integration.
93
+
54
94
  ## Real provider dogfood commands
55
95
 
56
96
  Run only after `intake` reports a usable OAuth/session/wrapper provider:
@@ -87,6 +127,25 @@ Behavior:
87
127
  Only use this with repositories you are allowed to send to the selected
88
128
  provider.
89
129
 
130
+ ### Cost-aware Best Route
131
+
132
+ Cost-aware routing is disabled unless both budget variables are set. Estimates
133
+ are user-supplied per invocation, not live provider billing data.
134
+
135
+ ```bash
136
+ QUORUM_ROUTER_MAX_BUDGET_USD=0.05 \
137
+ QUORUM_ROUTER_ESTIMATED_COSTS_JSON='{"openai/gpt-5":0.03,"xai/grok":0.02}' \
138
+ RUN_EXTERNAL_MODEL_DOGFOOD=1 \
139
+ deno task best-route --prompt "Choose the safest launch copy."
140
+ ```
141
+
142
+ Best Route preserves candidate quality/readiness order, admits candidates while
143
+ their configured estimates fit the budget, and records selected and excluded
144
+ model IDs plus reasons in `out/best-route-trace.json`. Models without an
145
+ estimate are excluded when cost-aware routing is enabled. If no candidate fits,
146
+ the command fails before invoking a provider. This is pre-invocation budget
147
+ control; it does not claim exact or live API spend.
148
+
90
149
  ### Forced wrapper provider/model selection
91
150
 
92
151
  Use these only when you want a specific local wrapper/model. The scaffold fails
@@ -152,17 +211,24 @@ not paste them into chat/logs and do not commit `.env`.
152
211
  - `.gitignore` — excludes `.env`, `out/`, `router.config.local.json`,
153
212
  `provider_config.json`, and `.quorum-router/`.
154
213
  - `router.config.example.json` — non-secret example boundaries.
214
+ - `supabase/migrations/20260701130000_workflow_access_audit.sql` — optional BYO
215
+ Supabase audit migration
216
+ - `supabase/migrations/20260712211500_workflow_access_audit_limits.sql` —
217
+ required RPC narrowing and payload-limit migration
155
218
  - `src/cli.ts` — command dispatcher.
219
+ - `src/supabase.ts` — offline status and selective audit RPC hook.
156
220
  - `src/intake.ts` — first-run onboarding.
157
221
  - `src/auth.ts`, `src/auth_oauth.ts`, `src/auth_session.ts`,
158
222
  `src/auth_env_fallback.ts` — auth/session/fallback boundaries.
159
223
  - `src/provider_registry.ts`, `src/model_inventory.ts`, `src/wrapper_client.ts`,
160
224
  `src/provider_client.ts` — provider discovery and safe invocation.
161
225
  - `src/best_route.ts`, `src/agent_chat.ts` — gated dogfood commands.
226
+ - `src/cost_aware.ts` — estimated-cost budget selection for Best Route.
227
+ - `src/calibration.ts`, `src/calibration_demo.ts` — strict advisory calibration
228
+ aggregation and an offline runnable example.
162
229
  - `src/trace.ts`, `src/redact.ts`, `src/schema.ts`, `src/fixture_smoke.ts` —
163
230
  trace/redaction/schema/fixture support.
164
231
  - `out/.gitkeep` — local output directory placeholder.
165
232
 
166
- Public Product Hunt/X launch remains blocked until the user personally runs a
167
- local pre-release workspace and approves release continuation. This scaffold
168
- does not publish npm, create a GitHub release, or mutate tags/dist-tags.
233
+ This generated scaffold operates locally. It does not publish npm packages,
234
+ create GitHub releases, or mutate tags or dist-tags.
@@ -6,12 +6,14 @@
6
6
  "tasks": {
7
7
  "check": "deno check main.ts src/*.ts",
8
8
  "smoke": "deno run main.ts",
9
+ "calibration:demo": "deno run src/calibration_demo.ts",
9
10
  "intake": "deno run --allow-read --allow-write --allow-env --allow-net --allow-run=grok,codex,claude,gemini,qwen,devin src/cli.ts intake",
10
11
  "auth:status": "deno run --allow-read --allow-env --allow-run=grok,codex,claude,gemini,qwen,devin src/cli.ts auth:status",
11
12
  "auth:login": "deno run src/cli.ts auth:login",
12
13
  "auth:logout": "deno run --allow-read --allow-write --allow-env src/cli.ts auth:logout",
13
14
  "models:list": "deno run --allow-read --allow-write --allow-env --allow-run=grok,codex,claude,gemini,qwen,devin src/cli.ts models:list",
14
15
  "health": "deno run --allow-read --allow-write --allow-env --allow-run=grok,codex,claude,gemini,qwen,devin src/cli.ts health",
16
+ "supabase:status": "deno run --allow-read --allow-env src/cli.ts supabase:status",
15
17
  "route:once": "deno run --allow-read --allow-write --allow-env --allow-net --allow-run=grok,codex,claude,gemini,qwen,devin src/cli.ts route:once",
16
18
  "best-route": "deno run --allow-read --allow-write --allow-env --allow-net --allow-run=grok,codex,claude,gemini,qwen,devin src/cli.ts best-route",
17
19
  "agent-chat": "deno run --allow-read --allow-write --allow-env --allow-net --allow-run=grok,codex,claude,gemini,qwen,devin src/cli.ts agent-chat"
@@ -5,6 +5,17 @@
5
5
  "enabled_by": "QUORUM_ROUTER_AUTH_MODE=env",
6
6
  "usage": "private manual fallback only"
7
7
  },
8
+ "features": {
9
+ "supabase": {
10
+ "audit": {
11
+ "mode": "disabled"
12
+ }
13
+ }
14
+ },
15
+ "cost_aware": {
16
+ "enabled_by": "QUORUM_ROUTER_MAX_BUDGET_USD + QUORUM_ROUTER_ESTIMATED_COSTS_JSON",
17
+ "pricing_source": "user-supplied estimates, not live billing"
18
+ },
8
19
  "runtime_boundaries": {
9
20
  "best_route_direct": "production-ready best-answer routing",
10
21
  "agent_chat": "experimental explicit opt-in only",
@@ -88,6 +88,7 @@ export function discoverInventory(
88
88
  can_invoke: exists,
89
89
  command: spec.command,
90
90
  args_template: spec.args_template,
91
+ prompt_transport: spec.prompt_transport,
91
92
  notes: exists
92
93
  ? [
93
94
  ...spec.notes,
@@ -21,6 +21,7 @@ import {
21
21
  import { buildTrace, score, writeTrace } from "./trace.ts";
22
22
  import { callWrapper } from "./wrapper_client.ts";
23
23
  import { preparePromptWithContext } from "./context.ts";
24
+ import { selectCandidatesWithinBudget } from "./cost_aware.ts";
24
25
 
25
26
  function hasExplicitSelection(request: ProviderSelectionRequest): boolean {
26
27
  return Boolean(
@@ -261,7 +262,12 @@ export async function runBestRoute(
261
262
  > {
262
263
  assertOptIn();
263
264
  const authMode = parseAuthMode(readRouterEnv("QUORUM_ROUTER_AUTH_MODE"));
264
- const { request, candidates } = await discoverCandidates(authMode);
265
+ const { request, candidates: discoveredCandidates } =
266
+ await discoverCandidates(
267
+ authMode,
268
+ );
269
+ const costAwareDecision = selectCandidatesWithinBudget(discoveredCandidates);
270
+ const candidates = costAwareDecision.candidates;
265
271
  if (candidates.length === 0) {
266
272
  throw new Error(
267
273
  "OAuth/session-first provider unavailable. best-route has no usable OAuth/session/wrapper provider yet. Next: deno task auth:login",
@@ -316,6 +322,9 @@ export async function runBestRoute(
316
322
  selectionHonored(request, result)
317
323
  ),
318
324
  fallbackUsed: usedEnvFallback,
325
+ costAware: costAwareDecision.cost.enabled
326
+ ? costAwareDecision.cost
327
+ : undefined,
319
328
  });
320
329
  const tracePath = await writeTrace("best-route-trace", trace);
321
330
  return { results, tracePath, trace };
@@ -0,0 +1,249 @@
1
+ import { z } from "zod";
2
+
3
+ export const MAX_CALIBRATION_OBSERVATIONS = 10_000;
4
+ export const MAX_CALIBRATION_IDENTIFIER_LENGTH = 256;
5
+
6
+ function stableMetric(value: number): number {
7
+ const rounded = Number(value.toPrecision(15));
8
+ return rounded === 0 ? 0 : rounded;
9
+ }
10
+
11
+ function boundedCanonicalText(maxLength: number) {
12
+ return z.string().trim().transform((value) => value.normalize("NFC")).pipe(
13
+ z.string().min(1).max(maxLength).refine(
14
+ (value) => !/[\p{Cc}\p{Cf}\p{Default_Ignorable_Code_Point}]/u.test(value),
15
+ "control and default-ignorable characters are not allowed",
16
+ ),
17
+ );
18
+ }
19
+
20
+ function hasValidRfc3339Offset(value: string): boolean {
21
+ if (value.endsWith("Z")) return true;
22
+ const match = /[+-](\d{2}):(\d{2})$/.exec(value);
23
+ if (!match) return false;
24
+ return Number(match[1]) <= 23 && Number(match[2]) <= 59;
25
+ }
26
+
27
+ const ObservationIdSchema = boundedCanonicalText(
28
+ MAX_CALIBRATION_IDENTIFIER_LENGTH,
29
+ );
30
+ const TaskTypeSchema = boundedCanonicalText(MAX_CALIBRATION_IDENTIFIER_LENGTH);
31
+ const SourceLabelSchema = boundedCanonicalText(
32
+ MAX_CALIBRATION_IDENTIFIER_LENGTH,
33
+ );
34
+ const EvaluationTimestampSchema = z.string().datetime({ offset: true }).refine(
35
+ hasValidRfc3339Offset,
36
+ "evaluated_at must use a valid RFC 3339 UTC offset",
37
+ );
38
+
39
+ export const TaskCalibrationSourceSchema = z.object({
40
+ provider: SourceLabelSchema,
41
+ model: SourceLabelSchema,
42
+ }).strict();
43
+
44
+ /**
45
+ * Structurally validated calibration input supplied by the caller.
46
+ *
47
+ * `evaluation_basis` records an unverified provenance assertion; this schema
48
+ * cannot establish label independence, label quality, example matching, or
49
+ * freedom from leakage. `confidence` must be the model's probability, recorded
50
+ * before the correctness label is observed, that this specific answer is
51
+ * correct. The schema can enforce only the numeric range, not that provenance.
52
+ */
53
+ export const TaskCalibrationObservationSchema = z.object({
54
+ observation_id: ObservationIdSchema,
55
+ task_type: TaskTypeSchema,
56
+ source: TaskCalibrationSourceSchema,
57
+ evaluation_basis: z.literal("caller_attested_external_ground_truth"),
58
+ correct: z.boolean(),
59
+ confidence: z.number().finite().min(0).max(1),
60
+ evaluated_at: EvaluationTimestampSchema,
61
+ }).strict();
62
+
63
+ export const TaskCalibrationObservationListSchema = z.array(
64
+ TaskCalibrationObservationSchema,
65
+ ).max(MAX_CALIBRATION_OBSERVATIONS).superRefine((observations, context) => {
66
+ const ids = new Set<string>();
67
+ for (const [index, observation] of observations.entries()) {
68
+ if (ids.has(observation.observation_id)) {
69
+ context.addIssue({
70
+ code: "custom",
71
+ message: "observation_id must be unique",
72
+ path: [index, "observation_id"],
73
+ });
74
+ }
75
+ ids.add(observation.observation_id);
76
+ }
77
+ });
78
+
79
+ export const TaskCalibrationOptionsSchema = z.object({
80
+ minimum_sample_count: z.number().finite().int().positive().max(
81
+ MAX_CALIBRATION_OBSERVATIONS,
82
+ ).default(20),
83
+ }).strict();
84
+
85
+ export const TaskCalibrationGroupSchema = z.object({
86
+ task_type: TaskTypeSchema,
87
+ source: TaskCalibrationSourceSchema,
88
+ sample_count: z.number().int().positive().max(MAX_CALIBRATION_OBSERVATIONS),
89
+ accuracy: z.number().finite().min(0).max(1),
90
+ mean_confidence: z.number().finite().min(0).max(1),
91
+ brier_score: z.number().finite().min(0).max(1),
92
+ mean_calibration_bias: z.number().finite().min(-1).max(1),
93
+ // "sufficient" means only that the configured sample-count threshold is met.
94
+ sample_status: z.enum(["insufficient", "sufficient"]),
95
+ }).strict();
96
+
97
+ export const TaskCalibrationReportSchema = z.object({
98
+ schema_version: z.literal("quorum-router.calibration-by-task.v1"),
99
+ advisory_only: z.literal(true),
100
+ minimum_sample_count: z.number().int().positive().max(
101
+ MAX_CALIBRATION_OBSERVATIONS,
102
+ ),
103
+ groups: z.array(TaskCalibrationGroupSchema).max(MAX_CALIBRATION_OBSERVATIONS),
104
+ }).strict().superRefine((report, context) => {
105
+ const groupKeys = new Set<string>();
106
+ for (const [index, group] of report.groups.entries()) {
107
+ const groupKey = JSON.stringify([
108
+ group.task_type,
109
+ group.source.provider,
110
+ group.source.model,
111
+ ]);
112
+ if (groupKeys.has(groupKey)) {
113
+ context.addIssue({
114
+ code: "custom",
115
+ message: "calibration report groups must be unique",
116
+ path: ["groups", index],
117
+ });
118
+ }
119
+ groupKeys.add(groupKey);
120
+
121
+ const expectedStatus = group.sample_count < report.minimum_sample_count
122
+ ? "insufficient"
123
+ : "sufficient";
124
+ if (group.sample_status !== expectedStatus) {
125
+ context.addIssue({
126
+ code: "custom",
127
+ message: "sample_status must match the configured sample threshold",
128
+ path: ["groups", index, "sample_status"],
129
+ });
130
+ }
131
+
132
+ const expectedBias = stableMetric(
133
+ group.mean_confidence - group.accuracy,
134
+ );
135
+ if (group.mean_calibration_bias !== expectedBias) {
136
+ context.addIssue({
137
+ code: "custom",
138
+ message: "mean_calibration_bias must equal mean_confidence - accuracy",
139
+ path: ["groups", index, "mean_calibration_bias"],
140
+ });
141
+ }
142
+ }
143
+ });
144
+
145
+ export type TaskCalibrationSource = z.infer<
146
+ typeof TaskCalibrationSourceSchema
147
+ >;
148
+ export type TaskCalibrationObservation = z.infer<
149
+ typeof TaskCalibrationObservationSchema
150
+ >;
151
+ export type TaskCalibrationOptions = z.input<
152
+ typeof TaskCalibrationOptionsSchema
153
+ >;
154
+ export type TaskCalibrationGroup = z.infer<
155
+ typeof TaskCalibrationGroupSchema
156
+ >;
157
+ export type TaskCalibrationReport = z.infer<
158
+ typeof TaskCalibrationReportSchema
159
+ >;
160
+
161
+ function stableSum(values: readonly number[]): number {
162
+ const sorted = [...values].sort((left, right) => left - right);
163
+ let total = 0;
164
+ let compensation = 0;
165
+
166
+ for (const value of sorted) {
167
+ const next = total + value;
168
+ compensation += Math.abs(total) >= Math.abs(value)
169
+ ? (total - next) + value
170
+ : (value - next) + total;
171
+ total = next;
172
+ }
173
+
174
+ return total + compensation;
175
+ }
176
+
177
+ function compareCodeUnits(left: string, right: string): number {
178
+ if (left < right) return -1;
179
+ if (left > right) return 1;
180
+ return 0;
181
+ }
182
+
183
+ export function aggregateTaskCalibration(
184
+ observations: readonly unknown[],
185
+ options: TaskCalibrationOptions = {},
186
+ ): TaskCalibrationReport {
187
+ const parsedObservations = TaskCalibrationObservationListSchema.parse(
188
+ observations,
189
+ );
190
+ const { minimum_sample_count } = TaskCalibrationOptionsSchema.parse(options);
191
+ const grouped = new Map<string, TaskCalibrationObservation[]>();
192
+
193
+ for (const observation of parsedObservations) {
194
+ const key = JSON.stringify([
195
+ observation.task_type,
196
+ observation.source.provider,
197
+ observation.source.model,
198
+ ]);
199
+ const group = grouped.get(key);
200
+ if (group) {
201
+ group.push(observation);
202
+ } else {
203
+ grouped.set(key, [observation]);
204
+ }
205
+ }
206
+
207
+ const groups = [...grouped.values()].map((group) => {
208
+ const sample_count = group.length;
209
+ const correctCount = group.reduce(
210
+ (total, observation) => total + Number(observation.correct),
211
+ 0,
212
+ );
213
+ const confidenceTotal = stableSum(
214
+ group.map((observation) => observation.confidence),
215
+ );
216
+ const brierTotal = stableSum(
217
+ group.map((observation) => {
218
+ const outcome = Number(observation.correct);
219
+ return (observation.confidence - outcome) ** 2;
220
+ }),
221
+ );
222
+ const accuracy = stableMetric(correctCount / sample_count);
223
+ const mean_confidence = stableMetric(confidenceTotal / sample_count);
224
+
225
+ return {
226
+ task_type: group[0].task_type,
227
+ source: group[0].source,
228
+ sample_count,
229
+ accuracy,
230
+ mean_confidence,
231
+ brier_score: stableMetric(brierTotal / sample_count),
232
+ mean_calibration_bias: stableMetric(mean_confidence - accuracy),
233
+ sample_status: sample_count < minimum_sample_count
234
+ ? "insufficient" as const
235
+ : "sufficient" as const,
236
+ };
237
+ }).sort((left, right) =>
238
+ compareCodeUnits(left.task_type, right.task_type) ||
239
+ compareCodeUnits(left.source.provider, right.source.provider) ||
240
+ compareCodeUnits(left.source.model, right.source.model)
241
+ );
242
+
243
+ return TaskCalibrationReportSchema.parse({
244
+ schema_version: "quorum-router.calibration-by-task.v1",
245
+ advisory_only: true,
246
+ minimum_sample_count,
247
+ groups,
248
+ });
249
+ }
@@ -0,0 +1,25 @@
1
+ import { aggregateTaskCalibration } from "./calibration.ts";
2
+
3
+ const evaluated_at = "2026-07-13T00:00:00Z";
4
+ const report = aggregateTaskCalibration([
5
+ {
6
+ observation_id: "demo-1",
7
+ task_type: "code-review",
8
+ source: { provider: "OpenAI", model: "example-model" },
9
+ evaluation_basis: "caller_attested_external_ground_truth",
10
+ correct: true,
11
+ confidence: 0.8,
12
+ evaluated_at,
13
+ },
14
+ {
15
+ observation_id: "demo-2",
16
+ task_type: "code-review",
17
+ source: { provider: "OpenAI", model: "example-model" },
18
+ evaluation_basis: "caller_attested_external_ground_truth",
19
+ correct: false,
20
+ confidence: 0.6,
21
+ evaluated_at,
22
+ },
23
+ ], { minimum_sample_count: 2 });
24
+
25
+ console.log(JSON.stringify(report, null, 2));
@@ -11,6 +11,11 @@ import {
11
11
  } from "./model_inventory.ts";
12
12
  import { redact, summarize } from "./redact.ts";
13
13
  import { parseAuthMode } from "./schema.ts";
14
+ import {
15
+ auditRouteOutcome,
16
+ preflightRequiredSupabaseAudit,
17
+ runSupabaseStatus,
18
+ } from "./supabase.ts";
14
19
  import { buildTrace, writeTrace } from "./trace.ts";
15
20
 
16
21
  const DEFAULT_PROMPT = "Review this README for risky claims.";
@@ -22,6 +27,7 @@ type CommandName =
22
27
  | "auth:logout"
23
28
  | "models:list"
24
29
  | "health"
30
+ | "supabase:status"
25
31
  | "route:once"
26
32
  | "best-route"
27
33
  | "agent-chat";
@@ -32,11 +38,12 @@ function commandName(): CommandName {
32
38
  command === "intake" || command === "auth:status" ||
33
39
  command === "auth:login" || command === "auth:logout" ||
34
40
  command === "models:list" || command === "health" ||
41
+ command === "supabase:status" ||
35
42
  command === "route:once" || command === "best-route" ||
36
43
  command === "agent-chat"
37
44
  ) return command;
38
45
  throw new Error(
39
- "usage: deno task intake|auth:status|auth:login|auth:logout|models:list|health|route:once|best-route|agent-chat",
46
+ "usage: deno task intake|auth:status|auth:login|auth:logout|models:list|health|supabase:status|route:once|best-route|agent-chat",
40
47
  );
41
48
  }
42
49
 
@@ -101,10 +108,13 @@ try {
101
108
  else if (command === "auth:logout") await runAuthLogout();
102
109
  else if (command === "models:list") await runModelsList();
103
110
  else if (command === "health") await runHealth();
111
+ else if (command === "supabase:status") await runSupabaseStatus();
104
112
  else if (command === "route:once") {
113
+ await preflightRequiredSupabaseAudit();
105
114
  const { results, tracePath, trace } = await invokeSelected(
106
115
  promptFromArgs(),
107
116
  );
117
+ await auditRouteOutcome(trace);
108
118
  console.log("QuorumRouter route:once");
109
119
  console.log(`provider: ${results[0].provider}`);
110
120
  console.log(`model: ${results[0].model}`);
@@ -116,7 +126,9 @@ try {
116
126
  console.log(`final: ${summarize(results[0].response_summary, 500)}`);
117
127
  console.log(`trace: ${tracePath}`);
118
128
  } else if (command === "best-route") {
119
- const { results, tracePath } = await runBestRoute(promptFromArgs());
129
+ await preflightRequiredSupabaseAudit();
130
+ const { results, tracePath, trace } = await runBestRoute(promptFromArgs());
131
+ await auditRouteOutcome(trace);
120
132
  console.log("QuorumRouter best-route");
121
133
  console.log(`models_called: ${results.length}`);
122
134
  console.log(`trace: ${tracePath}`);