@codemem/server 0.44.0-alpha.1 → 0.44.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.
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
3
- import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, CoordinatorReciprocalApprovalRequestChangedError, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, LegacyTeamSetupAdditiveConvergenceError, MemoryStore, PROMPT_TRANSPORT_PROTOCOL_RANGE, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, RecipientPolicyTeamRenameError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_IDENTITY_TARGET_KEYS, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyLegacyTeamSetupCompletionManifest, applyLegacyTeamSetupCompletionManifestAndReturnActivation, applyReplicationOps, areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible, buildAuthHeaders, buildBaseUrl, buildDirectPeerAuthHeaders, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildViewerIdentityTarget, canonicalCoordinatorLegacyTeamCompletionManifestJson, canonicalMemoryKind, canonicalWorkspaceIdentity, claimRecipientPolicyActorMutations, claimRecipientPolicyPublicationMutation, cleanupNonces, clearLegacyTeamSetupDeviceDecision, clonePromptPackAttempt, coerceObserverCommand, commitDeviceIdentityBindings, commitRecipientPolicyEdges, containLegacyTeamSetupCompletionConflict, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateLegacyTeamCompletionAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGetLegacyTeamCompletionAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListConsumedTeamInvitesAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListLegacyTeamCompletionsAction, coordinatorListReviewedRecipientInviteEvidenceAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, deriveLegacyTeamSetupCompletionManifest, detectInstallKind, deterministicPolicyTeamId, diagnoseStalePeerReceivedRows, discoverLegacyTeamCandidates, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getLegacyTeamSetupDraft, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, inspectFreshLegacyTeamSetupActivation, inspectLegacyTeamSetupActivation, inviteTokenDigest, isLegacyTeamCandidateSelectable, isLegacyTeamSetupProjectMappingIdentity, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, legacyTeamCandidateId, legacyTeamCandidateProjectInventory, legacyTeamCanonicalProjectRef, legacyTeamDeviceRef, legacyTeamResolvedProjectRef, legacyTeamSetupApiErrorCode, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listDeviceIdentityInventory, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, migrateRecipientPolicyIntent, negotiateSyncCapability, normalizeAddress, normalizeHumanPresentationName, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parsePositiveMemoryId, parseReassignScopePayload, parseRecipientPolicyEdgeCommitRequest, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewDeviceIdentityBindings, previewLegacyTeamSetupActivation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientPolicyDigest, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, reconstructLegacyTeamSetupCompletionManifest, recordHighestObservedDirectSignatureVersion, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, refreshLegacyTeamCandidate, rejectInboundScopeFailures, renameRecipientPolicyTeam, replayLegacyTeamSetupActivation, requestJson, requireLegacyTeamSetupAccessDeltaWithinLimit, resolveDbPath, resolveProject, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, serializeRecipientPolicyCoordinatorGroupMutation, serializeRecipientPolicyPublicationMutation, serializeRecipientPolicyTeamMutation, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, summarizeInboundScopeRejections, supportsSyncFeature, syncScopeResetRequiredPayload, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, validateLegacyTeamSetupCompletionManifest, validateLegacyTeamSetupCompletionManifestBinding, verifyDirectPeerSignature, verifyRecipientReviewedIntent, verifySignature, writeCodememConfigFile } from "@codemem/core";
3
+ import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, CoordinatorReciprocalApprovalRequestChangedError, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, LegacyTeamSetupAdditiveConvergenceError, LegacyTeamSetupRosterCapacityError, MemoryStore, PROMPT_TRANSPORT_PROTOCOL_RANGE, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, RecipientPolicyTeamRenameError, RemoteCoordinatorRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_IDENTITY_TARGET_KEYS, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyLegacyTeamSetupCompletionManifest, applyLegacyTeamSetupCompletionManifestAndReturnActivation, applyReplicationOps, areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible, automaticRecallHealth, buildAuthHeaders, buildBaseUrl, buildDirectPeerAuthHeaders, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildViewerIdentityTarget, canonicalCoordinatorLegacyTeamCompletionManifestJson, canonicalMemoryKind, canonicalWorkspaceIdentity, claimRecipientPolicyActorMutations, claimRecipientPolicyPublicationMutation, cleanupNonces, clearLegacyTeamSetupDeviceDecision, clonePromptPackAttempt, coerceObserverCommand, commitDeviceIdentityBindings, commitRecipientPolicyEdges, containLegacyTeamSetupCompletionConflict, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateLegacyTeamCompletionAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGetLegacyTeamCompletionAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListConsumedTeamInvitesAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListLegacyTeamCompletionsAction, coordinatorListReviewedRecipientInviteEvidenceAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, deriveLegacyTeamSetupCompletionManifest, detectInstallKind, deterministicPolicyTeamId, diagnoseStalePeerReceivedRows, discoverLegacyTeamCandidates, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getLegacyTeamSetupDraft, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, inspectFreshLegacyTeamSetupActivation, inspectLegacyTeamSetupActivation, inviteTokenDigest, isAutomaticRecallMeasurement, isLegacyTeamCandidateSelectable, isLegacyTeamSetupProjectMappingIdentity, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, legacyTeamCandidateId, legacyTeamCandidateProjectInventory, legacyTeamCanonicalProjectRef, legacyTeamDeviceRef, legacyTeamResolvedProjectRef, legacyTeamSetupApiErrorCode, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listDeviceIdentityInventory, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, migrateRecipientPolicyIntent, negotiateSyncCapability, normalizeAddress, normalizeHumanPresentationName, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parsePositiveMemoryId, parseReassignScopePayload, parseRecipientPolicyEdgeCommitRequest, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewDeviceIdentityBindings, previewLegacyTeamSetupActivation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientPolicyDigest, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, reconstructLegacyTeamSetupCompletionManifest, recordAutomaticRecall, recordHighestObservedDirectSignatureVersion, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, refreshLegacyTeamCandidate, rejectInboundScopeFailures, renameRecipientPolicyTeam, replayLegacyTeamSetupActivation, requestJson, requireLegacyTeamSetupAccessDeltaWithinLimit, resolveDbPath, resolveProject, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, serializeRecipientPolicyCoordinatorGroupMutation, serializeRecipientPolicyPublicationMutation, serializeRecipientPolicyTeamMutation, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, summarizeInboundScopeRejections, summaryLikeSqlPredicate, supportsSyncFeature, syncScopeResetRequiredPayload, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, validateLegacyTeamSetupCompletionManifest, validateLegacyTeamSetupCompletionManifestBinding, verifyDirectPeerSignature, verifyRecipientReviewedIntent, verifySignature, writeCodememConfigFile } from "@codemem/core";
4
4
  import { serveStatic } from "@hono/node-server/serve-static";
5
5
  import { Hono } from "hono";
6
6
  import { createMiddleware } from "hono/factory";
7
7
  import { RateLimiter } from "limiter";
8
+ import { createCipheriv, createDecipheriv, createHash, randomBytes, randomUUID } from "node:crypto";
8
9
  import { and, count, desc, eq, inArray, max, ne } from "drizzle-orm";
9
10
  import { drizzle } from "drizzle-orm/better-sqlite3";
10
11
  import { homedir, hostname, networkInterfaces } from "node:os";
11
- import { createHash, randomUUID } from "node:crypto";
12
12
  //#region src/middleware.ts
13
13
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
14
14
  "127.0.0.1",
@@ -513,6 +513,363 @@ function configRoutes(opts = {}) {
513
513
  return app;
514
514
  }
515
515
  //#endregion
516
+ //#region src/routes/diagnostics.ts
517
+ var DEFAULT_LIMIT = 50;
518
+ var MAX_LIMIT = 100;
519
+ var CURSOR_KEY = randomBytes(32);
520
+ var DIAGNOSTIC_SEVERITIES = [
521
+ "info",
522
+ "warning",
523
+ "error"
524
+ ];
525
+ var DIAGNOSTIC_SUBSYSTEMS = [
526
+ "viewer",
527
+ "observer",
528
+ "capture",
529
+ "sync",
530
+ "storage",
531
+ "maintenance"
532
+ ];
533
+ function opaqueId(kind, sourceId) {
534
+ return createHash("sha256").update(`${kind}\0${String(sourceId)}`).digest("base64url").slice(0, 22);
535
+ }
536
+ function encodeCursor(event) {
537
+ const iv = randomBytes(12);
538
+ const cipher = createCipheriv("aes-256-gcm", CURSOR_KEY, iv);
539
+ const plaintext = JSON.stringify({
540
+ occurredAt: event.occurred_at,
541
+ orderKey: event.orderKey
542
+ });
543
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
544
+ return Buffer.concat([
545
+ iv,
546
+ cipher.getAuthTag(),
547
+ encrypted
548
+ ]).toString("base64url");
549
+ }
550
+ function parseCursor(raw) {
551
+ if (!raw) return null;
552
+ try {
553
+ const encoded = Buffer.from(raw, "base64url");
554
+ if (encoded.length < 29) return "invalid";
555
+ const decipher = createDecipheriv("aes-256-gcm", CURSOR_KEY, encoded.subarray(0, 12));
556
+ decipher.setAuthTag(encoded.subarray(12, 28));
557
+ const plaintext = Buffer.concat([decipher.update(encoded.subarray(28)), decipher.final()]).toString("utf8");
558
+ const parsed = JSON.parse(plaintext);
559
+ if (typeof parsed.occurredAt !== "string" || !/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:.+-]+Z$/.test(parsed.occurredAt) || !Number.isFinite(Date.parse(parsed.occurredAt))) return "invalid";
560
+ if (typeof parsed.orderKey !== "string" || parsed.orderKey.length < 3 || parsed.orderKey.length > 2048) return "invalid";
561
+ return {
562
+ occurredAt: parsed.occurredAt,
563
+ orderKey: parsed.orderKey
564
+ };
565
+ } catch {
566
+ return "invalid";
567
+ }
568
+ }
569
+ function parseLimit(raw) {
570
+ if (raw == null || raw === "") return DEFAULT_LIMIT;
571
+ if (!/^\d+$/.test(raw)) return "invalid";
572
+ const parsed = Number(raw);
573
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) return "invalid";
574
+ return Math.min(parsed, MAX_LIMIT);
575
+ }
576
+ function parseFilter(raw, allowed) {
577
+ if (raw == null) return new Set(allowed);
578
+ const values = raw.split(",").map((value) => value.trim()).filter(Boolean);
579
+ if (!values.length || values.some((value) => !allowed.includes(value))) return "invalid";
580
+ return new Set(values);
581
+ }
582
+ function parseTechnical(raw) {
583
+ if (raw == null || raw === "0") return false;
584
+ if (raw === "1") return true;
585
+ return "invalid";
586
+ }
587
+ function technicalDetail(available, text, include) {
588
+ if (!available) return { available: false };
589
+ return include ? {
590
+ available: true,
591
+ text: text.slice(0, 2e3)
592
+ } : { available: true };
593
+ }
594
+ var SYNC_OCCURRED_AT_SQL = "CASE WHEN finished_at IS NULL THEN started_at ELSE finished_at END";
595
+ function syncSelect(options) {
596
+ if (!options.subsystems.has("sync")) return null;
597
+ const predicates = [];
598
+ if (!options.severities.has("info")) predicates.push("ok = 0");
599
+ if (!options.severities.has("error")) predicates.push("ok <> 0");
600
+ if (!options.severities.has("info") && !options.severities.has("error")) return null;
601
+ if (options.cursor) predicates.push(`(${SYNC_OCCURRED_AT_SQL} < @cursorOccurredAt OR
602
+ (${SYNC_OCCURRED_AT_SQL} = @cursorOccurredAt AND
603
+ 's:' || printf('%020d', id) < @cursorOrderKey))`);
604
+ return `SELECT 'sync' AS source_type, id AS source_id,
605
+ 's:' || printf('%020d', id) AS order_key,
606
+ ${SYNC_OCCURRED_AT_SQL} AS occurred_at,
607
+ CASE WHEN ok <> 0 THEN 'succeeded' ELSE 'failed' END AS status,
608
+ ops_in AS metric_a, ops_out AS metric_b,
609
+ CASE
610
+ WHEN lower(error) LIKE '%auth%' OR lower(error) LIKE '%unauthorized%' THEN 'authentication'
611
+ WHEN lower(error) LIKE '%timeout%' OR lower(error) LIKE '%timed out%' THEN 'timeout'
612
+ WHEN lower(error) LIKE '%version%' OR lower(error) LIKE '%capability%' THEN 'compatibility'
613
+ WHEN lower(error) LIKE '%connect%' OR lower(error) LIKE '%network%'
614
+ OR lower(error) LIKE '%dns%' THEN 'connectivity'
615
+ ELSE 'unspecified'
616
+ END AS category
617
+ FROM sync_attempts ${predicates.length ? `WHERE ${predicates.join(" AND ")}` : ""}
618
+ ORDER BY ${SYNC_OCCURRED_AT_SQL} DESC, id DESC
619
+ LIMIT @sourceLimit`;
620
+ }
621
+ function observerSelect(options) {
622
+ if (!options.severities.has("error")) return null;
623
+ const observer = options.subsystems.has("observer");
624
+ const capture = options.subsystems.has("capture");
625
+ if (!observer && !capture) return null;
626
+ let providerFilter = "";
627
+ if (observer && !capture) providerFilter = "AND observer_provider IS NOT NULL";
628
+ if (capture && !observer) providerFilter = "AND observer_provider IS NULL";
629
+ const cursorFilter = options.cursor ? `AND (updated_at < @cursorOccurredAt OR
630
+ (updated_at = @cursorOccurredAt AND
631
+ 'o:' || printf('%020d', id) < @cursorOrderKey))` : "";
632
+ return [
633
+ "error",
634
+ "failed",
635
+ "gave_up"
636
+ ].map((status) => `SELECT * FROM (
637
+ SELECT
638
+ CASE WHEN observer_provider IS NULL THEN 'capture_failure' ELSE 'observer' END AS source_type,
639
+ id AS source_id, 'o:' || printf('%020d', id) AS order_key,
640
+ updated_at AS occurred_at, status, attempt_count AS metric_a,
641
+ NULL AS metric_b,
642
+ CASE
643
+ WHEN lower(COALESCE(observer_error_code, error_type)) LIKE '%auth%' THEN 'authentication'
644
+ WHEN lower(COALESCE(observer_error_code, error_type)) LIKE '%rate%'
645
+ OR lower(COALESCE(observer_error_code, error_type)) LIKE '%limit%' THEN 'rate limit'
646
+ WHEN lower(COALESCE(observer_error_code, error_type)) LIKE '%timeout%' THEN 'timeout'
647
+ WHEN lower(COALESCE(observer_error_code, error_type)) LIKE '%schema%'
648
+ OR lower(COALESCE(observer_error_code, error_type)) LIKE '%parse%' THEN 'response format'
649
+ ELSE 'unspecified'
650
+ END AS category
651
+ FROM raw_event_flush_batches
652
+ WHERE status = '${status}' ${providerFilter} ${cursorFilter}
653
+ ORDER BY updated_at DESC, id DESC
654
+ LIMIT @sourceLimit
655
+ )`).join(" UNION ALL ");
656
+ }
657
+ function maintenanceSelect(options) {
658
+ if (!options.subsystems.has("maintenance")) return null;
659
+ const statuses = [];
660
+ if (options.severities.has("info")) statuses.push("'pending'", "'running'");
661
+ if (options.severities.has("warning")) statuses.push("'cancelled'");
662
+ if (options.severities.has("error")) statuses.push("'failed'");
663
+ if (!statuses.length) return null;
664
+ return `SELECT 'maintenance' AS source_type, kind AS source_id,
665
+ 'm:' || hex(kind) AS order_key, updated_at AS occurred_at,
666
+ status, progress_current AS metric_a, progress_total AS metric_b,
667
+ NULL AS category
668
+ FROM maintenance_jobs WHERE status IN (${statuses.join(", ")})`;
669
+ }
670
+ function backlogSelect(options) {
671
+ if (!options.subsystems.has("capture")) return null;
672
+ const warning = options.severities.has("warning");
673
+ const error = options.severities.has("error");
674
+ if (!warning && !error) return null;
675
+ let having = "pending >= 200";
676
+ if (!warning) having = "pending >= 1000";
677
+ if (!error) having = "pending >= 200 AND pending < 1000";
678
+ return `WITH candidate_sessions AS MATERIALIZED (
679
+ SELECT source, stream_id, last_flushed_event_seq, updated_at
680
+ FROM raw_event_sessions
681
+ WHERE last_received_event_seq > last_flushed_event_seq
682
+ ORDER BY updated_at DESC
683
+ LIMIT 1001
684
+ ), bounded_sessions AS MATERIALIZED (
685
+ SELECT source, stream_id, last_flushed_event_seq, updated_at
686
+ FROM candidate_sessions
687
+ ORDER BY updated_at DESC
688
+ LIMIT 1000
689
+ ), retained_sessions AS MATERIALIZED (
690
+ SELECT updated_at,
691
+ (SELECT COUNT(*) FROM raw_events AS events
692
+ WHERE events.source = sessions.source
693
+ AND events.stream_id = sessions.stream_id
694
+ AND events.event_seq > sessions.last_flushed_event_seq) AS pending
695
+ FROM bounded_sessions AS sessions
696
+ )
697
+ SELECT 'backlog' AS source_type, 0 AS source_id,
698
+ 'b:' || printf('%020d', 0) AS order_key, occurred_at,
699
+ CASE WHEN pending >= 1000 THEN 'error' ELSE 'warning' END AS status,
700
+ pending AS metric_a, sessions AS metric_b,
701
+ CASE WHEN candidate_count > 1000 THEN 'capped' ELSE NULL END AS category
702
+ FROM (
703
+ SELECT MAX(updated_at) AS occurred_at,
704
+ SUM(pending) AS pending,
705
+ COUNT(*) FILTER (WHERE pending > 0) AS sessions,
706
+ (SELECT COUNT(*) FROM candidate_sessions) AS candidate_count
707
+ FROM retained_sessions
708
+ WHERE pending > 0
709
+ ) WHERE ${having}`;
710
+ }
711
+ function sourceSelects(options) {
712
+ return [
713
+ syncSelect(options),
714
+ observerSelect(options),
715
+ maintenanceSelect(options),
716
+ backlogSelect(options)
717
+ ].filter((select) => select != null);
718
+ }
719
+ function boundedSourceSelect(select, hasCursor) {
720
+ return `SELECT * FROM (
721
+ SELECT * FROM (${select})
722
+ ${hasCursor ? "WHERE occurred_at < @cursorOccurredAt OR (occurred_at = @cursorOccurredAt AND order_key < @cursorOrderKey)" : ""}
723
+ ORDER BY occurred_at DESC, order_key DESC
724
+ LIMIT @sourceLimit
725
+ )`;
726
+ }
727
+ function loadSourceRows(store, options) {
728
+ const selects = sourceSelects(options);
729
+ if (!selects.length) return [];
730
+ const boundedSelects = selects.map((select) => boundedSourceSelect(select, options.cursor != null));
731
+ const params = {
732
+ resultLimit: options.limit + 1,
733
+ sourceLimit: options.limit + 1
734
+ };
735
+ if (options.cursor) {
736
+ params.cursorOccurredAt = options.cursor.occurredAt;
737
+ params.cursorOrderKey = options.cursor.orderKey;
738
+ }
739
+ return store.db.prepare(`SELECT * FROM (${boundedSelects.join(" UNION ALL ")})
740
+ ORDER BY occurred_at DESC, order_key DESC
741
+ LIMIT @resultLimit`).all(params);
742
+ }
743
+ function syncEvent(row, includeTechnical) {
744
+ const succeeded = row.status === "succeeded";
745
+ const opsIn = Number(row.metric_a ?? 0);
746
+ const opsOut = Number(row.metric_b ?? 0);
747
+ const category = row.category ?? "unspecified";
748
+ return {
749
+ id: opaqueId("sync-attempt", row.source_id),
750
+ orderKey: row.order_key,
751
+ occurred_at: row.occurred_at,
752
+ severity: succeeded ? "info" : "error",
753
+ subsystem: "sync",
754
+ code: succeeded ? "sync_attempt_succeeded" : "sync_attempt_failed",
755
+ message: succeeded ? "A sync attempt completed." : "A sync attempt failed before all work completed.",
756
+ recovery: succeeded ? void 0 : {
757
+ label: "Open advanced sync diagnostics",
758
+ href: "#advanced/sync/diagnostics"
759
+ },
760
+ technical_detail: technicalDetail(true, succeeded ? `${opsIn} inbound and ${opsOut} outbound operations.` : `Failure category: ${category}. ${opsIn} inbound and ${opsOut} outbound operations.`, includeTechnical)
761
+ };
762
+ }
763
+ function failureEvent(row, includeTechnical) {
764
+ const subsystem = row.source_type === "observer" ? "observer" : "capture";
765
+ const attempts = Math.max(0, Number(row.metric_a ?? 0));
766
+ const gaveUp = row.status === "gave_up";
767
+ return {
768
+ id: opaqueId("processing-failure", row.source_id),
769
+ orderKey: row.order_key,
770
+ occurred_at: row.occurred_at,
771
+ severity: "error",
772
+ subsystem,
773
+ code: `${subsystem}_flush_${gaveUp ? "gave_up" : "failed"}`,
774
+ message: gaveUp ? "Processing stopped after all retry attempts were exhausted." : "Processing failed and queued events are waiting for retry.",
775
+ recovery: {
776
+ label: "Open Health",
777
+ href: "#health"
778
+ },
779
+ technical_detail: technicalDetail(true, `Failure category: ${row.category ?? "unspecified"}. Attempts: ${attempts}.`, includeTechnical)
780
+ };
781
+ }
782
+ function maintenanceEvent(row, includeTechnical) {
783
+ let severity = "info";
784
+ let code = "maintenance_job_active";
785
+ let message = "A background maintenance task is active.";
786
+ if (row.status === "failed") {
787
+ severity = "error";
788
+ code = "maintenance_job_failed";
789
+ message = "A background maintenance task failed.";
790
+ } else if (row.status === "cancelled") {
791
+ severity = "warning";
792
+ code = "maintenance_job_cancelled";
793
+ message = "A background maintenance task was cancelled.";
794
+ }
795
+ const total = row.metric_b == null ? "unknown" : String(row.metric_b);
796
+ return {
797
+ id: opaqueId("maintenance", row.source_id),
798
+ orderKey: row.order_key,
799
+ occurred_at: row.occurred_at,
800
+ severity,
801
+ subsystem: "maintenance",
802
+ code,
803
+ message,
804
+ recovery: severity === "error" ? {
805
+ label: "Open Health",
806
+ href: "#health"
807
+ } : void 0,
808
+ technical_detail: technicalDetail(true, `Progress: ${Number(row.metric_a ?? 0)} of ${total}.`, includeTechnical)
809
+ };
810
+ }
811
+ function backlogEvent(row, includeTechnical) {
812
+ const severity = row.status === "error" ? "error" : "warning";
813
+ const pending = Number(row.metric_a ?? 0);
814
+ const sessions = Number(row.metric_b ?? 0);
815
+ const countPrefix = row.category === "capped" ? "At least " : "";
816
+ const sessionLabel = sessions === 1 ? "session" : "sessions";
817
+ return {
818
+ id: opaqueId("capture-backlog", 0),
819
+ orderKey: row.order_key,
820
+ occurred_at: row.occurred_at,
821
+ severity,
822
+ subsystem: "capture",
823
+ code: severity === "error" ? "capture_backlog_high" : "capture_backlog_growing",
824
+ message: severity === "error" ? "The capture queue has a high pending backlog." : "The capture queue is growing and may need attention.",
825
+ recovery: {
826
+ label: "Open Health",
827
+ href: "#health"
828
+ },
829
+ technical_detail: technicalDetail(true, `${countPrefix}${pending} pending events across ${sessions} ${sessionLabel}.`, includeTechnical)
830
+ };
831
+ }
832
+ function collectEvents(store, options) {
833
+ return loadSourceRows(store, options).map((row) => {
834
+ if (row.source_type === "sync") return syncEvent(row, options.includeTechnical);
835
+ if (row.source_type === "maintenance") return maintenanceEvent(row, options.includeTechnical);
836
+ if (row.source_type === "backlog") return backlogEvent(row, options.includeTechnical);
837
+ return failureEvent(row, options.includeTechnical);
838
+ });
839
+ }
840
+ function diagnosticsRoutes(getStore) {
841
+ const app = new Hono();
842
+ app.get("/api/diagnostics/events", (c) => {
843
+ const limit = parseLimit(c.req.query("limit"));
844
+ const cursor = parseCursor(c.req.query("cursor"));
845
+ const severities = parseFilter(c.req.query("severity"), DIAGNOSTIC_SEVERITIES);
846
+ const subsystems = parseFilter(c.req.query("subsystem"), DIAGNOSTIC_SUBSYSTEMS);
847
+ const includeTechnical = parseTechnical(c.req.query("includeTechnical"));
848
+ if (limit === "invalid" || cursor === "invalid" || severities === "invalid" || subsystems === "invalid" || includeTechnical === "invalid") return c.json({ error: "invalid_diagnostics_query" }, 400);
849
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
850
+ const events = collectEvents(getStore(), {
851
+ cursor,
852
+ includeTechnical,
853
+ limit,
854
+ severities,
855
+ subsystems
856
+ });
857
+ const orderedItems = events.slice(0, limit);
858
+ const items = orderedItems.map(({ orderKey: _orderKey, ...event }) => event);
859
+ const lastItem = orderedItems.at(-1);
860
+ const nextCursor = events.length > limit && lastItem ? encodeCursor(lastItem) : null;
861
+ c.header("Cache-Control", "no-store");
862
+ return c.json({
863
+ contract_version: 1,
864
+ items,
865
+ next_cursor: nextCursor,
866
+ redacted: true,
867
+ generated_at: generatedAt
868
+ });
869
+ });
870
+ return app;
871
+ }
872
+ //#endregion
516
873
  //#region src/routes/health.ts
517
874
  function databaseReachable(getStore) {
518
875
  try {
@@ -801,18 +1158,6 @@ function countVisibleMemoryRows(store, filters) {
801
1158
  const row = store.db.prepare(`SELECT COUNT(*) AS total FROM ${from} WHERE ${clauses.join(" AND ")}`).get(...filterResult.params);
802
1159
  return Number(row?.total ?? 0);
803
1160
  }
804
- function summaryLikeSqlPredicate() {
805
- return `(
806
- LOWER(TRIM(COALESCE(memory_items.kind, ''))) = 'session_summary'
807
- OR (
808
- json_valid(COALESCE(memory_items.metadata_json, ''))
809
- AND (
810
- COALESCE(json_type(memory_items.metadata_json, '$.is_summary') = 'true', 0)
811
- OR LOWER(TRIM(COALESCE(json_extract(memory_items.metadata_json, '$.source'), ''))) = 'observer_summary'
812
- )
813
- )
814
- )`;
815
- }
816
1161
  function memorySearchSql(query) {
817
1162
  const textClause = `INSTR(codemem_casefold(
818
1163
  COALESCE(memory_items.title, '') || CHAR(10) ||
@@ -1351,9 +1696,12 @@ var PACK_KEYS = /* @__PURE__ */ new Set([
1351
1696
  "compact_detail_count",
1352
1697
  "db_path",
1353
1698
  "identity_target",
1354
- "attempt"
1699
+ "attempt",
1700
+ "automatic_context"
1355
1701
  ]);
1356
1702
  var LEDGER_KEYS = /* @__PURE__ */ new Set([
1703
+ "automatic_recall",
1704
+ "evaluation_key",
1357
1705
  "action",
1358
1706
  "attempt_id",
1359
1707
  "started_at",
@@ -1459,6 +1807,48 @@ function attemptMetadata(payload) {
1459
1807
  requestId: payload.request_id ?? null
1460
1808
  };
1461
1809
  }
1810
+ function validateAutomaticContext(value) {
1811
+ if (value === null) return null;
1812
+ if (!isRecord(value)) return "automatic_context must be an object or null";
1813
+ for (const key of Object.keys(value)) if (key !== "source" && key !== "host_session_id") return `automatic_context contains unsupported field: ${key}`;
1814
+ const source = value.source;
1815
+ const hostSessionId = value.host_session_id;
1816
+ if (typeof source !== "string" || !source.trim() || source.length > 64) return "automatic_context source is invalid";
1817
+ if (typeof hostSessionId !== "string" || !hostSessionId.trim() || hostSessionId.length > MAX_METADATA_FIELD_CHARS || isAbsolutePath(hostSessionId)) return "automatic_context host_session_id is invalid";
1818
+ return {
1819
+ source: source.trim(),
1820
+ hostSessionId: hostSessionId.trim()
1821
+ };
1822
+ }
1823
+ function resolveRequestAutomaticContext(value, attempt) {
1824
+ if (Object.hasOwn(value, "automatic_context")) return validateAutomaticContext(value.automatic_context);
1825
+ if (!attempt) return void 0;
1826
+ const source = typeof attempt.source === "string" ? attempt.source.trim().toLowerCase() : "";
1827
+ if (source && source !== "opencode") return void 0;
1828
+ const hostSessionId = attempt.source_session_id;
1829
+ if (source === "opencode" && typeof hostSessionId === "string" && hostSessionId.trim()) return {
1830
+ source,
1831
+ hostSessionId: hostSessionId.trim()
1832
+ };
1833
+ return null;
1834
+ }
1835
+ function packRenderOptions(value) {
1836
+ if (value.compact == null && value.compact_detail_count == null) return void 0;
1837
+ return {
1838
+ compact: value.compact === true || value.compact_detail_count != null,
1839
+ ...value.compact_detail_count != null ? { compactDetailCount: value.compact_detail_count } : {}
1840
+ };
1841
+ }
1842
+ function packRequestArgs(request) {
1843
+ return [
1844
+ request.context,
1845
+ request.limit,
1846
+ request.tokenBudget,
1847
+ request.filters,
1848
+ request.renderOptions,
1849
+ request.automaticContext
1850
+ ];
1851
+ }
1462
1852
  function validatePackRequest(value) {
1463
1853
  if (!isRecord(value)) return "request body must be an object";
1464
1854
  for (const key of Object.keys(value)) if (!PACK_KEYS.has(key)) return `request body contains unsupported field: ${key}`;
@@ -1490,6 +1880,8 @@ function validatePackRequest(value) {
1490
1880
  if (attemptError) return attemptError;
1491
1881
  attempt = value.attempt;
1492
1882
  }
1883
+ const automaticContext = resolveRequestAutomaticContext(value, attempt);
1884
+ if (typeof automaticContext === "string") return automaticContext;
1493
1885
  const filters = {};
1494
1886
  if (value.all_projects !== true) {
1495
1887
  const envProject = process.env.CODEMEM_PROJECT?.trim() || null;
@@ -1499,18 +1891,14 @@ function validatePackRequest(value) {
1499
1891
  if (project) filters.project = project;
1500
1892
  }
1501
1893
  if (workingSetFiles.length > 0) filters.working_set_paths = workingSetFiles;
1502
- let renderOptions;
1503
- if (value.compact != null || value.compact_detail_count != null) renderOptions = {
1504
- compact: value.compact === true || value.compact_detail_count != null,
1505
- ...value.compact_detail_count != null ? { compactDetailCount: value.compact_detail_count } : {}
1506
- };
1507
1894
  return {
1508
1895
  context,
1509
1896
  limit,
1510
1897
  tokenBudget,
1511
1898
  filters,
1512
- renderOptions,
1513
- attempt
1899
+ renderOptions: packRenderOptions(value),
1900
+ attempt,
1901
+ automaticContext
1514
1902
  };
1515
1903
  }
1516
1904
  function ledgerFailureStatus(reason) {
@@ -1519,19 +1907,37 @@ function ledgerFailureStatus(reason) {
1519
1907
  if (reason === "storage_unavailable") return 503;
1520
1908
  return 400;
1521
1909
  }
1910
+ function dispatchRecallMeasurement(store, payload) {
1911
+ const action = payload.action;
1912
+ if (payload.automatic_recall === void 0 && action !== "recall") return null;
1913
+ const isValidMeasurement = isAutomaticRecallMeasurement(payload.automatic_recall) && typeof payload.evaluation_key === "string" && /^[a-f0-9]{64}$/.test(payload.evaluation_key);
1914
+ if (action === "delivery") {
1915
+ if (!isValidMeasurement) return null;
1916
+ try {
1917
+ recordAutomaticRecall(store.db, payload.attempt_id, payload.evaluation_key, payload.automatic_recall);
1918
+ } catch {}
1919
+ return null;
1920
+ }
1921
+ if (action !== "recall" || !isValidMeasurement) return "invalid automatic recall measurement";
1922
+ return recordAutomaticRecall(store.db, payload.attempt_id, payload.evaluation_key, payload.automatic_recall);
1923
+ }
1522
1924
  function dispatchLedger(store, payload) {
1523
1925
  const action = payload.action;
1524
- if (action !== "record" && action !== "delivery" && action !== "cache_reuse") return "ledger action is invalid";
1525
1926
  const metadata = attemptMetadata(payload);
1526
1927
  if (action === "delivery") {
1527
1928
  const status = payload.delivery_status;
1528
1929
  if (status !== "handed_off" && status !== "failed" && status !== "unknown") return "delivery action requires a valid delivery_status";
1529
- return tryUpdateRetrievalDelivery(store.db, metadata.attemptId, status);
1930
+ const outcome = tryUpdateRetrievalDelivery(store.db, metadata.attemptId, status);
1931
+ if (outcome.ok) dispatchRecallMeasurement(store, payload);
1932
+ return outcome;
1530
1933
  }
1934
+ const measurement = dispatchRecallMeasurement(store, payload);
1935
+ if (measurement !== null) return measurement;
1531
1936
  if (action === "cache_reuse") {
1532
1937
  if (typeof payload.original_attempt_id !== "string" || !payload.original_attempt_id) return "cache_reuse action requires original_attempt_id";
1533
1938
  return clonePromptPackAttempt(store.db, payload.original_attempt_id, metadata);
1534
1939
  }
1940
+ if (action !== "record") return "ledger action is invalid";
1535
1941
  if (payload.retrieval_status !== "skipped" && payload.retrieval_status !== "failed" || typeof payload.failure_code !== "string" || !payload.failure_code || typeof payload.failure_stage !== "string" || !payload.failure_stage) return "record action requires retrieval_status, failure_code, and failure_stage";
1536
1942
  return recordPromptPackTerminal(store.db, metadata, payload.retrieval_status, payload.failure_code, payload.failure_stage);
1537
1943
  }
@@ -1568,10 +1974,10 @@ function packTransportRoutes(getStore) {
1568
1974
  const target = validateViewerTarget(store, parsed, { requireCurrentIdentity: true });
1569
1975
  if (!target.ok) return c.json(target.body, target.status);
1570
1976
  if (!request.attempt) {
1571
- const pack = await store.buildMemoryPackAsync(request.context, request.limit, request.tokenBudget, request.filters, request.renderOptions);
1977
+ const pack = await store.buildMemoryPackAsync(...packRequestArgs(request));
1572
1978
  return c.json(pack);
1573
1979
  }
1574
- const artifacts = await store.buildMemoryPackWithTraceAsync(request.context, request.limit, request.tokenBudget, request.filters, request.renderOptions);
1980
+ const artifacts = await store.buildMemoryPackWithTraceAsync(...packRequestArgs(request));
1575
1981
  let ledgerArtifactFingerprint;
1576
1982
  try {
1577
1983
  ledgerArtifactFingerprint = promptPackArtifactFingerprint(store.db, request.context, request.filters, artifacts);
@@ -2128,7 +2534,7 @@ function statsRoutes(getStore) {
2128
2534
  error: job.error
2129
2535
  }));
2130
2536
  return c.json({
2131
- ...store.stats(),
2537
+ ...healthStats(store),
2132
2538
  viewer_pid: process.pid,
2133
2539
  maintenance_jobs: surfacedJobs
2134
2540
  });
@@ -2193,6 +2599,15 @@ function statsRoutes(getStore) {
2193
2599
  });
2194
2600
  return app;
2195
2601
  }
2602
+ function healthStats(store) {
2603
+ return {
2604
+ ...store.stats(),
2605
+ automatic_recall: automaticRecallHealth(store.db, {
2606
+ actorId: store.actorId,
2607
+ deviceId: store.deviceId
2608
+ })
2609
+ };
2610
+ }
2196
2611
  //#endregion
2197
2612
  //#region src/routes/team-setup-view.ts
2198
2613
  var enabled = {
@@ -2922,10 +3337,10 @@ function projectMappingChoices(store) {
2922
3337
  excludePeerReceived: true
2923
3338
  }).filter((candidate) => !candidate.read_only && isLegacyTeamSetupProjectMappingIdentity(candidate.workspace_identity, store.db));
2924
3339
  } catch (error) {
2925
- if (error instanceof Error && (error.message === "project_scope_candidate_scan_too_large" || error.message === "project_scope_candidate_metadata_too_large")) throw new Error("legacy_team_setup_roster_too_large");
3340
+ if (error instanceof Error && (error.message === "project_scope_candidate_scan_too_large" || error.message === "project_scope_candidate_metadata_too_large")) throw new LocalProjectScanBudgetError(error);
2926
3341
  throw error;
2927
3342
  }
2928
- if (candidates.length > MAX_PROJECT_MAPPING_CHOICES) throw new Error("legacy_team_setup_roster_too_large");
3343
+ if (candidates.length > MAX_PROJECT_MAPPING_CHOICES) throw new LocalProjectScanBudgetError();
2929
3344
  return candidates.map((candidate) => ({
2930
3345
  projectIdentity: candidate.workspace_identity,
2931
3346
  sourceDisplayName: candidate.display_project
@@ -3001,6 +3416,156 @@ function completionApiError(error) {
3001
3416
  const code = legacyTeamSetupApiErrorCode(error);
3002
3417
  return code === "team_setup_failed" ? "team_setup_completion_unavailable" : code;
3003
3418
  }
3419
+ var TeamSetupFinishError = class extends Error {
3420
+ reason;
3421
+ alreadyLogged;
3422
+ constructor(code, reason, alreadyLogged, cause) {
3423
+ super(code, cause === void 0 ? void 0 : { cause });
3424
+ this.reason = reason;
3425
+ this.alreadyLogged = alreadyLogged;
3426
+ this.name = "TeamSetupFinishError";
3427
+ }
3428
+ };
3429
+ /**
3430
+ * The generic `legacy_team_setup_roster_too_large` string is thrown by ten
3431
+ * sites for unrelated limits (coordinator roster size, draft device count,
3432
+ * local project choices). Only the local project-candidate scan is a
3433
+ * "this device's history is too large" condition, so it carries a distinct
3434
+ * type. The message is kept identical so the frozen API-code mapping and every
3435
+ * existing caller that matches on it are unchanged.
3436
+ */
3437
+ var LocalProjectScanBudgetError = class extends Error {
3438
+ constructor(cause) {
3439
+ super("legacy_team_setup_roster_too_large", cause === void 0 ? void 0 : { cause });
3440
+ this.name = "LocalProjectScanBudgetError";
3441
+ }
3442
+ };
3443
+ /**
3444
+ * Remote error codes the finish path can legitimately receive from the
3445
+ * coordinator. Anything else — including identifier-shaped values, which a
3446
+ * hostile or misconfigured upstream could use to smuggle a credential through
3447
+ * a JSON `error` field — is reduced to a fixed classification before logging.
3448
+ * A shape check is not an allowlist; only this set is echoed verbatim.
3449
+ */
3450
+ var LOGGABLE_REMOTE_ERROR_CODES = /* @__PURE__ */ new Set([
3451
+ "body_too_large",
3452
+ "completion_manifest_invalid",
3453
+ "completion_manifest_unavailable",
3454
+ "completion_not_found",
3455
+ "completion_query_invalid",
3456
+ "group_archived",
3457
+ "group_id_and_candidate_ref_required",
3458
+ "group_not_found",
3459
+ "invalid_json",
3460
+ "missing_admin_header",
3461
+ "not_found",
3462
+ "rate_limited",
3463
+ "response_too_large",
3464
+ "unauthorized"
3465
+ ]);
3466
+ function loggableRemoteCode(code) {
3467
+ if (LOGGABLE_REMOTE_ERROR_CODES.has(code)) return code;
3468
+ if (code.startsWith("non_json_response")) return "non_json_response";
3469
+ return "unclassified_remote_error";
3470
+ }
3471
+ /**
3472
+ * Local causes the finish path can produce. Same allowlist discipline as
3473
+ * remote codes: everything else, including transport messages that embed the
3474
+ * target URL, is classified rather than echoed.
3475
+ */
3476
+ var LOGGABLE_LOCAL_CAUSES = /* @__PURE__ */ new Set([
3477
+ "completion_conflict",
3478
+ "legacy_team_setup_roster_too_large",
3479
+ "project_scope_candidate_metadata_too_large",
3480
+ "project_scope_candidate_scan_too_large",
3481
+ "team_setup_completion_conflict",
3482
+ "team_setup_completion_invalid",
3483
+ "team_setup_completion_unavailable",
3484
+ "team_setup_confirmation_stale",
3485
+ "team_setup_roster_unavailable"
3486
+ ]);
3487
+ function loggableLocalCause(error) {
3488
+ if (LOGGABLE_LOCAL_CAUSES.has(error.message)) return error.message;
3489
+ if (error.name === "AbortError" || error.name === "TimeoutError") return error.name;
3490
+ if (/fetch failed/iu.test(error.message)) return "fetch_failed";
3491
+ if (/timed out|timeout/iu.test(error.message)) return "timed_out";
3492
+ return `unclassified_error:${error.name || "Error"}`;
3493
+ }
3494
+ function errorChain(error) {
3495
+ const chain = [];
3496
+ let current = error;
3497
+ while (current && !chain.includes(current)) {
3498
+ chain.push(current);
3499
+ current = current instanceof Error ? current.cause : void 0;
3500
+ }
3501
+ return chain;
3502
+ }
3503
+ function remoteFailureReason(remote, fallback) {
3504
+ if (remote.status === 404 && (remote.code === "not_found" || remote.code.startsWith("non_json"))) return "coordinator_route_missing";
3505
+ if (remote.status === 400 && remote.code === "completion_manifest_invalid") return "coordinator_rejected_manifest";
3506
+ return fallback;
3507
+ }
3508
+ function teamSetupFinishFailureDetails(error) {
3509
+ const chain = errorChain(error);
3510
+ const wrapper = chain.find((item) => item instanceof TeamSetupFinishError);
3511
+ const remote = chain.find((item) => item instanceof RemoteCoordinatorRequestError);
3512
+ if (remote) {
3513
+ const reason = remoteFailureReason(remote, wrapper?.reason);
3514
+ return {
3515
+ ...reason ? { reason } : {},
3516
+ cause: "remote coordinator request failed",
3517
+ status: remote.status,
3518
+ code: loggableRemoteCode(remote.code)
3519
+ };
3520
+ }
3521
+ if (chain.some((item) => item instanceof LegacyTeamSetupRosterCapacityError || item instanceof LocalProjectScanBudgetError)) return {
3522
+ reason: "local_candidate_scan_budget_exceeded",
3523
+ cause: "local project candidate scan exceeded budget"
3524
+ };
3525
+ const errorCause = chain.find((item) => item instanceof Error && !(item instanceof TeamSetupFinishError));
3526
+ const cause = errorCause ? loggableLocalCause(errorCause) : "unclassified_non_error";
3527
+ if (wrapper?.reason) return {
3528
+ reason: wrapper.reason,
3529
+ cause
3530
+ };
3531
+ if (errorCause && (errorCause.name === "AbortError" || errorCause.name === "TimeoutError" || /fetch failed|timed out|timeout/iu.test(errorCause.message))) return {
3532
+ reason: "coordinator_unreachable",
3533
+ cause
3534
+ };
3535
+ return { cause };
3536
+ }
3537
+ function warnTeamSetupFinishFailure(error, details, context) {
3538
+ if (errorChain(error).some((item) => item instanceof TeamSetupFinishError && item.alreadyLogged)) return;
3539
+ const safe = (value) => {
3540
+ let sanitized = String(value).replace(/[\r\n\t]+/gu, " ");
3541
+ for (const secret of context.redactValues ?? []) if (secret) sanitized = sanitized.replaceAll(secret, "[redacted]");
3542
+ return sanitized.replace(/https?:\/\/[^\s]+/giu, (value) => {
3543
+ try {
3544
+ const url = new URL(value);
3545
+ return `${url.protocol}//${url.host}`;
3546
+ } catch {
3547
+ return "[redacted-url]";
3548
+ }
3549
+ });
3550
+ };
3551
+ const fields = [
3552
+ `candidateRef=${safe(context.candidateRef)}`,
3553
+ `attemptId=${safe(context.attemptId)}`,
3554
+ `apiCode=${safe(context.apiCode)}`,
3555
+ `cause=${safe(details.cause)}`,
3556
+ ...details.reason ? [`reason=${safe(details.reason)}`] : [],
3557
+ ...details.status == null ? [] : [`status=${safe(details.status)}`],
3558
+ ...details.code == null ? [] : [`code=${safe(details.code)}`]
3559
+ ];
3560
+ console.warn(`[team-setup finish] ${fields.join(" ")}`);
3561
+ }
3562
+ function finishErrorResponse(code, details) {
3563
+ const reason = details.reason;
3564
+ return reason ? {
3565
+ error: code,
3566
+ reason
3567
+ } : { error: code };
3568
+ }
3004
3569
  async function parseBoundedJsonObject(c) {
3005
3570
  if (c.req.header("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") return { ok: false };
3006
3571
  const lengthHeader = c.req.header("content-length");
@@ -3741,6 +4306,7 @@ function teamSetupRoutes(options) {
3741
4306
  let releaseMutation;
3742
4307
  let releasePublicationMutation;
3743
4308
  let releaseActorMutations;
4309
+ let finishLogRedactValues = [];
3744
4310
  try {
3745
4311
  const store = options.getStore();
3746
4312
  const draft = getLegacyTeamSetupDraft(store.db, candidateRef);
@@ -3803,8 +4369,9 @@ function teamSetupRoutes(options) {
3803
4369
  if (!completionDependencies || !authorization) throw new Error("team_setup_completion_unavailable");
3804
4370
  if (!authorizesGroup(authorization, group)) throw new Error("team_setup_completion_invalid");
3805
4371
  const { config, remoteUrl } = authorization;
4372
+ finishLogRedactValues = [config.syncCoordinatorAdminSecret];
3806
4373
  const freshGroup = await loadFreshGroupSnapshot(group, remoteUrl, publicationDeadlineMs);
3807
- if (!freshGroup) throw new Error("team_setup_roster_unavailable");
4374
+ if (!freshGroup) throw new TeamSetupFinishError("team_setup_roster_unavailable", "coordinator_roster_unavailable", false, /* @__PURE__ */ new Error("coordinator roster unavailable"));
3808
4375
  if (!stillAuthorizesGroup(authorization, group)) throw new Error("team_setup_completion_invalid");
3809
4376
  const projectInventory = legacyTeamCandidateProjectInventory(store.db, projectionOptions(store), candidateRef);
3810
4377
  const freshPreview = inspectFreshLegacyTeamSetupActivation(store.db, {
@@ -3845,8 +4412,17 @@ function teamSetupRoutes(options) {
3845
4412
  if (canonicalCoordinatorLegacyTeamCompletionManifestJson(canonicalManifest) !== canonicalCoordinatorLegacyTeamCompletionManifestJson(proposedManifest)) throw new Error("team_setup_completion_conflict");
3846
4413
  } catch (error) {
3847
4414
  const code = completionApiError(error);
4415
+ const details = teamSetupFinishFailureDetails(error);
3848
4416
  const getCompletion = completionDependencies.get;
3849
- if (code !== "team_setup_completion_conflict" || !getCompletion) throw new Error(code);
4417
+ if (code !== "team_setup_completion_conflict" || !getCompletion) {
4418
+ warnTeamSetupFinishFailure(error, details, {
4419
+ candidateRef,
4420
+ attemptId,
4421
+ apiCode: code,
4422
+ redactValues: finishLogRedactValues
4423
+ });
4424
+ throw new TeamSetupFinishError(code, details.reason, true, error);
4425
+ }
3850
4426
  try {
3851
4427
  const existing = await retryTransientCoordinatorRead((attemptTimeoutS) => getCompletion({
3852
4428
  groupId: group.groupId,
@@ -3862,17 +4438,17 @@ function teamSetupRoutes(options) {
3862
4438
  });
3863
4439
  if (canonicalManifest.finish_digest !== proposedManifest.finish_digest) throw new Error("team_setup_completion_conflict");
3864
4440
  } catch (recoveryError) {
3865
- throw new Error(completionApiError(recoveryError));
4441
+ throw new TeamSetupFinishError(completionApiError(recoveryError), teamSetupFinishFailureDetails(recoveryError).reason, false, recoveryError);
3866
4442
  }
3867
4443
  }
3868
4444
  let postPublicationGroup;
3869
4445
  try {
3870
4446
  postPublicationGroup = await loadFreshGroupSnapshot(group, remoteUrl, publicationDeadlineMs);
3871
4447
  } catch (error) {
3872
- if (legacyTeamSetupApiErrorCode(error) === "team_setup_completion_invalid") throw new Error("team_setup_roster_unavailable");
4448
+ if (legacyTeamSetupApiErrorCode(error) === "team_setup_completion_invalid") throw new TeamSetupFinishError("team_setup_roster_unavailable", "coordinator_roster_unavailable", false, error);
3873
4449
  throw error;
3874
4450
  }
3875
- if (!postPublicationGroup) throw new Error("team_setup_roster_unavailable");
4451
+ if (!postPublicationGroup) throw new TeamSetupFinishError("team_setup_roster_unavailable", "coordinator_roster_unavailable", false, /* @__PURE__ */ new Error("coordinator roster unavailable"));
3876
4452
  const result = await applyLegacyTeamSetupCompletionManifestAndReturnActivation(store.db, {
3877
4453
  coordinatorId: group.coordinatorId,
3878
4454
  groupId: group.groupId,
@@ -3889,7 +4465,14 @@ function teamSetupRoutes(options) {
3889
4465
  }));
3890
4466
  } catch (error) {
3891
4467
  const code = apiErrorCode(error);
3892
- return c.json({ error: code }, errorStatus$1(code));
4468
+ const details = teamSetupFinishFailureDetails(error);
4469
+ warnTeamSetupFinishFailure(error, details, {
4470
+ candidateRef,
4471
+ attemptId,
4472
+ apiCode: code,
4473
+ redactValues: finishLogRedactValues
4474
+ });
4475
+ return c.json(finishErrorResponse(code, details), errorStatus$1(code));
3893
4476
  } finally {
3894
4477
  releaseActorMutations?.();
3895
4478
  releasePublicationMutation?.();
@@ -10033,6 +10616,26 @@ function closeStore() {
10033
10616
  sharedStore?.close();
10034
10617
  sharedStore = null;
10035
10618
  }
10619
+ function registerStaticRoutes(app) {
10620
+ const staticRoot = process.env.CODEMEM_VIEWER_STATIC_DIR ?? join(import.meta.dirname ?? ".", "../static");
10621
+ app.use("/assets/*", async (c, next) => {
10622
+ c.header("Cache-Control", "no-cache");
10623
+ await next();
10624
+ });
10625
+ app.use("/assets/*", serveStatic({
10626
+ root: staticRoot,
10627
+ rewriteRequestPath: (path) => path.replace(/^\/assets/, ""),
10628
+ precompressed: true
10629
+ }));
10630
+ const indexPath = join(staticRoot, "index.html");
10631
+ if (!existsSync(indexPath)) throw new Error(`Viewer assets missing at ${indexPath}. Run \`pnpm build\` from the repo root before starting the viewer.`);
10632
+ const indexHtml = readFileSync(indexPath, "utf-8");
10633
+ app.get("*", (c) => {
10634
+ if (c.req.path.startsWith("/api/")) return c.json({ error: "not found" }, 404);
10635
+ c.header("Cache-Control", "no-store");
10636
+ return c.html(indexHtml);
10637
+ });
10638
+ }
10036
10639
  function createApp(opts) {
10037
10640
  const storeFactory = opts?.storeFactory ?? getStore;
10038
10641
  const sweeper = opts?.sweeper ?? null;
@@ -10043,6 +10646,7 @@ function createApp(opts) {
10043
10646
  app.use("*", preflightHandler());
10044
10647
  app.use("*", originGuard({ unsafeGetPathPrefixes: [TEAM_SETUP_ROUTE_PREFIX] }));
10045
10648
  app.route("/", healthRoutes(storeFactory));
10649
+ app.route("/", diagnosticsRoutes(storeFactory));
10046
10650
  app.route("/", statsRoutes(storeFactory));
10047
10651
  app.route("/", memoryRoutes(storeFactory));
10048
10652
  app.route("/", packTransportRoutes(storeFactory));
@@ -10069,24 +10673,7 @@ function createApp(opts) {
10069
10673
  }
10070
10674
  }));
10071
10675
  app.route("/", updateStatusRoutes({ getUpdateStatus: opts?.getUpdateStatus }));
10072
- const staticRoot = process.env.CODEMEM_VIEWER_STATIC_DIR ?? join(import.meta.dirname ?? ".", "../static");
10073
- app.use("/assets/*", async (c, next) => {
10074
- c.header("Cache-Control", "no-cache");
10075
- await next();
10076
- });
10077
- app.use("/assets/*", serveStatic({
10078
- root: staticRoot,
10079
- rewriteRequestPath: (path) => path.replace(/^\/assets/, ""),
10080
- precompressed: true
10081
- }));
10082
- const indexPath = join(staticRoot, "index.html");
10083
- if (!existsSync(indexPath)) throw new Error(`Viewer assets missing at ${indexPath}. Run \`pnpm build\` from the repo root before starting the viewer.`);
10084
- const indexHtml = readFileSync(indexPath, "utf-8");
10085
- app.get("*", (c) => {
10086
- if (c.req.path.startsWith("/api/")) return c.json({ error: "not found" }, 404);
10087
- c.header("Cache-Control", "no-store");
10088
- return c.html(indexHtml);
10089
- });
10676
+ registerStaticRoutes(app);
10090
10677
  return app;
10091
10678
  }
10092
10679
  /**