Skip to content

Report failures and warnings to the server, tagged with which they are - #152

Merged
mastacontrola merged 3 commits into
masterfrom
report-imaging-failures
Aug 19, 2026
Merged

Report failures and warnings to the server, tagged with which they are#152
mastacontrola merged 3 commits into
masterfrom
report-imaging-failures

Conversation

@mastacontrola

@mastacontrola mastacontrola commented Aug 19, 2026

Copy link
Copy Markdown
Member

The FOS half of FOGProject/fogproject#1206. The server half is FOGProject/fogproject#1207 (merged), which adds the endpoint this posts to, and FOGProject/fogproject#1208, which gives a report somewhere to live and adds the type this now sends.

Neither side needs the other to be safe: an older FOS sends no type and the server records it as an error; a newer FOS talking to an older server gets a 404 it already ignores.

What was wrong

handleError() printed its banner and exited. It made no request to the server at all, so a real imaging failure — bad image, mount failure, partition error, any of the handleError call sites across fog.download, fog.upload, fog.mount and friends — was invisible to FOG. HOST_IMAGE_FAIL could not fire for it, and the notification plugins registered for that event (slack, ntfy, pushbullet) had never run on any server.

The only failure FOG ever heard about was a storage node problem, via fog.checkmountblame.php, and that re-queues the task on another node rather than failing it.

handleWarning() had the same hole and a worse one: it printed its banner, waited its minute and told nobody. A warning is the machine saying it hit something and carried on — a disk smaller than the image it is about to take, a partition table it had to work around — which is exactly what turns up later as "the deploy worked but the machine is wrong", with no record of the warning that predicted it.

What this does

Both handlers go through reportToServer(), which posts the message, the script that raised it, the identity FOS already sends everywhere else (mac, sysuuid), and which kind of report it is.

The server records either against the task and in its own log, and fires HOST_IMAGE_FAIL only for an error — announcing a failed deploy for a task that went on to succeed would be worse than saying nothing.

The field carrying the message is text rather than error, now that it is not always one. Nothing has shipped with the old name.

Best effort, on purpose

This must not change anything the person standing in front of the machine sees or waits for:

  • --max-time 5, so an unreachable server cannot add to the wait before the reboot countdown;
  • output to /dev/null and || :, so nothing prints and a non-zero exit cannot trip errexit;
  • skipped entirely when $web is unset (registration paths, debug shells);
  • a server that predates the endpoint answers 404 and nothing here notices.

printf '%b' first, so the \n the callers embed in their message becomes a real newline rather than the two literal characters. The server flattens control characters back to spaces at its end and bounds the length, so nothing here needs to.

-Lks matches every other call in this file — the FOG server's certificate is its own.

Verification

The endpoint side is verified live in fogproject#1207, including the exact string shape handleError produces:

Could not mount images folder (fog.mount)\n   Args Passed: --target /images

arrives as one flattened line, with the reason preserved and the script name appended.

This side has tests/checks/error-report.sh, nine cases following the tests/checks/sector-size.sh pattern — sandbox copy of the library, PATH-shadowed stubs, and a curl stub that records its argv instead of making a request:

PASS: reports to service/taskerror.php with mac, sysuuid, type and the message
PASS: the call is time bounded and its fields are encoded
PASS: message escapes are expanded before sending
PASS: the report is silent and the banner is unaffected
PASS: a failed report does not stop handleError finishing
PASS: no $web means no attempt
PASS: handleWarning reports, tagged warning
PASS: the warning banner and wait are unaffected
PASS: a failed warning report does not stop handleWarning finishing

9 passed, 0 failed

Mutation-verified — hardcoding type=error, dropping handleWarning's call, renaming the field back to error, dropping the $web guard, the output redirect, --max-time, the printf %b, or the whole block each fails the harness.

One caveat stated in the harness header rather than papered over: no FOS script sets errexit, so the || : is defensive and cannot be isolated by a case. Cases 5 and 9 pin the behaviour that matters instead — each handler still finishes when the report fails.

Built and run in a real FOS image

./build.sh -nfa x64, exit 0. The built rootfs carries both call sites and today's stamp:

$ debugfs -R "cat /usr/share/fog/lib/funcs.sh" fssourcex64/output/images/rootfs.ext2 \
    | grep -n 'initversion=\|reportToServer\|type=$type\|text=$report'
2:export initversion=20260819
1638:reportToServer() {
1650:        --data-urlencode "type=$type" \
1651:        --data-urlencode "text=$report" \
1670:    reportToServer error "$str"
1714:    reportToServer warning "$str"

The image was then extracted and run under podman (--rootfs, --network host) against a FOG server carrying fogproject#1208 — so the shipped handlers ran with the shipped bash and the shipped curl, nothing from the host:

init version : export initversion=20260819
bash         : GNU bash, version 5.2.37(1)-release (x86_64-buildroot-linux-gnu)
curl         : curl 8.19.0 (x86_64-buildroot-linux-gnu) libcurl/8.19.0 OpenSSL/3.6.2 zlib/1.3.2 zstd/1.5.7

What the server ended up with, from those two calls alone:

id   taskID  logType  logText
334  64      warning  Disk is smaller than the image was captured from (getDiskSize)    Args Passed: /dev/sda (drive.sh)
335  64      error    Could not mount images folder (fog.mount)    Args Passed: --target /images (drive.sh)
[06:44:53] FOG: warning reported by host zz-taskerror-fx (task 64): Disk is smaller than …
[06:44:53] FOG: error   reported by host zz-taskerror-fx (task 64): Could not mount images folder …

and exactly one HOST_IMAGE_FAIL, for the error — the warning reached a registered listener not at all, which is the point.

case result
both banners printed unchanged, including the warning's "Will continue in 1 minute"
the embedded \n expanded by printf %b here, flattened to spaces by the server
server refuses the connection 0.004s
server accepts and never replies 5.007s--max-time 5 holds; without it this is where FOS would sit
$web unset zero requests, banner still printed, 0.001s

handleWarning with a reachable server added 0.13s to a function that already sleeps for sixty.

The one thing not exercised is TLS: the shadow server was plain HTTP. -Lks here is identical to every other call in this file, and getversion.php and blame.php already reach a FOG server's self-signed certificate the same way, so this adds no new transport behaviour.

Nothing was left behind — the build writes only to gitignored dist/ and fssource*/, the database was a throwaway container loaded from a dump (the live install was never written to), and the fixture host was removed.

🤖 Generated with Claude Code

https://claude.ai/code/session_013mJVe4CpK3rRbi9H5GubXd

handleError() printed a banner and exited, making no request at all, so a
real imaging failure -- bad image, mount failure, partition error, any of the
handleError call sites across fog.download, fog.upload and fog.mount -- was
invisible to the FOG server. HOST_IMAGE_FAIL could not fire for it and the
notification plugins registered for that event had never run on any server.
The only failure FOG ever heard about was a storage node problem, through
fog.checkmount -> blame.php, and that re-queues rather than fails.

It now posts to service/taskerror.php, added in fogproject#1206.

Best effort, and deliberately so: this must not change anything the person
standing in front of the machine sees or waits for. Time bounded at five
seconds, output discarded, exit status ignored, and skipped entirely when
$web is unset. A server that predates the endpoint answers 404 and nothing
here notices.

printf %b first, so the "\n" the callers embed in their message becomes a
real newline rather than two literal characters; the server flattens control
characters back to spaces at its end.

Co-authored-by: Claude <noreply@anthropic.com>
mastacontrola added a commit to FOGProject/fogproject that referenced this pull request Aug 19, 2026
The server half of #1206. The FOS half is [FOGProject/fos#152](FOGProject/fos#152) — this one is useful on its own (an older FOS just never calls it), the FOS one is not useful without this.

## The gap

`handleError()` in FOS prints a banner to the console and `exit 1`s. It makes **no request to the server**, so a real imaging failure — bad image, mount failure, partition error, any of the ~40 `handleError` call sites across `fog.download`, `fog.upload` and `fog.mount` — has always been invisible to FOG. The only failure the server ever heard about was a storage node problem, through `fog.checkmount` → `blame.php`, and that re-queues rather than fails.

#1205 wired `HOST_IMAGE_FAIL` to `TaskQueue::checkout()`'s catch, which covers "imaging ran and FOG then failed to *record* it". This covers the failure people actually mean.

`service/taskerror.php` takes the host identity FOS already sends everywhere else, plus the error text and the script that raised it, and fires `HOST_IMAGE_FAIL` with the payload #1205 defined — the same keys from either path, so a listener behaves identically whichever failure reached it.

## Deliberately narrow, in three ways

**It does not change the task's state.** `taskStates` has no Failed — the five are Queued, Checked In, In-Progress, Complete, Cancelled. Reusing Cancelled loses the difference between "an admin stopped this" and "this broke"; adding a sixth means every place that enumerates states has to learn about it or a failed task becomes invisible there. That is a decision with UI and API consequences, it stays on #1206, and making the event fire does not depend on taking it.

**It refuses a task that is not imaging.** `HOST_IMAGE_FAIL` is an imaging event and this endpoint is reachable from a wipe or an inventory task too. Firing it there would be the defect #1202 just removed, pointing the other way. There is no event for a non-imaging task failing; noted on the issue.

**It answers `##` with 200 on every path, including every rejection.** FOS calls this on its way out and cannot act on the reply. Answering identically also means the endpoint cannot be used to ask whether a given MAC has an active imaging task.

## The text

Bounded to 500 characters and flattened to one line. This is unauthenticated in the same way every other `service/*.php` is — identified by MAC, reachable by anything that can reach the web tier — and the text lands in an administrator's Slack, ntfy or pushbullet message. An embedded newline there forges what looks like a second, separate notification, which is a better lie than anything markup could manage:

```
error=Could not mount images folder (fog.mount)
   Args Passed: --target /images
Host imaging completed successfully
```

comes out as one line, with the forged "success" visibly part of the failure text.

`\p{C}` rather than a newline-only strip, because a console-facing error string can carry terminal escapes too; `preg_replace` returns null rather than throwing on invalid UTF-8, so there is a byte-wise fallback — a machine with the wrong locale must not silently report nothing.

## Not a REST route

`service/*.php` is served directly and is not in `Route::defineRoutes()`, so `OpenAPI::document()` is unaffected — same as `blame.php` and the `Post_Stage` endpoints. No schema change, so no `FOG_SCHEMA` bump. No change to `Route::$validClasses`, so no FogApi sync.

## Verification

Live, against this install's real database, using a throwaway host on a locally-administered MAC (`02:00:00:00:0E:11`) that matches no hardware, so nothing could ever PXE boot into the task:

| case | result |
|---|---|
| deploy task | `FOG: imaging failed on host … (task 62): Could not mount images folder (fog.mount) Args Passed: --target /images Host imaging completed successfully` — **newlines flattened** |
| capture task | notified and logged |
| wipe task | refused: `Task is not an imaging task` |
| unknown MAC | refused: `Invalid Host` |
| 4000-char report | cut to exactly 500 |
| every one of them | HTTP 200, body `##` |

`HOST_IMAGE_FAIL` now appears in `notifyEvents`, which is proof `notify()` was genuinely reached and got past its guards. Fixture and shadow tree removed afterwards; host count back to 86.

`tests/task-error-report.test.php`, mutation-verified — dropping the control-character strip, the length bound, the imaging gate, the uniform ack, or adding a state change each fails the suite. Full suite: `68 passed, 0 failed`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_013mJVe4CpK3rRbi9H5GubXd
Six cases over handleError(), following tests/checks/sector-size.sh: a
sandbox copy of the library with its /usr/share/fog/lib path rewritten, and
PATH-shadowed stubs, with curl recording its argv rather than making a
request.

What is pinned is not that the report arrives -- that is the server's
harness, in fogproject#1207 -- but that trying to send it cannot change what
the person standing at the machine sees. It goes to the right URL with mac,
sysuuid and the message; it is time bounded and url-encoded; the "\n" the
callers embed is expanded before sending rather than going out as two
literal characters; nothing it does reaches the console; a failed report
still lets handleError reach its reboot notice; and no $web means no attempt
at all.

Mutation-verified: dropping the $web guard, the output redirect, --max-time,
the printf %b, or the whole block each fails the harness.

Co-authored-by: Claude <noreply@anthropic.com>
handleError() reported to the server; handleWarning() still printed its banner,
waited its minute and told nobody. A warning is the machine saying it hit
something and carried on -- a disk smaller than the image it is about to take,
a partition table it had to work around -- which is exactly the kind of thing
that turns up later as "the deploy worked but the machine is wrong", with no
record anywhere of the warning that predicted it.

Both handlers now go through reportToServer(), which takes the type as its
first argument. The server (fogproject#1208) records either against the task in
`taskLog` and in its own log, and fires HOST_IMAGE_FAIL only for an error --
announcing a failed deploy for a task that went on to succeed would be worse
than saying nothing.

The field carrying the message is `text` rather than `error` now that it is not
always one. Nothing has shipped with the old name.

The properties that mattered for handleError still hold, and now hold for
handleWarning as well: time bounded, output discarded, exit status ignored,
skipped entirely when there is no $web. tests/checks/error-report.sh grows
three cases for the warning path and pins the type on both; all nine were
mutation-verified against a build of the shipped library.

Co-Authored-By: Claude <noreply@anthropic.com>
@mastacontrola mastacontrola changed the title Report an imaging failure to the server Report failures and warnings to the server, tagged with which they are Aug 19, 2026
mastacontrola added a commit to FOGProject/fogproject that referenced this pull request Aug 19, 2026
…hema 338) (#1208)

#1207 gave FOS an endpoint to report an imaging failure to. It fired
HOST_IMAGE_FAIL and called error_log(), and that was the whole of it -- so a
report either reached a notification plugin or it reached the web server's
error log, mixed in with everything else the web tier writes, correlated with
nothing. Neither is a place an admin goes to ask "what did this task actually
say before it stopped".

A report now lands in three places:

  a `taskLog` row, typed and with the text in it. This is the one correlated
    with the task: it carries taskID and the state the task was in;
  /var/log/fog/fos/fosreports.log, listed by the Log Viewer like any other
    log because 'fos' is now in FOGLogPaths;
  HOST_IMAGE_FAIL, as before -- errors only, imaging tasks only.

SCHEMA 338 adds `logType` (default 'state') and `logText` (NULL) to taskLog.
Every row in that table so far is a state transition, which is exactly what
the default backfills them as, so TaskingElement::taskLog() is untouched: a
state row costs no extra column. logText is NULL rather than '' so "no body"
and "an empty body" stay distinguishable. The step is a closure, like 336,
because ADD COLUMN has no IF NOT EXISTS below MariaDB 10.0.2 and a re-run has
to converge rather than error.

TYPES. FOS now sends `type=error` or `type=warning`, because handleWarning()
reports too (FOGProject/fos#152). A warning means the machine carried on, so
it is recorded and it does not fire the failure event -- announcing a failed
deploy for a task that went on to succeed would be worse than saying nothing.
A report with no type at all is an error: an older FOS reporting a real
failure must not be downgraded into silence.

The row is written BEFORE the imaging gate, so a failed Memtest or inventory
is recorded against its task even though no imaging event can fire for it.
That gate is still there for the event itself -- firing HOST_IMAGE_FAIL for a
non-imaging task is the defect #1202 removed, in the other direction.

THE LOG FILE gets its own subdirectory, created by the installer as the web
user with httpd_sys_rw_content_t. Both halves matter. It is not the top level
because $servicelogs is root's and holds the eight daemons' logs, and rotation
renames and unlinks -- the same split ADR 0010 made for the plugin runner. And
it is not created by PHP, because /opt/fog inherits usr_t and httpd_t may READ
usr_t but not write it (GH-964), so a directory made without the relabel looks
right and silently swallows every write on an enforcing host. error_log() is
kept as the fallback, not the destination: a server whose web tree is updated
but which has not been re-installed yet has nowhere to write, and a report
that reaches no log at all is the exact failure this path exists to end.

Rotation keeps one old copy at SERVICE_LOG_SIZE -- the daemons' setting, so an
admin who has already decided how big a FOG log may get does not decide again.
One generation rather than five: this file gains a line per failed task, not a
line per poll.

VERIFIED against a throwaway copy of a real FOG database in a container, with
the web tier served from a shadow tree, so nothing live was written:

  step 338 applied through its own closure, backfilled all 52 existing rows to
    'state', and ran clean a second time;
  an error on a Deploy task wrote its row, its log line and fired
    HOST_IMAGE_FAIL to a registered listener;
  a warning on the same task wrote its row and its log line and fired nothing;
  an error on a Memtest task wrote its row and its log line and fired nothing;
  a report with no type was recorded as an error;
  an embedded newline arrived flattened to spaces, as #1207 intended;
  the file rotated to .1 once it passed SERVICE_LOG_SIZE.

The `ip` column is empty in those rows only because filter_input(INPUT_SERVER)
returns NULL under the cli-server SAPI the harness used; the same TaskLog
constructor fills it under php-fpm, which the 52 pre-existing rows show.

tests/task-error-report.test.php grows the type routing, the row's position
relative to both gates, the model mapping, the FOGLogPaths entry and the
installer's two lines. Every one of them was mutation-verified.

Downstream: none. `tasklog` is already a Route::$validClasses entry, so
OpenAPI picks the two columns up from the regenerated manifest.

Co-authored-by: JJ Fullmer <7743340+darksidemilk@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
@mastacontrola
mastacontrola merged commit a89dec1 into master Aug 19, 2026
@mastacontrola
mastacontrola deleted the report-imaging-failures branch August 19, 2026 11:47
mastacontrola pushed a commit to FOGProject/fogproject that referenced this pull request Aug 19, 2026
…ema 280)

Ported from working-1.6 (#1207, #1208, #1209). This is a feature on a
maintenance branch, and the reason it belongs here anyway is that FOS is not
branched. FOGProject/fos#152 makes handleError() AND handleWarning() post a
report to whatever server the machine booted from, and a 1.5 server is just as
likely to be that server. Without this the POST reaches a 404 and 1.5 keeps
the behaviour it has had since the beginning: a machine stops mid-image and
says nothing to anyone. HOST_IMAGE_FAIL has two listeners in this tree (slack,
pushbullet) and no core caller, so neither has ever fired on any 1.5 server.

WHAT ARRIVES. service/taskerror.php takes mac, sysuuid, a type of error or
warning, the text, and the script that raised it. A report lands in three
places, none of which is the task's state:

  a `taskLog` row, typed, with the text in it -- the one correlated with the
    task, carrying taskID and the state the task was in;
  /var/log/fog/fos/fosreports.log, which the Log Viewer lists like any other;
  HOST_IMAGE_FAIL -- errors only, imaging tasks only.

SCHEMA 280 adds `logType` (default 'state') and `logText` (NULL) to taskLog.
Every row in that table so far is a state transition, which is what the
default backfills them as, so TaskingElement::taskLog() is untouched. A
closure, not a bare ALTER, because ADD COLUMN has no IF NOT EXISTS below
MariaDB 10.0.2 and a re-run has to converge.

A warning is recorded and fires nothing, because the machine carried on;
announcing a failed deploy for a task that went on to succeed would be worse
than silence. A report with NO type is an error -- on this branch that is the
normal case rather than the exotic one, because a FOS newer than the server is
what 1.5 will usually be talking to.

THE INSTALLER PROBE HAD TO COME WITH IT, and this is the part that is not
optional. installFOGDB() probes fogstorage's INSERT privilege with a throwaway
row and reads any failure as "the grants need redoing", which is what makes it
demand a database root password. That probe was positional:

    INSERT INTO taskLog VALUES ( 0, '999test', 3, '127.0.0.1', NOW(), 'fog');

Six values into what schema 280 makes an eight column table is error 1136,
"Column count doesn't match value count" -- so this schema step alone would
have made every 1.5 upgrade stop and ask for a root password nobody needs to
type, on servers whose grants are perfectly correct. 1.6 shipped exactly that
regression and had already been bitten once before by the same statement; see
same commit as the cause rather than after it.

THE LOG DIRECTORY is created by the installer as the web user with
httpd_sys_rw_content_t, in its own subdirectory: $servicelogs is root's and
holds the daemons' logs, and rotation renames and unlinks. error_log() stays
as the fallback so a server updated but not yet re-installed still records
something. 'fos' is added to all THREE lists 1.5 keeps -- StorageNode::
_getData(), status/getfiles.php and status/logtoview.php -- because they fail
differently: miss the first two and the selector has no entry, miss the last
and it answers "Invalid Folder".

VERIFIED against a throwaway copy of a real 1.5 database (2079 hosts, schema
278) in a container, with the web tier served from a shadow tree, so the live
1.5 install was never written to:

  step 280 applied through its own closure, backfilled all 7 existing rows to
    'state', and ran clean a second time;
  the OLD probe against the migrated table -> ERROR 1136, as predicted;
    the new one -> ROW_COUNT() = 1;
  error on a Deploy task    -> row, log line, HOST_IMAGE_FAIL to a listener;
  warning on the same task  -> row and log line, no event;
  no type at all            -> recorded as an error, event fired;
  error on a non-imaging task -> row and log line, no event;
  an embedded newline arrived flattened to spaces;
  the file rotated to .1 once it passed SERVICE_LOG_SIZE.

WHAT IS DELIBERATELY NOT PORTED. The slack and pushbullet listeners are left
exactly as they are. 1.6's #1202 rewrote them to name the image and the
reason; here they keep reading only $data['HostName'], and the extra payload
keys are simply ignored. Making them fire at all is the change this branch
needed; changing what they say is a separate one.

Two tests, both mutation-verified: task-error-report.test.php (the sanitizer
run for real against stubbed base classes, plus the type routing, the row's
position relative to both gates, and all three log-path lists) and
installer-db-probes-name-columns.test.php (any positional INSERT anywhere in
the installer).

Co-Authored-By: Claude <noreply@anthropic.com>
mastacontrola added a commit to FOGProject/fogproject that referenced this pull request Aug 19, 2026
…ema 280) (#1210)

Ported from working-1.6 (#1207, #1208, #1209). This is a feature on a
maintenance branch, and the reason it belongs here anyway is that FOS is not
branched. FOGProject/fos#152 makes handleError() AND handleWarning() post a
report to whatever server the machine booted from, and a 1.5 server is just as
likely to be that server. Without this the POST reaches a 404 and 1.5 keeps
the behaviour it has had since the beginning: a machine stops mid-image and
says nothing to anyone. HOST_IMAGE_FAIL has two listeners in this tree (slack,
pushbullet) and no core caller, so neither has ever fired on any 1.5 server.

WHAT ARRIVES. service/taskerror.php takes mac, sysuuid, a type of error or
warning, the text, and the script that raised it. A report lands in three
places, none of which is the task's state:

  a `taskLog` row, typed, with the text in it -- the one correlated with the
    task, carrying taskID and the state the task was in;
  /var/log/fog/fos/fosreports.log, which the Log Viewer lists like any other;
  HOST_IMAGE_FAIL -- errors only, imaging tasks only.

SCHEMA 280 adds `logType` (default 'state') and `logText` (NULL) to taskLog.
Every row in that table so far is a state transition, which is what the
default backfills them as, so TaskingElement::taskLog() is untouched. A
closure, not a bare ALTER, because ADD COLUMN has no IF NOT EXISTS below
MariaDB 10.0.2 and a re-run has to converge.

A warning is recorded and fires nothing, because the machine carried on;
announcing a failed deploy for a task that went on to succeed would be worse
than silence. A report with NO type is an error -- on this branch that is the
normal case rather than the exotic one, because a FOS newer than the server is
what 1.5 will usually be talking to.

THE INSTALLER PROBE HAD TO COME WITH IT, and this is the part that is not
optional. installFOGDB() probes fogstorage's INSERT privilege with a throwaway
row and reads any failure as "the grants need redoing", which is what makes it
demand a database root password. That probe was positional:

    INSERT INTO taskLog VALUES ( 0, '999test', 3, '127.0.0.1', NOW(), 'fog');

Six values into what schema 280 makes an eight column table is error 1136,
"Column count doesn't match value count" -- so this schema step alone would
have made every 1.5 upgrade stop and ask for a root password nobody needs to
type, on servers whose grants are perfectly correct. 1.6 shipped exactly that
regression and had already been bitten once before by the same statement; see
same commit as the cause rather than after it.

THE LOG DIRECTORY is created by the installer as the web user with
httpd_sys_rw_content_t, in its own subdirectory: $servicelogs is root's and
holds the daemons' logs, and rotation renames and unlinks. error_log() stays
as the fallback so a server updated but not yet re-installed still records
something. 'fos' is added to all THREE lists 1.5 keeps -- StorageNode::
_getData(), status/getfiles.php and status/logtoview.php -- because they fail
differently: miss the first two and the selector has no entry, miss the last
and it answers "Invalid Folder".

VERIFIED against a throwaway copy of a real 1.5 database (2079 hosts, schema
278) in a container, with the web tier served from a shadow tree, so the live
1.5 install was never written to:

  step 280 applied through its own closure, backfilled all 7 existing rows to
    'state', and ran clean a second time;
  the OLD probe against the migrated table -> ERROR 1136, as predicted;
    the new one -> ROW_COUNT() = 1;
  error on a Deploy task    -> row, log line, HOST_IMAGE_FAIL to a listener;
  warning on the same task  -> row and log line, no event;
  no type at all            -> recorded as an error, event fired;
  error on a non-imaging task -> row and log line, no event;
  an embedded newline arrived flattened to spaces;
  the file rotated to .1 once it passed SERVICE_LOG_SIZE.

WHAT IS DELIBERATELY NOT PORTED. The slack and pushbullet listeners are left
exactly as they are. 1.6's #1202 rewrote them to name the image and the
reason; here they keep reading only $data['HostName'], and the extra payload
keys are simply ignored. Making them fire at all is the change this branch
needed; changing what they say is a separate one.

Two tests, both mutation-verified: task-error-report.test.php (the sanitizer
run for real against stubbed base classes, plus the type routing, the row's
position relative to both gates, and all three log-path lists) and
installer-db-probes-name-columns.test.php (any positional INSERT anywhere in
the installer).

Co-authored-by: JJ Fullmer <7743340+darksidemilk@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants