Skip to content

Keep the legacy storage fingerprint for one release - #2118

Merged
ppXD merged 1 commit into
mainfrom
fix/keep-the-legacy-storage-fingerprint-for-one-release
Oct 18, 2026
Merged

ppXD merged 1 commit into
mainfrom
fix/keep-the-legacy-storage-fingerprint-for-one-release

Conversation

@ppXD

@ppXD ppXD commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Since Compare storage namespaces by physical location #2105, admission, the agent-run-log bootstrap and the deployment-default materializer stored the hash of the provider's PHYSICAL namespace projection in storage_profile_revision.namespace_fingerprint. The previous build's retention reaper decides whether one object key is one object across teams by comparing that stored column (ArtifactRetentionReaper.cs:469-489 at 583efb0). During a rolling deploy, or after a rollback, it therefore treated a revision admitted for R/ as a different namespace from one admitted for R, and collected another team's live bytes. OSS -internal or https endpoints and an explicit region had the same effect.

  • For this release, new revisions store the legacy value again: StorageProfileRules.StoredNamespaceFingerprint calls LegacyNamespaceFingerprint, which matches the 583efb0 algorithm byte for byte. This applies at admission (StorageProfileService.cs:275, which the deployment-default materializer also goes through) and at the agent-run-log bootstrap (AgentRunLogStorageReadiness.cs:81).

    • The physical projection is still computed, so a configuration that reaches no namespace is refused as before.
    • This build's reaper keeps deriving the physical object at sweep time and never decides on the column.
  • StorageNamespaceFingerprintBackfillRecurringJob runs every 10 minutes as a thin Job → Command → Service chain. It restores the legacy value on revisions that a physical-identity build already wrote, or that an older pod writes during the rollout. It disables the revision immutability trigger only inside its own transaction and turns it back on before commit, so no migration is needed.

  • Follow-up release, not in this PR: switch the stored value to the physical identity once no build that compares the column can run against the database. That change deletes the backfill and must also remove its schedule, because the worker registrar only adds recurring entries and a deleted job class makes every tick fail with JobLoadException. Do one of:

    • call ICodeSpaceBackgroundJobClient.RemoveRecurringJobIfExists("StorageNamespaceFingerprintBackfillRecurringJob") at worker boot, or
    • keep the job class as a no-op for one more release.

    This is documented on StorageProfileRules.StoredNamespaceFingerprint and on the job class.

Test plan

  • Unit (StorageNamespaceLegacyFingerprintTests, StorageNamespaceFingerprintBackfillDispatchTests):
    • Golden legacy fingerprints equal the 583efb0 values for every shipped module and config shape, and admission stores them.
    • A configuration that reaches no namespace is still refused.
    • The job dispatches only its command, and the handler is a thin delegate.
  • Unit: full suite, 13538 passed and 1 skipped out of 13539.
  • Integration, mixed-version (ArtifactRetentionReaperFlowTests.PreviousBuild): the previous build's stored-column comparison, over two revisions this build admitted with different spellings of one root, now says shared, and physically distinct roots still compare as different.
  • Integration (ArtifactRetentionReaperFlowTests.Namespaces): this build's reaper behaves as before.
  • Integration (StorageNamespaceFingerprintBackfillFlowTests): the backfill restores the legacy value, is idempotent, leaves the table immutable again, and is safe on concurrent workers.
  • E2E, Worker surface (StorageNamespaceFingerprintBackfillE2ETests):
    • Triggers the real worker host's registered StorageNamespaceFingerprintBackfillRecurringJob entry through Hangfire.
    • A revision stamped with the physical value is restored, a legacy one is untouched, and storage_profile_revision_enforce_immutability stays enabled.
    • Mutation-checked: the test goes red when the trigger is not re-enabled and when the job dispatches nothing.
  • E2E, Worker surface: the recurring-job smoke census is still green.
  • E2E, Http surface: StorageNamespaceSharingE2ETests and the rest of CodeSpace.E2ETests.Storage pass (59/59).

@ppXD
ppXD force-pushed the fix/connect-oauth-without-a-public-base-url branch from 779546d to 142a062 Compare October 18, 2026 02:46
Since namespaces were derived physically, admission, the agent-run-log
bootstrap and the deployment-default materializer stored the hash of
the provider's physical projection rather than the hash of the
configuration as written. This build never decides on that column, but
the previous build's retention reaper does: it treats one object key as
one object across teams only when the revisions' stored fingerprints
match. During a rolling deploy, or after a rollback, that reaper saw a
revision this build admitted for 'R/' (or an OSS -internal or https
endpoint, or an explicit region) as another namespace than one it had
admitted for the same text, and collected another team's live bytes.

New revisions store the legacy value again for one release, through
StorageProfileRules.StoredNamespaceFingerprint. The physical projection
is still taken, so a configuration that reaches no namespace is refused
as before, and the reaper keeps deriving the physical object at sweep
time. A recurring backfill restores the legacy value on revisions a
physical-identity build already wrote, and on any an older pod writes
during the rollout. Revisions are immutable under a trigger, so the
pass switches it off inside its own transaction and back on before it
commits; no migration is needed.

Storing the physical identity is a follow-up release, once no build
that compares the column can run against the database. That change
also deletes the backfill, and must remove its schedule with it: the
worker registrar only adds recurring entries, so a deleted job class
leaves StorageNamespaceFingerprintBackfillRecurringJob registered and
every ten-minute tick fails to load its type. Remove the entry at
worker boot with ICodeSpaceBackgroundJobClient.RemoveRecurringJobIfExists,
or keep the job class as a no-op for one more release.
@ppXD
ppXD changed the base branch from fix/connect-oauth-without-a-public-base-url to main October 18, 2026 02:47
@ppXD
ppXD force-pushed the fix/keep-the-legacy-storage-fingerprint-for-one-release branch from 094ce5e to a0eaee1 Compare October 18, 2026 02:47
@ppXD
ppXD merged commit c8cdc86 into main Oct 18, 2026
6 of 7 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.

1 participant