Skip to content

feat(update): inert electron-updater 6.8.9 adapter for the draw.io proof (import-time surface, recorded no-op setFeedURL, honest check outcome) #491

Description

@0monish

Generated by an AI agent (Claude Code) on behalf of @0monish during Wayfinder charting of the Electron compatibility program; rewritten 2026-10-07 by the doctrine-audit repair pass (change log at the end). Planning only — no implementation is authorized by this issue.

Parent

Epic #490 · Map #391 · Unit F09 · Kind: feature · Tier: Tier 2 · Maturity target: L0 · Size: S · Milestone: first-proof

What to build

Build a Keld-owned package that stands in for the electron-updater specifier. Migrate swaps it in as a dependency replacement in the app manifest: X02-T3 owns that swap, under the single-resolver rule closed in X02-A1. The package exposes the AppUpdater subset that draw.io touches, as listed by the PANEL-P3 trace. It must evaluate at import with no host frames and no network or filesystem I/O, and must never fabricate an updater outcome.

Behaviour, mirroring electron-updater 6.8.9 wherever the upstream shape can be honoured without a host updater:

  • A default export carrying autoUpdater, which is an EventEmitter.
  • A logger accessor that stores and returns the identical object; null yields a no-op logger.
  • autoDownload, autoInstallOnAppQuit and autoRunAppAfterInstall are plain booleans with no side effect.
  • setFeedURL(string or options) returns undefined. It never throws, constructs no client, and stores nothing any host path reads. This is a ▲: the feed is host-declared and the app feed is ignored.
  • getFeedURL() returns upstream's deprecated literal.
  • checkForUpdates() mirrors upstream's packaged gate and reads app.isPackaged from F01-T4. When not packaged it resolves null with no event. When packaged and no host updater exists, it emits exactly one error carrying a newly allocated registry code and rejects the same promise.
  • downloadUpdate() before any update is offered rejects, as upstream's "check first" does.
  • quitAndInstall() with nothing staged emits one error and never quits.
  • Never emitted: update-available, update-not-available, download-progress, update-downloaded, checking-for-update.

Spec gate: the first deliverable is a spec from docs/agents/spec-template.md, opened as a PR and stopped for human approval; implementation starts only at Status: approved.

Acceptance criteria

  • Every F09-T3 match cell flips from its recorded expected verdict to pass in this change, and every F09-T3 ▲ cell carries its divergence verdict; no F09-T3 cell is skipped, retried or left silently red
  • Under Bun 1.4.2, the default-import form followed by destructuring autoUpdater yields an EventEmitter, and the app-link frame count attributable to the adapter during import is 0
  • Assigning an object to logger and reading it back returns the identical object (strict equality), so property writes made through it land on the assigned logger
  • setFeedURL called with draw.io's github provider options and with a plain string returns undefined both times, throws nothing, opens no socket and writes no file
  • A fixture that replays the PANEL-P3-recorded draw.io updater call sequence with DRAWIO_DISABLE_UPDATE=true set explicitly in the role environment makes zero checkForUpdates calls and receives zero error events
  • The same replay with the variable absent and app.isPackaged false makes checkForUpdates resolve null with zero events
  • With the variable absent, app.isPackaged true and no host updater, checkForUpdates emits exactly one error whose code is a registry-allocated code outside KELD-COMPAT-001..009 and KELD-UPDATE-*, and the returned promise rejects
  • Across every case above, no update-not-available, update-available or update-downloaded event is ever emitted
  • quitAndInstall() with nothing staged emits exactly one error and app.quit is never called

Negative controls (each names the one mutation that must fail)

  • Making setFeedURL throw fails the setFeedURL no-throw cell and the replay fixture (draw.io's safeUpdaterCall would open its Update Error dialog)
  • Returning a fresh wrapper from the logger getter fails the identity cell
  • Emitting update-not-available from checkForUpdates with no host updater fails the never-fabricate cell
  • Removing the isPackaged gate makes the unpackaged replay emit an error and fails the unpackaged-check cell
  • Calling app.quit from quitAndInstall with nothing staged fails the no-quit cell
  • Reusing a KELD-UPDATE-* code for the no-host-updater error fails the code-provenance assertion

External predecessors (outside this tracker)

Ownership and gates

The authoritative agent contract is the latest Agent Brief comment on this issue (triage skill); this body is the planning record.

Out of scope

Any host update channel, feed contact, download, staging or activation (F09-T5); the Electron-core autoUpdater facade (F09-T4); feed-base reporting (F09-A2 closed: parked under KEL-53 / arch 06 §4a); comparison of the setFeedURL argument with a build config (no schema and no consumer); declaring or delivering the role environment variable (F01-T4).

Notes

Adjacent owners (one owner per atom):

  • The declared role environment (its field, the validation that rejects KELD_* names, and delivery at spawn): F01-T4, as an explicit KEL-75 schema amendment behind a permission-model gate. KEL-75 is In Progress and unassigned (Linear, fetched 2026-10-07). X02-T1's key→owner map lists the keld.config.ts key as the F01-T4 payload field.
  • Writing DRAWIO_DISABLE_UPDATE=true into that field: X02-T3, the migrate output.
  • Running draw.io's real boot as product cells: X02-T4, which should take an edge on this ticket.

FACT (keld-runtime at origin/main 29a4cd7): RoleConfig already sets explicit per-generation environment keys and removals. That is a delivery primitive F01-T4 can reuse, not the schema owner.

INFERENCE: delivering this variable as an environment key is consistent with the KEL-75 spec. That spec keeps KELD_APP_LINK as the sole bootstrap variable and forbids grant exceptions arising from an environment value, and the declared variable carries no authority.

INFERENCE (keld-runtime read at b4b907c): the macOS non-strict spawn path inherits the operator environment. The replay fixture therefore sets or clears the variable explicitly and never relies on the shell.

UNKNOWN: whether the first-proof activation cell runs with isPackaged false (keld dev) or true (installed root). It is decided by F01-T4 and X02-T4, which is why both cases are covered.

If the variable is absent and the app is packaged, draw.io's Update Error dialog appears at boot. That is the honest outcome, and it would fail the activation cell, so the env declaration is a product-cell dependency, not an adapter one.

Verification SHA for the current-behaviour facts: origin/main 29a4cd7 (2026-10-07).

Change log (doctrine-audit repair, 2026-10-07)

  • Narrowed from 'core facade + shim over the KEL-53 state machine' to the inert electron-updater adapter slice (critic yagni F09-T1, first_proof_frontier F09-T1, quality_problems oversized F09-T1); remainder moved to F09-T4 (Electron-core facade, parked) and F09-T5 (host update channel and real adapter path, parked)
  • Removed the F09-A2 blocker and the 'compared with keld.build.ts' clause (critic edge_problems F09-T1; F09-A2 closed resolution: park, option D, no longer blocks the inert slice; F09-A6 closed: no schema and no consumer)
  • Added blockers F09-T3 (conformance first; critic invariant_violations F07/F08/F09/X03) and PANEL-P3 (critic coverage_gaps: the PANEL-P3 member list is a named artifact F09-T1 consumes; frontier consumes)
  • Added blocker F01-T4: the adapter mirrors electron-updater 6.8.9's packaged gate and reads app.isPackaged (new primary evidence: tarball AppUpdater checkForUpdates/isUpdaterActive, verified 2026-10-07)
  • Acceptance criteria cover env-present, env-absent+unpackaged and env-absent+packaged boots (critic hidden_coupling PANEL-D14/X02-T3/F01-T4/F09-T1); the adjacent env-declaration owners are named in notes
  • The no-host-updater error is a newly allocated registry code, never KELD-COMPAT-001..009 or a host KELD-UPDATE code (refuter lens 1 extra finding: KELD-COMPAT prefix reuse)
  • Pinned the upstream oracle to electron-updater 6.8.9 by npm integrity, not the v26.0.12 tag that carries 6.6.2 (refuter lens 2 F09-A1 correction and receipt-drift extra finding)
  • The ordering is now shim first, core facade later, by demand (refuter lens 1 F09-A1 correction (2) and low-severity extra finding: core facade has zero corpus demand)
  • Review gates reduced to public API: this slice adds no host call, wire frame or grant (the permission model and wire protocol gates moved to F09-T5)
  • Linear owner rewritten from 'KEL-53' to the missing-owner blocker with live status (program_wide_findings: task-level artifacts; X06-D6 both lenses: a repo-writing ticket without a KEL owner is not claimable); verification SHA stamped (critic hidden_coupling: SHA stamp)
  • Removed the dependency on X03-T1's alias: the adapter is resolved through the migrate dependency replacement owned by X02-T3 under closed X02-A1 (see unapplied_findings)
  • Replaced the 'missing Linear owner' blocker with the binding X06-D7 rule. Evidence: the owner adopted option D on GitHub Linear owner for repo-writing Electron-compat slices (owner decision: align to nearest Linear issue, else GitHub is the tracker of record) #517 (comment 6024917784), which supersedes the X06-D6 reading and the packet's option C recommendation. GitHub feat(update): inert electron-updater 6.8.9 adapter for the draw.io proof (import-time surface, recorded no-op setFeedURL, honest check outcome) #491 is the tracker of record and the reference link lives on KEL-127; ready_state stays needs-spec because a feature needs an approved spec (cross-checker high finding X06-D7 vs unit repairs)
  • Declared role environment now has one owner, F01-T4 (KEL-75 schema amendment, permission-model gate, KELD_* rejected), matching X02-T1/X02-T3. RoleConfig env keys are recorded as a reusable delivery primitive (FACT at 29a4cd7), not as the owner (cross-checker medium finding on contradictory env owners)

Activity

  1. added
    electron-compatElectron compatibility program area
    compat:tier-2Electron compat Tier 2 (arch 04 §4)
    needs-specNeeds an approved docs/agents/spec-template.md spec before implementation
    on Oct 6, 2026
  2. added theissue type on Oct 6, 2026
  3. added
    milestone:first-proofNeeded for the first migration proof (drawio-desktop on macOS, explicit legacy profile)
    on Oct 6, 2026
  4. 0monish commented on Oct 6, 2026

    @0monish
    MemberAuthor

    Generated by an AI agent (Claude Code, Fable 5.1) on behalf of @0monish, 2026-10-06, under the map's execution doctrine. Statements are labelled FACT / INFERENCE / UNKNOWN by their author; nothing here authorizes implementation.

    Doctrine audit findings for this issue

    YAGNI classification: first-proof — Slice only: an inert electron-updater adapter — autoUpdater.logger.transports., autoDownload, autoInstallOnAppQuit are set unconditionally at import (:82-86), on('error') is registered at top level (:2233) and setFeedURL runs outside the disable guard (:2078). Park the KEL-53 state machine, update. guarded calls and quitAndInstall.

    Disposition: each finding stays open on this issue until its fix is applied to the body or refuted with evidence.

  5. changed the title [-]feat(update): autoUpdater core facade + electron-updater shim over the KEL-53 state machine with recorded no-op setFeedURL and info-only events[/-] [+]feat(update): inert electron-updater 6.8.9 adapter for the draw.io proof (import-time surface, recorded no-op setFeedURL, honest check outcome)[/+] on Oct 6, 2026
  6. 0monish commented on Oct 6, 2026

    @0monish
    MemberAuthor

    This was generated by AI during triage.

    Triage Notes

    What we've established so far:

    • The slice is specified in the issue body (what to build, binary acceptance criteria, negative controls, ownership and gates).
    • Milestone first-proof; tracker of record as stated in the body.

    What we still need from you (@0monish):

    Once those resolve, this ticket moves to ready-for-agent with an Agent Brief.

  7. 0monish commented on Oct 7, 2026

    @0monish
    MemberAuthor

    This was generated by AI during triage.

    Agent Brief

    Category: enhancement
    Summary: feat(update): inert electron-updater 6.8.9 adapter for the draw.io proof (import-time surface, recorded no-op setFeedURL, honest check outcome)

    Current behavior:
    No updater surface exists: @keld/electron exports only app, and no package in the workspace answers the electron-updater specifier (verified at origin/main 29a4cd7).

    Desired behavior:
    Build a Keld-owned package that stands in for the electron-updater specifier. Migrate swaps it in as a dependency replacement in the app manifest: X02-T3 owns that swap, under the single-resolver rule closed in X02-A1. The package exposes the AppUpdater subset that draw.io touches, as listed by the PANEL-P3 trace. It must evaluate at import with no host frames and no network or filesystem I/O, and must never fabricate an updater outcome.

    Behaviour, mirroring electron-updater 6.8.9 wherever the upstream shape can be honoured without a host updater:

    • A default export carrying autoUpdater, which is an EventEmitter.
    • A logger accessor that stores and returns the identical object; null yields a no-op logger.
    • autoDownload, autoInstallOnAppQuit and autoRunAppAfterInstall are plain booleans with no side effect.
    • setFeedURL(string or options) returns undefined. It never throws, constructs no client, and stores nothing any host path reads. This is a ▲: the feed is host-declared and the app feed is ignored.
    • getFeedURL() returns upstream's deprecated literal.
    • checkForUpdates() mirrors upstream's packaged gate and reads app.isPackaged from F01-T4. When not packaged it resolves null with no event. When packaged and no host updater exists, it emits exactly one error carrying a newly allocated registry code and rejects the same promise.
    • downloadUpdate() before any update is offered rejects, as upstream's "check first" does.
    • quitAndInstall() with nothing staged emits one error and never quits.
    • Never emitted: update-available, update-not-available, download-progress, update-downloaded, checking-for-update.

    First deliverable: a spec from docs/agents/spec-template.md as a PR, stopped for human approval; implement only after Status: approved.

    Key interfaces:

    • The Keld-owned adapter package; @keld/electron app.isPackaged (F01-T4); the migrate dependency replacement (X02-T3);
    • F09-T3 conformance cells in the KEL-237 corpus-runner pattern.

    Acceptance criteria:

    • Every F09-T3 match cell flips from its recorded expected verdict to pass in this change, and every F09-T3 ▲ cell carries its divergence verdict; no F09-T3 cell is skipped, retried or left silently red
    • Under Bun 1.4.2, the default-import form followed by destructuring autoUpdater yields an EventEmitter, and the app-link frame count attributable to the adapter during import is 0
    • Assigning an object to logger and reading it back returns the identical object (strict equality), so property writes made through it land on the assigned logger
    • setFeedURL called with draw.io's github provider options and with a plain string returns undefined both times, throws nothing, opens no socket and writes no file
    • A fixture that replays the PANEL-P3-recorded draw.io updater call sequence with DRAWIO_DISABLE_UPDATE=true set explicitly in the role environment makes zero checkForUpdates calls and receives zero error events
    • The same replay with the variable absent and app.isPackaged false makes checkForUpdates resolve null with zero events
    • With the variable absent, app.isPackaged true and no host updater, checkForUpdates emits exactly one error whose code is a registry-allocated code outside KELD-COMPAT-001..009 and KELD-UPDATE-*, and the returned promise rejects
    • Across every case above, no update-not-available, update-available or update-downloaded event is ever emitted
    • quitAndInstall() with nothing staged emits exactly one error and app.quit is never called
    • Negative control: Making setFeedURL throw fails the setFeedURL no-throw cell and the replay fixture (draw.io's safeUpdaterCall would open its Update Error dialog)
    • Negative control: Returning a fresh wrapper from the logger getter fails the identity cell
    • Negative control: Emitting update-not-available from checkForUpdates with no host updater fails the never-fabricate cell
    • Negative control: Removing the isPackaged gate makes the unpackaged replay emit an error and fails the unpackaged-check cell
    • Negative control: Calling app.quit from quitAndInstall with nothing staged fails the no-quit cell
    • Negative control: Reusing a KELD-UPDATE-* code for the no-host-updater error fails the code-provenance assertion

    Out of scope:

    • Any host update channel, feed contact, download, staging or activation (F09-T5); the Electron-core autoUpdater facade (F09-T4); feed-base reporting (F09-A2 closed: parked under KEL-53 / arch 06 §4a); comparison of the setFeedURL argument with a build config (no schema and no consumer); declaring or delivering the role environment variable (F01-T4).

    Tracker of record and claim location: see "Ownership and gates" in the issue body.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    compat:tier-2Electron compat Tier 2 (arch 04 §4)electron-compatElectron compatibility program areaenhancementNew feature or requestmilestone:first-proofNeeded for the first migration proof (drawio-desktop on macOS, explicit legacy profile)needs-infoWaiting on more informationneeds-specNeeds an approved docs/agents/spec-template.md spec before implementation

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions