Commit graph

124 commits

Author SHA1 Message Date
470ef1de0e fix(quickadd): return focus to the previous app when the popover hides
All checks were successful
Build / validate (pull_request) Successful in 5m52s
The global ⌘' quick-add overlay is a borderless, transparent, always-on-top
accessory window that winit hides with `Visible(false)`. That orders the window
out visually but leaves heph-quickadd the *active* application — so after a
capture (or Esc / toggle) keyboard focus never returns to the app the user was
in, and the lingering overlay can keep intercepting clicks where it used to sit.

Hide at the application level instead via `NSApplication.hide:`, which fully
orders our windows out and activates the next app in line (the previously
focused one). On re-show, `unhide:` clears that hidden flag before the existing
viewport `Focus` command makes the field key again. Both are macOS-only no-ops
elsewhere, wired through new `app_yield_focus`/`app_take_focus` helpers backed by
objc2 / objc2-app-kit (unified to the 0.6/0.3 line global-hotkey already pulls).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:08:07 -07:00
b04a71421e fix(hephd): reconnect the socket client across daemon restarts
All checks were successful
Build / validate (pull_request) Successful in 8m7s
`Client` connected to the unix socket once and never reconnected, so after an
opt-in self-update or `heph daemon restart` dropped the socket, every later
`call()` failed — `heph-tui` would sit on errors until relaunched (and the work
we just shipped makes restarts more frequent).

`Client` now stores the socket path and reconnects on a dropped connection,
classifying the failure to stay safe:
- write-side failure (request never reached the daemon) → reconnect + retry once;
- reply lost after sending (daemon closed mid-request) → reconnect for next time
  but surface this one, so a mutation is never silently double-applied;
- genuine RPC errors are passed through untouched.

heph-tui and the CLI use `Client` unchanged, so the TUI self-heals on its next
refresh tick. Adds an integration test driving a mock daemon that drops the
connection after each request.

Closes the "heph-tui: reconnect on a dropped daemon socket" backlog task.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:19:10 -07:00
Forgejo Actions
2ca1e246f0 Update changelog for v1.3.0 [skip ci] 2026-06-08 14:15:03 -07:00
e943a940f1 feat(hephd,heph,heph-tui): distinguish IdP rejection from unreachable + actionable re-auth
All checks were successful
Build / validate (pull_request) Successful in 6m12s
The spoke OAuth path funneled every failure into one `AuthError::Provider`
whose Display was hardcoded "identity provider unreachable". So a reachable IdP
returning `400 invalid_grant` on a refresh was reported as "unreachable",
misdirecting incident response toward the network when the fix is re-auth. The
real refresh cause was also swallowed — `bearer()` logged it and returned None,
so sync health only ever showed the downstream 401 on /sync/pull.

Wording fix (auth.rs / oauth.rs):
- Split AuthError into Unreachable (transport), Rejected (IdP returned an HTTP
  error — carries the RFC 6749 §5.2 error/error_description), and Other
  (keyring / malformed response, previously mislabeled too).
- refresh()/discover()/start()/poll() classify transport vs status; refresh
  reads the OAuth error body on a non-2xx.
- Hub-side token verify maps IdP-infra failures → 503, token failures → 401.

Recovery UX (server.rs / heph / heph-tui):
- bearer() returns Result; the sync paths record the real acquisition failure
  (with a re-login hint when it's a rejection) instead of a masked 401.
- sync health's last_error carries the exact `heph auth login --hub-url …
  --issuer … --client-id …` command (keyed to the configured hub); sync.status
  also returns issuer/client_id + the command.
- New `heph auth status` prints auth health and the re-login command.
- heph-tui's auth chip points at it: `⚠ auth · heph auth status`.

Closes the duplicate "misleading identity provider unreachable" tasks and the
"actionable re-auth guidance" task. Also corrects a now-stale set-up-sync-hub
gap note (daemon config baking landed in the prior PR).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 14:06:08 -07:00
f6b27414a8 fix(heph): make macOS heph daemon restart race-free
All checks were successful
Build / validate (pull_request) Successful in 8m39s
`restart` bootstrapped immediately after `bootout`, but `launchctl bootout` is
asynchronous: launchd may still be killing/reaping the job and removing its
label when the command returns. Bootstrapping into that transitional domain
fails with a generic `5: Input/output error`, intermittently — the odds depend
on how fast hephd (sync client + SQLite + a heph-quickadd child) shuts down.

- Wait for the label to actually clear (poll `launchctl print`, bounded) before
  re-bootstrapping, and retry the bootstrap to cover the residual settle window.
- When the plist is unchanged (the common binary-upgrade restart), use
  `launchctl kickstart -k` to restart the loaded job atomically — no
  bootout/bootstrap, no race. The full reload path is reserved for genuine
  config changes, where launchd must re-read the plist.

Start's bootstrap shares the same retry helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 13:38:47 -07:00
626c796e6c feat(heph): bake daemon mode/hub/oidc/self-update-interval into the service
All checks were successful
Build / validate (pull_request) Successful in 5m45s
`heph daemon start`/`restart` previously hardcoded `hephd --mode local` and
only wired the bare `--self-update` bool — the poll interval and all spoke/hub
sync config (`--hub-url`, `--http-addr`, `--oidc-*`) could not be set on the
managed service without hand-editing the plist/unit (which a later
start/restart would clobber).

Generate the hephd arg vector from a DaemonConfig and add the corresponding
`heph daemon start/restart` flags: --mode, --hub-url, --http-addr,
--oidc-issuer, --oidc-audience, --oidc-client-id, and
--self-update-interval-secs. Regenerating now reads the existing service file
and preserves any flags not passed (start as well as restart), so a bare
invocation never silently drops baked config.

Closes the "pass through --self-update-interval-secs" and "bake hub/spoke
config into the generated service" backlog tasks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 13:25:15 -07:00
c9bb2cbe64 feat(heph-tui): show sync age in seconds under a minute
All checks were successful
Build / validate (push) Successful in 6m28s
The background sync loop runs every 30s, so the last-sync age never crossed
the 60s 'just now' threshold — the chip always read 'just now', which also
masked the first missed sync (age 30-60s looked identical to a fresh one).
Show seconds under a minute ('⟳ 26s') so the chip is a visible heartbeat and a
stalled sync surfaces ~30s sooner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:24:09 -07:00
Forgejo Actions
1a8752f124 Update changelog for v1.2.3 [skip ci] 2026-06-06 11:03:45 -07:00
11aa25c9f4 feat(heph-tui,hephd): surface sync health (last-sync age, conflicts, auth failure)
All checks were successful
Build / validate (pull_request) Successful in 6m11s
A spoke could be silently failing to sync (expired token → 401, or hub
unreachable) with the only signal buried in the daemon log. Now:

- hephd tracks SyncHealth (last attempt/success time, last error, auth-failure
  flag) from the background sync loop and sync.now, classifying a 401 as an auth
  failure. sync.status returns it plus the pending merge-conflict count.
- heph-tui shows a live status-line indicator (spoke only): '⟳ <age>' since the
  last good sync, red '⚠ auth' when re-login is needed, '⚠ offline' when the hub
  is unreachable, and '⚠ N conflicts' when conflicts are pending. The event loop
  polls on a 2s tick so the age advances and failures appear while idle.
- docs: recommended Authentik access/refresh token validity to stop frequent
  re-logins (with the iOS PWA localStorage-eviction caveat).

Closes the 'Add hub connection status to heph-tui' and 'Spoke sync health:
surface unhealthy state instead of silent 401 spam' backlog items.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:19:11 -07:00
Forgejo Actions
4bf255b211 Update changelog for v1.2.2 [skip ci] 2026-06-06 09:30:28 -07:00
9a487cbe3b feat(heph-tui,heph-pwa): humanized recurrence + indented/counted/scrolling project sidebar
All checks were successful
Build / validate (pull_request) Successful in 6m57s
Bundles the cosmetic/UI-polish backlog for the agenda surfaces. All read-side;
no schema or sync change (see hub-spoke-data-evolution).

- humanize_rrule (hephd::datespec): inverse of parse_recurrence — renders an
  RRULE as 'every other week', 'weekdays', 'yearly on Apr 15', etc.; falls back
  to the raw rule for unmodeled parts (COUNT/UNTIL/ordinal BYDAY). Mirrored in
  the PWA's datespec.js. Shown in the TUI recurs detail line and PWA task/qa
  previews instead of the raw FREQ= string.
- project.overview RPC + Store::project_overview: each project's parent (via the
  existing 'parent' links) and direct outstanding-task count, a read-only query.
- TUI sidebar: subprojects indented by depth, per-project counts, wider pane,
  and ListState + scrollbar so it scrolls instead of clipping on overflow.

Tests: humanize parity (Rust + JS), round-trip through parse_recurrence,
raw-passthrough; project_overview count/parent; sidebar tree ordering + cycle
safety.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:44:43 -07:00
00da36c637 doc(explanation): hub+spoke data-evolution / migration rules
All checks were successful
Build / validate (pull_request) Successful in 6m18s
Document why heph's op-based sync lets most new features (new link types,
read-side queries, optional payload fields) ship without a coordinated
migration across the hub and spokes, and the narrow case — a new required
SQLite column the apply path writes — that does need a hub-first rollout.

Groundwork for the indented/counted project sidebar, which is pure read-side
(existing parent links + a GROUP BY) and needs no migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:31:11 -07:00
Forgejo Actions
c8512b2b50 Update changelog for v1.2.1 [skip ci] 2026-06-05 07:36:46 -07:00
a0be0f1085 doc(heph-pwa): in-app Authentik login replaces manual token paste
Document the PKCE 'Login with Authentik' flow, the hub /config zero-config
discovery, and the redirect-URI prerequisite on the Authentik heph provider.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 07:09:42 -07:00
Forgejo Actions
5f3e3225ec Update changelog for v1.2.0 [skip ci] 2026-06-04 17:51:55 -07:00
936c2635ef doc(heph-pwa): production runbook — host the app from the hub (indri) with OIDC
All checks were successful
Build / validate (pull_request) Successful in 6m18s
Add host-heph-pwa.md: a deployment how-to for serving the PWA from the canonical
hub in the hub/spoke OIDC setup (post-release) — fetch the shell at the hub's
tag, add --web-root, terminate TLS (tailscale serve / reverse proxy), and the
token-paste caveat with the device-code-login follow-up. Cross-linked from
heph-pwa and the how-to index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:17:25 -07:00
b24a148add doc(heph-pwa): how-to card, index entry, changelog fragment
Document serving the app from the hub (--web-root), connecting (hub URL +
optional token), quick-add syntax, voice, triage, and the deliberate
design choices (PWA over native iOS; online-only; token paste vs device flow)
with their known limitations to revisit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:59:38 -07:00
Forgejo Actions
b75d7a8d7a Update changelog for v1.1.1 [skip ci] 2026-06-04 15:36:38 -07:00
fac39386d0 fix: self-update poll uses ureq (reqwest has no TLS backend)
All checks were successful
Build / validate (push) Successful in 4m31s
hephd's reqwest client is built default-features=false with no TLS
feature, so the self-update release poll's HTTPS GET always failed
('release check failed: requesting forge releases/latest') — the bug
never surfaced before because nothing in production used reqwest over
HTTPS (hub sync is plain http://). Switch the poll to ureq, which is
already a dependency and ships a rustls/ring TLS stack needing no system
libs (notably no cmake/aws-lc-sys, which would break the rust:bookworm CI
image). Verified end-to-end: a 0.0.0 build now detects v1.1.0, installs,
and restarts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:26:13 -07:00
Forgejo Actions
854d25c68b Update changelog for v1.1.0 [skip ci] 2026-06-04 15:07:55 -07:00
443763489b C2(hephd-self-update): finalize — single self-update how-to + changelog
All checks were successful
Build / validate (pull_request) Successful in 6m10s
Collapse the eight Mikado scaffolding cards (+ goal card) into one
user-facing how-to, docs/how-to/self-update.md: what self-update is and
how to enable it. The per-card breakdown was build-time scaffolding, not
documentation. Keeps the changelog fragment; updates the how-to index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:00:37 -07:00
240c8a9f68 C2(hephd-self-update): close service-env-forge-access
Public repo => anonymous HTTPS clone, no credentials (the SSH/canonical
premise was wrong: that was the access-restricted cargo registry, not git
clone). Install URL points at the canonical public host (verified end to
end); the service template bakes cargo onto PATH. Card rewritten to
reflect what actually happened.
2026-06-04 14:47:05 -07:00
59822d7257 C2(hephd-self-update): impl service-env-forge-access (public HTTPS, cargo on PATH)
The repo is public, so self-update needs no credentials: cargo install
--git is a plain anonymous clone (NOT the access-restricted Forgejo cargo
registry, which is what required forge.ops.eblu.me). Point INSTALL_GIT_URL
and the releases poll at the canonical public host over HTTPS — verified
end-to-end (cargo install --git https://forge.eblu.me/... --tag v1.0.3
builds a working hephd with zero auth).

Make the headless service able to run the apply path: 'heph daemon
start --self-update' (default off) generates a launchd/systemd service
that passes --self-update and bakes a PATH (incl ~/.cargo/bin) + HOME so
the minimal service env can find cargo. restart preserves the setting.
Default (no flag) services are byte-identical to before. Template + URL
behavior covered by unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:46:34 -07:00
c237be6604 C2(hephd-self-update): close self-restart-after-update
Restarter + ProcessRestarter wired: install then exit(0) so the service
manager respawns the new binary; restart only on a successful install.
Unit-tested via injection.
2026-06-04 13:54:50 -07:00
e7cb86efdf C2(hephd-self-update): close cargo-install-from-tag
Installer trait + CargoInstaller + apply_update landed and unit-tested
via injection. Real cargo execution is gated on the deployment env
(service-env-forge-access).
2026-06-04 13:52:45 -07:00
fd76aa0b3a C2(hephd-self-update): close verify-hub-dropout-resilience
All checks were successful
Build / validate (pull_request) Successful in 5m24s
Spoke survival across hub downtime is now covered by a test; added a
client timeout so a black-hole hub can't stall the loop.
2026-06-04 13:47:53 -07:00
2a7a3ec270 C2(hephd-self-update): close service-respawn-on-clean-exit
systemd unit now Restart=always; both managers respawn after a clean exit.
2026-06-04 13:44:56 -07:00
544c8bba0e C2(hephd-self-update): impl systemd Restart=always for clean-exit respawn
Self-restart works by exiting cleanly and letting the service manager
respawn the new binary. launchd already does this (KeepAlive=true), but
the systemd user unit was Restart=on-failure, which ignores a clean
exit (code 0). Switch to Restart=always + RestartSec=1, update the unit
test, and note in run-the-daemon that existing Linux installs must
`heph daemon restart` once to regenerate the unit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 13:44:36 -07:00
758854478b C2(hephd-self-update): close self-update-poll-loop
All checks were successful
Build / validate (pull_request) Successful in 3m56s
Notify-only poller landed: ticks on the interval, logs when a newer
release is available. The daemon now self-reports update availability.
2026-06-04 13:42:48 -07:00
35569b0649 C2(hephd-self-update): close self-update-opt-in-flag
--self-update flag + config plumbing landed; opt-in observable via the
startup log line.
2026-06-04 13:40:47 -07:00
4a0094f955 C2(hephd-self-update): close release-poll-version-check
Version-compare + forge release parsing landed and unit-tested.
2026-06-04 13:37:14 -07:00
e6524fddbb C2(hephd-self-update): plan add goal + prerequisite cards for hephd self-update
All checks were successful
Build / validate (pull_request) Successful in 5m23s
Kick off the C2 Mikado chain for an opt-in (default-off) hephd
self-update mode (forge-poll -> cargo install from tag -> self-restart).
Goal card plus eight prerequisite cards, indexed from how-to.md:

  release-poll-version-check, self-update-opt-in-flag (leaves)
    -> self-update-poll-loop                 (notify-only core)
  service-env-forge-access (leaf, the cargo/forge blocker)
    + self-update-poll-loop -> cargo-install-from-tag
  service-respawn-on-clean-exit (leaf, systemd Restart=always)
    + cargo-install-from-tag -> self-restart-after-update
  verify-hub-dropout-resilience (leaf, lock in the base-case guarantee)

Grounded in research of hephd's sync loop, daemon lifecycle, the
launchd/systemd service templates, and the forge releases API.
Captured from Hephaestus task 01KTA2NSNRYT902HC3VRW00S1J.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 13:20:46 -07:00
Forgejo Actions
254c83036b Update changelog for v1.0.3 [skip ci] 2026-06-04 12:42:46 -07:00
7f48a2a1c5 infra: add cargo-fmt-check pre-push prek hook (mirror CI)
All checks were successful
Build / validate (pull_request) Successful in 5m25s
The cargo-fmt failure on this PR slipped to CI because the pre-commit
prek hooks were never installed in the working clone. The existing
cargo-fmt hook reformats in place but only when it runs. Add a
pre-push cargo-fmt-check hook (`cargo fmt --all --check`) that mirrors
CI's Dagger `check` step exactly, so an unformatted commit is blocked
locally before it can reach the runner — even if the pre-commit hook was
skipped or not installed. Filtered to .rs pushes so Rust-free pushes pay
nothing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 12:36:12 -07:00
7914232ec4 feat: add a version RPC returning the daemon build version
Some checks failed
Build / validate (pull_request) Failing after 15s
RPC clients (the hephaestus.nvim plugin, for its `:Heph version`
command) had no way to learn which hephd they are talking to — `health`
returns counts, not a version. Add a tiny `version` method returning
`{ version: heph_core::VERSION }`, the same `X.Y.Z (sha)` string the
binaries print for --version. No store access needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 11:13:47 -07:00
babdb21c0a feat: heph context <task-id> reads/edits a task's context doc by id
Editing a task's canonical-context doc body previously meant looking up
its `canonical_context_id` (e.g. via `heph list --json`) and then
`heph node update <doc-id> --body`. Add a `heph context <task-id>`
command that resolves the canonical-context doc from the task's outgoing
links and:

  * prints the body with no flag,
  * `--body <text>` replaces it (`-` reads stdin, matching `node update`),
  * `--append <text>` adds a blank-line-separated paragraph.

Errors clearly when the id has no canonical-context doc (e.g. a plain
doc node rather than a task). Purely a client-side CLI convenience — no
new RPC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 11:09:53 -07:00
fc25f6ac51 feat: --project arg is case-insensitive / prefix-fuzzy when unambiguous
The `--project <name>` argument matched titles case-sensitively and
exactly, so `--project hephaestus` or `--project heph` failed against a
`Hephaestus` project. Make project-name resolution forgiving but
deterministic, via a tiered match in `resolve_project_id`:

  1. exact (case-sensitive) — the historical behavior; always wins
  2. case-insensitive exact — only when unambiguous
  3. case-insensitive prefix — only when unambiguous

Ambiguous fuzzy matches resolve to None (callers report "no project
named X") rather than silently picking one. This single resolver already
backed `heph list --project` (via project_scope); route the CLI's
task/edit/promote/parent path through it too with a new `project.resolve`
RPC + `Store::resolve_project`, so every `--project` surface behaves the
same.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 10:57:37 -07:00
58a5544d44 feat: heph-tui --version reports version + build SHA
heph-tui was the one daily-driver binary that did not answer --version.
Add the same clap `version = heph_core::VERSION` attribute that heph and
hephd already carry, so all three report `X.Y.Z (sha)` consistently.

Addresses the heph-tui half of the cross-binary --version task.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 10:52:52 -07:00
Forgejo Actions
67627a0930 Update changelog for v1.0.2 [skip ci] 2026-06-04 10:22:04 -07:00
1603b37f74 fix: --version no longer false-positives -dirty on clean release installs
All checks were successful
Build / validate (push) Successful in 6m13s
The build-time dirty check used `git status --porcelain`, which counts
untracked files — including cargo's own `.cargo-ok` marker in a
`cargo install --git` checkout — so a clean tagged build reported e.g.
`1.0.1 (bcab3c16b-dirty)`. Use `git diff --quiet HEAD` (tracked changes only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 10:20:23 -07:00
Forgejo Actions
defaa56b23 Update changelog for v1.0.1 [skip ci] 2026-06-04 10:14:40 -07:00
521e5d62df docs: install-heph defaults to the v1.0.0 release tag (v1 is out)
All checks were successful
Build / validate (pull_request) Successful in 5m5s
Replace the pre-release 'install from feature/v1-prototype' instructions with
`--tag v1.0.0` as the default, and document `--branch main` as the track-
unreleased-work alternative.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:47:23 -07:00
598dc59580 fix: --version reports release version + build SHA; release tags a version-bump commit
Some checks failed
Build / validate (pull_request) Has been cancelled
heph-core gains a build.rs that captures the short git SHA and a
`heph_core::VERSION` const ("<crate-version> (<sha>)"); heph and hephd use it
for clap's --version. The crate version stays sourced from Cargo.toml.

release.yaml now bumps the workspace version into Cargo.toml + Cargo.lock on a
commit that only the tag points at, tags it manually, and pushes just the tag —
so cargo install --git --tag vX.Y.Z reports the real version while main stays at
0.0.0. The changelog commit moved ahead of the tag so the release includes its
own changelog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:43:10 -07:00
c266f7ee4a docs: release bumps Cargo.toml/lock on a tag-only commit; --version reports X.Y.Z (sha)
Documents the desired end state before the code change (C1 docs-first):
- release.yaml bumps the workspace version into a commit only the tag points
  at, keeping main at 0.0.0
- heph/hephd --version will report the release version plus the build SHA

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:37:14 -07:00
Forgejo Actions
774770ce39 Update changelog for v1.0.0 [skip ci] 2026-06-04 08:23:35 -07:00
ab6701d610 C0: pin Quartz docs build to v4.5.2 (v5.0.0 broke build)
All checks were successful
Build / validate (push) Successful in 6m1s
The Dagger build_docs pipeline cloned Quartz from the default branch
unpinned. Quartz v5.0.0 restructured its config layout (.quartz/plugins,
../quartz imports), breaking the docs build against our existing
quartz.config.ts / quartz.layout.ts. Pin the clone to the last v4
release (v4.5.2) to restore known-good behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 08:11:59 -07:00
b6a96013ca fix(ci): vendor libdbus in the Linux keyring store (no system libdbus-1-dev)
All checks were successful
Build / validate (pull_request) Successful in 8m11s
The rust:1-bookworm CI image has no libdbus-1-dev, so libdbus-sys's
pkg-config build failed. Enable the dbus store's `vendored` feature to build
libdbus from bundled source (self-contained, the proven path the earlier
keyring-4 build used). `crypto-rust` keeps it OpenSSL-free; openssl-sys is only
an inert lock entry (the conditional `openssl?/vendored` reference), compiled
nowhere. Linux footprint unchanged at 235 crates; vendored libdbus is a
build-time C compile, not new crates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 07:32:41 -07:00
6ba94119e4 infra: slim the keyring dependency (keyring meta-crate -> keyring-core + one store/OS)
Some checks failed
Build / validate (pull_request) Failing after 45s
keyring 4's `keyring` meta-crate has no feature gating and compiles every
platform credential backend for the target. On Linux that dragged in the zbus
async stack, a redundant libdbus secret-service, the keyutils store, a
sqlite/zstd db-keystore, and OpenSSL (~290 crates in its subtree) — a real cost
on the RAM/CPU-constrained CI runner building with CARGO_BUILD_JOBS=1.

Depend on keyring-core (the API) + exactly one store crate per OS instead:
- macOS  -> apple-native-keyring-store (keychain feature)
- Linux  -> dbus-secret-service-keyring-store (crypto-rust; libdbus, no openssl)

oauth.rs registers the per-target store as the keyring-core default itself
(replacing keyring::use_native_store). Runtime behavior is unchanged (tokens
still go to the macOS Keychain / Linux Secret Service).

hephd's Linux dependency graph: 401 -> 235 crates (-166), dropping the zbus
ecosystem and two C builds (zstd-sys, plus the redundant secret-service path).

macOS builds + the full suite are green here (228 tests, clippy -D warnings,
fmt, prek); the Linux store path is CI-verified (API confirmed from source).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 07:26:39 -07:00
a0b04eefda feat: multi-tenancy seam (resolve_owner) + hub-setup how-to (v1 prep)
All checks were successful
Build / validate (pull_request) Successful in 10m34s
The cheap "seam" that keeps the single-owner hub from calcifying, ahead of
the gilbert -> indri bring-up:

- Replace the single-tenant gate `Store::authorize_owner_sub(sub) -> bool`
  with `resolve_owner(sub) -> Option<owner_id>`. The hub auth middleware now
  resolves the token's identity to the owner it may act as (Some -> allow,
  None -> 403). Behavior is identical for the single-owner hub (claim-on-first;
  strangers still 403), but the contract no longer assumes one global owner, so
  serving N owners later is additive, not a rewrite. The per-request owner is
  marked at the exact line where downstream scoping wires through.
- New how-to docs/how-to/set-up-sync-hub.md: stand up the hub and connect an
  existing device as an offline-capable spoke, the data-safe way (Path A: the
  hub adopts the device's identity rather than rewriting the device).

The decision (cheap seam now, defer full multi-tenancy + adoption rewrite) is
recorded in the Adoption + multi-tenant task's context doc. Two enabler gaps
the how-to surfaced (heph daemon hub/spoke service flags; Path-A seeding tool)
are filed as Hephaestus tasks.

Green: 228 tests, clippy -D warnings + fmt + prek clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 07:08:39 -07:00
9fa9a9b9c1 Merge remote-tracking branch 'origin/main' into feature/extract-heph-nvim
Some checks failed
Build / validate (pull_request) Failing after 14m23s
# Conflicts:
#	.dagger/src/hephaestus_ci/main.py
2026-06-03 23:24:10 -07:00