Skip to content

build(deps): clear every high and critical npm advisory, and record the ledger in docs/operations/ - #231

Merged
axross merged 5 commits into
mainfrom
claude/issue-225-vwjtx0
Aug 10, 2026
Merged

build(deps): clear every high and critical npm advisory, and record the ledger in docs/operations/#231
axross merged 5 commits into
mainfrom
claude/issue-225-vwjtx0

Conversation

@axross

@axross axross commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Summary

npm audit reported 45 advisories — 1 critical, 24 high — and reports 11 after this change, none above moderate. npm audit --audit-level=high is clean.

package.json moves for exactly one package, sharp. Every other version change is a lockfile resolution the committed ranges already permitted.

Two findings had no in-range escape, and they decided the shape of the change:

  • sharp needed a major, 0.34.5 → 0.35.3. None of 0.35's breaking changes touches what this repository calls — .tint().blur().jpeg({ quality: 90 }) in the post thumbnail route, and the module handed to Payload as a config value — and its Node floor of >=20.9.0 sits under this project's >=24. It also removes sharp's install script, shortening the install-time supply chain by one entry.
  • next had to reach 16.3.0 regardless. Every 16.2.x release through the last one, 16.2.12, pins sharp ^0.34.5 as an optional dependency, so anything below 16.3.0 reintroduces a vulnerable sharp nested under next however the root manifest reads.

payload 3.82.1 → 3.87.1 carries six of the highs at once. 3.87.1 drops image-size, whose every published version is affected and which therefore has no other exit, and lifts undici to 7.29.0, uuid to 13.0.2, and @payloadcms/drizzle's drizzle-orm to 0.45.2. The @payloadcms/* packages peer-depend on each other at an exact version, so both npm audit fix and npm update refuse the move — it has to happen as one resolution, which is why the lockfile is regenerated rather than patched. That takes every other in-range update with it: 31 of 61 direct dependencies move.

Three source changes are fallout from that, each caught by a check rather than by reading:

  1. The post thumbnail rendered blank. The route derived its own directory from new URL(import.meta.url).pathname, whose brackets stay percent-encoded, so the font resolved to a %5Bslug%5D directory that does not exist and the render failed with ENOENT. fileURLToPath is the conversion that decodes them. The index thumbnail route shares neither the bug nor the fix — its path has only parentheses, which a URL pathname does not encode.
  2. A refused link destination stopped being inert. @payloadcms/richtext-lexical 3.87.1 added sanitizeUrl() to its Lexical-to-markdown export, whose refusal value is the literal string https://. A javascript: destination therefore no longer reaches classifyLinkHref intact; it arrives as https:// and rendered as a live anchor with target="_blank". The dangerous protocol was still neutralized, but docs/specs/markdown-rendering.md guarantees something stronger — a refused destination loses its href, never its text — so classifyLinkHref now requires an http(s) destination to carry a host. That holds independently of Payload continuing to substitute anything.
  3. Biome 2.4.11 → 2.5.7 rejects the 2.4 config outright. biome migrate rewrites it, relocating six nursery rules into the style and suspicious groups they graduated into with their comments intact. Its new checks then found a deprecated FormEvent import (React 19.2 replaces it with SubmitEvent for a form's onSubmit), two spec assertions whose optional chain short-circuited into a non-optional member access, and one decorative SVG asset.

Finally, .github/dependabot.yml's "their own piece of work" comment now points at docs/operations/dependency-advisories.md, which holds the counts, the reachability judgement behind everything still open, and the decision that no CI step runs npm audit.

Related issues

Closes #225

Verification

Every command was run in this session on Linux, against the committed lockfile.

Command Result
npm audit 45 (1 critical / 24 high / 18 moderate / 2 low) → 11 (0 / 0 / 10 / 1)
npm audit --audit-level=high no findings
npm run lint pass — 253 files, no diagnostics
npm run typecheck pass
npm run test:unit pass — 31 suites, 325 tests (323 before; 2 added)
npm run build pass
npx playwright test (PLAYWRIGHT_SERVER_MODE=production) pass — 267 tests, no snapshot changes
node e2e/check-scenario-coverage.mjs pass — overall 64/67, must 17/17
npm ls sharp one version, 0.35.3, deduped under next

CI on 6d036be is green across Lint, Typecheck, Unit Tests, Payload Artifacts, Preflight, and Deploy Preview.

Acceptance criteria from the issue:

  • npm audit --audit-level=high reports zero findings — met, so no high/critical needed a written rationale.
  • package.json and package-lock.json moved in the same commit — met (e71169d).
  • npm run lint, typecheck, test:unit, build pass — met.
  • npm ls sharp resolves a single 0.35.xmet.
  • Both thumbnail.png e2e checks pass with no snapshot changes — met. sharp 0.35 (libvips 8.18.x) produced byte-identical thumbnails, so no image comparison was needed; the post thumbnail did have to be fixed first, for the unrelated font-path reason above.
  • docs/operations/dependency-advisories.md exists, is listed in docs/index.md, cross-referenced from docs/conventions/security.mdmet.
  • dependabot.yml's deferral comment links to it — met.
  • The pull request states that no CI audit step was added, and why — met (above, and in the new document).
  • "CI surfaces the advisory count on every run"retired, not unmet. The maintainer decided on 2026-08-10 that advisory-count drift is handled by tooling outside this repository. The reason is recorded in the new document so the absence does not read later as the same unowned deferral this issue was filed about.

Local database migrations (npm run migrate:up) were applied before the build, since next build prerenders /sitemap.xml through Payload.

Risks and breaking changes

The blast radius is the 31 moved direct dependencies, not the two source fixes. payload moves five minors and next one; @vercel/blob 2.3.3 → 2.7.0, @clerk/nextjs 7.5.19 → 7.7.1, @base-ui/react 1.6.0 → 1.7.0, @playwright/test 1.61.1 → 1.62.1 and @biomejs/biome 2.4.11 → 2.5.7 all move too. react/react-dom are pinned exactly and do not.

The full e2e suite passing at 267/267 with zero snapshot drift is the strongest evidence here — a Playwright bump alongside a sharp major and a next minor could each have shifted rendered pixels, and none did.

Two things worth scrutiny:

  • classifyLinkHref is a content-safety control, and this change widens what it refuses. It is called from both rehypeAllowedLinkProtocols and the link component, which docs/conventions/security.md requires to stay a single source of truth — both still call the one helper.
  • app/(payload)/admin/importMap.js was regenerated (npx payload generate:importmap) and gained a JSDoc type annotation from Payload 3.87.1's generator. payload/types.ts regenerated identically, and no migration was needed.

Not included, and left for the maintainer to decide: Next.js 16.3's next dev writes a <!-- BEGIN:nextjs-agent-rules --> block into CLAUDE.md on every run. It appeared during verification and was reverted rather than committed — CLAUDE.md is this repository's working agreement, and vendor-injected text in it is a call for its owner, not a mechanical consequence of a bump. The cost of leaving it out is that anyone running npm run dev will see a dirty working tree until it is either committed or suppressed.

Notes for reviewers

Read e71169d's message first — it is where the resolution reasoning lives, and the lockfile diff is unreadable without it. Then the two fix(app) commits, which are small and independent of each other.

The plan was approved at revision sha256:5c30e6d8…, and the scope grew after that. The approved plan expected npm audit fix to clear 24 findings without touching package.json. It did not: the @payloadcms/* exact-peer cross-pin blocked the payload family, which forced the lockfile regeneration, which pulled in next 16.3.0 and biome 2.5.7 and everything downstream. The maintainer was asked in-session which way to take it and chose to absorb the Biome 2.5 migration into this pull request rather than pin Biome back or leave the six payload-family highs open. The plan text in #225 was deliberately left at the approved revision rather than rewritten, so it stays a record of what was approved; this section is where the delivered scope is stated.

Three judgement calls that a reviewer might reasonably decide differently:

  • public/**/*.svg gets an a11y/noSvgWithoutTitle override rather than a <title> element. brush-grunge.svg is consumed as a CSS mask-image, so it never reaches the accessibility tree and has nowhere to carry an accessible name. Biome only began checking standalone SVG assets in 2.5.
  • The migrated Biome rules are left where biome migrate appended them, at the end of their groups, rather than re-sorted alphabetically. The suspicious group already carried a run appended by an earlier migration, so this matches the file's existing shape and keeps the diff to what the tool did.
  • docs/operations/ rather than conventions/ or decisions/ for the ledger: it moves with upstream, so it is neither a rule a change follows nor a one-time constraint.

claude added 4 commits August 10, 2026 04:47
`npm audit` reported 45 advisories — 1 critical, 24 high — and now reports
11, none above moderate. `package.json` moves only for `sharp`; every other
version change is a lockfile resolution the committed ranges already
permitted.

Two of the twenty-five had no in-range escape and decided the shape of this
change:

- `sharp` needed 0.34.5 -> 0.35.3, a major. None of its breaking changes
  touches what this repository calls — `.tint().blur().jpeg()` in the post
  thumbnail route and the module handed to Payload as a config value — and
  0.35's Node floor of >=20.9.0 sits under this project's >=24. It also
  drops sharp's install script, which shortens the install-time supply
  chain by one entry.
- `next` had to reach 16.3.0 regardless: every 16.2.x through 16.2.12 pins
  `sharp ^0.34.5` as an optional dependency, so anything below 16.3.0
  reintroduces a vulnerable sharp nested under `next` however the root
  manifest reads.

`payload` 3.82.1 -> 3.87.1 carries six of the highs at once: 3.87.1 drops
`image-size`, whose every published version is affected and which therefore
has no other exit, and lifts `undici` to 7.29.0, `uuid` to 13.0.2, and
`@payloadcms/drizzle`'s `drizzle-orm` to 0.45.2. The `@payloadcms/*`
packages peer-depend on each other at an exact version, so `npm audit fix`
and `npm update` both refuse the move — it has to happen as one resolution,
which is why the lockfile is regenerated rather than patched. That takes
every other in-range update with it; 31 of 61 direct dependencies move.

The rest of this commit is what the regeneration required to keep the tree
green. Biome 2.4.11 -> 2.5.7 rejects the 2.4 config outright, so
`biome migrate` rewrites it: the schema version, `recommended: true` ->
`preset: "recommended"`, and six nursery rules relocated to the `style` and
`suspicious` groups they graduated into, comments intact. Its new checks
then found four things — a deprecated `FormEvent` import, which React 19.2
replaces with `SubmitEvent` for a form's `onSubmit`; two spec assertions
whose optional chain short-circuited into a non-optional member access; and
`public/images/brush-grunge.svg`, newly in scope now that Biome checks
standalone SVG assets, where an accessible name is meaningless because the
file is consumed as a CSS `mask-image` and never reaches the accessibility
tree. That last one is an override rather than a fix.

`next.config.ts` loses `experimental.viewTransition`, which 16.3 removed;
the feature is React's `<ViewTransition>` component, imported directly in
`blog-post-header/loaded.tsx` and unaffected.

Refs #225

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EBvZfUJFqpE68Cqbp1fuC
The Open Graph thumbnail for a post rendered as a blank white image. The
route loads its font relative to its own module, and it derived that
directory from `new URL(import.meta.url).pathname` — which is the file URL's
*raw* pathname, so the brackets of the `[slug]` segment it lives under stay
percent-encoded. `readFile` was handed
`app/(app)/posts/%5Bslug%5D/thumbnail.png/_assets/ibm-plex-sans-jp-700.ttf`,
a directory that does not exist, and the render failed with ENOENT.

`fileURLToPath` is the conversion that decodes them, and is what should have
been used all along. The index thumbnail route shares neither the bug nor
the fix: its own path contains only parentheses, which a URL pathname does
not encode.

Found by the e2e snapshot for `post.thumbnail`, which is exactly the failure
that check exists to catch — it compared a blank frame against the committed
thumbnail.

Refs #225

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EBvZfUJFqpE68Cqbp1fuC
`classifyLinkHref` classified any `http(s)` destination as external without
asking whether it resolved anywhere, so `https://` — a scheme and nothing
else — rendered as a live anchor with `target="_blank"` and
`rel="noopener noreferrer"`. It now has to carry a host, and a host-less one
is refused like any other unrenderable destination.

That string is not hypothetical. `@payloadcms/richtext-lexical` 3.87.1 added
`sanitizeUrl()` to its Lexical-to-markdown export, and its refusal value is
the literal `https://`. So a `javascript:` destination authored through the
admin or the MCP server no longer reaches this helper intact; it arrives as
`https://` and, until this change, rendered as an anchor pointing at it.

The dangerous protocol was still neutralized — no `javascript:` href ever
reached the DOM — but the guarantee `docs/specs/markdown-rendering.md`
states is stronger than that: a refused destination loses its `href`, never
its text. An anchor to `https://` is not a refused destination, it is a
broken external link. This restores the documented behaviour and does not
depend on Payload continuing to substitute anything, since a host-less
`http(s)` URL is unrenderable on its own terms.

The spec's two affected rows are updated: the guarantee row names the host
requirement, and the note that the CMS is not a filter records the
substitution as an upstream convenience this site does not rely on.

Caught by the `post.content.links` e2e scenario, which is tagged `must`.

Refs #225

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EBvZfUJFqpE68Cqbp1fuC
…l at it

The only record of the outstanding advisories was a line in
`.github/dependabot.yml` saying they were "their own piece of work" — which
named the work but neither the reasoning nor a destination. A deferral
nobody can find reads the same as an oversight, which is what #225 was filed
about.

`docs/operations/dependency-advisories.md` is that destination: the current
counts, the standing position that no `high` or `critical` finding is left
without a written reachability judgement, the reachability of each moderate
and low still open, and a procedure for reconciling the ledger when the
count moves. `operations/` rather than `conventions/` or `decisions/`
because it is a ledger that moves with upstream, not a rule a change follows
or a one-time constraint.

Three things are written down because each would otherwise look like an
oversight to the next reader:

- **No CI step runs `npm audit`**, decided deliberately: the count is
  watched by tooling outside this repository, and a gate that fails on a
  freshly published upstream advisory turns an unrelated pull request red,
  which is how audit gates get switched off.
- **`npm audit fix` cannot move a cross-pinned family.** The `@payloadcms/*`
  packages peer-depend on each other at an exact version, so npm's
  incremental resolver fails with `ERESOLVE` and leaves the findings
  standing. Regenerating the lockfile resolves them together.
- **A `fixAvailable` naming a lower version is a downgrade**, reported
  identically to an upgrade. `@payloadcms/plugin-mcp@3.76.1` is the live
  example.

The `tar` critical is kept as a worked example even though it is fixed,
because it is the shape of judgement the ledger asks for: `re2` reaches the
server graph, but `node-gyp` is its install script and the archives `tar`
unpacks there are Node headers, so nothing a request influences reaches that
parser.

`docs/conventions/security.md` points at the ledger from its Dependencies
section, and `docs/index.md` lists it under Operations.

Refs #225

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EBvZfUJFqpE68Cqbp1fuC
@axross axross self-assigned this Aug 10, 2026
@axross

axross commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

@claude review


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview deployment

https://btnopen-pr-231.vercel.app

Deployed 6d036be. Served by an isolated Turso database (preview-pr-231) seeded from repository fixtures — no production data — with media in a dedicated preview store; both are destroyed automatically when this pull request closes. This URL is stable for the pull request and always serves the latest preview.

@claude

claude Bot commented Aug 10, 2026

Copy link
Copy Markdown

Code review

0 important, 0 nits.

Mandatory checks (REVIEW.md):

  • Skill conformance — checked the diff against every installed skill whose trigger matches the changed files (application-security for the classifyLinkHref/hasHost change and the dependency bump, next-app-development for the thumbnail.png route and next.config.ts, react-component-development for comment-composer.tsx, unit-testing for the spec changes, living-product-specification/technical-document-authoring for the new docs/operations/dependency-advisories.md and the spec/security doc edits, and docs/conventions/code-style.md's line-comment casing rule for every new // comment). No deviation found.
  • Acceptance criteria — this PR closes #225. Verified each stated criterion against the diff/lockfile directly: sharp resolves to a single 0.35.3 in package-lock.json; package.json and package-lock.json both move in commit e71169d; docs/operations/dependency-advisories.md exists, is listed under Operations in docs/index.md, and is cross-referenced from the Dependencies section of docs/conventions/security.md; .github/dependabot.yml's deferral comment links to it; and the PR states plainly that no CI audit step was added. All met.

Also independently verified (not just taken on faith from the PR body):

  • SubmitEvent<T> is a real, correctly-shaped export in @types/react@19.2.18 (the version pinned in this PR's lockfile), so the FormEventSubmitEvent swap in comment-composer.tsx type-checks.
  • experimental.viewTransition genuinely exists in Next.js 16.2.12's config type and is genuinely absent from 16.3.0's, confirming the next.config.ts removal is required rather than incidental.
  • fileURLToPath(import.meta.url) vs. the old new URL(import.meta.url).pathname — confirmed the latter leaves %5Bslug%5D percent-encoded, matching the described ENOENT root cause.
  • hasHost()'s new URL(href).host !== "" correctly classifies https:// / http:// (and other host-less variants) as blocked while continuing to treat every legitimately-hosted http(s) URL as external; both call sites (link.tsx, markdown.ts's rehypeAllowedLinkProtocols) already react to the "blocked" kind, so the fix requires no call-site changes.

No bugs, logic errors, or CLAUDE.md/skill violations found in the reviewable diff (excluding package-lock.json and the generated app/(payload)/admin/importMap.js, per REVIEW.md's do-not-report list).

@axross
axross marked this pull request as ready for review August 10, 2026 05:00
`main` moved to a73195e, which migrated the unit suite from Jest to Vitest
(#230). Everything merged cleanly except `package-lock.json`, where both
sides had regenerated the tree — this branch to clear the advisories, `main`
to swap `jest`/`@jest/globals` for `vite`/`vitest`.

A lockfile conflict is not resolvable hunk by hunk, so the file is
regenerated from the merged manifest rather than hand-merged. The result
keeps this branch's `sharp ^0.35.3` and its `payload` 3.87.1 resolution and
picks up `vitest` 4.1.10, and `npm audit` still reports 11 findings with
nothing above moderate.

Re-verified against the merged tree: lint, typecheck, `vitest run` (31 files,
325 tests), `next build`, the full Playwright suite in production mode (267
passed, no snapshot changes), and the scenario-coverage gate.

Refs #225

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EBvZfUJFqpE68Cqbp1fuC
@axross

axross commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

@claude review


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview deployment

https://btnopen-pr-231.vercel.app

Deployed 91dfbbd. Served by an isolated Turso database (preview-pr-231) seeded from repository fixtures — no production data — with media in a dedicated preview store; both are destroyed automatically when this pull request closes. This URL is stable for the pull request and always serves the latest preview.

@axross
axross merged commit 4fae7e4 into main Aug 10, 2026
7 checks passed
@axross
axross deleted the claude/issue-225-vwjtx0 branch August 10, 2026 05:14
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview deployment

Torn down — the isolated Turso database (preview-pr-231) and this pull request's preview media were destroyed.

@claude

claude Bot commented Aug 10, 2026

Copy link
Copy Markdown

Code review

0 important, 0 nits.

Mandatory checks (REVIEW.md):

  • Skill conformance — checked the diff against every installed skill whose trigger matches the changed files: application-security for the classifyLinkHref/hasHost change and the dependency-advisory work; next-app-development for the thumbnail.png route and next.config.ts; react-component-development for comment-composer.tsx; unit-testing/vitest-testing for the spec changes; living-product-specification/technical-document-authoring for the new docs/operations/dependency-advisories.md and the spec/security doc edits; and docs/conventions/code-style.md's line-comment casing rule for every new // comment. No deviation found — every new comment starts lowercase or with an allowed proper noun/acronym/code identifier, and the new noSvgWithoutTitle override in biome.jsonc is narrowly scoped (public/**/*.svg) to the one SVG (public/images/brush-grunge.svg) that is genuinely consumed only as a CSS mask-image and never reaches the accessibility tree.
  • Acceptance criteria — this PR closes Clear the open npm advisories: in-range lockfile refresh, sharp 0.35, and a documented advisory ledger #225. Verified every stated criterion against the diff, and independently re-ran the security claim rather than taking it on faith: installed this branch's package.json/package-lock.json into a scratch directory and ran npm audit myself. npm audit --audit-level=high exits 0 with zero high/critical findings, and the full report is 11 findings (0 critical/high, 10 moderate, 1 low) — matching both the PR body's numbers and the three grouped packages (@hono/node-server family, dompurify/monaco-editor, esbuild/drizzle-kit) recorded in the new ledger exactly. package.json declares sharp@^0.35.3 and moves with package-lock.json in the same commit (e71169d). npm ls sharp resolves a single 0.35.3. docs/operations/dependency-advisories.md exists, is listed under Operations in docs/index.md, and is cross-referenced from the Dependencies section of docs/conventions/security.md. .github/dependabot.yml's deferral comment links to it. The PR states plainly that no CI audit step was added, and why. All met.

Also independently verified (not just taken on faith from the PR body):

  • fileURLToPath(import.meta.url) vs. the old new URL(import.meta.url).pathname: confirmed the latter leaves the [slug] segment's brackets percent-encoded (%5Bslug%5D), matching the described ENOENT root cause; the index thumbnail route's path has no brackets, so it needed no equivalent fix.
  • hasHost()'s new URL(href).host !== "" blocks https:// / http:// (host-less) while continuing to classify every genuinely-hosted http(s) URL as external; both call sites (link.tsx, markdown.ts's rehypeAllowedLinkProtocols) already branch on the "blocked" kind, so no call-site changes were needed.
  • @payloadcms/plugin-mcp@3.76.1, the version npm audit fix proposes for the @hono/node-server finding, is genuinely a downgrade from this branch's pinned ^3.82.1 — confirms the ledger's "a fixAvailable naming a lower version is a downgrade" note holds for its own worked example.
  • CI on the current head (91dfbbd) is green on Lint, Typecheck, Unit Tests, and Payload Artifacts.

No bugs, logic errors, or CLAUDE.md/skill violations found in the reviewable diff (excluding package-lock.json and the generated app/(payload)/admin/importMap.js, per REVIEW.md's do-not-report list).

axross pushed a commit that referenced this pull request Aug 10, 2026
Integrates the seven commits main gained since this branch's merge base
(#228, #229, #230, #231, #234, #233, #236). Merged rather than rebased:
the branch carries 31 commits and several conflicting files were touched
by more than one of them, so a rebase would replay each conflict once per
commit. One merge resolves each once and needs no force-push.

Conflict resolutions:

- jest.config.cjs: deleted. #230 removed it; this branch's edits to it
  are obsolete under Vitest.
- app/(app)/_/repositories/shared-types.ts: stays deleted. #234 deleted
  it on main and this branch deleted it too, so no conflict arose.
- .claude/skills/project-structure/references/component-conventions.md
  and testing-conventions.md: accepted main's deletion. #228 retired the
  repository-owned skills into docs/; the rule changes relocate there.
- app/(app)/layout.tsx: kept both sides. #236's AnalyticsConsentProvider,
  Footer, and AnalyticsConsentBanner wiring survives alongside this
  branch's Suspense-boundary comments, and the third bare <Suspense>
  #236 introduced around <Footer> gets a comment of its own.
- app/(app)/_/translations/catalogs.spec.ts: took main's Vitest import
  with this branch's relocated relative paths.

The four auto-merged files — both locale catalogs,
blog-post-list/loaded.module.css, and loading-placeholder.module.css —
were each read back rather than trusted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FjDKycriTUnQXxYT9mgjgu
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.

Clear the open npm advisories: in-range lockfile refresh, sharp 0.35, and a documented advisory ledger

2 participants