Skip to content

fix(registry): invalidate stale documentation builds automatically - #178

Open
TheRealBecks wants to merge 1 commit into
neuledge:mainfrom
TheRealBecks:TheRealBecks/issues144
Open

TheRealBecks wants to merge 1 commit into
neuledge:mainfrom
TheRealBecks:TheRealBecks/issues144

Conversation

@TheRealBecks

Copy link
Copy Markdown
Contributor

Documentation could remain stale after definition or parser changes because publication only checked whether a version existed or its source commit matched.

This change adds deterministic build fingerprints covering the resolved source revision, effective definition, and an automatically generated ingestion revision. Both publish commands skip matching fingerprints and rebuild changed inputs.

The ingestion revision follows helper imports and locked dependencies automatically. It is embedded in the build output, so runtime fingerprinting needs no source checkout.

Adds --force for explicit rebuilding, clear HTTP 409 conflict reporting, and preservation of failed upload artifacts. Includes regression coverage and migration documentation.

Servers must expose build_fingerprint from uploaded package metadata to enable automatic checks. Older servers retain their existing skip behavior. Unversioned ZIP sources continue rebuilding because they lack an immutable revision.

Validation: lint, production builds, and 390 tests passed.

Fixes #144.

@changeset-bot

changeset-bot Bot commented Oct 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0ea0b0f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@neuledge/context Patch
@neuledge/registry Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@human0-bot human0-bot 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.

Approved — the fingerprint inputs and shared publish flow look sound; no blocker prevents landing.
Data & state — high risk, comment: freshness depends on server metadata being updated on replacements; retries and --force have edge cases worth confirming.
Behavior — high risk, comment: missing upstream refs can now fail an existing bulk publish, and the skip summary mislabels unpublished-tag skips.
Product & UX — low risk, comment: bulk skip logs can overwhelm nightly output; non-409 failures also leave artifacts without a path message.
Maintainability — high risk, comment: peer-suffixed lock entries can break revision generation; add parity coverage and simplify the long traversal.
Simplicity — low risk, comment: source resolution and build summaries are duplicated, while the generator invalidates all task caches and hashes an irrelevant ZIP input.


This codebase is managed by Human0.

Comment thread SERVER_SPEC.md
| `name` | string | Package name |
| `version` | string | Semver version or `"latest"` |
| `source_commit` | string? | Git SHA for unversioned packages |
| `source_commit` | string? | Git commit SHA for versioned or unversioned Git packages |

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.

Data & state · comment: The freshness check depends on the server copying build_fingerprint and ingestion_revision from the uploaded database into metadata on every upload and replacement. This checkout has no server implementation, and the test is a mock; track or verify server adoption before relying on automatic invalidation.


This codebase is managed by Human0.

{
method: "POST",
headers: {
Authorization: `Bearer ${getPublishKey()}`,

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.

Data & state · question: requestWithRetry resends the POST after a dropped response. If the first upload landed but its response was lost, a server that rejects replacement can return 409 on the retry, making the client report failure although the version is already published. Is that replacement policy idempotent, or should the client verify metadata after this retry case?


This codebase is managed by Human0.

"Only the N most recent minor versions per package",
)
.option(
"--force",

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.

Data & state · question: --force bypasses fingerprint checks but does not invalidate the HTML-index source cache. A forced rebuild can therefore reuse stale downloaded pages instead of fetching corrected content; clarify that cache refresh is separate or invalidate it here.


This codebase is managed by Human0.

const source = resolveBuildSource(definition, version);
// An unversioned archive has no immutable source revision to compare.
if (!isVersioned(definition) && source.type === "zip") return;
const commit =

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.

Behavior · comment: For an already-published versioned Git package, this now resolves the upstream ref before skipping. If that repo or tag was removed, publish-all records a hard failure and exits instead of skipping the existing version. This is a deliberate new failure mode; consider whether nightly publishing should fail for an unresolvable old source.


This codebase is managed by Human0.

console.log(`\n--- Summary ---`);
console.log(`Succeeded: ${succeeded}`);
console.log(`Skipped (already published): ${skipped}`);
console.log(`Skipped (up to date or legacy metadata): ${skipped}`);

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.

Behavior · comment: This summary also counts the “git tag not published yet” skip branch, which is neither up-to-date nor legacy metadata. Split the counter or use a neutral “Skipped” label so the reported reason matches the count.


This codebase is managed by Human0.

}

/** Only the entry selected for this release contributes to its fingerprint. */
export function resolveBuildSource(

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.

Simplicity · comment: resolveBuildSource re-derives ref, url, and docs_path already resolved separately in buildFromDefinition. Share one resolver so the fingerprint cannot drift from the inputs actually built.


This codebase is managed by Human0.

const result = isVersioned(definition)
? await buildFromDefinition(definition, version, outputDir)
: await buildUnversioned(definition, outputDir);
const skipped =

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.

Simplicity · comment: This section/token/file summary duplicates formatBuilt in cli.ts; reuse the existing formatter so the two command summaries stay consistent.


This codebase is managed by Human0.

Comment thread turbo.json
@@ -1,5 +1,6 @@
{
"$schema": "https://turborepo.com/schema.json",
"globalDependencies": ["scripts/generate-ingestion-revision.mjs"],

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.

Simplicity · comment: Making the generator a global Turbo dependency invalidates lint, tests, and builds across every package, although only the registry build consumes this output. Scope the input to registry tasks.


This codebase is managed by Human0.

ref: source.type === "git" ? (source.ref ?? "HEAD") : undefined,
docs_path: "docs_path" in source ? source.docs_path : undefined,
exclude_paths: [...new Set(source.exclude_paths ?? [])].sort(),
lang: "lang" in source ? (source.lang ?? "en") : undefined,

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.

Simplicity · question: ZIP ingestion ignores lang, but the fingerprint includes it, so changing only that field rebuilds an unchanged ZIP package. Is that intentional, or should the ZIP fingerprint omit it?


This codebase is managed by Human0.

return value;
}

export function generateIngestionRevision(root = repository) {

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.

Simplicity · question: The generator implements a substantial AST, barrel-export, dynamic-import, and lockfile traversal to discover ingestion dependencies. Is automatic discovery worth this machinery over an explicit maintained list plus lockfile hashing? The automatic benefit is real, so this is a design choice, not a blocker.


This codebase is managed by Human0.

@human0-bot

human0-bot Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Thanks for the work on this. Could you please update the PR for these remaining cases before it can land?

  • Handle peer-suffixed lockfile entries and resolve annotated refs through their peeled ^{} ref.
  • Give skipped versions an accurate label and summarize skips instead of logging each version.
  • If an upload succeeded but its retry gets 409, verify the published metadata rather than reporting failure.
  • If an old source tag is gone, skip it with a warning instead of failing the nightly.

This codebase is managed by Human0.

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.

Rebuild documentation when its definition or ingestion pipeline changes

1 participant