Skip to content

fix(sync): close the sync cycle with per-resource pull reconciliation#2853

Open
daryllimyt wants to merge 6 commits into
feat/revamp-git-sync-1from
feat/revamp-git-sync-2
Open

fix(sync): close the sync cycle with per-resource pull reconciliation#2853
daryllimyt wants to merge 6 commits into
feat/revamp-git-sync-1from
feat/revamp-git-sync-2

Conversation

@daryllimyt

@daryllimyt daryllimyt commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

What

M1 ("Close the loop") for Revamp Git Sync: makes the edit → stage → export → merge → pull cycle complete repeatedly without wedging.

  • Per-resource pull reconciliation (ENG-1472): pull no longer rejects on any local drift. It computes per-resource local/remote change sets and blocks only on true conflicts (both sides changed with differing specs). Convergent resources — local and remote specs already identical, e.g. your exported change merged back into the base branch — are rebaselined without re-import. Previously the cycle deadlocked permanently after the first export.
  • Dual hash spaces (ENG-1475): mappings track last_synced_spec_hash (repo spec space, for remote drift) and last_projected_spec_hash (local projection space, for pending changes), recorded from a post-reconcile re-projection that doubles as the P(W_next) == S postcondition check. Hand-authored repo files omitting optional keys (e.g. webhook:) no longer leave the workspace permanently dirty.
  • Export no longer fakes sync (ENG-1473): export_workflow stops advancing mapping sync metadata — exports land on a branch, so the resource stays pending until the base advances on pull. Status and pending counts can no longer contradict each other after a publish.
  • Projection dedup parity (ENG-1474): read-only projections (status/pending endpoints) apply the same -N slug dedup as mapping creation, so duplicate workflow titles can't silently shadow each other or flip status.
  • Drop dead provenance table (ENG-1477): workspace_sync_event was never written; removed from models, RLS config, and the migration. The CRUD journal ships post-v1.
  • Carries webhook include_headers (feat(webhooks): add include_headers toggle to webhook trigger #2837) through the projection so it survives the store round-trip.

Database

Edits the (unreleased, this-stack-only) 25f4e2a1c9d8 migration in place: reparents onto main's head 9b52f7f18a31, folds in rendered_files, adds last_projected_spec_hash, drops workspace_sync_event. Single additive revision, single alembic head.

Testing

New regression tests in tests/unit/test_workspace_sync.py:

  • Full double sync cycle: export → simulated merge → pull ends clean, twice.
  • Remote-only edits import cleanly (remote_ahead → pull → clean).
  • Conflicting local + remote edits block the pull with per-resource diagnostics.
  • Hand-written spec without webhook: stays clean after pull.
  • Duplicate-slug projections are identical between read-only and mapping-creating paths.

ruff, ruff format, and basedpyright clean; 25 tests pass across the sync, store service, and publish API suites.


Summary by cubic

Closes the Git sync loop so edit → export → merge → pull can repeat. Pull now reconciles per resource, blocks only on true conflicts, handles deletes cleanly, and rebaselines resources that already match.

  • New Features

    • Per-resource pull reconciliation with conflict-only blocking; convergent resources rebaselined (ENG-1472).
    • Dual hash tracking: last_synced_spec_hash (repo spec) and last_projected_spec_hash (local projection) recorded post-reconcile; optional-key omissions no longer leave dirty state (ENG-1475).
    • Export no longer advances mapping sync metadata; resources stay pending until base updates on pull (ENG-1473).
    • Delete policy: local deletes surface as non-exportable pending; remote deletes untrack mappings back to pending creates; remote edits resurrect locally deleted resources; only divergent dual edits conflict.
    • Read-only projections use the same slug dedup as mapping creation; whitespace-only aliases fall back to the title (ENG-1474).
    • Git transport and validation hardening: read only manifest and canonical workflow definition paths, skip non-UTF-8 blobs, paginate off the event loop; require non-empty changeset titles and export messages; validate pr_base_branch when provided; auto-publish branches get a random suffix; preserve webhook include_headers.
  • Migration

    • Edit 25f4e2a1c9d8: add last_projected_spec_hash, fold rendered_files, drop workspace_sync_event, and remove redundant unique constraints on sync tables; reparent onto main head. Single additive Alembic head.

Written for commit ee74c72. Summary will update on new commits.

Review in cubic

@daryllimyt daryllimyt added fix Bug fix engine Improvements or additions to the workflow engine migration Database migration tests Changes to unit and integration tests labels Jun 12, 2026

@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: a25c0fdd64

ℹ️ 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 tracecat/workspace_sync/service.py
@zeropath-ai

zeropath-ai Bot commented Jun 12, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to ee74c72.

Security Overview
Detected Code Changes

| Change Type | Relevant files

... (code changes summary truncated to fit VCS comment limits.)

@daryllimyt daryllimyt force-pushed the feat/revamp-git-sync-2 branch from a25c0fd to 7379e69 Compare June 12, 2026 10:24

@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: 7379e6982c

ℹ️ 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 tracecat/workspace_sync/service.py
- Pull now blocks only on per-resource conflicts (local AND remote changed
  with differing specs) instead of any local drift, and rebaselines
  convergent resources so export -> merge -> pull ends clean (ENG-1472)
- Track resource hashes in two spaces: last_synced_spec_hash (repo spec)
  and last_projected_spec_hash (local projection), recorded from a
  post-reconcile re-projection that doubles as the P(W_next) == S
  postcondition check (ENG-1475)
- Stop advancing mapping sync metadata on branch export; it only moves
  on pull when the base advances (ENG-1473)
- Apply mapping-style source id dedup in read-only projections so
  duplicate slugs cannot shadow workflows (ENG-1474)
- Drop the unwritten WorkspaceSyncEvent provenance table from the v1
  migration (journal ships post-v1, ENG-1477) and fold rendered_files
  into the base migration; reparent onto 9b52f7f18a31
- read_files fetches only the manifest and canonical workflow definition
  paths, via blob SHAs, and skips non-UTF-8 blobs instead of crashing
- Strip and require a non-empty commit message in write_files
- Fetch commit/branch pages off the event loop (PaginatedList iteration
  triggers lazy page requests)
- Add is_workflow_definition_path predicate; non-canonical paths under
  the workflow root are ignored rather than parsed
- Normalize slug inputs: whitespace-only aliases fall back to the title
  before the generic 'workflow' fallback
- Pending changes now include non-exportable DELETE entries for synced
  resources missing from the local projection, so status and counts stay
  honest
- Introduce PullReconciliationPlan shared by pull and status: convergent
  resources (equal specs or deleted on both sides) rebaseline; resources
  deleted remotely but unchanged locally are untracked back into pending
  creates; resources deleted locally but changed remotely are re-imported
  (Git owns desired state); only divergent dual edits conflict
- parse_files only parses canonical workflow definition paths
- Strip and reject whitespace-only ChangeSetCreate.title and
  ChangeSetExport.message
- Validate pr_base_branch when provided (empty string no longer silently
  falls back to the default base branch)
- Add a random suffix to legacy auto-generated publish branch names to
  avoid same-second collisions
The unique index on id already enforces uniqueness; the extra UNIQUE
constraint created a second identical index on every sync table.
@daryllimyt daryllimyt force-pushed the feat/revamp-git-sync-2 branch from 7379e69 to ee74c72 Compare June 12, 2026 10:46

@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: ee74c72335

ℹ️ 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 on lines +48 to +52
) -> bool:
"""Return True for canonical `<root>/<source-id>/definition.yml` paths."""
parts = path.strip("/").split("/")
return (
len(parts) == 3

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle nested workflow roots from the manifest

When tracecat.json sets resources.workflows to a nested path such as sync/workflows/, this helper splits sync/workflows/foo/definition.yml into four parts and then compares only parts[0] to the full root string, so both read_files() and parse_files() skip every workflow under that valid manifest root. A pull/status for such a repo will see an empty remote spec and can untrack previously synced mappings as if the files were deleted; compare the path against the root's path components instead of assuming the root is one segment.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engine Improvements or additions to the workflow engine fix Bug fix migration Database migration tests Changes to unit and integration tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant