model-runtime-lane.mjs file-level

at sha256:3 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 πŸ’₯ blast risk
sha256:6 feat(calendar): enforce agent context tiers in retrieval API (Phase 1E)… · aaronrene · Jun 18, 2026
1 /**
2 * Model-runtime lane selection, consent enforcement, and metering boundary.
3 *
4 * Phase 1 of the Companion App build plan (feat/companion-app).
5 * Implements:
6 * - D2.2 default-lane selection logic (selectLane)
7 * - D1.2 metering boundary: which lanes bill against Knowtation packs (isManagedLane)
8 * - D1.4 consent and workspace-policy gate (enforceConsentPolicy)
9 *
10 * DESIGN CONSTRAINTS (read before modifying):
11 * - Pure functions only β€” no I/O, no process.env reads, no network.
12 * - All inputs are passed explicitly so functions are composable at every layer
13 * (browser tab, companion app, gateway, CLI) without environment coupling.
14 * - Unknown / absent capability fields default to false: fail-closed, never fail-open.
15 * - See docs/COMPANION-APP-PHASE-1-ADAPTER-SEAM.md for the full seam contract.
16 *
17 * Hard constraint from docs/COMPANION-APP-MODEL-ROUTING-AND-ENRICHMENT-ARCHITECTURE.md Β§3:
18 * The cloud gateway NEVER proxies local inference. Local/companion lanes are invoked
19 * only by something on the user's machine. selectLane reflects this β€” it returns 'local'
20 * only when inBrowserAvailable or companionAvailable is true (client-side evidence).
21 */
22
23 /**
24 * The runtime lane identifiers β€” must match Scooling's runtimeLaneSchema exactly (D2.4).
25 * @readonly
26 */
27 export const RUNTIME_LANES = /** @type {const} */ ([
28 'local',
29 'self_hosted',
30 'enterprise',
31 'openrouter',
32 'direct_provider',
33 'disabled',
34 ]);
35
36 /**
37 * @typedef {'local'|'self_hosted'|'enterprise'|'openrouter'|'direct_provider'|'disabled'} RuntimeLane
38 */
39
40 /**
41 * Runtime capability snapshot.
42 * Describes what inference resources are available in THIS execution context.
43 * All fields default to false β€” unknown = unavailable (fail-closed).
44 *
45 * @typedef {Object} LaneCapabilities
46 * @property {boolean} [inBrowserAvailable]
47 * WebGPU/WebLLM is usable in the current browser context.
48 * Always false in Node.js, gateway, and companion process contexts.
49 * @property {boolean} [companionAvailable]
50 * Companion loopback endpoint confirmed reachable (prior health-check passed).
51 * Never true in cloud/gateway contexts β€” the cloud cannot reach localhost (Β§3 constraint).
52 * @property {boolean} [selfHostedAvailable]
53 * Self-hosted or enterprise endpoint configured and confirmed reachable.
54 * @property {boolean} [enterpriseAvailable]
55 * Enterprise endpoint configured and confirmed reachable.
56 * @property {boolean} [openrouterKeyAvailable]
57 * OPENROUTER_API_KEY is present. Enables the BYO-key lane (user's own provider contract).
58 * @property {boolean} [managedKeyAvailable]
59 * At least one managed cloud key (DeepInfra/OpenAI/Anthropic) is available AND the
60 * operator has not locked out the managed lane.
61 */
62
63 /**
64 * Workspace/user lane preferences.
65 * Expressed by the authenticated user or enforced by workspace policy.
66 *
67 * @typedef {Object} LanePreferences
68 * @property {boolean} [keepOnDevice]
69 * User enabled "keep my data on my device" toggle. Biases selection toward local lanes.
70 * Does NOT hard-block managed β€” if no local compute is available the caller falls through
71 * to direct_provider, and enforceConsentPolicy then requires explicit per-request consent
72 * for private data (D2.2 fallback chain: in-browser β†’ companion β†’ managed-with-consent).
73 * @property {boolean} [orgPrivacyMode]
74 * Org-level policy: managed lane is OFF. Default to self-hosted/BYO/local.
75 * When true, selectLane never returns 'direct_provider'.
76 * @property {boolean} [delegatedManagedAllowed]
77 * Owner opt-in: delegates may trigger the managed lane against owner packs (D1.4).
78 * Relevant only when isDelegate=true.
79 * @property {boolean} [isDelegate]
80 * Requesting actor is acting on another user's partition (delegate=true from
81 * resolveEffectiveCanisterUser). Checked against delegatedManagedAllowed in the consent gate.
82 */
83
84 /**
85 * @typedef {'allow'|'cloud_consent_required'|'lane_policy_denied'} ConsentDecision
86 */
87
88 /** Default capabilities β€” all false (fail-closed). */
89 const DEFAULT_CAPABILITIES = {
90 inBrowserAvailable: false,
91 companionAvailable: false,
92 selfHostedAvailable: false,
93 enterpriseAvailable: false,
94 openrouterKeyAvailable: false,
95 managedKeyAvailable: false,
96 };
97
98 /** Default preferences β€” individual user, no special mode or delegation. */
99 const DEFAULT_PREFERENCES = {
100 keepOnDevice: false,
101 orgPrivacyMode: false,
102 delegatedManagedAllowed: false,
103 isDelegate: false,
104 };
105
106 /**
107 * Select the highest-privacy capable lane for the current context.
108 * Implements D2.2 default-lane selection logic from the Phase 0 Decision Record.
109 *
110 * Lane priority (org privacy mode OFF):
111 * 1. local β€” inBrowser or companion (highest privacy, free, client-side only)
112 * 2. self_hosted β€” org or power-user self-hosted endpoint
113 * 3. enterprise β€” enterprise org endpoint
114 * 4. openrouter β€” BYO key (user pays provider; not metered against packs)
115 * 5. direct_provider β€” managed cloud (metered; requires explicit consent for private data)
116 * 6. disabled β€” no lane can satisfy; caller falls back to embeddings-only / no inference
117 *
118 * When orgPrivacyMode=true:
119 * managed (direct_provider) is never selected. Priority ranks org-controlled infra first,
120 * then by data egress (local has zero egress; openrouter routes to a third party), so:
121 * self_hosted β†’ enterprise β†’ local β†’ openrouter β†’ disabled
122 *
123 * @param {LaneCapabilities} capabilities
124 * @param {LanePreferences} preferences
125 * @returns {RuntimeLane}
126 */
127 export function selectLane(capabilities, preferences) {
128 const caps = { ...DEFAULT_CAPABILITIES, ...capabilities };
129 const prefs = { ...DEFAULT_PREFERENCES, ...preferences };
130
131 if (prefs.orgPrivacyMode) {
132 if (caps.selfHostedAvailable) return 'self_hosted';
133 if (caps.enterpriseAvailable) return 'enterprise';
134 // Privacy mode ranks zero-egress local above third-party openrouter egress.
135 if (caps.inBrowserAvailable || caps.companionAvailable) return 'local';
136 if (caps.openrouterKeyAvailable) return 'openrouter';
137 return 'disabled';
138 }
139
140 if (caps.inBrowserAvailable || caps.companionAvailable) return 'local';
141
142 if (caps.selfHostedAvailable) return 'self_hosted';
143 if (caps.enterpriseAvailable) return 'enterprise';
144 if (caps.openrouterKeyAvailable) return 'openrouter';
145 if (caps.managedKeyAvailable) return 'direct_provider';
146
147 return 'disabled';
148 }
149
150 /**
151 * Returns true when the given lane emits a metered billing event against Knowtation packs.
152 *
153 * D1.2: billing principal is the workspace owner of the target partition.
154 * Only 'direct_provider' (managed cloud) is metered β€” local, self_hosted, enterprise,
155 * and openrouter lanes are never metered (brief Β§6 principle 1):
156 * - local: zero provider cost (user's own compute)
157 * - self_hosted / enterprise: org pays its own infrastructure
158 * - openrouter: user pays their provider contract directly; Knowtation packs uninvolved
159 *
160 * @param {string} lane
161 * @returns {boolean}
162 */
163 export function isManagedLane(lane) {
164 return lane === 'direct_provider';
165 }
166
167 /**
168 * Lanes that route an owner's note text OFF the owner's own controlled infrastructure
169 * when invoked by a delegate enriching the owner's partition:
170 * - 'local' β€” runs on the DELEGATE's device (companion / in-browser), not the owner's.
171 * - 'openrouter' β€” routes to the DELEGATE's own third-party provider contract.
172 * Both are gated by the owner's "allow delegated enrichment" opt-in (D1.3(2)).
173 * Org lanes ('self_hosted'/'enterprise') are governed by org policy, not this individual
174 * opt-in. The managed lane ('direct_provider') is governed by D1.4 (delegatedManagedAllowed).
175 * @type {ReadonlySet<string>}
176 */
177 const DELEGATED_ENRICHMENT_GATED_LANES = new Set(['local', 'openrouter']);
178
179 /**
180 * Consent and workspace-policy gate for model calls.
181 *
182 * Evaluation order β€” workspace policy (cannot be overridden by a consentId) before
183 * per-request consent:
184 *
185 * 1. MANAGED-LANE DELEGATE POLICY (D1.4): lane is managed AND isDelegate AND
186 * NOT delegatedManagedAllowed β†’ 'lane_policy_denied'.
187 * Reason: the owner has not opted in to delegate-triggered managed-lane spend.
188 * Surface as a permission/policy message, not a consent prompt.
189 *
190 * 2. DELEGATED-ENRICHMENT POLICY (D1.3(2)): the call writes a derived artifact to a
191 * partition the actor does NOT own (enrichesDelegatedPartition) via a lane that runs
192 * off the owner's own infrastructure (local companion or the delegate's BYO openrouter)
193 * AND the owner has NOT enabled delegatedEnrichmentAllowed β†’ 'lane_policy_denied'.
194 * Reason: "may a member's companion enrich an owner's notes?" β€” only with the owner's
195 * opt-in (default OFF). This closes the gate Β§12 canonical defect (a member's companion
196 * silently enriching an owner's notes). FAIL-CLOSED: default-OFF until the owner opts in.
197 *
198 * 3. CONSENT REQUIRED (per-request; fixable by providing a consentId): lane is managed
199 * AND containsPrivateData AND NOT consentId β†’ 'cloud_consent_required'.
200 * Reason: private learner data must not reach a managed (cloud) provider without
201 * explicit per-action consent (D2.3 / D1.4 / brief Β§6).
202 *
203 * 4. ALLOW: all other cases.
204 *
205 * Notes:
206 * - A non-enrichment completion (enrichesDelegatedPartition=false) on a non-managed lane
207 * always allows β€” the delegate already has read scope (D1.3(1)); no artifact is written.
208 * - Org lanes (self_hosted / enterprise) are never gated here for delegated enrichment;
209 * the org controls the endpoint and governs that path by org policy.
210 *
211 * @param {{
212 * lane: string,
213 * containsPrivateData: boolean,
214 * consentId?: string,
215 * isDelegate: boolean,
216 * delegatedManagedAllowed: boolean,
217 * enrichesDelegatedPartition?: boolean,
218 * delegatedEnrichmentAllowed?: boolean,
219 * }} params
220 * @returns {ConsentDecision}
221 */
222 export function enforceConsentPolicy({
223 lane,
224 containsPrivateData,
225 consentId,
226 isDelegate,
227 delegatedManagedAllowed,
228 enrichesDelegatedPartition = false,
229 delegatedEnrichmentAllowed = false,
230 }) {
231 // 1. Managed-lane delegate spend policy (D1.4) β€” precedes consent.
232 if (isManagedLane(lane)) {
233 if (isDelegate && !delegatedManagedAllowed) return 'lane_policy_denied';
234 if (containsPrivateData && !consentId) return 'cloud_consent_required';
235 return 'allow';
236 }
237
238 // 2. Delegated-enrichment policy (D1.3(2)) for non-managed, off-owner-infra lanes.
239 if (
240 isDelegate &&
241 enrichesDelegatedPartition &&
242 DELEGATED_ENRICHMENT_GATED_LANES.has(lane) &&
243 !delegatedEnrichmentAllowed
244 ) {
245 return 'lane_policy_denied';
246 }
247
248 return 'allow';
249 }