Skip to content

feat(serve): add serve-metadata headers; harden CI against flaky tests#31

Merged
MichaelTaylor3d merged 1 commit into
mainfrom
feat/serve-metadata-headers
Jul 12, 2026
Merged

feat(serve): add serve-metadata headers; harden CI against flaky tests#31
MichaelTaylor3d merged 1 commit into
mainfrom
feat/serve-metadata-headers

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

Summary

Part A of #486 only (Part B — the extension's toolbar Tip button — is a release-first follow-up once this ships). Also folds in #489 (CI flaky-test hardening), triggered by the #488 flaky-test release block.

Every dig-node /s/<storeId>[:<root>]/<path> content-serve response (GET and HEAD) now carries, alongside the existing X-Dig-Verified/X-Dig-Root/X-Dig-Source provenance set (#292):

Header Source Omitted when
X-Dig-Store-Id: <64-hex> the resolved StorePath.store_id never (always known once resolved)
X-Dig-Owner-Puzzle-Hash: <64-hex> the store's on-chain owner, from the same chain-anchored-root read the existing pin already does (no extra coinset call) — THE tippability gate pin off (DIG_NODE_PIN=off) or the resolver can't supply it
X-Dig-Generation: <n> the 0-based commit ordinal, from the store's embedded PublicManifest — local-only, no chain call module carries no manifest, or no entry for the exact key
X-Dig-Capsule: <storeId:root> the canonical capsule id never (only emitted on a Served outcome, which always has a concrete root)
X-Dig-Resource-Key: <key> the resource actually served (a bare/empty request normalizes to index.html, never a blank header) never

All five are attached only via served_response (i.e. only on a genuine served resource) — a 404/502/400 never carries any of them, no empty placeholder ever emitted.

Owner puzzle hash plumbing. AnchoredRootResolver gains a new anchored_state() method (default impl wraps anchored_root() with owner_puzzle_hash: None, so it is backward compatible for any resolver that doesn't override it). CoinsetResolver overrides it to capture both the root and the owner puzzle hash from the single sync_datastore walk it already performs — the same field (store.info.owner_puzzle_hash) dig-wallet::sage::tipping::ChainOwnerResolver already reads elsewhere in this repo, so this is not a new chain-read pattern.

HEAD support needs no new route/handler. axum 0.7's MethodRouter dispatches a HEAD request to the registered GET handler and strips the response body, preserving headers — verified with a new integration test (head_request_returns_the_same_serve_metadata_headers_with_no_body).

CI hardening (#489)

  • ci.yml: the workspace test job now runs via cargo-nextest (--retries 2) driven through cargo llvm-cov nextest (one combined invocation — nextest and llvm-cov as separate steps collects no coverage at all). Coverage stays measure-only for now (not newly gated) — see "Coverage gate" below for why.
  • release.yml: the tag/main-push check gate no longer re-runs cargo test (that redundant re-run of an already-green PR tree is exactly what blocked the #488 v1.96.0 release on a flaky test). fmt + clippy stay — deterministic, never flaky, cheap fail-fast before 4 platforms' worth of build minutes.

Coverage gate — why not --fail-under-lines 80 yet

I measured real coverage before deciding: a scoped cargo llvm-cov nextest -p dig-node-service run shows several CLI/service-shell files (cli.rs, main.rs, meta.rs, pair.rs, pairing.rs, service.rs, win_service.rs) at or near 0% — matching the pre-existing "measure-only... floor tracked separately" comment already in ci.yml before this PR. A full-workspace run to get the exact aggregate number hit ENOSPC on the shared build disk (freed by deleting my own scratch target dir, not touching anyone else's). Given the demonstrated gap is pre-existing and unrelated to this change, flipping the hard gate on in this PR would very likely turn CI red for reasons this PR didn't cause. I kept coverage measure-only (now on the nextest-combined runner) and left the "one-line addition" comment in place — raising the workspace floor to ≥80% is a separate, larger tracked effort, not bundled into this PR's scope.

Tests

  • crates/dig-node-service/tests/content_serve.rs: extended the GET/index.html test with all 5 header assertions (present+correct) + owner omitted (pin off); extended the JS-asset and SPA-fallback tests to assert X-Dig-Resource-Key reports the actual resource served; extended the 404 test to assert none of the 5 headers appear; added head_request_returns_the_same_serve_metadata_headers_with_no_body (HEAD → same headers, empty body).
  • crates/dig-node-core/src/lib.rs: fixed a pre-existing test whose PlaintextOutcome::Served { .. } match didn't destructure the 2 new fields (would not have compiled) and added assertions for both; added serve_content_plaintext_reports_the_resolver_owner_puzzle_hash_when_pin_enforced (owner present + correct when the resolver supplies it), serve_content_plaintext_omits_owner_puzzle_hash_when_pin_is_off (never guessed), and serve_content_plaintext_omits_generation_when_manifest_absent.
  • Full suite green locally: cargo test -p dig-node-core -p dig-node-service (95+45+8+5 passed, 0 failed) and cargo check --workspace --all-targets --locked (dig-node-core, dig-node-service, dig-wallet, dig-runtime all clean). cargo fmt --check and cargo clippy --all-targets -- -D warnings clean on the touched crates.

Blast radius (gitnexus)

  • served_response (server.rs): impact → 2 direct callers (serve_resource, serve_miss), both updated; risk LOW.
  • AnchoredRootResolver trait: impact flags HIGH (26 symbols / 2 processes reach it — chain-watch, gap-fill, subscriptions all consume anchored_root). The actual change is a single additive, default-provided trait method (anchored_state) — no existing method signature/semantics changed. Verified empirically: full workspace compile + full test suite green (including dig-wallet/dig-runtime, which depend on this trait but don't override the new method).
  • detect_changes(scope: all): 46 touched symbols across content_serve.rs/lib.rs/server.rs/the test file — mostly line-shift noise from inserting 3 new tests ahead of existing ones (e.g. spawn_authed_remote, test_node_with_resolver reported "touched" only because their line numbers moved, not their bodies).
  • Note: this repo's gitnexus index is stale (predates this branch) per the post-commit hook notice — the numbers above are the tool's best current read; ground truth is the full green build+test above.

SYSTEM.md draft (for the orchestrator to commit to the superproject)

One paragraph to append after the existing /s/ provenance-headers sentence (superproject SYSTEM.md, the dig-node "Exposes" bullet, ~line 102):

Serve-metadata headers (#486). Alongside the X-Dig-Verified/X-Dig-Root/X-Dig-Source provenance set, every dig-node /s/ content-serve response (GET and HEAD — a HEAD returns the identical header set with no body) additionally carries: X-Dig-Store-Id: <64-hex> (the storeId serving the resource); X-Dig-Owner-Puzzle-Hash: <64-hex> — the store's on-chain OWNER puzzle hash, resolved from the SAME chain-anchored-root read (no extra coinset call) — this header is THE gate for tippability: a consumer (the dig-chrome-extension toolbar Tip button, #486 Part B) treats its presence as "this page's content is tippable" and its absence as "not tippable," never inferring tippability any other way; X-Dig-Generation: <n> — the 0-based commit ordinal that last wrote the resource, from the store's embedded PublicManifest, a local-only lookup; X-Dig-Capsule: <storeId:root> — the canonical capsule id; X-Dig-Resource-Key: <key> — the resource/retrieval key served (empty/bare request normalizes to index.html). All five describe the MAIN resource and are OMITTED (never an empty placeholder) when unknowable — X-Dig-Owner-Puzzle-Hash when the chain-anchored pin is off or the resolver can't supply it, X-Dig-Generation when the module carries no PublicManifest — and never appear on an error/non-DIG response. Node ships this first (release-first); the extension's Tip button (Part B, follow-up) consumes X-Dig-Owner-Puzzle-Hash via dig-serve-headers.ts's ServeVerdict.

Version bump

feat: → minor: root workspace 0.25.00.26.0 (the released dig-node binary version); dig-node-core 0.6.00.7.0 (its own independently-versioned library), matching this repo's convention of bumping both together on a feature commit.

Also touched

SPEC.md §4.6 "Provenance headers" — documented the 5 new headers, the omission rules, and the HEAD-needs-no-new-code-path note, in the same unit of work.

Co-Authored-By: Claude noreply@anthropic.com

Every /s/ content-serve response (GET and HEAD) now carries, alongside the
existing X-Dig-Verified/-Root/-Source provenance set: X-Dig-Store-Id,
X-Dig-Owner-Puzzle-Hash (the on-chain store owner - the tip gate, #486
Part A), X-Dig-Generation, X-Dig-Capsule, and X-Dig-Resource-Key. Owner
puzzle hash rides the SAME chain-anchored-root read as the existing pin (no
extra coinset call, via a new AnchoredRootResolver::anchored_state default
method); generation is a local-only PublicManifest lookup. Unknowable values
are omitted, never a placeholder, and none of the five appear on a
404/error response. HEAD support needs no new code path: axum dispatches
HEAD to the registered GET handler and strips the body.

Also folds in #489 (triggered by the #488 flaky-test release block):
- ci.yml now runs the workspace suite via cargo-nextest (--retries 2) driven
  through cargo-llvm-cov so a flaky test gets one retry before failing the
  job, without losing coverage measurement.
- release.yml's tag/main-push gate drops its redundant `cargo test` re-run
  (fmt+clippy stay - deterministic, cheap fail-fast); the PR's own CI already
  tested this exact merged tree before merge.

Part B (the dig-chrome-extension toolbar Tip button gated on
X-Dig-Owner-Puzzle-Hash) is a release-first follow-up once this ships.

Bump: feat -> minor (0.25.0 -> 0.26.0 workspace release version;
dig-node-core 0.6.0 -> 0.7.0).

Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d MichaelTaylor3d force-pushed the feat/serve-metadata-headers branch from 5db7e6d to 4b6ca71 Compare July 12, 2026 22:47
@MichaelTaylor3d MichaelTaylor3d merged commit b100bc4 into main Jul 12, 2026
9 checks passed
@MichaelTaylor3d MichaelTaylor3d deleted the feat/serve-metadata-headers branch July 12, 2026 23:21
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