Skip to content

direct: detect stale dashboard publishes via publish/update timestamps - #6119

Merged
denik merged 7 commits into
mainfrom
denik/dashboard-published-timestamps
Aug 3, 2026
Merged

direct: detect stale dashboard publishes via publish/update timestamps#6119
denik merged 7 commits into
mainfrom
denik/dashboard-published-timestamps

Conversation

@denik

@denik denik commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Changes

Detect stale dashboard publishes in the direct engine by comparing the published
revision_create_time against the draft update_time, instead of treating "a
published version exists" as "the published content is current".

A dashboard is deployed as a draft update followed by a separate publish. Once
published, GetPublished keeps returning the previous revision even after the
draft changes, so the old published = (GetPublished succeeds) check could not
tell that the published content was stale. Now published is true only when
revision_create_time >= update_time; a draft updated without a republish reads
as published=false and the planner surfaces a pending republish.

Why not the draft etag

Verified on AWS, GCP and Azure: the draft etag tracks the serialized dashboard
content
, so a metadata-only draft change (e.g. display_name) with an unchanged
serialized payload does not bump the etag — while update_time still advances.
So an etag-based "did the draft change since we published" check would miss that
case; the revision_create_time vs update_time comparison does not. A publish
always leaves revision_create_time >= update_time, so there is no
perpetual-republish risk.

Scope

This improves detection only. Recovering a stale publish via a plain re-deploy
also requires persisting state before the publish call (so the etag-drift
"modified remotely" guard does not block); that is a separate change. Here, a
stale publish is detected in the plan but recovery may still require --force
(unchanged from today).

Tests

New regression test republish-after-draft-update: an out-of-band draft update
makes bundle plan report published needs an update (it did not before). Goldens
updated for detect-change and publish-failure-stale-content. The testserver now
emits strictly-increasing timestamps so update/publish ordering is deterministic.

DoRead computed a dashboard's published state as "does GetPublished succeed",
but once a dashboard is published GetPublished keeps returning the previous
revision even after the draft is updated, so that check cannot tell whether the
published content is current. Compare the published revision_create_time against
the draft update_time instead: published is true only when GetPublished succeeds
AND revision_create_time >= update_time. When the draft was updated without a
republish, published becomes false, which differs from the desired true and the
planner schedules a republish.

Verified on AWS, GCP, Azure and dogfood: a publish always leaves
revision_create_time >= update_time, and a draft update pushes update_time ahead
until the next publish (so there is no perpetual-republish risk). The draft etag,
by contrast, does not reliably change on a display-name update on cloud, so it
cannot be used for this.

The testserver now emits strictly-increasing RFC3339Nano timestamps
(nextTimestamp) so update/publish ordering is deterministic. published is also
excluded from config-remote-sync as an internal state field.

Co-authored-by: Isaac
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 50ea47d

Run: 30807771512

Env 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 4 4 312 1066 4:41
💚​ aws windows 4 4 314 1064 6:57
💚​ azure linux 4 4 311 1066 4:47
💚​ azure windows 4 4 313 1064 4:43
💚​ gcp linux 1 5 312 1066 4:27
💚​ gcp windows 1 5 314 1064 6:48
8 interesting tests: 4 RECOVERED, 4 SKIP
Test Name aws linux aws windows azure linux azure windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R
Top 3 slowest tests (at least 2 minutes):
duration env testname
5:29 aws windows TestAccept
5:09 gcp windows TestAccept
3:09 azure windows TestAccept

… update

Deploys a dashboard, makes an out-of-band draft update without republishing, and
asserts `bundle plan` reports the dashboard's published state needs an update
(published: {old: true, remote: false}). Before the timestamp-based fix, DoRead
reported published=true (GetPublished still returns the previous revision), so
the stale publish was missed and the plan showed no change.

Direct engine only: published is a direct-engine state field.

Co-authored-by: Isaac

@janniklasrose janniklasrose 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.

add a changelog entry

Replace the inline `bundle summary | jq` + manual ACC_REPLS append with
read_id.py, which reads the id from state, prints it, and registers the
[DASHBOARD1_ID] repl in one step. Goldens are unchanged.

Co-authored-by: Isaac
@denik
denik enabled auto-merge August 3, 2026 09:59
denik added 4 commits August 3, 2026 12:32
read_id.py cannot be used in republish-after-draft-update because the test
runs with MSYS_NO_PATHCONV=1, which mangles the helper's own script path on
Windows Git Bash. Restore the inline id capture and note why.

Co-authored-by: Isaac
The inherited MSYS_NO_PATHCONV=1 is only needed for scripts that pass
leading-slash paths to the CLI; this one does not. Unset it (matching the
sibling dashboard tests) so read_id.py resolves its own script path on
Windows, and drop the now-redundant test.toml override.

Co-authored-by: Isaac
The `Ignore` key sat under the `[EnvMatrix]` header, so TOML parsed it as
`EnvMatrix.Ignore` (a bogus matrix variable) rather than the top-level
`Ignore`. It was redundant anyway: the parent bundle/test.toml already
ignores databricks.yml.

Co-authored-by: Isaac
A top-level key placed below a [EnvMatrix] header is silently parsed as
EnvMatrix.<key> (a bogus matrix variable), leaving the real field unset.
Document the dotted form as the safe idiom.

Co-authored-by: Isaac
@denik
denik added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 2e9f928 Aug 3, 2026
23 checks passed
@denik
denik deleted the denik/dashboard-published-timestamps branch August 3, 2026 11:42
denik added a commit that referenced this pull request Aug 5, 2026
Now that DoRead derives published from the publish/update timestamps (#6119), the
reason DoUpdate avoided saving state before the publish is gone: a stale publish
reports remote published=false, so the remote_already_set skip that would have
stranded it can no longer happen.

Save the post-update etag and published=false before publishing. A failed publish
is now recoverable with a plain deploy: the next plan sees desired published=true
against a remote reported as false and republishes. Saving the post-update etag
also keeps state in sync with remote, so CheckDashboardsModifiedRemotely no longer
misreports this as an out-of-band edit and --force is no longer required.

Drops the Badness marker from publish-failure-stale-content and updates it to
assert recovery on a plain re-deploy.

Co-authored-by: Isaac
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.

3 participants