forkit-connect 0.1.0 → 0.1.1

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.
@@ -50,6 +50,7 @@ const SMART_INBOX_CONFIDENCE_PRIORITY = {
50
50
  medium: 1,
51
51
  low: 2,
52
52
  };
53
+ const SMART_INBOX_FRESH_MAX_AGE_MS = 45 * 1000;
53
54
  function isRecord(value) {
54
55
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
55
56
  }
@@ -568,6 +569,7 @@ function isGovernorRevokedStatus(status, code) {
568
569
  class ConnectV1Service {
569
570
  stateStore;
570
571
  credentialStore;
572
+ smartInboxBackgroundRefreshInFlight = false;
571
573
  constructor(stateDir, options) {
572
574
  this.stateStore = new state_1.LocalStateStore(stateDir);
573
575
  this.stateStore.ensureInitialized();
@@ -580,6 +582,68 @@ class ConnectV1Service {
580
582
  getStateStore() {
581
583
  return this.stateStore;
582
584
  }
585
+ cloneInboxSnapshot(inbox) {
586
+ return JSON.parse(JSON.stringify(inbox));
587
+ }
588
+ withInboxFreshness(inbox, freshness, ageMs) {
589
+ const next = this.cloneInboxSnapshot(inbox);
590
+ next.summary = {
591
+ ...next.summary,
592
+ freshness_state: freshness,
593
+ snapshot_age_seconds: Math.max(0, Math.floor(ageMs / 1000)),
594
+ };
595
+ return next;
596
+ }
597
+ persistSmartInboxSnapshot(inbox) {
598
+ const state = this.stateStore.readState();
599
+ state.smart_inbox_snapshot = this.cloneInboxSnapshot(inbox);
600
+ state.smart_inbox_snapshot_updated_at = nowIso();
601
+ this.stateStore.writeState(state);
602
+ }
603
+ getSmartRegistrationInbox(options) {
604
+ const preferSnapshot = options?.preferSnapshot !== false;
605
+ const forceRefresh = options?.forceRefresh === true;
606
+ const maxSnapshotAgeMs = Math.max(1000, options?.maxSnapshotAgeMs ?? SMART_INBOX_FRESH_MAX_AGE_MS);
607
+ const refreshInBackground = options?.refreshInBackground !== false;
608
+ const state = this.stateStore.readState();
609
+ const snapshot = state.smart_inbox_snapshot;
610
+ const updatedAtMs = state.smart_inbox_snapshot_updated_at ? Date.parse(state.smart_inbox_snapshot_updated_at) : Number.NaN;
611
+ const ageMs = Number.isFinite(updatedAtMs) ? Math.max(0, Date.now() - updatedAtMs) : Number.MAX_SAFE_INTEGER;
612
+ if (!forceRefresh && preferSnapshot && snapshot) {
613
+ if (this.smartInboxBackgroundRefreshInFlight) {
614
+ return this.withInboxFreshness(snapshot, 'syncing', ageMs);
615
+ }
616
+ if (ageMs <= maxSnapshotAgeMs) {
617
+ return this.withInboxFreshness(snapshot, 'fresh', ageMs);
618
+ }
619
+ if (refreshInBackground) {
620
+ this.refreshSmartRegistrationInboxInBackground();
621
+ }
622
+ return this.withInboxFreshness(snapshot, refreshInBackground ? 'syncing' : 'stale', ageMs);
623
+ }
624
+ const liveInbox = this.buildSmartRegistrationInbox();
625
+ this.persistSmartInboxSnapshot(liveInbox);
626
+ return this.withInboxFreshness(liveInbox, 'fresh', 0);
627
+ }
628
+ refreshSmartRegistrationInboxInBackground() {
629
+ if (this.smartInboxBackgroundRefreshInFlight) {
630
+ return;
631
+ }
632
+ this.smartInboxBackgroundRefreshInFlight = true;
633
+ void Promise.resolve().then(() => {
634
+ try {
635
+ const liveInbox = this.buildSmartRegistrationInbox();
636
+ this.persistSmartInboxSnapshot(liveInbox);
637
+ }
638
+ finally {
639
+ this.smartInboxBackgroundRefreshInFlight = false;
640
+ }
641
+ });
642
+ }
643
+ async prewarmSmartRegistrationInbox() {
644
+ const liveInbox = this.buildSmartRegistrationInbox();
645
+ this.persistSmartInboxSnapshot(liveInbox);
646
+ }
583
647
  getStoredSessionRef() {
584
648
  return this.credentialStore.getSessionRef();
585
649
  }
@@ -1547,6 +1611,9 @@ class ConnectV1Service {
1547
1611
  backendBaseUrl: DEFAULT_BASE_URL,
1548
1612
  };
1549
1613
  }
1614
+ getCredentialStoreStatus() {
1615
+ return this.credentialStore.getStatus();
1616
+ }
1550
1617
  getConfig() {
1551
1618
  return this.stateStore.readState().connect_config;
1552
1619
  }
@@ -1691,6 +1758,16 @@ class ConnectV1Service {
1691
1758
  // Preserve the current item failure and let the next pass reconcile binding state again.
1692
1759
  }
1693
1760
  }
1761
+ if (input.source === 'runtimeSignal' && input.passportGaid && isCredentialInvalidStatus(input.status, code)) {
1762
+ this.clearRuntimeSignalApiKeyForGaid(input.passportGaid, 'recovery');
1763
+ try {
1764
+ await this.autoProvisionRuntimeSignalKeys({ suppressErrors: true });
1765
+ }
1766
+ catch {
1767
+ // Keep the failure visible in local state. A later cycle or manual key
1768
+ // configuration can recover if provisioning is still blocked.
1769
+ }
1770
+ }
1694
1771
  const observedSession = this.observeBackendCommunicationState(input);
1695
1772
  return {
1696
1773
  observedSession,
@@ -1946,10 +2023,18 @@ class ConnectV1Service {
1946
2023
  if (!sessionRef) {
1947
2024
  return this.getC2SessionSummary();
1948
2025
  }
2026
+ const workspaceId = String(state.workspace_binding.workspaceId || '').trim();
2027
+ const projectId = String(state.project_binding.projectId || '').trim();
2028
+ const scope = workspaceId || projectId
2029
+ ? {
2030
+ ...(workspaceId ? { workspaceId } : {}),
2031
+ ...(projectId ? { projectId } : {}),
2032
+ }
2033
+ : undefined;
1949
2034
  const boundGaids = [...new Set(state.model_bindings.filter((binding) => binding.status !== 'ignored' && typeof binding.gaid === 'string' && binding.gaid?.trim()).map((binding) => binding.gaid))];
1950
2035
  for (const passportGaid of boundGaids) {
1951
2036
  try {
1952
- const result = await this.getApiClient(state).getDeployments(passportGaid);
2037
+ const result = await this.getApiClient(state).getDeployments(passportGaid, scope);
1953
2038
  if (!result.ok) {
1954
2039
  this.observeBackendCommunicationState({
1955
2040
  passportGaid,
@@ -2104,6 +2189,41 @@ class ConnectV1Service {
2104
2189
  }
2105
2190
  return null;
2106
2191
  }
2192
+ hasExactBoundPassportForModel(state, model) {
2193
+ const modelName = String(model.model || '').trim().toLowerCase();
2194
+ const digest = String(model.digest || '').trim();
2195
+ if (!modelName || !digest) {
2196
+ return false;
2197
+ }
2198
+ return state.model_bindings.some((binding) => {
2199
+ if (binding.status !== 'bound') {
2200
+ return false;
2201
+ }
2202
+ const boundGaid = String(binding.gaid || '').trim();
2203
+ if (!boundGaid) {
2204
+ return false;
2205
+ }
2206
+ const bindingModelName = modelNameFromKey(binding.modelKey).trim().toLowerCase();
2207
+ const bindingDigest = String(modelDigestFromKey(binding.modelKey) || '').trim();
2208
+ return bindingModelName === modelName && bindingDigest === digest;
2209
+ });
2210
+ }
2211
+ runtimeHasExactBoundModelDuplicate(state, runtimePassport) {
2212
+ for (const model of state.detected_models) {
2213
+ if (this.getModelReviewDisposition(model) !== 'active') {
2214
+ continue;
2215
+ }
2216
+ const detectedRuntime = this.findDetectedRuntimeForModel(state, model);
2217
+ const candidateRuntimePassport = detectedRuntime ? this.findRuntimePassportForRuntime(state, detectedRuntime) : null;
2218
+ if (!candidateRuntimePassport || candidateRuntimePassport.runtime_gaid !== runtimePassport.runtime_gaid) {
2219
+ continue;
2220
+ }
2221
+ if (this.hasExactBoundPassportForModel(state, model)) {
2222
+ return true;
2223
+ }
2224
+ }
2225
+ return false;
2226
+ }
2107
2227
  clearModelReviewDeferral(model) {
2108
2228
  if (model.review_state !== 'deferred' && !model.review_deferred_until) {
2109
2229
  return;
@@ -2544,6 +2664,87 @@ class ConnectV1Service {
2544
2664
  return undefined;
2545
2665
  return state.model_bindings.find((binding) => binding.gaid === gaid);
2546
2666
  }
2667
+ findTrainingAnchorBinding(state, input) {
2668
+ const normalizedModelName = input.modelName.trim().toLowerCase();
2669
+ if (!normalizedModelName)
2670
+ return undefined;
2671
+ const normalizedWorkspaceId = String(input.workspaceId || '').trim() || null;
2672
+ const normalizedProjectId = String(input.projectId || '').trim() || null;
2673
+ const candidates = state.model_bindings.filter((binding) => {
2674
+ if (!binding.gaid || binding.status === 'ignored')
2675
+ return false;
2676
+ return modelNameFromKey(binding.modelKey).trim().toLowerCase() === normalizedModelName;
2677
+ });
2678
+ if (candidates.length === 0)
2679
+ return undefined;
2680
+ return [...candidates].sort((left, right) => {
2681
+ const leftProjectMatch = normalizedProjectId && String(left.projectId || '').trim() === normalizedProjectId;
2682
+ const rightProjectMatch = normalizedProjectId && String(right.projectId || '').trim() === normalizedProjectId;
2683
+ if (leftProjectMatch !== rightProjectMatch)
2684
+ return leftProjectMatch ? -1 : 1;
2685
+ const leftWorkspaceMatch = normalizedWorkspaceId && String(left.workspaceId || '').trim() === normalizedWorkspaceId;
2686
+ const rightWorkspaceMatch = normalizedWorkspaceId && String(right.workspaceId || '').trim() === normalizedWorkspaceId;
2687
+ if (leftWorkspaceMatch !== rightWorkspaceMatch)
2688
+ return leftWorkspaceMatch ? -1 : 1;
2689
+ const leftBound = left.status === 'bound';
2690
+ const rightBound = right.status === 'bound';
2691
+ if (leftBound !== rightBound)
2692
+ return leftBound ? -1 : 1;
2693
+ return String(right.updatedAt || '').localeCompare(String(left.updatedAt || ''));
2694
+ })[0];
2695
+ }
2696
+ anchorBuildSessionToBinding(state, session) {
2697
+ if (session.passport_gaid)
2698
+ return session;
2699
+ const binding = this.findTrainingAnchorBinding(state, {
2700
+ modelName: session.model_name,
2701
+ workspaceId: session.workspaceId,
2702
+ projectId: session.projectId,
2703
+ });
2704
+ if (!binding?.gaid)
2705
+ return session;
2706
+ return {
2707
+ ...session,
2708
+ passport_gaid: binding.gaid,
2709
+ workspaceId: session.workspaceId || binding.workspaceId || null,
2710
+ projectId: session.projectId || binding.projectId || null,
2711
+ };
2712
+ }
2713
+ resolveRuntimeRunScope(state, passportGaid) {
2714
+ const trimScope = (value) => String(value || '').trim() || null;
2715
+ const binding = this.findBindingByGaid(state, passportGaid);
2716
+ if (binding) {
2717
+ const primaryBound = this.getPrimaryBoundBinding(state);
2718
+ return {
2719
+ workspaceId: trimScope(binding.workspaceId) ?? (primaryBound?.gaid === binding.gaid ? trimScope(state.workspace_binding.workspaceId) : null),
2720
+ projectId: trimScope(binding.projectId) ?? (primaryBound?.gaid === binding.gaid ? trimScope(state.project_binding.projectId) : null),
2721
+ };
2722
+ }
2723
+ const latestSession = [...state.c2_sessions]
2724
+ .filter((session) => session.passport_gaid === passportGaid)
2725
+ .sort((left, right) => String(right.last_seen_at || right.last_control_seen_at || '').localeCompare(String(left.last_seen_at || left.last_control_seen_at || '')))
2726
+ .at(0);
2727
+ if (latestSession) {
2728
+ return {
2729
+ workspaceId: trimScope(latestSession.workspaceId),
2730
+ projectId: trimScope(latestSession.projectId),
2731
+ };
2732
+ }
2733
+ const runtimeGaid = state.agent_links.find((link) => link.passport_gaid === passportGaid)?.runtime_gaid ?? null;
2734
+ if (runtimeGaid) {
2735
+ const runtimePassport = state.runtime_passports.find((runtime) => runtime.runtime_gaid === runtimeGaid);
2736
+ if (runtimePassport) {
2737
+ return {
2738
+ workspaceId: trimScope(runtimePassport.workspaceId),
2739
+ projectId: trimScope(runtimePassport.projectId),
2740
+ };
2741
+ }
2742
+ }
2743
+ return {
2744
+ workspaceId: null,
2745
+ projectId: null,
2746
+ };
2747
+ }
2547
2748
  getPrimaryBoundBinding(state) {
2548
2749
  return state.model_bindings.find((binding) => binding.status === 'bound' && typeof binding.gaid === 'string' && binding.gaid.trim());
2549
2750
  }
@@ -2562,6 +2763,28 @@ class ConnectV1Service {
2562
2763
  updatedAt: nowIso(),
2563
2764
  });
2564
2765
  }
2766
+ clearRuntimeSignalApiKeyForGaid(gaid, source = 'reset') {
2767
+ if (!gaid)
2768
+ return;
2769
+ const state = this.stateStore.readState();
2770
+ const binding = this.findBindingByGaid(state, gaid);
2771
+ this.credentialStore.clearRuntimeSignalApiKey(gaid);
2772
+ if (!binding)
2773
+ return;
2774
+ this.stateStore.upsertModelBinding({
2775
+ ...binding,
2776
+ runtimeSignalKeyPresent: false,
2777
+ runtimeSignalKeyRef: null,
2778
+ updatedAt: nowIso(),
2779
+ });
2780
+ this.stateStore.addEvidenceEvent({
2781
+ type: 'credential_invalid_observed',
2782
+ details: {
2783
+ source,
2784
+ gaid,
2785
+ },
2786
+ });
2787
+ }
2565
2788
  resolveRuntimeSignalApiKeyForEvent(state, passportGaid) {
2566
2789
  const binding = this.findBindingByGaid(state, passportGaid);
2567
2790
  return String(this.credentialStore.getRuntimeSignalApiKey(passportGaid)
@@ -2834,6 +3057,65 @@ class ConnectV1Service {
2834
3057
  boundGaid: boundBinding?.gaid ?? null,
2835
3058
  };
2836
3059
  }
3060
+ async emitRuntimeRunLog(input) {
3061
+ const state = this.stateStore.readState();
3062
+ const gaid = String(input.gaid || '').trim();
3063
+ const apiKey = String(input.apiKey || this.resolveRuntimeSignalApiKeyForEvent(state, gaid) || '').trim();
3064
+ if (!gaid || !apiKey) {
3065
+ return {
3066
+ ok: false,
3067
+ status: 0,
3068
+ body: {
3069
+ error: 'Runtime signal API key required. Run `forkit-connect c2 set-key --heartbeat-gaid <gaid> --heartbeat-key <key>` or pass --api-key from a secure environment.',
3070
+ code: 'MISSING_RUNTIME_SIGNAL_API_KEY',
3071
+ },
3072
+ contract: null,
3073
+ };
3074
+ }
3075
+ const startedAt = input.startedAt || nowIso();
3076
+ const endedAt = input.endedAt || (input.status && input.status !== 'running' ? startedAt : undefined);
3077
+ const runId = String(input.runId || `forkit-connect-${(0, node_crypto_1.randomUUID)()}`).trim();
3078
+ const promptTokens = Math.max(0, Math.trunc(input.promptTokens ?? 0));
3079
+ const completionTokens = Math.max(0, Math.trunc(input.completionTokens ?? 0));
3080
+ const totalTokens = promptTokens + completionTokens;
3081
+ const { workspaceId, projectId } = this.resolveRuntimeRunScope(state, gaid);
3082
+ return this.getApiClient(state).pushRuntimeRunLog({
3083
+ gaid,
3084
+ apiKey,
3085
+ schemaVersion: 'runtime.run.v1',
3086
+ run: {
3087
+ runId,
3088
+ passportGaid: gaid,
3089
+ provider: String(input.provider || 'custom').trim() || 'custom',
3090
+ model: String(input.model || 'unknown').trim() || 'unknown',
3091
+ serviceName: String(input.serviceName || 'Forkit Connect CLI').trim() || 'Forkit Connect CLI',
3092
+ serviceKind: String(input.serviceKind || 'custom').trim() || 'custom',
3093
+ ...(input.externalRunId ? { externalRunId: String(input.externalRunId).trim() } : {}),
3094
+ workspaceId,
3095
+ projectId,
3096
+ status: String(input.status || 'completed').trim() || 'completed',
3097
+ startedAt,
3098
+ ...(endedAt ? { endedAt } : {}),
3099
+ usage: {
3100
+ promptTokens,
3101
+ completionTokens,
3102
+ ...(input.cachedPromptTokens !== undefined ? { cachedPromptTokens: Math.max(0, Math.trunc(input.cachedPromptTokens)) } : {}),
3103
+ ...(input.reasoningTokens !== undefined ? { reasoningTokens: Math.max(0, Math.trunc(input.reasoningTokens)) } : {}),
3104
+ totalTokens,
3105
+ ...(input.latencyMs !== undefined ? { latencyMs: Math.max(0, Math.trunc(input.latencyMs)) } : {}),
3106
+ ...(input.estimatedCostCents !== undefined ? { estimatedCostCents: Math.max(0, Number(input.estimatedCostCents)) } : {}),
3107
+ currency: String(input.currency || 'USD').trim().toUpperCase().slice(0, 3) || 'USD',
3108
+ },
3109
+ ...(input.summary ? { summary: String(input.summary).trim().slice(0, 4000) } : {}),
3110
+ metadata: {
3111
+ source: 'forkit-connect-cli',
3112
+ safe_metadata_only: true,
3113
+ connect_device_id: state.connect_identity.connect_device_id,
3114
+ },
3115
+ },
3116
+ steps: [],
3117
+ });
3118
+ }
2837
3119
  findAgentByIdOrName(state, selector) {
2838
3120
  const normalized = selector.trim().toLowerCase();
2839
3121
  const exactId = state.detected_agents.find((agent) => agent.agent_id === selector || agent.agent_id.startsWith(selector));
@@ -4083,10 +4365,12 @@ class ConnectV1Service {
4083
4365
  continue;
4084
4366
  }
4085
4367
  const connectableModel = this.findConnectableModelForRuntime(finalState, runtimePassport);
4086
- const runtimeIsOnlyDuplicatingModelRegistration = Boolean(connectableModel
4368
+ const runtimeHasExactDuplicateBoundModel = this.runtimeHasExactBoundModelDuplicate(finalState, runtimePassport);
4369
+ const runtimeIsOnlyDuplicatingModelRegistration = Boolean(!connectableModel
4087
4370
  && runtimePassport.status !== 'unavailable'
4088
4371
  && runtimePassport.linked_model_gaids.length === 0
4089
- && runtimePassport.linked_agent_ids.length === 0);
4372
+ && runtimePassport.linked_agent_ids.length === 0
4373
+ && runtimeHasExactDuplicateBoundModel);
4090
4374
  if (runtimeIsOnlyDuplicatingModelRegistration) {
4091
4375
  continue;
4092
4376
  }
@@ -4240,13 +4524,16 @@ class ConnectV1Service {
4240
4524
  website_is_governance_authority: true,
4241
4525
  };
4242
4526
  }
4243
- getConnectStatusOverview() {
4527
+ getConnectStatusOverview(options) {
4244
4528
  const state = this.stateStore.readState();
4245
- const inbox = this.buildSmartRegistrationInbox();
4529
+ const includeInbox = options?.includeInbox !== false;
4530
+ const inbox = includeInbox ? this.buildSmartRegistrationInbox() : null;
4246
4531
  const sessionSummary = this.getC2SessionSummary();
4247
4532
  const binding = this.getEffectiveBindingState(state);
4248
4533
  const bindingReconnectRequired = this.bindingRequiresReconnect(state);
4249
4534
  const eventSummary = this.getC2EventSyncSummary(state);
4535
+ const discoveredModels = state.detected_models.filter((item) => item.status !== 'ignored').length;
4536
+ const connectedCount = state.model_bindings.filter((bindingItem) => bindingItem.status === 'bound').length;
4250
4537
  return {
4251
4538
  device_paired: state.connect_identity.connect_identity_status === 'paired',
4252
4539
  workspace_id: state.workspace_binding.workspaceId,
@@ -4254,18 +4541,18 @@ class ConnectV1Service {
4254
4541
  binding_state: binding?.binding.state ?? null,
4255
4542
  lifecycle_note: this.getBindingLifecycleNotice(state),
4256
4543
  daemon_status: this.getDaemonStatus().daemon_running ? 'running' : 'stopped',
4257
- models_discovered: state.detected_models.filter((item) => item.status !== 'ignored').length,
4544
+ models_discovered: discoveredModels,
4258
4545
  agents_discovered: state.detected_agents.length,
4259
4546
  runtimes_discovered: state.runtime_passports.length,
4260
4547
  paused_session_count: sessionSummary.paused_session_count,
4261
4548
  revoked_session_count: sessionSummary.revoked_session_count,
4262
4549
  credential_reconnect_needed: bindingReconnectRequired || sessionSummary.credential_reconnect_needed,
4263
- ready_to_connect_count: inbox.summary.ready_to_connect_count,
4264
- needs_confirmation_count: inbox.summary.needs_confirmation_count,
4265
- connected_count: inbox.summary.connected_count,
4550
+ ready_to_connect_count: inbox?.summary.ready_to_connect_count ?? discoveredModels,
4551
+ needs_confirmation_count: inbox?.summary.needs_confirmation_count ?? 0,
4552
+ connected_count: inbox?.summary.connected_count ?? connectedCount,
4266
4553
  c2_sync_pending: eventSummary.pendingSyncable,
4267
4554
  privacy_mode: 'metadata only',
4268
- next_recommended_action: inbox.summary.next_recommended_action,
4555
+ next_recommended_action: inbox?.summary.next_recommended_action ?? null,
4269
4556
  };
4270
4557
  }
4271
4558
  resolveRecommendedNextAction(state) {
@@ -4529,6 +4816,11 @@ class ConnectV1Service {
4529
4816
  const inbox = this.buildSmartRegistrationInbox();
4530
4817
  const state = this.stateStore.readState();
4531
4818
  const candidates = [];
4819
+ const inboxActionableCounts = {
4820
+ inbox_ready_to_connect_count: inbox.summary.ready_to_connect_count,
4821
+ inbox_needs_confirmation_count: inbox.summary.needs_confirmation_count,
4822
+ inbox_actionable_count: inbox.summary.ready_to_connect_count + inbox.summary.needs_confirmation_count,
4823
+ };
4532
4824
  const pulseSummary = state.last_pulse_summary;
4533
4825
  const sessionSummary = this.getC2SessionSummary();
4534
4826
  const pendingDraftEvent = [...state.evidence_events]
@@ -4583,6 +4875,7 @@ class ConnectV1Service {
4583
4875
  suggested_action: 'connect_model',
4584
4876
  severity: scopeMismatchDetected ? 'red' : 'amber',
4585
4877
  metadata: {
4878
+ ...inboxActionableCounts,
4586
4879
  item_id: entry.item_id,
4587
4880
  model_name: entry.display_name,
4588
4881
  runtime_gaid: entry.runtime_gaid,
@@ -4641,6 +4934,7 @@ class ConnectV1Service {
4641
4934
  suggested_action: 'review_agent',
4642
4935
  severity: scopeMismatchDetected ? 'red' : 'amber',
4643
4936
  metadata: {
4937
+ ...inboxActionableCounts,
4644
4938
  item_id: entry.item_id,
4645
4939
  agent_id: entry.agent_id,
4646
4940
  display_name: entry.display_name,
@@ -4675,6 +4969,7 @@ class ConnectV1Service {
4675
4969
  suggested_action: 'open_inbox',
4676
4970
  severity: 'green',
4677
4971
  metadata: {
4972
+ ...inboxActionableCounts,
4678
4973
  item_id: entry.item_id,
4679
4974
  matched_passport_gaid: entry.matched_passport_gaid,
4680
4975
  match_reason: entry.match_reason,
@@ -4705,6 +5000,7 @@ class ConnectV1Service {
4705
5000
  suggested_action: 'open_inbox',
4706
5001
  severity: 'amber',
4707
5002
  metadata: {
5003
+ ...inboxActionableCounts,
4708
5004
  item_id: entry.item_id,
4709
5005
  display_name: entry.display_name,
4710
5006
  relationship_summary: 'Existing Passport metadata changed',
@@ -4810,6 +5106,7 @@ class ConnectV1Service {
4810
5106
  suggested_action: 'open_inbox',
4811
5107
  severity: 'amber',
4812
5108
  metadata: {
5109
+ ...inboxActionableCounts,
4813
5110
  process_name: finding.process_name,
4814
5111
  reason: finding.reason,
4815
5112
  relationship_summary: 'System-detected runtime candidate',
@@ -4837,9 +5134,14 @@ class ConnectV1Service {
4837
5134
  suggested_action: 'view_pulse_history',
4838
5135
  severity: 'red',
4839
5136
  metadata: {
5137
+ runtime_gaid: entry.runtime_gaid ?? null,
4840
5138
  runtime_name: entry.runtime_name,
4841
5139
  model_name: entry.model_name,
4842
5140
  pulse_status: entry.pulse_status,
5141
+ bound_passport_gaid: entry.bound_passport_gaid ?? null,
5142
+ binding_status: entry.binding_status ?? null,
5143
+ registration_state: entry.registration_state ?? null,
5144
+ connection_classification: entry.connection_classification ?? null,
4843
5145
  relationship_summary: 'Existing runtime availability changed',
4844
5146
  source_summary: sourceSummary,
4845
5147
  scope_summary: null,
@@ -4900,8 +5202,203 @@ class ConnectV1Service {
4900
5202
  }
4901
5203
  return candidates;
4902
5204
  }
5205
+ getNotificationMetadataNumber(candidate, key) {
5206
+ const value = candidate.metadata[key];
5207
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
5208
+ }
5209
+ getMaxNotificationMetadataNumber(candidates, key) {
5210
+ return candidates.reduce((max, candidate) => Math.max(max, this.getNotificationMetadataNumber(candidate, key)), 0);
5211
+ }
5212
+ pluralizeNotificationNoun(count, singular, plural = `${singular}s`) {
5213
+ return `${count} ${count === 1 ? singular : plural}`;
5214
+ }
5215
+ buildConnectInboxReviewNotification(candidates) {
5216
+ const reviewCandidates = candidates.filter((candidate) => (candidate.type === 'model_detected'
5217
+ || candidate.type === 'agent_detected'
5218
+ || candidate.type === 'passport_match_suggested'
5219
+ || candidate.type === 'version_candidate_ready'
5220
+ || candidate.type === 'shadow_candidate'));
5221
+ if (reviewCandidates.length === 0)
5222
+ return null;
5223
+ const modelCount = reviewCandidates.filter((candidate) => candidate.type === 'model_detected').length;
5224
+ const agentCount = reviewCandidates.filter((candidate) => candidate.type === 'agent_detected').length;
5225
+ const runtimeCount = reviewCandidates.filter((candidate) => candidate.type === 'shadow_candidate').length;
5226
+ const confirmationCount = reviewCandidates.filter((candidate) => (candidate.type === 'passport_match_suggested'
5227
+ || candidate.type === 'version_candidate_ready')).length;
5228
+ const totalReadyCount = this.getMaxNotificationMetadataNumber(reviewCandidates, 'inbox_ready_to_connect_count');
5229
+ const totalConfirmationCount = this.getMaxNotificationMetadataNumber(reviewCandidates, 'inbox_needs_confirmation_count');
5230
+ const totalActionableCount = this.getMaxNotificationMetadataNumber(reviewCandidates, 'inbox_actionable_count');
5231
+ const hasHiddenInboxItems = totalActionableCount > reviewCandidates.length;
5232
+ const redCount = reviewCandidates.filter((candidate) => candidate.severity === 'red').length;
5233
+ const parts = [
5234
+ modelCount > 0 ? this.pluralizeNotificationNoun(modelCount, 'model') : null,
5235
+ agentCount > 0 ? this.pluralizeNotificationNoun(agentCount, 'agent') : null,
5236
+ runtimeCount > 0 ? this.pluralizeNotificationNoun(runtimeCount, 'runtime') : null,
5237
+ confirmationCount > 0 ? this.pluralizeNotificationNoun(confirmationCount, 'confirmation') : null,
5238
+ ].filter((item) => Boolean(item));
5239
+ const previewSummary = parts.length > 0 ? parts.join(', ') : this.pluralizeNotificationNoun(reviewCandidates.length, 'item');
5240
+ const inboxSummary = [
5241
+ totalReadyCount > 0 ? `${this.pluralizeNotificationNoun(totalReadyCount, 'item')} ready to connect` : null,
5242
+ totalConfirmationCount > 0 ? `${this.pluralizeNotificationNoun(totalConfirmationCount, 'item')} needing confirmation` : null,
5243
+ ].filter((item) => Boolean(item)).join(', ');
5244
+ const itemSummary = hasHiddenInboxItems && inboxSummary
5245
+ ? inboxSummary
5246
+ : previewSummary;
5247
+ const reviewSentence = hasHiddenInboxItems && inboxSummary
5248
+ ? `${this.pluralizeNotificationNoun(totalActionableCount, 'inbox item')} ${totalActionableCount === 1 ? 'needs' : 'need'} approval or confirmation before registration: ${itemSummary}`
5249
+ : `${itemSummary} ${reviewCandidates.length === 1 ? 'needs' : 'need'} approval or confirmation before registration`;
5250
+ return {
5251
+ id: 'connect-inbox-review',
5252
+ type: modelCount > 0 ? 'model_detected' : agentCount > 0 ? 'agent_detected' : 'shadow_candidate',
5253
+ title: 'Forkit Connect needs your review',
5254
+ message: joinNotificationSentences([
5255
+ reviewSentence,
5256
+ 'Nothing has been registered automatically',
5257
+ ]),
5258
+ suggested_action: 'open_inbox',
5259
+ severity: redCount > 0 ? 'red' : 'amber',
5260
+ metadata: {
5261
+ notification_group: 'connect_inbox_review',
5262
+ grouped_count: reviewCandidates.length,
5263
+ model_count: modelCount,
5264
+ agent_count: agentCount,
5265
+ runtime_count: runtimeCount,
5266
+ confirmation_count: confirmationCount,
5267
+ inbox_ready_to_connect_count: totalReadyCount,
5268
+ inbox_needs_confirmation_count: totalConfirmationCount,
5269
+ inbox_actionable_count: totalActionableCount,
5270
+ red_count: redCount,
5271
+ relationship_summary: 'Connect inbox items need operator review',
5272
+ source_summary: 'Source: Forkit Connect local discovery',
5273
+ scope_summary: null,
5274
+ },
5275
+ };
5276
+ }
5277
+ buildRuntimeAttentionNotification(candidates) {
5278
+ const runtimeCandidates = candidates.filter((candidate) => (candidate.type === 'runtime_unavailable'
5279
+ && candidate.severity === 'red'
5280
+ && (normalizeDisplayText(candidate.metadata.bound_passport_gaid) !== null
5281
+ || candidate.metadata.binding_status === 'bound'
5282
+ || candidate.metadata.registration_state === 'registered'
5283
+ || candidate.metadata.connection_classification === 'registered_runtime')));
5284
+ if (runtimeCandidates.length === 0)
5285
+ return null;
5286
+ const modelCount = runtimeCandidates
5287
+ .filter((candidate) => normalizeDisplayText(candidate.metadata.model_name))
5288
+ .length;
5289
+ return {
5290
+ id: 'runtime-attention',
5291
+ type: 'runtime_unavailable',
5292
+ title: 'Forkit Connect needs runtime attention',
5293
+ message: joinNotificationSentences([
5294
+ `${this.pluralizeNotificationNoun(runtimeCandidates.length, 'runtime')} reported an unavailable state`,
5295
+ modelCount > 0 ? `${this.pluralizeNotificationNoun(modelCount, 'model')} may be affected` : null,
5296
+ ]),
5297
+ suggested_action: 'view_pulse_history',
5298
+ severity: 'red',
5299
+ metadata: {
5300
+ notification_group: 'runtime_attention',
5301
+ grouped_count: runtimeCandidates.length,
5302
+ affected_model_count: modelCount,
5303
+ relationship_summary: 'Existing runtime availability changed',
5304
+ source_summary: 'Source: Connect runtime pulse',
5305
+ scope_summary: null,
5306
+ },
5307
+ };
5308
+ }
5309
+ buildGovernanceControlNotification(candidates) {
5310
+ const controlCandidates = candidates.filter((candidate) => (candidate.type === 'session_revoked'
5311
+ || candidate.type === 'session_paused'));
5312
+ if (controlCandidates.length === 0)
5313
+ return null;
5314
+ const revokedCount = controlCandidates.filter((candidate) => candidate.type === 'session_revoked').length;
5315
+ const pausedCount = controlCandidates.filter((candidate) => candidate.type === 'session_paused').length;
5316
+ const severity = revokedCount > 0 ? 'red' : 'amber';
5317
+ return {
5318
+ id: 'governance-control',
5319
+ type: revokedCount > 0 ? 'session_revoked' : 'session_paused',
5320
+ title: revokedCount > 0 ? 'Forkit governance revoked access' : 'Forkit governance paused communication',
5321
+ message: joinNotificationSentences([
5322
+ revokedCount > 0 ? `${this.pluralizeNotificationNoun(revokedCount, 'session')} revoked` : null,
5323
+ pausedCount > 0 ? `${this.pluralizeNotificationNoun(pausedCount, 'session')} paused` : null,
5324
+ 'Review the Connect inbox before continuing governed communication',
5325
+ ]),
5326
+ suggested_action: 'open_inbox',
5327
+ severity,
5328
+ metadata: {
5329
+ notification_group: 'governance_control',
5330
+ grouped_count: controlCandidates.length,
5331
+ revoked_count: revokedCount,
5332
+ paused_count: pausedCount,
5333
+ relationship_summary: 'Existing governed sessions need operator attention',
5334
+ source_summary: 'Source: Forkit governance control',
5335
+ scope_summary: null,
5336
+ },
5337
+ };
5338
+ }
5339
+ buildSyncAttentionNotification(candidates) {
5340
+ const syncCandidate = candidates.find((candidate) => (candidate.type === 'c2_sync_pending'
5341
+ && candidate.severity !== 'grey'));
5342
+ if (!syncCandidate)
5343
+ return null;
5344
+ const pendingEvents = this.getNotificationMetadataNumber(syncCandidate, 'pending_events');
5345
+ return {
5346
+ ...syncCandidate,
5347
+ id: 'c2-sync-attention',
5348
+ title: 'Forkit Connect sync needs attention',
5349
+ message: joinNotificationSentences([
5350
+ pendingEvents > 0
5351
+ ? `${this.pluralizeNotificationNoun(pendingEvents, 'metadata update')} could not sync cleanly`
5352
+ : 'A governed metadata sync needs attention',
5353
+ 'Open Connect to review the local queue',
5354
+ ]),
5355
+ metadata: {
5356
+ ...syncCandidate.metadata,
5357
+ notification_group: 'c2_sync_attention',
5358
+ relationship_summary: 'Existing local metadata queue needs sync attention',
5359
+ },
5360
+ };
5361
+ }
5362
+ getNotificationDeliveryCandidates(candidates) {
5363
+ const reconnectCandidate = candidates.find((candidate) => candidate.type === 'credential_reconnect_needed');
5364
+ if (reconnectCandidate) {
5365
+ return [{
5366
+ ...reconnectCandidate,
5367
+ id: 'account-reconnect',
5368
+ title: 'Forkit Connect needs account approval',
5369
+ message: joinNotificationSentences([
5370
+ 'Reconnect this device before governed metadata sync can continue',
5371
+ String(reconnectCandidate.metadata.source_summary || ''),
5372
+ ]),
5373
+ metadata: {
5374
+ ...reconnectCandidate.metadata,
5375
+ notification_group: 'account_reconnect',
5376
+ },
5377
+ }];
5378
+ }
5379
+ const grouped = [];
5380
+ const governanceNotification = this.buildGovernanceControlNotification(candidates);
5381
+ if (governanceNotification)
5382
+ grouped.push(governanceNotification);
5383
+ const runtimeNotification = this.buildRuntimeAttentionNotification(candidates);
5384
+ if (runtimeNotification)
5385
+ grouped.push(runtimeNotification);
5386
+ const inboxReviewNotification = this.buildConnectInboxReviewNotification(candidates);
5387
+ if (inboxReviewNotification)
5388
+ grouped.push(inboxReviewNotification);
5389
+ const syncNotification = this.buildSyncAttentionNotification(candidates);
5390
+ if (syncNotification)
5391
+ grouped.push(syncNotification);
5392
+ return grouped.slice(0, 3);
5393
+ }
5394
+ getNotificationDeliveryPreview(candidates = this.getNotificationCandidates()) {
5395
+ return this.getNotificationDeliveryCandidates(candidates);
5396
+ }
4903
5397
  getNotificationTargetId(candidate) {
4904
5398
  const metadata = candidate.metadata;
5399
+ if (metadata.notification_group) {
5400
+ return String(metadata.notification_group);
5401
+ }
4905
5402
  switch (candidate.type) {
4906
5403
  case 'model_detected':
4907
5404
  return String(metadata.discovery_hash || candidate.id);
@@ -4955,10 +5452,13 @@ class ConnectV1Service {
4955
5452
  const deliveredAt = options?.deliveredAt ?? nowIso();
4956
5453
  let state = this.stateStore.readState();
4957
5454
  const candidates = options?.candidates ?? this.getNotificationCandidates();
5455
+ const deliveryCandidates = options?.force && options?.candidates
5456
+ ? candidates
5457
+ : this.getNotificationDeliveryCandidates(candidates);
4958
5458
  if (!state.connect_config.notifications_enabled && !options?.force) {
4959
5459
  return {
4960
5460
  delivered: 0,
4961
- suppressed: candidates.length,
5461
+ suppressed: deliveryCandidates.length,
4962
5462
  fallback: false,
4963
5463
  disabled: true,
4964
5464
  notifications: [],
@@ -4969,7 +5469,7 @@ class ConnectV1Service {
4969
5469
  let delivered = 0;
4970
5470
  let suppressed = 0;
4971
5471
  let fallback = false;
4972
- for (const candidate of candidates) {
5472
+ for (const candidate of deliveryCandidates) {
4973
5473
  if (!options?.force && this.wasNotificationRecentlyDelivered(state, candidate, deliveredAt)) {
4974
5474
  suppressed += 1;
4975
5475
  continue;
@@ -5784,29 +6284,30 @@ class ConnectV1Service {
5784
6284
  };
5785
6285
  }
5786
6286
  buildBuildSessionSummary(state, session) {
6287
+ const anchoredSession = this.anchorBuildSessionToBinding(state, session);
5787
6288
  const queuedDraft = state.sync_queue.some((item) => item.type === 'passport-draft'
5788
- && String(item.payload?.metadata?.build_session_id || '') === session.build_session_id);
5789
- const draftStatus = session.passport_gaid
6289
+ && String(item.payload?.metadata?.build_session_id || '') === anchoredSession.build_session_id);
6290
+ const draftStatus = anchoredSession.passport_gaid
5790
6291
  ? 'bound'
5791
- : session.draft_id
6292
+ : anchoredSession.draft_id
5792
6293
  ? 'draft_created'
5793
6294
  : queuedDraft
5794
6295
  ? 'draft_queued'
5795
6296
  : 'local_only';
5796
- const latestLifecycleEvent = session.lifecycle_events.at(-1)?.event_type ?? null;
5797
- const c2Status = this.countC2ForBuildSession(state, session.build_session_id);
6297
+ const latestLifecycleEvent = anchoredSession.lifecycle_events.at(-1)?.event_type ?? null;
6298
+ const c2Status = this.countC2ForBuildSession(state, anchoredSession.build_session_id);
5798
6299
  return {
5799
- build_session_id: session.build_session_id,
5800
- model_name: session.model_name,
5801
- framework: session.framework,
5802
- task: session.task,
5803
- build_status: session.status,
6300
+ build_session_id: anchoredSession.build_session_id,
6301
+ model_name: anchoredSession.model_name,
6302
+ framework: anchoredSession.framework,
6303
+ task: anchoredSession.task,
6304
+ build_status: anchoredSession.status,
5804
6305
  draft_status: draftStatus,
5805
- draft_id: session.draft_id,
5806
- passport_gaid: session.passport_gaid,
5807
- dataset_refs_count: session.dataset_refs.length,
5808
- versions_count: session.versions.length,
5809
- metrics_count: session.metrics.length,
6306
+ draft_id: anchoredSession.draft_id,
6307
+ passport_gaid: anchoredSession.passport_gaid,
6308
+ dataset_refs_count: anchoredSession.dataset_refs.length,
6309
+ versions_count: anchoredSession.versions.length,
6310
+ metrics_count: anchoredSession.metrics.length,
5810
6311
  latest_lifecycle_event: latestLifecycleEvent,
5811
6312
  c2_sync_status: c2Status.pending > 0 || c2Status.synced > 0 || c2Status.lastError
5812
6313
  ? {
@@ -5815,14 +6316,16 @@ class ConnectV1Service {
5815
6316
  last_error: c2Status.lastError,
5816
6317
  }
5817
6318
  : null,
5818
- updated_at: session.updated_at,
6319
+ updated_at: anchoredSession.updated_at,
5819
6320
  };
5820
6321
  }
5821
6322
  saveBuildSession(session) {
5822
- this.stateStore.upsertBuildSession({
6323
+ const state = this.stateStore.readState();
6324
+ const anchored = this.anchorBuildSessionToBinding(state, {
5823
6325
  ...session,
5824
6326
  updated_at: session.updated_at || nowIso(),
5825
6327
  });
6328
+ this.stateStore.upsertBuildSession(anchored);
5826
6329
  return this.requireBuildSession(this.stateStore.readState(), session.build_session_id);
5827
6330
  }
5828
6331
  shouldFallbackToQueuedTrainingDraft(status) {
@@ -5862,7 +6365,7 @@ class ConnectV1Service {
5862
6365
  ],
5863
6366
  artifact_refs: [],
5864
6367
  };
5865
- this.saveBuildSession(session);
6368
+ session = this.saveBuildSession(session);
5866
6369
  this.stateStore.addEvidenceEvent({
5867
6370
  type: 'training_session_initialized',
5868
6371
  details: {
@@ -5875,19 +6378,30 @@ class ConnectV1Service {
5875
6378
  this.queueTrainingC2Event(session, 'connect_training_initialized', {
5876
6379
  dataset_refs_count: datasetRefs.length,
5877
6380
  }, now);
5878
- this.recordC2LifecycleEvent({
5879
- eventType: 'connect_passport_draft_requested',
5880
- modelName: session.model_name,
5881
- passportGaid: null,
5882
- workspaceId: session.workspaceId,
5883
- projectId: session.projectId,
5884
- metadata: {
5885
- build_session_id: session.build_session_id,
5886
- source_mode: 'connect_training',
5887
- connection_status: 'building',
5888
- },
5889
- });
6381
+ if (!session.passport_gaid) {
6382
+ this.recordC2LifecycleEvent({
6383
+ eventType: 'connect_passport_draft_requested',
6384
+ modelName: session.model_name,
6385
+ passportGaid: null,
6386
+ workspaceId: session.workspaceId,
6387
+ projectId: session.projectId,
6388
+ metadata: {
6389
+ build_session_id: session.build_session_id,
6390
+ source_mode: 'connect_training',
6391
+ connection_status: 'building',
6392
+ },
6393
+ });
6394
+ }
5890
6395
  const payload = this.buildTrainingDraftPayload(session);
6396
+ if (session.passport_gaid) {
6397
+ return {
6398
+ buildSession: session,
6399
+ draftAction: 'bound',
6400
+ draftId: null,
6401
+ gaid: session.passport_gaid,
6402
+ payload,
6403
+ };
6404
+ }
5891
6405
  if (this.hasSessionRef()) {
5892
6406
  const result = await this.getApiClient(state).createPassportDraft(payload);
5893
6407
  if (!result.ok) {
@@ -6123,13 +6637,21 @@ class ConnectV1Service {
6123
6637
  }
6124
6638
  getTrainingStatus(buildSessionId) {
6125
6639
  const state = this.stateStore.readState();
6640
+ const maybePersistAnchoredSession = (session) => {
6641
+ const anchored = this.anchorBuildSessionToBinding(state, session);
6642
+ if (anchored !== session) {
6643
+ this.stateStore.upsertBuildSession(anchored);
6644
+ return anchored;
6645
+ }
6646
+ return session;
6647
+ };
6126
6648
  if (buildSessionId) {
6127
- const session = this.requireBuildSession(state, buildSessionId);
6649
+ const session = maybePersistAnchoredSession(this.requireBuildSession(state, buildSessionId));
6128
6650
  return this.buildBuildSessionSummary(state, session);
6129
6651
  }
6130
6652
  return [...state.build_sessions]
6131
6653
  .sort((left, right) => right.updated_at.localeCompare(left.updated_at))
6132
- .map((session) => this.buildBuildSessionSummary(state, session));
6654
+ .map((session) => this.buildBuildSessionSummary(state, maybePersistAnchoredSession(session)));
6133
6655
  }
6134
6656
  markBuildSessionDraftPublished(draftId, passportGaid, workspaceId, projectId) {
6135
6657
  const state = this.stateStore.readState();
@@ -7782,6 +8304,7 @@ class ConnectV1Service {
7782
8304
  const result = options?.processScoutResults
7783
8305
  ? await this.scanRuntime({ processScoutResults: options.processScoutResults })
7784
8306
  : await this.scanRuntime();
8307
+ this.syncEvolutionCandidates();
7785
8308
  const queue = result.discoveryMode === 'auto_draft'
7786
8309
  ? this.queueModelDraftsFromDetected()
7787
8310
  : emptyQueueSummary();
@@ -8129,7 +8652,7 @@ class ConnectV1Service {
8129
8652
  const secureStorage = this.credentialStore.getStatus();
8130
8653
  checks.push({
8131
8654
  name: 'secure_storage',
8132
- status: secureStorage.available ? 'PASS' : 'FAIL',
8655
+ status: !secureStorage.available ? 'FAIL' : secureStorage.plaintextFallbackActive ? 'WARN' : 'PASS',
8133
8656
  details: secureStorage.legacyPlaintextFilePresent
8134
8657
  ? `${secureStorage.detail} Legacy plaintext credential file still detected at ${this.credentialStore.getCredentialFilePath()}.`
8135
8658
  : secureStorage.detail,
@@ -8165,6 +8688,11 @@ class ConnectV1Service {
8165
8688
  status: 'WARN',
8166
8689
  details: 'No session reference stored. Set FORKIT_CONNECT_SESSION_REF or run connect with session binding.',
8167
8690
  });
8691
+ checks.push({
8692
+ name: 'backend_session_truth',
8693
+ status: 'WARN',
8694
+ details: 'session_state=missing. Local scope (if present) is cached-only until login verifies account truth.',
8695
+ });
8168
8696
  return checks;
8169
8697
  }
8170
8698
  checks.push({
@@ -8174,11 +8702,39 @@ class ConnectV1Service {
8174
8702
  ? 'Using session reference from FORKIT_CONNECT_SESSION_REF. Headless mode can run without local credential persistence.'
8175
8703
  : 'Secure session reference is present in local credential storage.',
8176
8704
  });
8705
+ const api = new api_1.ConnectApiClient({
8706
+ baseUrl: DEFAULT_BASE_URL,
8707
+ sessionRef: this.readSessionRef(),
8708
+ });
8709
+ let sessionTruth = 'unavailable';
8710
+ let profileStatus = null;
8711
+ try {
8712
+ const profileAccess = await api.getProfileAccess();
8713
+ profileStatus = profileAccess.status;
8714
+ if (profileAccess.ok) {
8715
+ sessionTruth = 'authorized';
8716
+ }
8717
+ else if (profileAccess.status === 401 || profileAccess.status === 403) {
8718
+ sessionTruth = 'expired';
8719
+ }
8720
+ }
8721
+ catch {
8722
+ sessionTruth = 'unavailable';
8723
+ profileStatus = null;
8724
+ }
8725
+ checks.push({
8726
+ name: 'backend_session_truth',
8727
+ status: sessionTruth === 'authorized' ? 'PASS' : 'WARN',
8728
+ details: sessionTruth === 'authorized'
8729
+ ? `session_state=authorized via GET /api/profiles/access (${profileStatus ?? 'ok'})`
8730
+ : sessionTruth === 'expired'
8731
+ ? `session_state=expired via GET /api/profiles/access (${profileStatus ?? 'unknown'}). Renew login before governed actions.`
8732
+ : `session_state=unavailable via GET /api/profiles/access (${profileStatus ?? 'timeout_or_network_error'}). Account truth is temporarily offline.`,
8733
+ });
8734
+ if (sessionTruth !== 'authorized') {
8735
+ return checks;
8736
+ }
8177
8737
  try {
8178
- const api = new api_1.ConnectApiClient({
8179
- baseUrl: DEFAULT_BASE_URL,
8180
- sessionRef: this.readSessionRef(),
8181
- });
8182
8738
  const mine = await api.getPassportsMine();
8183
8739
  checks.push({
8184
8740
  name: 'backend_passports_mine',