[Foundry] foundry_links document-mapping table for idempotent push #146

Open
opened 2026-07-15 22:02:49 +00:00 by claude-bot · 0 comments
Contributor

Motivation/Context

Every push path in the Foundry pillar — Phase 1 member sync (#8), Phase 2 NPC push (#9), Phase 3 writeback (#14), and live push — needs to know whether a given Quest Board entity has already been pushed to Foundry, so repeated pushes update the existing document instead of spawning duplicates. Today nothing in the codebase tracks this relationship. This is shared plumbing, not specific to any one push feature, and the live-push investigation (2026-07-15) calls it out as a hard prerequisite for "Send to live scene" (its update semantics depend entirely on knowing which Foundry Actor a given LoreEntry already maps to).

Approach

Add a foundry_links table with columns: campaign_id, lore_entry_id (nullable — also used for member_id for #8/#14 sync), foundry_document_type (e.g. actor, journal_entry, item), foundry_document_id, last_pushed_at, last_push_hash.

Push flow: on any push operation, check for an existing link for (campaign_id, source_entity, foundry_document_type). If found, call the adapter's update path against the existing foundry_document_id; if not found, create and record a new link row. last_push_hash lets callers cheaply detect whether the source entity changed since the last push (used later by a future "stale push" indicator for live-push update semantics).

On the Foundry side, stamp every pushed document with a module flag — flags.questboard.entryId — so the mapping can be re-derived if the foundry_links row is lost or the Foundry world is re-imported/restored from backup. This stamping is the module-side half of the idempotency guarantee and should be validated during the same module-testing pass as #143 (connection topology decision).

Update semantics (which fields get overwritten on re-push, confirmation requirements, etc.) belong to each specific push feature, not this table — this issue is just the mapping/lookup layer plus the flag-stamping mechanism.

Dependencies

  • Depends on #143 (connection topology decision) and Phase 1 foundation (#4–#7) being in place — needs the foundry/client.py HTTP layer to call the module's flag-setting/document APIs.
  • Consumed by #8 (member sync), #9 (NPC push), #14 (member writeback), and the live-push slice that follows in v4.4.0 (stat mapping, token image, "Send to live scene", live-push UX, "Push update").

Out of scope

  • Any specific adapter's create/update field-mapping logic (that's #7/#9/#14's job — this table only tracks that a mapping exists, not what gets synced).
  • Conflict resolution beyond "link exists → update, else → create" (three-way merge, diffing individual fields) — explicitly deferred; live-push v1 semantics are create-once + explicit overwrite.
  • Scene/token-level tracking — this table maps LoreEntry/member to a document (Actor, JournalEntry, Item), not to a placed token instance on a scene.

Acceptance criteria

  • foundry_links table exists with the columns above, scoped per campaign.
  • A shared service function resolves "is there a link for this source entity + document type" and returns either an existing foundry_document_id for update, or signals create-and-record.
  • Pushed Foundry documents are stamped with flags.questboard.entryId (or equivalent), verified against the real Foundry instance.
  • At least one consumer (e.g. #9's NPC push, once it lands) demonstrably reuses this table rather than re-implementing its own tracking.
  • Repeated pushes of the same source entity update the existing Foundry document rather than creating a duplicate.
## Motivation/Context Every push path in the Foundry pillar — Phase 1 member sync (#8), Phase 2 NPC push (#9), Phase 3 writeback (#14), and live push — needs to know whether a given Quest Board entity has already been pushed to Foundry, so repeated pushes update the existing document instead of spawning duplicates. Today nothing in the codebase tracks this relationship. This is shared plumbing, not specific to any one push feature, and the live-push investigation (2026-07-15) calls it out as a hard prerequisite for "Send to live scene" (its update semantics depend entirely on knowing which Foundry Actor a given `LoreEntry` already maps to). ## Approach Add a `foundry_links` table with columns: `campaign_id`, `lore_entry_id` (nullable — also used for `member_id` for #8/#14 sync), `foundry_document_type` (e.g. `actor`, `journal_entry`, `item`), `foundry_document_id`, `last_pushed_at`, `last_push_hash`. Push flow: on any push operation, check for an existing link for `(campaign_id, source_entity, foundry_document_type)`. If found, call the adapter's update path against the existing `foundry_document_id`; if not found, create and record a new link row. `last_push_hash` lets callers cheaply detect whether the source entity changed since the last push (used later by a future "stale push" indicator for live-push update semantics). On the Foundry side, stamp every pushed document with a module flag — `flags.questboard.entryId` — so the mapping can be re-derived if the `foundry_links` row is lost or the Foundry world is re-imported/restored from backup. This stamping is the module-side half of the idempotency guarantee and should be validated during the same module-testing pass as #143 (connection topology decision). Update semantics (which fields get overwritten on re-push, confirmation requirements, etc.) belong to each specific push feature, not this table — this issue is just the mapping/lookup layer plus the flag-stamping mechanism. ## Dependencies - Depends on #143 (connection topology decision) and Phase 1 foundation (#4–#7) being in place — needs the `foundry/client.py` HTTP layer to call the module's flag-setting/document APIs. - Consumed by #8 (member sync), #9 (NPC push), #14 (member writeback), and the live-push slice that follows in v4.4.0 (stat mapping, token image, "Send to live scene", live-push UX, "Push update"). ## Out of scope - Any specific adapter's create/update field-mapping logic (that's #7/#9/#14's job — this table only tracks *that* a mapping exists, not *what* gets synced). - Conflict resolution beyond "link exists → update, else → create" (three-way merge, diffing individual fields) — explicitly deferred; live-push v1 semantics are create-once + explicit overwrite. - Scene/token-level tracking — this table maps `LoreEntry`/`member` to a *document* (Actor, JournalEntry, Item), not to a placed token instance on a scene. ## Acceptance criteria - `foundry_links` table exists with the columns above, scoped per campaign. - A shared service function resolves "is there a link for this source entity + document type" and returns either an existing `foundry_document_id` for update, or signals create-and-record. - Pushed Foundry documents are stamped with `flags.questboard.entryId` (or equivalent), verified against the real Foundry instance. - At least one consumer (e.g. #9's NPC push, once it lands) demonstrably reuses this table rather than re-implementing its own tracking. - Repeated pushes of the same source entity update the existing Foundry document rather than creating a duplicate.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#146
No description provided.