Skip to content

fix(syncdeck): stop Learn entry mappings from expiring under live sessions - #332

Merged
mrbdahlem merged 15 commits into
mainfrom
fix/learn-syncdeck-session-split
Aug 9, 2026
Merged

fix(syncdeck): stop Learn entry mappings from expiring under live sessions#332
mrbdahlem merged 15 commits into
mainfrom
fix/learn-syncdeck-session-split

Conversation

@mrbdahlem

@mrbdahlem mrbdahlem commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Root-caused a class of session splits where an instructor's editor showed an active session but a student's launch landed in a waiting room, and a later reopen created a separate session: the active-entry TTL fallback (sessions.ttlMs ?? WAITING_TTL_MS) silently resolved to the 10-minute waiting TTL in production, since the Valkey-backed store never exposes a top-level ttlMs. On top of that, the entry's expiry was refreshed only by Learn REST polling, never by the live session's own websocket activity, so a genuinely busy class could still orphan its mapping.

  • Add resolveActiveEntryTtlMs() to check sessions.valkeyStore?.ttlMs before falling back, matching the existing statusRoute.ts idiom.
  • Add a generic linkedSessionId keepalive convention to the session store (mirrors embeddedParentSessionId): touch() now propagates to a linked record, so stamping the live SyncDeck session with its Learn entry's mapping id keeps the entry alive for as long as anyone stays connected, independent of Learn's polling cadence.
  • Add non-reversible identity-fingerprint logging (learn-identity-resolved) across status/student-entry/start/stop so a future split is traceable without logging raw provider/resourceLinkId/session identity, and document the exact algorithm plus a worked test vector in the shared Learn/ActiveBits integration plan so Learn can implement a matching scheme for cross-system correlation.

Summary by CodeRabbit

  • New Features

    • Improved Learn SyncDeck session keepalive so directly linked sessions remain active while connected.
    • Added secure, non-reversible identity-fingerprint logging without exposing raw identifiers or handoff data.
    • Improved session lifetime handling across supported session stores.
    • Preserved linked-session information during session normalization.
  • Bug Fixes

    • Corrected active-entry expiration and TTL fallback behavior.
    • Improved linkage cleanup during session stops and failed launches.
    • Prevented unrelated sessions from being refreshed and handled cyclic links safely.
  • Documentation

    • Added guidance on session lifecycle, deployment, security, and testing behavior.

…sions

Root-caused a class of session splits where an instructor's editor showed
an active session but a student's launch landed in a waiting room, and a
later reopen created a separate session: the active-entry TTL fallback
(`sessions.ttlMs ?? WAITING_TTL_MS`) silently resolved to the 10-minute
waiting TTL in production, since the Valkey-backed store never exposes a
top-level `ttlMs`. On top of that, the entry's expiry was refreshed only
by Learn REST polling, never by the live session's own websocket
activity, so a genuinely busy class could still orphan its mapping.

- Add resolveActiveEntryTtlMs() to check sessions.valkeyStore?.ttlMs
  before falling back, matching the existing statusRoute.ts idiom.
- Add a generic linkedSessionId keepalive convention to the session
  store (mirrors embeddedParentSessionId): touch() now propagates to a
  linked record, so stamping the live SyncDeck session with its Learn
  entry's mapping id keeps the entry alive for as long as anyone stays
  connected, independent of Learn's polling cadence.
- Add non-reversible identity-fingerprint logging (learn-identity-resolved)
  across status/student-entry/start/stop so a future split is traceable
  without logging raw provider/resourceLinkId/session identity, and
  document the exact algorithm plus a worked test vector in the shared
  Learn/ActiveBits integration plan so Learn can implement a matching
  scheme for cross-system correlation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ccb92072-918d-4f49-8f6d-3b1df9f043fb

📥 Commits

Reviewing files that changed from the base of the PR and between 0d77670 and 5a28708.

📒 Files selected for processing (5)
  • .agent/knowledge/data-contracts.md
  • activities/syncdeck/server/learnIntegration.test.ts
  • activities/syncdeck/server/learnIntegration.ts
  • server/core/sessions.ts
  • server/sessionStore.test.ts

📝 Walkthrough

Walkthrough

Learn SyncDeck now records HMAC-based identity fingerprints, resolves active-entry TTLs through session stores, and links live sessions to Learn entries. Session stores propagate touches to directly linked sessions. Tests cover lifecycle logging, privacy, expiry, and linkage.

Changes

Learn SyncDeck integration

Layer / File(s) Summary
Linked-session keepalive infrastructure
server/core/sessions.ts, activities/syncdeck/server/routes.ts, server/sessionStore.test.ts, .agent/knowledge/*, ARCHITECTURE.md, DEPLOYMENT.md
Session normalization preserves linkedSessionId. Session stores refresh one directly linked record in memory and Valkey-backed modes. Tests cover one-hop traversal, cycles, cache revalidation, and TTL behavior.
Learn identity logging and entry lifetime
activities/syncdeck/server/learnIntegration.ts, .agent/knowledge/data-contracts.md, .agent/knowledge/security-notes.md, .agent/plans/learn-syncdeck-session-integration.md
Learn flows derive HMAC fingerprints, remove raw identifiers from lifecycle logs, resolve store TTLs, refresh active entries, and link live sessions to entry mappings.
Learn lifecycle validation
activities/syncdeck/server/learnIntegration.test.ts
Integration tests cover fingerprint vectors, lifecycle states, reuse, distinct resources, TTL fallback, session linkage, stop cleanup, and sensitive-log exclusions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LearnIntegration
  participant SessionStore
  participant LearnEntry
  LearnIntegration->>LearnEntry: resolve mapping and log identity fingerprints
  LearnIntegration->>SessionStore: create live session with linkedSessionId
  SessionStore->>LearnEntry: refresh linked entry mapping
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary fix: keeping Learn entry mappings alive during active sessions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/learn-syncdeck-session-split

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@activities/syncdeck/server/learnIntegration.ts`:
- Line 623: Remove the raw resourceLinkId field from the
learn-instructor-session-start-pending log in the pending-start logging flow,
while retaining requestId, state, and the identityFingerprints output. Add or
update a test verifying that this event payload does not contain the raw
resource identifier.

In `@server/core/sessions.ts`:
- Around line 155-158: Make linked-session refresh propagation exactly one hop:
in server/core/sessions.ts lines 155-158, replace the recursive touch call with
a direct refresh of linkedSessionId; apply the same non-propagating direct-touch
behavior to cached and Valkey-backed records in server/core/sessions.ts lines
263-284. In server/sessionStore.test.ts lines 151-196, add chain and
two-record-cycle tests verifying only the direct linked record is refreshed and
touch() completes.

In `@server/sessionStore.test.ts`:
- Around line 172-195: Increase the timing margin in the test around
createSessionStore and the wait calls: use a substantially larger TTL and
proportionally longer delays so event-loop scheduling cannot expire the linked
session before the first cleanup assertion, while preserving the final cleanup
assertion that verifies expiration after the touched session’s TTL.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0141b127-3b5b-4c11-bae7-27e6e17ac4ff

📥 Commits

Reviewing files that changed from the base of the PR and between f83f059 and 4f13430.

📒 Files selected for processing (7)
  • .agent/knowledge/data-contracts.md
  • .agent/knowledge/security-notes.md
  • .agent/plans/learn-syncdeck-session-integration.md
  • activities/syncdeck/server/learnIntegration.test.ts
  • activities/syncdeck/server/learnIntegration.ts
  • server/core/sessions.ts
  • server/sessionStore.test.ts

Comment thread activities/syncdeck/server/learnIntegration.ts Outdated
Comment thread server/core/sessions.ts
Comment thread server/sessionStore.test.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SyncDeck Learn entry expiration and adds privacy-preserving identity correlation.

Changes:

  • Adds linked-session keepalive and production TTL resolution.
  • Adds fingerprint-based integration logging and tests.
  • Documents lifecycle and logging contracts.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
server/core/sessions.ts Adds single-hop linked-session touches.
server/sessionStore.test.ts Tests linked-session behavior.
activities/syncdeck/server/learnIntegration.ts Adds TTL resolution, linkage, and fingerprints.
activities/syncdeck/server/learnIntegration.test.ts Tests integration behavior and logging.
README.md Documents mapping keepalive.
ARCHITECTURE.md Documents linked-session lifecycle.
DEPLOYMENT.md Adds deployment guidance.
.agent/plans/learn-syncdeck-session-integration.md Defines cross-system fingerprint contract.
.agent/knowledge/security-notes.md Records security and expiry decisions.
.agent/knowledge/data-contracts.md Records persistent integration contracts.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread server/core/sessions.ts Outdated
Comment thread activities/syncdeck/server/learnIntegration.ts Outdated
Comment thread .agent/plans/learn-syncdeck-session-integration.md Outdated
Comment thread activities/syncdeck/server/learnIntegration.test.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (1)

server/core/sessions.ts:276

  • The production Valkey implementation of linked-session propagation is not exercised by the added tests: every new sessionStore.test.ts case uses createSessionStore(null, ...), while the Learn test's mock touch() only returns true. A regression in this cache/Valkey branch would therefore leave the original production failure undetected. Please add coverage that drives the wrapped-store path and verifies that touching a cached and uncached live record refreshes the linked record in Valkey while remaining single-hop.
  const touchDirect = async (id: string): Promise<{ touched: boolean; session: SessionRecord | null }> => {
    const cached = cache.getFresh(id)
    if (cached) {
      cache.touch(id)
      return { touched: true, session: cached }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/core/sessions.ts`:
- Around line 211-218: Update createSessionStore to replace the two plain
console.log calls for in-memory and Valkey store selection with structured JSON
logs, using stable event names and consistent fields that identify the selected
session-store type and relevant configuration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6aa2437c-862c-4608-8366-651931f0a41f

📥 Commits

Reviewing files that changed from the base of the PR and between 4f13430 and c0bf266.

📒 Files selected for processing (11)
  • .agent/knowledge/data-contracts.md
  • .agent/knowledge/security-notes.md
  • .agent/knowledge/testing-patterns.md
  • .agent/plans/learn-syncdeck-session-integration.md
  • ARCHITECTURE.md
  • DEPLOYMENT.md
  • README.md
  • activities/syncdeck/server/learnIntegration.test.ts
  • activities/syncdeck/server/learnIntegration.ts
  • server/core/sessions.ts
  • server/sessionStore.test.ts

Comment thread server/core/sessions.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment thread activities/syncdeck/server/learnIntegration.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (1)

activities/syncdeck/server/learnIntegration.ts:641

  • The documented cross-system log contract says the pending-start variant includes operation, reused, and sessionFingerprint, but this event omits all three. Consumers normalizing learn-identity-resolved and pending-start records therefore cannot rely on the schema described in the integration plan and data-contract notes. Emit the start operation, reused: false, and a null session fingerprint here (there is no session yet).
        console.info(JSON.stringify({ activity: ACTIVITY_ID, event: 'learn-instructor-session-start-pending', requestId, ...identityFingerprints(auth.key.secret, provider, resourceLinkId, id), state: 'starting' }))

The event now includes:
operation: "start"
reused: false
sessionFingerprint: null
Added explicit regression assertions and updated the log contract note.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (1)

activities/syncdeck/server/learnIntegration.ts:350

  • This back-reference is never cleared when Learn stops the session or when activation rolls back after this write. session-ended only sends a message; it does not close the WebSocket, so an old client can continue touching the stopped session. If the same mapping ID is recreated, that stale session then refreshes the new entry (and can retain its provider/resource data beyond the intended waiting TTL). Clear linkedSessionId on stop and on every failed activation path, or otherwise make the linkage lifecycle-bound to the active entry.
  liveSession.data = { ...liveSession.data, linkedSessionId: entryMappingId }
  await sessions.set(sessionId, liveSession)

linkedSessionId is cleared on Learn stop only when it still matches that entry mapping.
Both normal and substitute instructor activation rollbacks clear a link created before the failure.
Added stop-path coverage proving the matching link is removed.
Documented the lifecycle contract.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

activities/syncdeck/server/learnIntegration.test.ts:699

  • This no-raw-ID check only scans infoLogs. The failure case above emits learn-instructor-session-start-failed to console.error with the raw failedInstructorResourceId, so the test passes while a Learn lifecycle event still violates the stated fingerprint-only logging contract. Include errorLogs in this assertion and fingerprint/scrub those failure events too.
    console.info('[TEST] Verifying Learn lifecycle logs never leak raw resourceLinkId, sessionId, or handoff material.')
    const learnLifecycleLogs = infoLogs.filter((message) => message.includes('"event":"learn-identity-resolved"') || message.includes('"event":"learn-instructor-session-') || message.includes('"event":"learn-integration-stop-noop"'))

Comment thread server/core/sessions.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (2)

activities/syncdeck/server/learnIntegration.test.ts:699

  • This filter does not verify the stated no-raw-identifiers guarantee. It excludes learn-integration-request-failed, while the start/student-entry failure paths still pass raw resourceLinkId and sometimes sessionId into that event (for example, presentation mismatch and recovery-unavailable). Those incident logs can therefore still expose exactly the identifiers this test says are absent. Convert those contexts to fingerprints and include all Learn integration lifecycle/failure events in this assertion.
    const learnLifecycleLogs = [...infoLogs, ...errorLogs].filter((message) => message.includes('"event":"learn-identity-resolved"') || message.includes('"event":"learn-instructor-session-') || message.includes('"event":"learn-integration-stop-noop"'))

server/core/sessions.ts:309

  • This bypasses the cache optimization for every cached touch(): wsRouter calls touch() on every WebSocket message and pong, so even sessions with no link now perform a Valkey GET per event. High-frequency SyncDeck messages can therefore create many concurrent reads, while the cache was introduced specifically to batch keepalives. Preserve a bounded authoritative-link refresh (for example, track source-data freshness separately from touch freshness or invalidate links cross-instance) rather than re-reading Valkey on every touch.
    const authoritativeSession = fromCache ? await loadSessionRecord(id) : session

…t fingerprints when authenticated identity is available; privacy coverage includes those logs.

Cached Valkey touches revalidate source link data at most once per five seconds, then update the cache—preventing stale cross-instance links without a Valkey read on every websocket event.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.agent/knowledge/data-contracts.md:
- Around line 755-758: Correct the future-dated knowledge entries in
.agent/knowledge/data-contracts.md:755-758 and
.agent/knowledge/data-contracts.md:760-764 by changing both August 9, 2026 dates
to August 8, 2026 or their actual record dates; no other content needs
modification.

In `@activities/syncdeck/server/learnIntegration.ts`:
- Around line 333-348: Update logLearnLifecycleError in
activities/syncdeck/server/learnIntegration.ts (lines 333-348) to accept and
serialize only an allowlisted lifecycle-error schema, using a stable error class
or code and excluding Error.message and arbitrary context values. In
activities/syncdeck/server/learnIntegration.test.ts (lines 699-704), add a
[TEST] marker before the intentional failure and assert captured logs exclude
sentinel resource, session, URL, and token values.

In `@server/core/sessions.ts`:
- Around line 229-230: Bound linkedSessionRevalidatedAt so it cannot retain
entries indefinitely as sessions are evicted or deleted. Integrate pruning with
the relevant session/cache lifecycle, or enforce a bounded-size eviction policy,
while preserving the existing revalidation behavior governed by
LINKED_SESSION_REVALIDATION_MS.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d68982bc-cef8-4166-986b-e553d32d8cc7

📥 Commits

Reviewing files that changed from the base of the PR and between c0bf266 and 0d77670.

📒 Files selected for processing (9)
  • .agent/knowledge/data-contracts.md
  • .agent/knowledge/testing-patterns.md
  • ARCHITECTURE.md
  • DEPLOYMENT.md
  • activities/syncdeck/server/learnIntegration.test.ts
  • activities/syncdeck/server/learnIntegration.ts
  • activities/syncdeck/server/routes.ts
  • server/core/sessions.ts
  • server/sessionStore.test.ts

Comment thread .agent/knowledge/data-contracts.md Outdated
Comment thread activities/syncdeck/server/learnIntegration.ts
Comment thread server/core/sessions.ts
Restricted lifecycle error logs to an allowlisted schema with stable error codes; sentinel resource, session, URL, and token values are verified absent.
Bounded linkedSessionRevalidatedAt to 1,000 entries, pruned it with cache cleanup/deletes, and cleared it on close.
Comment thread activities/syncdeck/server/learnIntegration.test.ts Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (3)

server/sessionStore.test.ts:12

  • The test imports ../activities/syncdeck/server/routes.js to access normalizeSyncDeckSessionData, but that module has top-level side effects (it calls registerSessionNormalizer('syncdeck', ...) on import). This means unrelated tests earlier in this file run with the SyncDeck normalizer pre-registered, which can make the suite order-dependent and harder to reason about.

Prefer importing a side-effect-free normalizer (e.g. move/export normalizeSyncDeckSessionData from a dedicated helper module that does not self-register), or use a scoped dynamic import within the specific test and immediately reset/re-register the normalizer to avoid polluting global state.

import { registerSessionNormalizer, resetSessionNormalizersForTests } from './core/sessionNormalization.js'
import { listenForTest } from './testPortBinding.js'
import { normalizeSyncDeckSessionData } from '../activities/syncdeck/server/routes.js'

activities/syncdeck/server/learnIntegration.ts:904

  • This unlink-failure log includes the raw error message. Even though this route avoids logging raw identifiers directly, upstream/store errors can embed session IDs, mapping keys, or URLs in their messages, which would violate the fingerprint-only logging goal of this PR. Prefer logging a stable errorCode + identity fingerprints (like the other Learn lifecycle errors) and omit the raw error text.
          try {
            if (sessionId) await unlinkLiveSessionFromEntry(sessions, sessionId, id)
          } catch (unlinkError) {
            console.error(JSON.stringify({ activity: ACTIVITY_ID, event: 'learn-substitute-link-unlink-failed', jti: link.jti, error: unlinkError instanceof Error ? unlinkError.message : String(unlinkError) }))
          }

server/sessionStore.test.ts:71

  • This test monkey-patches console.info for the duration of the whole test via t.after(). If the Node test runner executes files concurrently (common when --test-concurrency is used), this global override can capture/affect logs from other tests and become order-dependent.

Prefer scoping the override with a local try/finally around just the createSessionStore() calls you’re asserting, so the patch window is as small as possible.

  const previousInfo = console.info
  const logs: string[] = []
  console.info = (...args: unknown[]) => { logs.push(args.map(String).join(' ')) }
  t.after(() => {
    console.info = previousInfo
  })

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (1)

activities/syncdeck/server/learnIntegration.ts:280

  • identityFingerprint is a cross-system contract (Learn must implement it byte-for-byte). The current variadic ...parts signature makes it easy for future call sites to accidentally hash additional segments (or change separator semantics) while still type-checking. Consider locking the API to exactly (secret, domainTag, value) and hashing ${domainTag}|${value} to better encode the contract in the function signature.
export function identityFingerprint(secret: string, ...parts: string[]): string {
  return createHmac('sha256', secret).update(parts.join('|'), 'utf8').digest('hex').slice(0, 16)
}

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

@mrbdahlem
mrbdahlem merged commit a77cb83 into main Aug 9, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants