Skip to content

Releases: AlfaCode-Team/hkm-kernel

v1.17.0

Choose a tag to compare

@github-actions github-actions released this 17 Sep 20:07
Immutable release. Only release title and notes can be modified.
502ef6f

Added

  • Plugins and plugin_installed() — is a module actually installed in
    this application? Answered from the compiled service-manifest.php, whose
    keys are the solves domains of everything the project passed to
    withModules([...]), so it needs no knowledge of any particular plugin and no
    directory scan. plugin_installed('tenancy.routing') takes a domain or a
    module.json name, and several at once; installed_plugins() returns the
    whole map; Plugins::ensure(...) throws a KernelException naming what is
    missing AND what IS registered, because the usual cause is a domain spelled
    differently from the plugin's solves rather than a plugin that is really
    absent.

    A module that cannot work without another one should still declare it in its
    own module.json requires[]CompileServiceManifestStage fails the BOOT
    there, which an operator sees at deploy time instead of when a request happens
    to reach the feature. This is for what requires[] cannot cover: an OPTIONAL
    integration that should light up when a plugin is present and degrade quietly
    when it is not, and code running outside the module graph — a standalone CLI
    entry point, a bootstrap file, a template — which has no module.json to
    declare anything in. An absent plugin is therefore a false, never a throw,
    unless ensure() is the call.

What's Changed

🔄 Other Changes

  • chore(homebrew): point the formula at v1.16.0 by @hakeemRash in #174
  • release: v1.17.0 — ask whether a plugin is installed without guessing by @hakeemRash in #175

Full Changelog: v1.16.0...v1.17.0

v1.16.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 18:47
Immutable release. Only release title and notes can be modified.
3e76a5a

Added

  • RangeReadableStorage — an optional interface a StoragePort implements
    to report an object's size() and open part of it with readRange().
    readStream() was the only read the port declared, and on S3 it returns a
    network body that can be neither stat'ed nor seeked, so a consumer honouring
    Range (a PDF reader paging a large document) served ranges on the local
    driver and silently sent whole objects on S3. Checked with instanceof, like
    DriverAware, so existing StoragePort fakes need no change; a consumer
    falls back to readStream() when the bound port does not implement it.

What's Changed

🔄 Other Changes

  • chore(homebrew): point the formula at v1.15.0 by @hakeemRash in #171
  • release: v1.16.0 — RangeReadableStorage, so storage consumers can serve ranges on S3 by @hakeemRash in #172

Full Changelog: v1.15.0...v1.16.0

v1.15.0

Choose a tag to compare

@github-actions github-actions released this 14 Sep 18:10
Immutable release. Only release title and notes can be modified.
189299e

Added

  • Scheduled tasks, declared in module.json. A module lists schedule[]
    entries — a name, a cron expression or alias such as @hourly in at, and
    EITHER a job or a command, with optional queue, timezone and
    withoutOverlapping. CompileScheduleManifestStage parses every expression
    at boot, so an impossible schedule ("0 25 * * *") fails the boot naming the
    field instead of silently never running; naming both a job and a command, or
    neither, fails it too. One crontab line drives the application:
    hkm cli schedule:run every minute dispatches whatever is due — a job is
    pushed onto the queue, so it gets the worker's retries and timeout, and a
    command runs through the CLI pipeline exactly as if typed. schedule:list
    shows what is declared. withoutOverlapping takes a CachePort lock keyed by
    task name, which is what makes the scheduler safe on several servers at once;
    with no cache bound, a guarded task is skipped and logged rather than run
    unguarded.
  • MetricsPort and TracerPort, and an ObservabilityStage on every
    request.
    The kernel already propagated a correlation id; it had no seam for
    the aggregate numbers or for where a request's time went. Both ports are
    StatsD/OpenTelemetry-shaped and dependency-free. The stage sits inside
    CorrelationIdStage and outside security and routing, so a denied request and
    an unmatched one (labelled unmatched) are still measured, and it labels by
    route TEMPLATE, never by path, so a metric cannot mint one series per id. With
    neither port bound, shared no-op implementations cost one hrtime() pair per
    request. An adapter must not throw.
  • DriverAware — an optional interface a DatabasePort implements to name
    the SQL dialect behind it. Checked with instanceof, like RequestAware, so
    the many in-memory test fakes of the port need no change; a consumer falls
    back to configuration when the bound port does not implement it.
  • hkm ppkg — the native package manager. The Composer-compatible package
    manager from modules/hkm-ppkg, now a submodule, runs under the launcher:
    install, update, require, remove, autoload, audit, outdated and
    the rest, writing the same composer.lock and vendor/ Composer does.
    Composer PLUGINS are not run; hkm ppkg compat names each one a project uses
    and what it would have done. hkm ppkg test-env <dir> builds a plugin's test
    vendor/ from the kernel's own and the checkouts already on disk.
  • A feature test bench. tests/Feature boots a real kernel and pushes real
    Requests through the real HttpPipeline — manifests, matcher, dependency
    graph, scoped containers and stages all real, only the database, cache and
    queue faked — and runs as its own Feature suite. It exists for the class of
    defect a unit test cannot see: a controller signature a stage no longer
    matches, or a route that resolves to nothing.

Changed

  • The route compiler is split into Kernel\Boot\Routing.
    CompileRouteManifestStage was 1,200 lines doing six jobs and could only be
    tested by compiling a whole manifest. The jobs now live in RouteNormalizer,
    DomainComposer, GroupExpander, RouteCompiler and RoutePolicy, each
    tested on its own, and the stage keeps only their order. The public shape of
    route-manifest.php is unchanged; each dynamic route in route-index.php now
    also carries its route key, which is what lets a metric be labelled by
    template. An index compiled by an older kernel still serves.
  • A fetched plugin is verified without Composer when that is possible.
    hkm plugins install used to run composer install inside every plugin to
    reach phpunit — a dependency resolution across a few dozen vcs repositories,
    throttled by GitHub's anonymous rate limit. It now first builds the test
    environment from the kernel's own vendor/ plus the sibling checkouts on
    disk, and falls back to Composer whenever that cannot be trusted: no phpunit,
    or a test dependency with no checkout. HKM_PLUGIN_TESTS_COMPOSER=1 forces the
    Composer route.
  • RichGraph articles take inLanguage — the language of the CONTENT,
    which can differ from the page's — and an article with no authorName names
    the publishing Organization as its author, as Google recommends for content
    that is contributed rather than bylined.

Fixed

  • Error responses had no correlation id. ErrorStage wrapped
    CorrelationIdStage, so the request it caught an exception on was the one from
    before the id was attached: every error envelope's requestId was empty,
    every notifier received an empty correlation id, and error responses carried
    no X-Correlation-ID header — the id was on every response except the ones
    anyone would need it for. The two stages now run the other way round.
  • hkm plugins update overwrote files a project had edited. It replaced
    every published file whose bytes differed from the plugin's copy, with no way
    to tell a project's customisation from a stale copy — a customised layout, view
    override or translation was lost on the next update — and when two plugins
    published the same path, each update overwrote the other's. The installer now
    records a SHA-256 of every file it publishes in var/plugin-assets.json. A
    file still exactly as published is refreshed; one that was edited, never
    recorded, or written by another plugin is KEPT, and the plugin's version is
    written beside it as <file>.plugin-new, listed with the other plugin named.
    --overwrite restores the old behaviour for one run, and enable never
    overwrites. A manifest written before this release has no hashes, so its first
    update keeps every file that differs rather than guessing.
  • Every page head carried two <title> elements and two descriptions when
    SeoHead embedded a SiteSEO Open Graph document, which renders its own. Only
    that document's og: and twitter: tags are kept.
  • Ctrl+C left the Vite dev server running. The frontend template's hot-file
    cleanup installed SIGINT/SIGHUP listeners, and Node removes its default
    terminate the moment one exists — so the shell got its prompt back while the
    orphaned server kept the port, and its readline failed with Error: read EIO
    on top of the next command typed. The handler now cleans up, restores the
    default and re-raises, and registers once per process across dev-server
    restarts.

Security

  • composer/composer 2.10.2 → 2.10.3 in the kernel's development
    dependencies, for CVE-2026-84361 — arbitrary command execution through a
    malicious package's Perforce source URL. It was never in a release bundle,
    which installs without development dependencies, but composer audit failed
    every build until it moved.

Removed

  • templates/app/apache.conf.example and templates/app/nginx.conf.example,
    and hkm new no longer writes them into a new project. The nginx or Apache
    configuration a deployment runs is generated by the Edge plugin
    (hkm edge:apply).

What's Changed

🔄 Other Changes

  • chore(homebrew): point the formula at v1.14.0 by @hakeemRash in #168
  • release: v1.15.0 — scheduled tasks, observability ports, hkm ppkg, and edited plugin files are kept by @hakeemRash in #169

Full Changelog: v1.14.0...v1.15.0

v1.14.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 03:51
Immutable release. Only release title and notes can be modified.
d888179

Added

  • hkm service — run a project's queue worker as a supervised service.
    hkm worker --queue=mails is a foreground process: it dies with the terminal,
    it does not come back after a crash or a reboot, and nothing collects its
    output. Every deployment therefore hand-wrote the same unit file. The command
    generates it for whichever supervisor the host runs — systemd or launchd, with
    --platform to override so a Mac can produce the Linux unit it will deploy —
    in four verbs: preview (the default, which writes nothing), write,
    install [--start] and remove. Scope is --system or --user, defaulting
    to system on Linux and to a user agent on macOS, where a LaunchDaemon running
    as root is the wrong answer on a developer machine. --dry-run (-n) reports
    every write and every command for the three mutating verbs and performs none
    of them.

    Three things the generated unit gets right that a hand-written one usually
    does not:

    • ExecStart runs the LAUNCHERhkm worker -p <root>, not php plus an
      absolute vendor/autoload.php. The launcher self-locates the kernel, so a
      kernel upgrade that moves a version-stamped install directory cannot
      silently break the queue. --exec=php emits the direct form for a server
      with no launcher installed, and states the pinned autoload's cost in the
      unit itself.
    • TimeoutStopSec / ExitTimeOut is 90s. The worker traps SIGTERM and
      finishes the job in flight before exiting — that is what makes a redeploy
      safe — and launchd's 20s default SIGKILLs it mid-transaction instead.
    • PATH and HKM_PHP_BIN are pinned. A service inherits none of a login
      shell's PATH, and /opt/homebrew/bin is on neither manager's default. The
      entire diagnostic without the pin is error: FileNotFound, with nothing
      anywhere naming php. Found by running the generated unit, not by reading it.

    Values reaching the unit are validated rather than interpolated: a queue name
    may hold only [A-Za-z0-9._:-], so nothing can append an argument or a
    directive; ExecStart tokens containing whitespace are quoted; plist strings
    are XML-escaped.

Fixed

  • The worker entry point ignored every command-line flag. hkm worker and
    hkm run --worker forward their arguments verbatim to app/worker/run.php,
    which read only WORKER_QUEUE from the environment — so
    hkm worker --queue=mails was accepted in silence and drained default
    instead. That is the failure mode with no signal at all: no error, no warning,
    a running worker, and the wrong queue. The entry point (and the scaffolding
    template new projects get) now parses -q/--queue, -n/--max-iterations,
    --memory and -h/--help, each overriding the matching environment variable,
    and rejects an argument it does not recognise rather than ignoring it. The
    environment fallbacks moved from getenv() to env() at the same time: the
    loader injects .env into $_ENV and deliberately skips putenv(), so
    getenv('WORKER_QUEUE') could not see a value set in the project's .env.

What's Changed

🔄 Other Changes

  • chore(homebrew): point the formula at v1.13.1 by @hakeemRash in #165
  • release: v1.14.0 — the worker takes its flags, and can be run as a service by @hakeemRash in #166

Full Changelog: v1.13.1...v1.14.0

v1.13.1

Choose a tag to compare

@github-actions github-actions released this 03 Sep 21:26
Immutable release. Only release title and notes can be modified.
51cb438

Changed

  • EventBus::dispatch() now returns the listener failures it isolated
    (array<class-string, \Throwable>, empty on full success). Isolation was
    right — one broken subscriber must not stop the others — but callers had no
    way to tell it apart from success, and one of them was a transactional
    outbox. A mis-scoped listener threw, the bus swallowed it exactly as designed,
    the outbox marked the row dispatched because dispatch() had returned
    normally, and the row was consumed and never retried: a tenant membership was
    lost permanently while the table recorded status=1, attempts=1, last_error=NULL. Adding the value is backward compatible — every existing
    $bus->dispatch($e); ignores it and behaves exactly as before — but anything
    that RECORDS delivery should now check it and re-queue rather than consume.

Fixed

  • A listener that could not be resolved was reported as a broken constructor.
    resolveListener() caught every container failure and fell back to
    new $listenerClass(). For a listener with constructor arguments that threw
    ArgumentCountError, so a bindInternal() binding — which the container had
    already refused with a ScopeViolationException naming the scope, the class
    and the fix — was logged as "Too few arguments to function
    …::__construct()". Every reader then went to the listener's constructor,
    which was correct, instead of to the binding, which was not; the same shape
    had already been misdiagnosed twice before. new is now attempted only when
    it can actually succeed (a constructor with no required parameters);
    otherwise the container's own exception is rethrown untouched.

What's Changed

🔄 Other Changes

  • chore(homebrew): point the formula at v1.13.0 by @hakeemRash in #162
  • fix(events): stop a failed dispatch from looking like a delivered one by @hakeemRash in #163

Full Changelog: v1.13.0...v1.13.1

v1.13.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 23:46
Immutable release. Only release title and notes can be modified.
30bc339

Fixed

  • hkm install --owner= left every plugin file owned by the deploying user.
    A project's plugins are not in the project: hkm plugins install keeps one
    copy per (plugin, version, origin) in the global store and links the project
    at it, so plugins/Logger is a symlink out of the tree. Both halves of the
    hardening pass stopped at that boundary by design — hardenTree skips
    symlinks because a chmod would follow one and rewrite a target outside the
    project, and the chown only walked the project root. The result was a project
    that verified clean and could not serve: every file the pool has to read
    first, every Provider and every controller a route resolves to, still belonged
    to whoever ran the command, under a report that said Project owned by deploy:www-data. --owner now also chowns the store versions the project
    links to, plus the directories between them and the store root so the trees it
    just chowned can be reached. Only the versions THIS project links to: the store
    is shared by every project on the machine, and claiming all of it for one
    project's web account is not that command's call.
  • --production reported a reachable project while the plugins were
    unreachable.
    The traversal check walked the parents of the project root only.
    Since the store moved out of the project it defaults to $HOME/.cache, which a
    deploy under sudo resolves to /root/.cache — 0700 on every mainstream distro
    — so the chown succeeded on every entry and the site still could not read one
    of them. The check now covers the store's own parents, with its own remedy:
    relocate the store (hkm plugins store --set=, or HKM_PLUGIN_STORE) rather
    than widen a home directory to reach a cache.
  • A plugin that gained an env var never got it. hkm plugins enable returns
    early when the plugin and its dependencies are already wired, so a plugin
    declaring a new config[] entry in a later version left an .env block that
    was now incomplete — and the boot failed on the missing key with nothing
    pointing at the cause. Enabling an already-enabled plugin now tops up its
    block. Safe by construction: the seeder only ever ADDS keys the file does not
    already mention, in any form, so a real secret is never rewritten.
  • .env.example documented the Tenancy control-plane switch as a hostname.
    TENANCY_CONTROL_PLANE=admin.example.com reads as "the control plane lives
    here"; the plugin declares the key as type: bool, where any non-empty string
    is truthy — so the example value silently turned tenant routing OFF for anyone
    who uncommented it. Corrected to a bool, with TENANCY_CENTRAL_DOMAINS (a
    real declared key that was missing) added beside it and the mode values named.
    The plugin's own module.json stays the authority; this is the example
    catching up to it.
  • Re-seeding wrote a second block for the same plugin. The append was
    unconditional, so a plugin seeded twice got two # ─── Auth ─── headings, and
    three after that. Every key was still present exactly once, so nothing broke —
    the grouping the block exists to provide just quietly stopped being true. New
    keys are now merged into the block the plugin already owns, keeping the blank
    line that separates it from the next one.

Added

  • hkm env — audit and tidy a project's .env. A dotenv file accumulates:
    a plugin seeds its block on enable, someone appends a key at the bottom to try
    something, a second plugin declares a variable the first one already did. None
    of that is an error anywhere. The loader resolves a repeated key silently, the
    boot succeeds, and the value in effect is whichever line happens to be last —
    a file that works and does not say what it is doing.
    • hkm env reports duplicates with every occurrence's line number and marks
      which one is live. That marker is the point: LoadEnvironment::setVar
      overwrites on each call and the cascade reads a file top to bottom, so the
      LAST active assignment wins — the opposite of what most people assume when
      they append a key to the bottom of a .env.
    • hkm env dedupe asks per key rather than choosing. The right survivor is
      not derivable: DB_HOST=localhost on line 12 and DB_HOST=10.0.0.4 on line
      88 are both plausible, and the one in effect is as likely to be the accident
      as the intent. --keep=effective is the scriptable form that cannot change
      behaviour; --keep=first / --keep=last are positional.
    • hkm env group reorders the file into blocks: a key a plugin declares in its
      module.json config[] goes under that plugin, otherwise under the feature
      its prefix names, otherwise Ungrouped. Comments attached to a key move with
      it, comments attached to nothing are rescued into a Notes block rather than
      dropped, and the pass refuses to write unless every key AND every
      informational comment that went in comes out again.
    • Every write leaves the previous file beside it as .env.bak, at 0600.
  • A project is found from anywhere inside it. resolveRoot checked the exact
    working directory, so hkm env in <project>/app answered "'.' is neither a
    project folder (with proj.json) nor a registered name" about a project one
    directory up. It now walks up to the filesystem root, the way git, composer and
    npm all find theirs — for every command that takes a [path|name], not just
    env. An EXPLICIT path stays exact: the same resolver backs
    hkm install --owner, and a command that chowns a tree must never quietly
    retarget itself above where it was pointed.

What's Changed

🔄 Other Changes

  • chore(homebrew): point the formula at v1.12.0 by @hakeemRash in #159
  • chore(homebrew): point the formula at v1.12.1 by @hakeemRash in #158
  • release: v1.13.0 — the account that serves a project can read its plugins, and .env can be audited by @hakeemRash in #160

Full Changelog: v1.12.1...v1.13.0

v1.12.1

Choose a tag to compare

@github-actions github-actions released this 02 Sep 15:51
Immutable release. Only release title and notes can be modified.
68f8fd9

Fixed

  • A plugin fetch asked for a GitHub account, for a repo that is public.
    FileManager was the one hyphenated plugin missing from the slug override
    table, so it resolved to hkm-plugin-filemanager — a repository that does not
    exist. GitHub answers 404 for "does not exist" and "not yours" alike; it
    will not confirm a private repo to an anonymous request. Git cannot tell those
    apart, assumed the second, and stopped to ask for a username and password that
    no account could have satisfied. Added the override, plus tests pinning every
    multi-word folder to its real hyphenated slug (and round-tripping back to the
    PSR-4 folder name) so the next repo added with a hyphen cannot drift the same
    way.
  • Git could block a deploy on a credential prompt. The plugin fetch inherited
    the terminal, so an unreachable remote hung hkm install on a password box
    until somebody killed it — on a deploy box or in CI, indefinitely. Every git
    invocation now runs with GIT_TERMINAL_PROMPT=0 and SSH BatchMode=yes: a bad
    remote fails immediately and the call site names the plugin and URL, which is
    the information actually needed. HKM_GIT_INTERACTIVE=1 restores the prompt
    for a genuinely private remote you intend to authenticate against by hand.

What's Changed

🔄 Other Changes

  • release: v1.12.1 — a public plugin repo no longer asks for a GitHub account by @hakeemRash in #156

Full Changelog: v1.12.0...v1.12.1

v1.12.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 15:15
Immutable release. Only release title and notes can be modified.
62b76df

Fixed

  • A project installed with --production --owner= still could not be served
    by PHP-FPM.
    The pass only ever touched var/ and userdata/, so every
    directory a request actually reads — app/public_html, src/, vendor/,
    plugins/ — kept the deploying user's ownership and whatever mode the clone
    arrived with. The pool could write logs it was never going to reach the code
    to produce. Three separate reasons a boot failed, each fixed:

    • the pass now covers the WHOLE project tree, and runs LAST — after
      composer install and the plugin fetch, both of which create vendor/ and
      plugins/ as whoever ran the command. Running at step 3, as it did, meant
      the two largest directories in the project were created after the
      permissions were "fixed".
    • .env was chmod'd 0600. PHP-FPM running as another account cannot read
      APP_KEY through that, and the boot fails on a file whose mode bits look
      deliberate. It is now 0640 — group-readable, never group-writable, never
      world-anything.
    • nothing reported that the pool could not TRAVERSE to the project. Reaching
      app/public_html/index.php needs execute on every parent directory, and a
      home directory is 0700 on a stock Debian install — unfixable from inside
      the project, so the offending parents are now named (reported only, never
      changed).
  • The installed kernel was left at whatever the installing account's umask
    produced
    (tools/install.sh). /opt/hkm-kernel is shared infrastructure —
    every PHP-FPM pool on the box loads its PHP out of that one tree, and none of
    those pools runs as the account that installed it. With umask 027 or 077
    the whole tree landed 0750/0700 and every site died with "Permission denied"
    on a kernel file, while the install reported success because the installer
    could obviously read what it had just written. The installer now normalises
    the tree it lays down: directories traversable, files readable, and anything
    that WAS executable still executable.

Changed

  • hkm install --production / --owner= now apply a split-ownership model:
    code owned by the deploy user and only READABLE through the web server's
    group (2750/0640), var/ and userdata/ group-writable (2770/0660).
    EVERY directory carries setgid, code included: the group is the only thing
    granting the pool access, so a file created later — a log written at 3am, a
    file a git pull lands — would otherwise take the creating account's primary
    group and drop out of the share, and each deploy would silently un-share
    whatever it touched.
    Code is never group-writable in either profile — an FPM pool that can rewrite
    the PHP it executes turns any file-write bug into code execution. An
    already-executable file keeps its exec bit (re-granted only where the profile
    grants read, so bin/psp and vendor/bin/* survive at 0750, not 0751),
    and .git is skipped by both the chown and the chmod.
  • The pass re-stats what it changed and reports any mode the filesystem
    refused, instead of reporting success for a chmod the kernel rejected.

What's Changed

🔄 Other Changes

  • chore(homebrew): point the formula at v1.10.0 by @hakeemRash in #152
  • chore(homebrew): point the formula at v1.10.1 by @hakeemRash in #153
  • chore(homebrew): point the formula at v1.11.0 by @hakeemRash in #151
  • release: v1.12.0 — a deployed project is readable by the account that serves it by @hakeemRash in #154

Full Changelog: v1.11.0...v1.12.0

v1.11.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 09:45
Immutable release. Only release title and notes can be modified.
7e20679

Fixed

  • The migration engine only worked on MySQL (modules/let-migrate). A
    commit titled "refactor: remove deprecated methods" had restored src/,
    tests/ and the README byte-for-byte to their state before a day of merged
    PR work — undoing four driver fixes and a Laravel-parity alias, and deleting
    the eight tests that covered them. Nothing was failing that the deletion
    fixed. Restored and carried forward:
    • ALTER TABLE compiled MySQL syntax for every driver — additions batched
      into one comma-separated statement, indexes added with ADD KEY, and drops
      running columns BEFORE the indexes over them. A rollback written in the
      correct order was reordered by the compiler into one that could not run
      anywhere but MySQL, in the one direction nobody exercises until they
      uninstall a plugin.
    • PostgreSQL rejected BOOLEAN DEFAULT 1, and modifyColumn() emitted three
      ;-joined statements into a clause the extended query protocol refuses.
    • SQLite could not add a foreign key to an existing table, and modifyColumn()
      compiled CREATE TABLE "__tmp_users" () — it was broken outright, because
      the rebuild SQLite requires was driven from a blueprint holding only the
      delta. It now reconstructs the full table from the SchemaInspector,
      carrying existing indexes across and unwrapping defaults so a literal is
      not re-quoted on every rebuild.
    • Seeding a second database in one run died with Cannot redeclare class
      reachable the moment one run seeds once per driver, which is what
      hkm ground migrate does.
  • PostgreSQL: every schema lookup silently matched nothing. The driver and
    inspector used libpq's $1 placeholders, which PDO neither understands nor
    rejects — so tableExists() answered false for a table with seven columns,
    and the inspector reported no columns, indexes or foreign keys. Anything
    guarded by hasTable(), and everything built on schema diffing or dumping,
    was quietly wrong on that driver. Found only by executing against a live
    server.
  • SQL Server emitted invalid T-SQL for every column addition and every
    foreign key
    ALTER TABLE … ADD COLUMN (T-SQL has no COLUMN keyword
    there) and ON DELETE RESTRICT (unimplemented; its actions are NO ACTION,
    CASCADE, SET NULL, SET DEFAULT). Both are argued from the T-SQL
    specification and are not verified against a live server — none was
    reachable — but each replaces SQL the server rejects outright.
  • MigrationConfig: singular path overrode plural paths instead of
    acting as its fallback, so a config carrying both silently ran one directory
    and ignored the array — failing by doing less work rather than by erroring.
  • Blueprint::dropColumn() accepted one column, so dropColumn('a', 'b')
    silently dropped only a. Now variadic, and the drop* methods chain.

Added

  • useCurrent() / useCurrentOnUpdate() / bigIncrements() — Laravel
    parity, so a ported migration compiles unchanged. Without them the failure is
    a fatal Call to undefined method raised the moment the migration runs,
    during a deploy.
  • StatusRenderermigrate:status as normalised data, aligned table
    lines and JSON from one source, so the human and --json views cannot
    disagree.
  • tests/Live — the migration compiler executed against every reachable
    engine.
    Configured with LETMIGRATE_DB_MYSQL / _PGSQL / _SQLSRV
    (GROUND_DB_* honoured); each run uses its own scratch database and drops it.
    A driver that is unconfigured or not answering SKIPS with the reason, never
    counted as a pass. This release was verified on SQLite, MariaDB 12.3 and
    PostgreSQL 18; SQL Server skipped, and says so.

Changed

  • docs/guides/18_MIGRATIONS.md->useCurrent() and ->useCurrentOnUpdate()
    now exist, so the anti-pattern entry saying they do not is corrected. The
    ->index() half stands: an index is declared on the Blueprint, not the
    column.

What's Changed

🔄 Other Changes

New Contributors

Full Changelog: v1.10.1...v1.11.0

v1.10.1

Choose a tag to compare

@github-actions github-actions released this 01 Sep 12:17
Immutable release. Only release title and notes can be modified.
888cdd8

Fixed

  • hkm ground init generated a CI workflow that called a binary nothing
    installs.
    The generated .github/workflows/ground.yml ran
    vendor/bin/hkm-ground check --here and vendor/bin/hkm-ground migrate --strict, but this package declares "bin": ["bin/hkm-cli", "modules/ground/bin/ground"] — so what a plugin actually gets in its
    vendor/bin is ground. No package has ever shipped a hkm-ground
    executable, and alfacode-team/ground is not published separately at all: it
    is a path repo inside this repository, reachable only because the kernel
    autoloads it.

    So every plugin that ran ground init got a workflow whose first step could
    only ever exit 127, No such file or directory — and it failed at the step
    AFTER composer install succeeded, which reads like the plugin is broken
    rather than like the workflow named the wrong file. Five plugin repositories
    had already been scaffolded with it.

    The generator now emits vendor/bin/ground. Existing checkouts need the two
    lines changed by hand or ground init re-run; nothing else in the workflow
    moves.

What's Changed

🔄 Other Changes

  • release: v1.10.1 — ground init generates a workflow that calls a real binary by @hakeemRash in #144

Full Changelog: v1.10.0...v1.10.1