Commit Graph
20 Commits
Author SHA1 Message Date
mr.zeroandClaude Opus 5 06030c43cd The dummy schema still stamped the old migration version
CI builds the engine's test database from spec/dummy/db/schema.rb, and that file
still said `version: 2026_08_19_000001`. Loading it marks every migration up to
that number as applied — which no longer includes device_grants at
20260819093412 — so rspec aborted with a pending migration before running
anything, and both the push and the tag pipeline failed.

It passed on my machine because I had renumbered the row in the live test
database by hand and never re-dumped the schema. Verified this time the way CI
does it: dropped the database, `rake app:db:test:prepare` from schema.rb, 227
examples green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 11:39:45 +02:00
mr.zeroandClaude Opus 5 4eed0aaee9 warp_engine 0.5.1: the device_grants migration collided with the host's
`db:migrate` on the catalog API stopped before running anything:

    Multiple migrations have the version number 20260819000001.

The engine appends its `db/migrate` to the host's migration paths instead of
copying migrations in, so engine and host share one version namespace. Both had
picked 20260819000001 on the same day by the same habit — the engine for
device_grants, apps/api for carry_the_store_config_in_the_registry — and neither
repository could see the other's number.

Worse than a clash of our own making: it takes the *host's* migrations down with
it, for the whole application, before anything runs.

device_grants is renumbered to 20260819093412 — a real second-resolution
timestamp, which is the actual defence. A round hand-written number is precisely
what another repository lands on. Nothing had run it in production, so this is a
rename rather than a data migration; a host that already applied the old version
renumbers its schema_migrations row.

The older engine migrations keep their round numbers: renumbering one that has
been deployed everywhere is worse than the risk it carries. They are named in the
new spec's grandfather list rather than excused by a rule that would also let the
next one through.

That spec found a second thing, older than this change: the install template
creates `application_tokens`, and so does one of our own migrations — so a fresh
host runs CREATE TABLE twice and has to delete the block from its generated copy
by hand, which is exactly what teletype-orbit's migration header describes. I had
just made it worse by putting device_grants in the template too; that is out
again, and the template says why. `application_tokens` is grandfathered and left
for a change that is not a hotfix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 11:29:53 +02:00
mr.zeroandClaude Opus 5 76427bab91 WarpEngine: let the host say who a browser is
The access policy asks who the caller is, and until now only a bearer token
could answer. That is what a desktop client carries — but a person clicking a
download link on the site carries a session instead, and the engine has no idea
what a session is. So a host that gated its catalog found its own signed-in
visitors refused at /api/download, which is a regression the shadowed route used
to hide.

c.subject_resolver is a callable taking the Rack request and returning the
host's subject: `->(request) { request.env["warden"]&.user }` for a Devise app.
Unset — every deployment today — a non-bearer request stays anonymous, exactly
as before. A resolver that raises is logged and treated as anonymous, because a
broken one turning every read into a 500 is worse than an anonymous request.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 10:36:30 +02:00
mr.zeroandClaude Opus 5 6c8b026590 WarpEngine 0.5.0: a catalog that can say a title is not yours
A desktop client reading /api/software had no way to learn that a title costs
money. There was nothing in the response to say so, no way to sign in, and no
way to be told "you do not own this" — so a store with paid titles could only
hand the client a 403 at download time and let it guess why.

The fix belongs here rather than in the client. A client serves more than one
store, so anything it knows about a particular one has to arrive from that
store's own API; a rule compiled into the client is a rule that breaks every
other catalog it reads. Three seams, each following the storage adapter's
shape — documented contract, default that is byte for byte the old behaviour,
one config key to replace it:

- **access policy** — visible_software_scope / access_for / authorize_download.
  Every catalog entry now carries an `access` block (gated, entitled, price,
  purchaseUrl, webUrl) and both /api/download and /file/* ask before serving.
  The vocabulary is deliberately generic: a word from one host's domain would
  make every client that reads it specific to that host.
- **client sign-in** — the device authorization grant (RFC 8628), over the
  host's own user model. The approval page stays the host's, because approving
  needs a session and HTML. Tokens are ApplicationTokens with a `catalog`
  scope, so publishing and reading stay separable.
- **service descriptor** — GET /api/service says what this deployment is and
  whether it has a sign-in at all, which is how a client stops guessing.

With no policy and no subject class configured — every deployment today — the
API is unchanged: /api/auth/* answers 404, /api/service reports auth: null, and
the 187 pre-existing examples pass untouched.

A policy that raises is treated as a refusal, not permission. An artifact
served because the gatekeeper crashed is the one failure mode this must not
have, so a broken policy empties the catalog and denies the download.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 10:26:28 +02:00
mr.zeroandClaude Opus 5 ccf38bd9fd Do not let a response header take the API down
The version header was set from WarpEngine::VERSION_HEADER, a constant introduced in the
same commit. The deploy that followed ran these controllers with an older `lib/`, so the
before_action raised NameError on every request and every engine endpoint answered 500 —
the catalog, the images, the file server and the config extension Woodpecker calls, which
is how it surfaced: a pipeline could no longer fetch its own configuration.

The controller now spells the header name out. A response header is not worth a dependency
that can take the API down when one half of a deploy is older than the other, and the
constant remains the documented name with a spec holding the two in step.

Confirmed in production mode against the same code that failed: 200 with the header.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 20:20:39 +02:00
mr.zeroandClaude Opus 5 b652615e32 A version header on every WarpEngine response, and a movable pipeline link
Every WarpEngine API response now carries `WarpEngine-Version`, so a client can branch on
the engine's age without a round trip to ask. Set in a before_action rather than after:
`rescue_from` never reaches an after_action, and a client needs the version most when
something came back wrong. The name lives in `WarpEngine::VERSION_HEADER`. The host's own
endpoints — the store registry — do not carry it, because they are not the engine.

A software has one pipeline, and the newest assignment now wins. Two pipelines pointing at
the same software was not an error the database caught; it was a link that silently did
nothing, with the software still showing whichever row came first. Assigning a software
another pipeline holds therefore moves it, the admin says which pipeline it was taken
from, and `Pipeline#software_taken_from` carries that for anything else that cares.
Deliberately a callback and not a unique index: rows here are soft-deleted, and a unique
index counts deleted rows, so a pipeline removed last year would block its software from
ever being linked again.

The engine is 0.4.0. The site's /stores page and its screenshot follow the client's new
name, and the shot is a fresh one showing the greyed-out titles the client now lists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 19:46:29 +02:00
mr.zero 03a51a5b30 warp_engine 0.2.0: pluggable storage adapter and publish notifications
Two seams the hosts needed, both backward compatible.

Storage: artifacts are served through WarpEngine::Storage.adapter instead of
raw filesystem calls. The default :local adapter keeps the previous behaviour
byte for byte, including the path traversal guard. A host can now set
config.storage_adapter to any object answering file?/directory?/locate and
serve builds from an object store - FileService and /api/download both honour
a Location.redirect, so a signing adapter turns them into redirects.
DownloadService#create still returns an absolute path (nil when missing) for
existing callers; #locate is the new entry point that can also return a
redirect. Ingestion (upload, extraction, file manager) stays local for now.

Publish: PublishService emits ActiveSupport::Notifications
("warp_engine.publish") with platform/name/version/software/release, so hosts
can react to a new build without hanging callbacks on the models.
WarpEngine.instruments_publish? lets a host feature-detect and keep its
fallback for older engine versions.
2026-08-10 11:16:17 +02:00
mr.zero 2a94fc785f warp_engine: CiRepository -> Pipeline rename everywhere, ci_ service prefixes dropped, unknown platform allowed 2026-08-06 19:46:06 +02:00
mr.zero 161f923dc8 fixes 2026-08-06 19:23:04 +02:00
mr.zero 91c31e05bc pipeline fix 2026-08-06 16:04:50 +02:00
mr.zero 867f71f7c0 woodpecker integration 2026-08-06 13:58:58 +02:00
mr.zero 265e1092a1 platform files refact 2026-08-06 10:00:34 +02:00
mr.zero 1781b62f07 Verify RFC 9421 signatures on the Woodpecker config endpoint 2026-08-06 07:24:47 +02:00
mr.zero 57e47a15a0 Translate code comments and admin strings to English 2026-08-06 01:25:53 +02:00
mr.zero 1c7498ca4a Serve Woodpecker pipeline configs from the engine (/build/config) 2026-08-06 01:14:22 +02:00
mr.zero 4e2c45dc45 build endpoints 2026-08-05 20:12:35 +02:00
mr.zero 1e974bdcc9 Rename the update auth switch to application_token_source and drop its ENV default 2026-08-05 18:53:04 +02:00
mr.zero 710594efda Add DB-backed application tokens for the update endpoint 2026-08-05 18:30:20 +02:00
mr.zeroandClaude Fable 5 2b2a9df136 Phase 4: move catalog controllers and routes into WarpEngine
- update, files and the 6 /api catalog controllers now live in the engine on
  a new WarpEngine::ApiController base (same rescue/mime behavior as host)
- engine routes serve /update, /file/*path, /api/software*, /api/builds*,
  /api/image/:id, /api/download at unchanged public paths via the root mount;
  host routes keep only TTG endpoints (events, members, wiki, rss, swagger)
- /update secret comes from WarpEngine.config.update_secret and an
  unconfigured secret now rejects every request (previously an empty
  UPDATE_SECRET env accepted empty secrets)
- apipie-rails is an engine dependency (DSL in engine controllers); dummy app
  configures apipie with validation off, mirroring the host
- engine request specs: catalog controller specs moved from host plus new
  /update auth contract spec

Verified: engine suite 53 green, host suite 6 green, /api/software and
/api/builds byte-identical to baselines, /update 401/400 behavior intact,
admin and TTG endpoints OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:09:11 +02:00
mr.zeroandClaude Fable 5 ca25f72c76 Phase 3: move service layer, serializers and DTOs into WarpEngine + dummy-app test suite
- all catalog services (update/software/highlighted/builds/file/file-manager/
  download/image + SoftwareResponseBuilder), the SoftwareUpdater platform
  services and their concerns, Blueprinter serializers (incl. TimestampFields)
  and the 4 DTOs now live in the engine under WarpEngine::
- constantize dispatch strings use absolute names
  (WarpEngine::SoftwareUpdater::<Platform>Service)
- container paths read from WarpEngine.config everywhere (FileService,
  DownloadService, FileManagerService, ArchiveExtraction, ReleaseSerializer
  path rewriting); FileManagerService base path is now lazy
- engine requires blueprinter itself; gemspec declares blueprinter + rubyzip
- engine test suite: spec/dummy app (mysql warp_engine_test, catalog-only
  schema), rails_helper with engine-local factories; catalog model/service
  specs and factories moved from the host
- host suite keeps TTG specs and loads catalog factories from the engine

Verified: engine suite 40 green, host suite 14 green, /api/software and
/api/builds byte-identical to baselines, admin OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:03:58 +02:00