Skip to content

c1z replay#1002

Closed
kans wants to merge 17 commits into
mainfrom
kans/c1z-sync-replay
Closed

c1z replay#1002
kans wants to merge 17 commits into
mainfrom
kans/c1z-sync-replay

Conversation

@kans

@kans kans commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

POCs:
ConductorOne/baton-okta#182
ConductorOne/baton-github#177
https://github.com/ConductorOne/baton-sharepoint/pull/72
https://github.com/ConductorOne/baton-microsoft-entra/pull/164

PR: c1z source-cache replay, type-scoped listing, and ingestion invariants

122 files, +22,960/−661. Pebble-only where noted; SQLite artifacts are
untouched by every new mechanism (pinned by tests).

What this adds

Source-cache replay

Connectors that can answer "has this scope changed?" cheaply (etags,
delta tokens) can now skip re-fetching unchanged data. The previous
sync's c1z acts as the cache: rows are stamped with a connector-chosen
scope key at ingest, a manifest records (scope, cache validator) pairs,
and on the next sync the connector is handed a lookup — on a validator
match it returns an empty page with a SourceCacheReplay annotation and
the engine copies the scope's rows (grants, entitlements, or resources)
from the previous file directly. Delta-capable APIs (e.g. MS Graph) use
overlay rounds: replay the base, apply changed rows and tombstones
(canonical-id and principal-scoped), rotate the token.

Gating: full syncs only, Pebble only, and never from compacted
artifacts (compaction is an upsert-merge, so no input's validator can
vouch for the merged row set — compacted files carry a compacted flag
and no manifest entries, and every lookup against them misses).

Failure handling: any evidence that replay lost or corrupted rows
(copy failures, count mismatches against the scope index, stale-index
refusals, unrepairable I3 violations, previous-file read errors) is
classified under an ErrReplayIntegrity sentinel. Both task runners
respond by discarding the output, retiring the previous-sync file, and
re-running the sync cold. The retry doubles as attribution: a clean
cold run means replay was at fault and the sync self-healed; a cold run
that still fails is connector data, and those failures deliberately do
not carry the sentinel, so a recurring connector bug fails plainly
instead of looping warm-attempt-plus-retry forever.

Type-scoped grants and entitlements

Resource types annotated with TypeScopedGrants / TypeScopedEntitlements
are enumerated once per type instead of once per resource. The request
carries the marker annotation over a {type, type} stub resource;
connectors can fan out shards via EnqueuePageTokens (each token
becomes an ordinary checkpointed page action). The connectorbuilder
enforces registration coherence: the annotation and the corresponding
syncer interface must be declared together. This composes with
source-cache scopes, which is what makes chunked delta APIs (50-id
Graph chunks) practical.

Ingestion invariants (I1–I9)

Replay added a second ingestion path, which turned every side effect
attached to the connector-response loop into a latent bug class. This
branch replaces stream-coupled side effects with store-derived
invariants evaluated at consuming seams — each is a pure function of
store contents, so stream, replay, resume, expansion, and external-match
ingestion all yield identical verdicts:

  • I1/I2: expansion and external-match arming from engine facts (repair-flavored).
  • I3: grant-inserted resources verified post-collection; lost rows are
    repaired from the previous sync's copy; unrepairable ⇒ ErrReplayIntegrity.
  • I4: child-scheduling completeness (fail-fast mode only; the scan is a
    bad trade at whale scale for a warning).
  • I5: exclusion-group validation moved from streaming to one stored-keyspace
    pass (engine-agnostic; replaces both old arm sites).
  • I6: every scope-index entry has a manifest entry at seal.
  • I7/I8/I9: referential integrity — entitlement→resource,
    grant→entitlement, grant→principal — via prefix-skip scans that are
    O(distinct referents), never O(rows).

WithFailFastInvariants() (tests, the equivalence harness) turns every
invariant verdict into a hard failure with the offending reference
named; production default warns/repairs/drops.

Dangling-reference drops

Production data motivated going further than warning: the expansion
path alone logs ~2.9M missing-entitlement edges per week, dominated by
connector magic-id bugs and disabled-by-default resource types, and the
platform's uplift provably discards referentially-dangling rows. In
default mode I7/I8/I9 drop the dangling rows at the post-collection
seam under one rule: connector-owned dangling references are dropped;
SDK-machinery-owned shapes are kept
(InsertResourceGrants grants,
unprocessed ExternalResourceMatch* carriers; I3 repairs rather than
drops). Each drop emits a single aggregated warning with totals, capped
id examples, and a config-gap vs magic-id-bug attribution based on
whether the referenced resource type was synced at all. Deletes stream
through chunked batches (measured: 100k rows in 171ms). Full rationale
and safety argument: docs/tasks/dangling-reference-drops.md.

Verification machinery

  • Replay-equivalence differential harness: seeded scenario matrix
    proving warm syncs produce semantically identical artifacts to cold
    syncs (grants/resources/entitlements oracles, manifest and scope-index
    comparison, multi-page scopes, deletion mutations, model-derived
    grant oracle), plus an env-gated 20-seed soak.
  • Halt-point sweep: crash injection at named replay seams
    (replay-copied, rows-committed, tombstones-applied, manifest-written)
    with resume verification.
  • Whale probes (env-gated): invariant cost measurement against a
    57.7M-grant post-expansion artifact — production invariant overhead
    totals ~1.5s of scans plus 3–5s for I9, against a multi-minute
    expansion phase; the rejected O(grants) design measures 170× worse.
  • baton source-cache: CLI audit of manifest entries, scope-index
    counts, validators, and orphan scopes (non-zero exit on I6 violations).

Proto changes

New annotations (SourceCacheCapability, SourceCacheRecord,
SourceCacheReplay, SourceCacheLookup*, TypeScopedGrants,
TypeScopedEntitlements, EnqueuePageTokens), storage v3 record
fields (source_scope_key, cache_validator), and sync-run metadata
(compacted). buf breaking is clean against main.

Verification

  • Full repo suite green (53 packages), race detector clean over
    pkg/sync and all of pkg/dotc1z, lint clean, generated code in
    sync, no vendor drift.
  • Equivalence harness + soak + halt sweep green with drops active.
  • SQLite inertness and partial-sync inertness pinned by tests.
  • Concurrency fixes landed along the way: syncer state flags and
    C1File dirty/closed flags moved to atomics (races found by the
    batteries, not by review).

Notes for reviewers

  • The invariant design doc (docs/tasks/source-cache-ingestion-invariants.md)
    is the map for the I1–I9 code; the annotation coverage table in
    ingest_invariants.go is enforced by a meta-test, so new
    side-effect-implying annotations can't ship without an invariant.
  • Replay is a pure optimization: deleting a previous-sync file (or the
    runner retiring one on ErrReplayIntegrity) only costs a cold sync.
  • Compaction's expand-grants pass runs the invariants over merged
    output on purpose — upsert-merge can manufacture dangling references
    no input contained, and the drop pass converges the artifact to what
    a fresh sync would assert.

@kans
kans requested a review from a team July 9, 2026 20:12
@pulumi

pulumi Bot commented Jul 12, 2026

Copy link
Copy Markdown

⚠️ Pulumi could not deploy preview(s) for this pull request because GitHub reports it is not mergeable (mergeable state: dirty). This usually means the branch has merge conflicts with its base branch. Resolve the conflicts and push a new commit to retry.

@kans
kans force-pushed the kans/c1z-sync-replay branch from 2c28a48 to ae0ccd4 Compare July 13, 2026 15:05
Comment thread pkg/dotc1z/engine/pebble/source_cache.go Outdated
Comment thread pkg/cli/commands.go Outdated
Comment thread pkg/cli/commands.go Outdated
Comment thread pkg/dotc1z/engine/pebble/source_cache.go Outdated
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

General PR Review: c1z replay

Blocking Issues: 0 | Suggestions: 3 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 78a5d72003c6.
Review mode: full
View review run

Review Summary

Scanned the full PR diff for security and correctness, using the GitHub PR file patches as the authoritative diff (the recorded base has advanced past the PR branch point, so a local base..head diff spuriously surfaces pkg/sdk/version.go, pkg/types/resource/resource_attrs.go, and pkg/uhttp/* changes that are not part of this PR). The core replay, continuation, type-scoped, and I1–I9 invariant logic is carefully written: %w wrapping throughout, fresh slice allocations to avoid aliasing proto backing arrays, atomic set-once state flags for the parallel-worker races, disciplined Pebble iterator/batch/closer lifetimes, and new capability surfaces added via new versioned interfaces + additive fields rather than breaking existing contracts. Proto/wire changes are additive. No blocking security or correctness issues found; three non-blocking suggestions below.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • pkg/connectorbuilder/resource_syncer.go:303-308 (and the entitlements/grants deferred blocks ~487, ~584) — the deferred ask/answer path returns a nil-error response while the function-scoped err still holds ErrLookupDeferred, so defer uotel.EndSpanWithError(span, err) records every normal protocol bounce as a trace error. Set err = nil before returning. (confidence: high)
  • pkg/sync/ingest_invariants.go:567maxDanglingIDExamples-len(entIDExamples) can go negative if a prior call returns more ids than requested; clamp to max(0, ...) for parity with the sibling grant/principal checks. (confidence: low)
  • pkg/dotc1z/source_cache.go:~334-360 (ApplyTombstones) — a mid-loop delete error returns before the trailing MarkDirty(), leaving already-applied engine deletes unreflected in the dirty flag; harmless today since a tombstone-parse failure aborts and discards the artifact, but calling MarkDirty() as soon as a delete succeeds is more robust. (confidence: low)
Prompt for AI agents

```
Verify each finding against the current code and only fix it if needed.

Suggestions

In `pkg/connectorbuilder/resource_syncer.go`:

  • Around lines 303-308 (and the analogous entitlements/grants deferred blocks near 487 and 584): the deferred ask/answer path returns a successful response with a literal nil error, but the function-scoped named/closure variable `err` still holds the wrapped `ErrLookupDeferred` returned by the handler. The `defer func() { uotel.EndSpanWithError(span, err) }()` reads `err` at return time and records these normal protocol turns as span errors. Fix by setting `err = nil` immediately before each deferred-path return (or use a distinct local variable for the handler error so it does not leak into the span defer).

In `pkg/sync/ingest_invariants.go`:

  • Around line 567: `maxDanglingIDExamples-len(entIDExamples)` is passed as the example cap to the store delete call and can become negative if a prior call ever returns more ids than requested. Clamp it to a non-negative value (e.g. max(0, maxDanglingIDExamples-len(entIDExamples))) to match the explicit `< maxDanglingIDExamples` guards used in checkGrantEntitlementReferences and checkGrantPrincipalReferences.

In `pkg/dotc1z/source_cache.go`:

  • Around lines 334-360 (ApplyTombstones per-id loops): a successful delete of an earlier id followed by a mid-loop error returns before the trailing `if total > 0 { s.MarkDirty() }`, so already-applied engine deletes are not reflected in the store dirty flag. This is harmless today because a tombstone parse failure aborts the whole sync and discards the artifact, but call MarkDirty() as soon as a delete succeeds so the accounting stays correct if the caller's error handling changes.
    ```

@github-actions github-actions 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.

No blocking issues found. 2 non-blocking suggestions posted inline; see the summary comment for details.

@kans
kans force-pushed the kans/c1z-sync-replay branch from 5fd87b4 to b00db4e Compare July 13, 2026 17:53
Comment thread pkg/sync/syncer.go Outdated
Comment thread pkg/sync/source_cache.go Outdated
Comment thread pkg/sourcecache/continuation.go Outdated
Comment thread pkg/cli/commands.go Outdated

@github-actions github-actions 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.

No blocking issues found.

@kans
kans force-pushed the kans/c1z-sync-replay branch from b00db4e to 79f5efd Compare July 13, 2026 20:24
Comment thread pkg/connectorbuilder/resource_syncer.go Outdated
Comment thread pkg/sync/source_cache_continuation.go
Comment thread pkg/sourcecache/grpc_lookup.go

@github-actions github-actions 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.

Blocking issues found — see review comments.

@github-actions github-actions 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.

No blocking issues found.

@kans kans changed the title [EXPERIMENTAL] c1z sync replay [Draft] c1z sync replay Jul 13, 2026
@kans
kans force-pushed the kans/c1z-sync-replay branch from 87b604c to be70f42 Compare July 13, 2026 22:23
Comment thread pkg/sourcecache/grpc_lookup.go

@github-actions github-actions 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.

No blocking issues found.

@kans
kans force-pushed the kans/c1z-sync-replay branch from 9a5647d to 194e255 Compare July 13, 2026 23:22

@github-actions github-actions 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.

No blocking issues found.

@linear-code

linear-code Bot commented Jul 14, 2026

Copy link
Copy Markdown

CE-964

kans and others added 2 commits July 14, 2026 16:35
Connector-driven source-cache replay for pebble c1z stores: scope
manifests with ETag revalidation, engine-side row replay with overlay
and delete tombstones, continuation (ask/answer) lookups for
single-shot transports, type-scoped grants via SpawnCursors, sync run
stats, and the replay-equivalence differential harness together with
the replay side-effect bug fixes it drove out (child scheduling,
external-match grants, related-resource rows, exclusion groups,
continuation caps).

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread pkg/sync/ingest_invariants.go Outdated

@github-actions github-actions 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.

No blocking issues found.

@kans
kans force-pushed the kans/c1z-sync-replay branch from dee4cbc to 3d9e7dd Compare July 15, 2026 18:30

@github-actions github-actions 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.

Blocking issues found — see review comments.

@kans
kans force-pushed the kans/c1z-sync-replay branch from 3d9e7dd to 8c090ac Compare July 15, 2026 19:15

@github-actions github-actions 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.

No blocking issues found.

@kans
kans force-pushed the kans/c1z-sync-replay branch from 8c090ac to 0b83cab Compare July 15, 2026 19:52
Comment on lines +303 to +308
if deferred {
b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start))
// Session usage rides the ask too: the phase-1 handler may have
// touched the session before deferring, and the protocol turn is
// the only response that work will ever produce.
return v2.ResourcesServiceListResourcesResponse_builder{Annotations: appendSessionUsage(askAnnos, sessionUsage)}.Build(), nil

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.

🟡 Suggestion: On the deferred (ask/answer) path this returns a successful response with nil error, but the function-scoped err still holds the wrapped ErrLookupDeferred. The defer uotel.EndSpanWithError(span, err) reads err at return, so every normal protocol bounce is recorded on its trace span as an error. Set err = nil before returning here (same for the entitlements/grants deferred blocks around lines 487 and ~584). (confidence: high)

"(resource type synced: %t — false means a config gap, true a magic-id construction bug)",
rt, rid, probe.typeExists(ctx, rt))
}
n, ids, err := facts.DeleteEntitlementsForResource(ctx, rt, rid, maxDanglingIDExamples-len(entIDExamples))

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.

🟡 Suggestion: maxDanglingIDExamples-len(entIDExamples) can go negative if a prior call returns more ids than requested, and the negative cap is passed into the store. The sibling checks (checkGrantEntitlementReferences/checkGrantPrincipalReferences) guard with an explicit < maxDanglingIDExamples check; clamp this to max(0, ...) for parity. (confidence: low)

@github-actions github-actions 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.

No blocking issues found.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

General PR Review: c1z replay

Blocking Issues: 0 | Suggestions: 1 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 78a5d72003c6.
Review mode: full
View review run

Review Summary

The full PR diff (155 files, ~+29k) was scanned for security and correctness across the new source-cache replay, type-scoped listing, and ingestion-invariant machinery, with deep reads of the sourcecache/connectorbuilder, dotc1z + pebble engine, and sync/tasks/compactor clusters. No new blocking security or correctness issues were found: the proto changes are purely additive (new field numbers 9/10/11 and new messages, no renumbering, type changes, or removals) with matching regenerated pb/; the new TypeScoped*Syncer interfaces are opt-in (type-asserted and wired through newResourceSyncerV1toV2), so existing connectors keep compiling; and the serialized-state/compat records degrade absent-or-mismatched artifacts to a cold sync rather than stranding in-progress syncs. Source-cache replay is gated to full syncs, Pebble-only, non-compacted, with ErrReplayIntegrity classification driving cold-retry attribution. The three prior findings (span-error on the deferred ask/answer path, I7 negative example clamp, ApplyTombstones MarkDirty ordering) remain valid and are not re-flagged here.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • pkg/sdk/version.go remains v0.18.2 (unchanged) despite this PR adding substantial new exported API (WithSourceCache, SetSourceCache, TypeScopedGrantsSyncer/TypeScopedEntitlementsSyncer, SyncOpAttrs.SourceCache) and new serialized-state records. The changes are additive (non-breaking), but per the repo-local versioning criteria a 0.x minor bump is the conventional signal so downstream connectors can pin the new surface. (confidence: low)
Prompt for AI agents

```
Verify each finding against the current code and only fix it if needed.

Suggestions

In pkg/sdk/version.go:

  • The Version constant is still v0.18.2. This PR adds new exported SDK API
    (connectorbuilder.WithSourceCache, builder.SetSourceCache,
    TypeScopedGrantsSyncer / TypeScopedEntitlementsSyncer, resource.SyncOpAttrs.SourceCache)
    and new serialized-state records (SourceCacheEntryRecord, SourceCacheCompatRecord,
    new source_scope_key / compacted proto fields). These additions are backward-compatible,
    but bump the minor version (e.g. v0.19.0) so downstream connectors can pin the new
    source-cache / type-scoped surface, per the repo versioning-communication policy.
    ```

@github-actions github-actions 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.

No blocking issues found.

Replay: scope pre-clear makes resumed copies idempotent (count check
stays exact); lookup slot is sync-aware so concurrent syncs can't
cross-wire validators; compaction artifacts from pre-flag binaries are
gated by their provenance token; I6 is gated off for compacted runs.

Drops: I8/I9 exemptions judged per grant (machinery rows can't shield
connector rows); deletes stream chunked batches instead of per-row
fsyncs; IngestFactStore renamed IngestInvariantStore to match what it
now does. Local runner keeps the user-supplied previous-sync file and
names it in an actionable error instead of deleting it.

Tests: warning-silence oracle in the equivalence harness (invariant
warnings are verdicts nobody asserts on — now they fail clean
scenarios); mixed-population exemption fixtures; replay-resume,
mixed-version, and drop-scale tests; deferred ask/answer turns no
longer mark trace spans as errors.

@github-actions github-actions 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.

No blocking issues found.

Comment thread pkg/sync/ingest_invariants.go Outdated
Comment on lines +514 to +519
_, err := p.s.store.GetResourceType(ctx, reader_v2.ResourceTypesReaderServiceGetResourceTypeRequest_builder{
ResourceTypeId: resourceTypeID,
}.Build())
exists := err == nil
p.known[resourceTypeID] = exists
return exists

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.

🟡 Suggestion: typeExists collapses every GetResourceType error into "type does not exist" (exists := err == nil). A non-not-found error (transient I/O, unmarshal failure, ErrNoCurrentSync) is then memoized as "type not synced", which in default mode flips the routing at lines 590/682/782 from the loud unexpectedDanglingError (→ ErrReplayIntegrity on warm syncs) into the silent DROP arm — the exact silent-row-drop these invariants exist to prevent. Note the sibling existence check HasResourceRecord (line 577) correctly propagates errors. Consider distinguishing a genuine not-found (pebble.ErrNotFound / gRPC NotFound) from other errors and failing the check on the latter, only memoizing confirmed absence. (confidence: medium — severe if triggered, but requires a non-not-found error from a local store read.)

@github-actions github-actions 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.

No blocking issues found.

@github-actions github-actions 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.

No blocking issues found.

@github-actions github-actions 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.

No blocking issues found.

@github-actions github-actions 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.

No blocking issues found.

kans and others added 2 commits July 16, 2026 15:34
…emory stamp unreachable)

Co-authored-by: Cursor <cursoragent@cursor.com>
sync.Pool randomly discards Puts under -race, same as the other two
pool-reuse assertions already guarded in this file.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kans

kans commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

This work is being broken up into multiple, smaller, PRs. The first two have already been merged.

@kans kans closed this Jul 17, 2026
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