Skip to content

ENG-2141 Map the Roam referenced node to source on push - #1380

Open
sid597 wants to merge 5 commits into
mainfrom
eng-2141-map-the-roam-referenced-node-to-source-on-push
Open

ENG-2141 Map the Roam referenced node to source on push#1380
sid597 wants to merge 5 commits into
mainfrom
eng-2141-map-the-roam-referenced-node-to-source-on-push

Conversation

@sid597

@sid597 sid597 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Reviewer brief

  • Result: publish drops the sourceDocument slot when the referenced Source is not a Concept in the space, warns with the page name, and orders the upsert so an in-batch Source precedes the node that references it. An imported Source publishes its origin RID instead of its local uid.
  • Review focus: the existence check in omitMissingSource (publishNodesToGroups.ts). Decision points are marked as inline comments.
  • Risk or follow-up:
    • An imported RID skips the existence check. The DB resolves it under the publisher's current read access, so a revoked grant or a deleted origin concept still fails the row. Open question: accept for v0, or add a client check via rid_to_space_id_and_local_id?
    • The warning is console.warn only. Open question: also count omitted sources on PublishNodesResult and show it in the Export toast?
    • Already-synced Evidence that points at an imported Source repoints from the Roam mirror concept to the origin concept on the next sync. Intended, but it changes existing rows.
    • Draft PR ENG-2166 upsertConcepts overwrites too aggressively #1334 turns an unresolvable slot from a failed row into a silently empty one. This PR avoids sending unresolvable values, so it does not depend on either behavior.
    • Nothing reads CrossAppNode.slots on the Obsidian side yet. ENG-2140 owns that.

Verification

  • Roam: tsc, Prettier, and 173 unit tests pass. turbo run test:unit passes across workspaces.
  • ESLint reports 7 pre-existing directive warnings on untouched lines, none new.
  • New tests: source present, absent, in the same batch, imported RID, malformed RID, repeated publish, one lookup per source.
  • Driven against a local database in the demo video: on the main build the Evidence row fails to publish, on this branch it publishes with an empty reference_content and a warning that names the Source, and after the Source is published the slot resolves to its concept row. The imported-RID path is covered by unit tests only.

video

eng-2141-full.mp4

Scope check

  • Ran $scope-check against ENG-2141 and the final diff.
  • Scope beyond Done When: None.

Local delegated full review

  • Ran a comprehensive review of the entire final diff in a subagent with a fresh context. Use $dg-delegated-full-review when no other full-review workflow is available.
  • A pre-pr-review pass ran before the last fixes (source uid dedupe, RID guard, RID parse check). The full pass on the pushed diff is still to run.

Publish now looks the referenced Source up in my_concepts alongside the
schemas, drops the sourceDocument slot with a warning when it is absent,
and orders the upsert so an in-batch Source precedes the node that
references it. An imported Source publishes its origin RID instead of
its local uid; sourceUidOfNode becomes sourceIdOfNode because it can
return either.
@linear-code

linear-code Bot commented Sep 2, 2026

Copy link
Copy Markdown

ENG-2141

@supabase

supabase Bot commented Sep 2, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
discourse-graph Ready Ready Preview Sep 6, 2026 5:36pm UTC

Request Review

@sid597 sid597 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Inline notes on the decisions in this diff. Three are open questions for the reviewers.

),
...missingRelations.map((r) => crossAppRelationToDbConcept(r)),
].filter((r) => r !== undefined);
const omitMissingSource = (node: CrossAppNode): CrossAppNode => {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Decision point. A missing Source drops the slot instead of publishing the Source as a dependency (ticket default; scoping doc F1 and F5). If that decision changes, this closure is the one branch to replace.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Decided: option 1 stands (Michael on ENG-2141, 2026-09-02). Publishing the Source with the Evidence is ENG-2232, for after v0.

const sourceId = node.slots?.[SOURCE_SLOT];
if (
sourceId === undefined ||
isRid(sourceId) ||

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Open question. A RID is trusted without a lookup. The my_concepts query above is filtered to this space, and checking a RID needs its origin space id (a Space lookup or the rid_to_space_id_and_local_id RPC). rid_or_local_id_to_concept_db_id runs under the publisher's read access, so a revoked grant or a deleted origin concept still fails the row with -2. Proposal: accept for v0 and file a follow-up.

syncedUids.has(sourceId)
)
return node;
console.warn(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Open question. This matches the sync path's console.warn for dependencies missing from a batch. Export.tsx already builds the publish toast from PublishNodesResult, so an omitted-source count there plus a PostHog property is about 10 lines. Do we want it in this PR? The title is re-read by uid because threading it needs a field on CrossAppNode, which the ticket puts out of scope.

console.warn(
`Source "${getPageTitleByPageUid(sourceId)}" (${sourceId}) is not in this space yet; publishing "${node.content.direct.value}" without it.`,
);
return { ...node, slots: undefined };

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

slots carries only sourceDocument in v0; roamToCrossAppConverters.ts:149 is the single producer. Dropping the object equals dropping the key until a second slot exists.

return { ...node, slots: undefined };
};

const { ordered: upsertConcepts } = orderConceptsByDependency(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reuses the sync path's ordering so an in-batch Source is upserted before the Evidence that references it; upsert_concepts processes rows in order. The old order (schemas, nodes, relation schemas, relations) is preserved, so the index mapping to response.data below still holds. missing is ignored on purpose: already-synced schemas are not in the batch, so every node's schema would appear there.

// placeholder is usually filled with a page reference, and a title holding a slash is
// a namespaced page rather than a source, so it is left alone.
export const sourceUidOfNode = (
export const sourceIdOfNode = (

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Renamed from sourceUidOfNode because it can now return a RID. Both callers renamed their local variable to match. No behavior change for the uid case.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T09:51:21.837324Z 4bd9ba0 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 1 potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread apps/roam/src/utils/sourceSlot.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4bd9ba0bc5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/roam/src/utils/sourceSlot.ts Outdated
isRid only checks the prefix, so a value like orn:bad replaced a good uid
and then skipped the publish existence check. Require a space URI and a
local id from ridToSpaceUriAndLocalId, else keep the uid.
const sourceUid = getPageUidByPageTitle(sourceTitle);
if (!sourceUid) return undefined;
const sourceRid = readImportedSourceIdentity(sourceUid)?.sourceNodeRid;
return sourceRid !== undefined && isWellFormedRid(sourceRid)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Per maparent on #1214, a localId may itself be a RID, so CrossAppNode.slots keeps its LocalId type; rid_or_local_id_to_concept_db_id accepts both. The value must parse to a space URI and a local id because block props are user-editable; anything else keeps the page uid. Existing rows: sync now writes the origin RID where it used to write the Roam mirror's uid, so already-synced Evidence repoints on the next sync.

The shared parser falls back to splitting at the last slash, so checking
its output accepted values like orn:broken/node-1 that the database cannot
resolve; the null reference would fail the Evidence row's upsert. Match
the two RID shapes the database recognises instead.
@sid597

sid597 commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

Decision: a missing referenced Source drops the sourceDocument slot with a warning and is not published or granted with the Evidence. Michael chose this (option 1) on ENG-2141 on 2026-09-02. The warn-and-publish-both option is ENG-2232, for after v0; omitMissingSource stays the single branch to replace.

…-referenced-node-to-source-on-push

# Conflicts:
#	apps/roam/src/utils/__tests__/publishNodesToGroups.test.ts
#	apps/roam/src/utils/publishNodesToGroups.ts
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.

1 participant