Skip to main content
Version: v0.0.72

v0.0.49 — Slug URLs Phase 2 + supply-chain integrity + rollback

Released: 2026-05-05. Eight commits split across three themes: project-slug URLs Phase 2 (sub-routes, sidebar rewrite, cross-org interstitial, recipe-route collapse, ESLint guard against regression), a supply-chain integrity bundle (tarball + run.bin signature verification, opt-in service-user split, secrets-doctor, structured CLI audit log), and a honeyframe rollback command that finally closes the loop on v0.0.47's full-tree backups. Plus a real /api/audit/timeline 500 fix and a CI lint that prevents the underlying class of bug from recurring.

Project-slug URLs — Phase 2

Phase 1 (v0.0.48) wired the routes additively. Phase 2 makes the slug URLs the canonical UX without breaking anything.

Three new project-scoped sub-routes

/projects/:slug/scenarios/:name/steps → ScenariosPage auto-selects
/projects/:slug/jobs/:runId → OperationsPage opens Runs tab
/projects/:slug/recipes/:name → RecipeDispatcher resolves type inline

The recipe sub-route is a dispatcher: it fetches recipe_type and renders the matching editor inline (no redirect flicker), so a single URL resolves whatever the recipe is.

The Layout sidebar (DarkIconTab, MoreTab, WaffleMenu, mobile) now threads useProjectUrl(). Once a user is in a project, every nav click keeps the slug shape. Active-state checks compare against both URL forms so legacy bookmarks still highlight the right tab.

Phase 2B — 24 pages migrated

The bulk-disable block in eslint.config.js is gone. Every long-tail page (ChangePassword, ConversationsPage, EntityReviewQueue, MobileReleasesPage, NotebookPage, ProjectSetupWizard, RecipeEditorPage, ScenariosPage, builders, dataset tabs, flow sidebars, ER setup, AgentBuilder, PublishManager, DashboardsPage, …) now produces /projects/<slug>/<page> when a project context exists, and falls back to the legacy path when it doesn't. The ESLint no-restricted-syntax rule guards against new code regressing back to legacy hardcoded paths.

Recipe-route fanout collapsed

The 6 type-specific project-scoped recipe routes (/projects/:slug/recipes/{prepare,join,group_by,stack,sync,python}) fold into:

/projects/:slug/recipes/:name → edit existing (RecipeDispatcher)
/projects/:slug/recipes/new/:type → create new (RecipeNewDispatcher)

Pre-collapse bookmarks (?model= / ?name=) keep working as a fallback. Legacy non-slug /recipes/<type> routes survive because audit-log deep-links and the Layout's Python list nav still use them — they migrate when the Layout nav adopts a slug-aware list URL in a follow-up.

Cross-org slug interstitial — closes the Phase 2 backlog

When a user lands on /projects/<slug>/... and the slug isn't in their active org, the page shows a banner asking whether to switch to a different org where the slug exists.

  • Backend: GET /api/projects/find-by-slug/{slug} returns the orgs the caller belongs to where the slug lives, excluding the active org. Same user_orgs JOIN that gates project visibility everywhere else — a slug in an org the user isn't a member of stays invisible.
  • Frontend: useProjectKeySync's catch path probes the new endpoint when by-key 404s, then exposes matches via crossOrgMatches. <CrossOrgInterstitial> renders above <Outlet> when matches exist. Switch button writes selected_org_id + project context to localStorage and reloads — same client-side mechanism the OrgSwitcher uses. Multi-match (slug in 2+ orgs) lists every option.

Supply-chain integrity bundle

Six gaps from the v0.0.49 security review; four closed in this release with code, two deferred via memos to v0.0.50.

Tarball signature verification (gap #1)

honeyframe update now verifies releases/<file>.sha256 + releases/<file>.sha256.sig against the build-time-baked HONEYFRAME_PUBKEY_HEX (Ed25519, via openssl pkeyutl -rawin — pure stdlib, no pip deps in the CLI) before unpacking. Default behavior is soft-warn; --require-signature makes it a hard-fail:

honeyframe update --require-signature # rejects unsigned/invalid tarballs

CircleCI runs scripts/sign_release.py against every releases/*.tar.gz, extracts run.bin, signs it, and re-packs the tarball. Skipped silently when HONEYFRAME_SIGN_KEY_B64 is unset (probe builds aren't signed).

run.bin signature verification (gap #3)

A second sig binding catches binary swaps even when the tarball sig file is unchanged. _runbin_sig_verify reuses the same pkeyutl path. Hooked into:

  • honeyframe verify — post-deploy gate
  • honeyframe update — pre-restart gate

Service-user split — opt-in (gap #4)

Existing prod installs run hub-* services as root. The new migrate-to-svc-user command creates a honeyframe-svc system user, chowns INSTALL_DIR + DATA_DIR, hardens .env to 0640 root:honeyframe-svc, and patches every hub-* systemd unit to run under the new user. Idempotent on re-run; default systemd templates are unchanged so existing installs stay on root until you opt in.

honeyframe migrate-to-svc-user # dry-run: print plan
honeyframe migrate-to-svc-user --yes # actually do it

honeyframe secrets-doctor (gap #5, interim)

Audits every .env under INSTALL_DIR and DATA_DIR for mode (0600 / 0640), owner, and sensitive-key presence. Exits non-zero on findings, so it cron-ables. Interim measure until the v0.0.50+ Vault integration lands.

honeyframe secrets-doctor

Structured CLI audit log (gap #6)

Every CLI invocation (install, update, rollback, activate, verify, migrate-to-svc-user, secrets-doctor) now writes a JSON-line to /var/log/honeyframe/audit.log with standard fields: ts, action, status, user, host, pid, invocation_id. Mirrored to journald via logger -t honeyframe-audit for SIEM forwarding. Falls back to /tmp/honeyframe-audit.log if perms fail (so the ops surface never crashes on a logging error).

This is separate from the in-app honeyframe.audit_log table — that one records UI/API activity inside a tenant. The CLI audit log records host-level operator activity.

honeyframe rollback

v0.0.47 started writing full-install.tgz to backups/<name>/, but recovery still required a manual tar xzf + remembering the right -C target + remembering to restart services. v0.0.45→v0.0.46's iaas/backend wipe was only recoverable because a months-old hand-made tarball happened to still be on disk. The next incident of that class shouldn't depend on luck.

honeyframe rollback # dry-run plan for the newest backup
honeyframe rollback --list # list available backups
honeyframe rollback <name> # target a specific backup
honeyframe rollback --yes # skip confirmation, actually restore

Flow:

  1. Extract archive to /tmp/honeyframe-rollback-<ts>/ before moving INSTALL_DIR (the archive lives at INSTALL_DIR/backups/<x>/full-install.tgz — swapping first would invalidate the path we're reading from).
  2. Stop every hub-* service via systemd (autodetected, same path as cmd_update).
  3. Atomically rename: live install → parking, staged install → live.
  4. SELinux relabel.
  5. Restart every hub-* service.
  6. Print a honeyframe verify reminder + parking-dir cleanup hint.

Pre-rollback install is preserved at INSTALL_DIR.rollback-parked-<ts> so a botched rollback is reversible with mv + restart.

Default is dry-run. Without --yes the command prints a plan and exits — operators must explicitly opt in to the destructive swap. Pre-v0.0.47 backups (no full-install.tgz) are listed but rejected for restore with a helpful "v0.0.47+" message.

Audit timeline 500 fix + reserved-keyword lint

The v0.0.48 /api/audit/timeline endpoint 500'd in production: WITH window AS (...) is a PG reserved keyword and the planner refused it. FakeDB tests missed it because they string-match SQL fragments without parsing. Reproduced live on PG14, fixed by renaming the CTE alias to win.

To prevent the wider class of bug, the original audit-timeline-only lint generalized into a parametrized scan over every paas/backend/routers/*.py — the build now fails if any CTE alias matches a PG reserved keyword (window, order, user, …). Quoting ("window") is allowed. 57 routers currently clean.

Three e2e specs added at the same time: admin-override smoke, timeline drilldown, webhook deliveries filter (spec 63 covers the recipe asset type as the third admin-override surface).

What's not in this release

  • Full Vault integration for secrets management — multi-day infra; deferred to v0.0.50+.
  • Pilot LICENSE_SIGN_KEY rotation — runbook only this release; key rotation pending.
  • Service-user split as default — v0.0.49 ships it as opt-in only. Existing installs keep running under root until you call honeyframe migrate-to-svc-user --yes.
  • Layout's Python recipe list nav migration to a slug-aware URL — would let the legacy non-slug /recipes/<type> routes retire. Deferred follow-up.