Skip to content

fix(pdf-viewer-demo): use assertEnabled at the actual gate — deny() now bites - #32

Merged
mdheller merged 1 commit into
mainfrom
rescue/capability-ledger-gate-parity
Jul 29, 2026
Merged

fix(pdf-viewer-demo): use assertEnabled at the actual gate — deny() now bites#32
mdheller merged 1 commit into
mainfrom
rescue/capability-ledger-gate-parity

Conversation

@mdheller

Copy link
Copy Markdown
Contributor

Closes a register item: capability-ledger.deny() records a receipt but blocks nothing — because the sole production caller (this demo) used isEnabled() at the use-gate, and isEnabled reports a blocked_by_policy state as false and drops silently.

The drift

The inline CapabilityLedger in apps/pdf-viewer-demo/index.html was a manual copy of packages/capability-ledger/src/index.js and had drifted:

package inline (before)
assertEnabled (fail-closed gate) present, tested missing
enable() requires policyDecisionRef throws on null accepted pdr = null

What changed

  • Inline ledger mirrors packaged behaviour: assertEnabled added; enable requires a non-empty pdr.
  • Click handlers use assertEnabled via a gatedClick wrapper; the diagnostic surfaces in #feature-output.
  • Buttons stay clickable even when denied — enforcement is at the click site, and letting the click fire is what surfaces the diagnostic.
  • Added pdf-export capability denied by policy. Clicking it now shows the diagnosable refusal citing the policyDecisionRef.

Verification

  • node --check on the extracted inline JS: exit 0
  • node --test tests/ledger.test.js: 38 pass / 0 fail — this PR doesn't touch the package
  • Bootstrap unchanged: existing ledger.enable('pdf-viewer', ...) already carries a real pdr

Anti-drift note

"Inline copy of a module drifts silently" is worth a lint. Not landing here — one thing at a time — but flagging since this is the second inline-copy drift we've found.

…ow bites

Closes a register item: capability-ledger.deny() writes a blocked_by_policy
receipt into the ledger, but the sole production caller (the pdf-viewer
demo) used isEnabled() at the use-gate. isEnabled reports 'blocked_by_policy'
as false and drops silently — a policy refusal indistinguishable from a
not-yet-declared capability, which is the entire failure mode the register
item names.

  - Inline CapabilityLedger drifted from the packaged version: it lacked
    assertEnabled and let enable() take a null policyDecisionRef. Both fixed;
    the packaged behaviour (enable requires a non-empty pdr, assertEnabled
    throws on any non-enabled state naming the reason) is now mirrored.
  - Click handlers use assertEnabled via a gatedClick wrapper; the diagnostic
    surfaces in feature-output so the refusal is visible instead of dropped.
  - Buttons stay clickable even when not enabled: the enforcement is at the
    click site, and letting the click fire is what surfaces the diagnostic.
  - Added pdf-export capability that is denied by policy, so the deny path
    is now demonstrated end-to-end alongside pdf-sign (missing_plugin) and
    pdf-viewer (enabled).

Package tests unchanged, 38/38 pass on origin/main head. This PR only
touches the demo — the surface most likely to drift because it's a manual
copy of the module.
@mdheller
mdheller merged commit 2dbdbb4 into main Jul 29, 2026
1 check passed
@mdheller
mdheller deleted the rescue/capability-ledger-gate-parity branch July 29, 2026 21:36
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