Merge pull request #1795 from netalertx/fix/notification-content-truncation

Fix/notification content truncation
This commit is contained in:
Jokob @NetAlertX authored and GitHub committed 2026-09-18 08:54:57 +10:00
commit b870601275
16 files changed
+254 -15

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
---
name: git-workflow
description: Read before running any git command that changes branch state (checkout -b, branch, push) in this repo. The default workflow is commit-and-push directly to next_release, not a feature-branch/PR flow - never create a branch without asking first.
---
# Git Workflow
## Never create a branch without asking first
Don't run `git checkout -b <new-branch>`, `git branch <new-branch>`, or anything else that creates a new branch, unless the user has explicitly said to. This applies even when a task sounds like it implies a branch-and-PR flow (e.g. "prep a PR") — ask first rather than assuming that's the intended workflow here.
**Why:** this repo's working tree is not necessarily an isolated checkout. The user may have their own terminal open on the exact same repo (e.g. a NAS/server shell alongside this session's working directory) at the same time. Switching branches changes shared repository state — a `git checkout -b` run from one place silently changes what `git push`/`git status` does from every other place touching the same repo, which has caused real, confusing failures (a `git push` from the user's own terminal failing with "no upstream branch" because this assistant had switched branches without saying so).
## Default: commit and push directly to `next_release`
Absent other instructions, work lands on `next_release` directly — commit there, `git push` targets `origin next_release`. Don't invent a feature-branch/PR workflow unless asked for one.
## Confirm before every push
Ask for explicit confirmation immediately before running `git push`, even to the default `next_release` target. Don't fold a push into a larger task silently — surface it as its own step and wait for a go-ahead.
## Before any git command that changes shared state
Run `git status` and `git branch --show-current` first, and don't assume the branch you last left the repo on is still checked out — another process/terminal may have changed it.
+28
View File
@@ -0,0 +1,28 @@
---
name: install-scripts
description: Read before editing anything under install/ (production-filesystem/, ubuntu24/, etc.) or reviewing a PR that touches it. Covers uninstall safety, why check-*.sh scripts don't all exit the same way, and what's legacy vs. active.
---
# Install Scripts
Conventions mined from real review comments on `install/*` PRs (#1214, #1230, #1235) — not derived from reading the scripts alone, so check current behavior before relying on any of these if the surrounding code has since changed.
## Uninstall safety
`install/*/uninstall.sh` must only remove NetAlertX's own files (`/app` and its own data) — never touch system packages it depends on (PHP, nginx, avahi, etc.) or other shared system components. A hardware/bare-metal install shares the host with other software; removing a system package on uninstall can break things NetAlertX didn't install.
## `check-*.sh` scripts don't all exit the same way — by design
`install/production-filesystem/services/scripts/check-*.sh` scripts are **not uniform** in whether a failure is fatal:
- Some are fail-fast on purpose: `check-first-run-config.sh` exits non-zero on a critical failure (can't create the config dir, can't copy default config) so the container doesn't start in a broken state.
- Some are warning-only on purpose: `check-ramdisk.sh` exits 0 even when it detects a real issue, letting the app start anyway with suboptimal config.
Don't assume one check's exit-code convention applies to another — check what that specific script is actually guarding before suggesting a change to its exit behavior.
## `/back` is legacy, not the active code path
`back/cron_script.sh` (and the rest of `/back`) is legacy, kept only for compatibility with older/external components. The active, maintained version is `install/production-filesystem/services/scripts/cron_script.sh`. Changes to `/back` are out of scope for most PRs unless the PR is specifically about compatibility with whatever still depends on it.
## Build-time vs. runtime, and generated files
See the `devcontainer-configs`/`devcontainer-management` skills for the Dockerfile-generation and build-vs-runtime-file-availability rules — the same "source files aren't present at Docker build time" constraint applies to `install/production-filesystem/build/*` scripts, not just the devcontainer image.
+8
View File
@@ -5,6 +5,14 @@ description: How to analyze and respond to GitHub PR review comments in NetAlert
# PR Analysis # PR Analysis
## Standing Rule: A Repeated Comment Becomes a Skill Update
If a review comment corrects something a skill *should* already cover, don't just fix that one instance — update the relevant skill as part of addressing the comment, same PR, same turn. If no skill covers it yet, that's the signal to create one. Check the skill first, though — sometimes it already covers the point and just wasn't consulted; the fix is in *applying* it, not in the skill being incomplete.
## Code Style
- **Prefer explicit, readable logic over compact-but-opaque expressions.** When a built-in/clever expression (e.g. a `max()`-based one-liner) saves a line or two but a reader has to reverse-engineer *why* it produces the right answer, write it out as plain conditional logic instead.
## Before Writing Any Test Code — Non-Negotiable Checklist ## Before Writing Any Test Code — Non-Negotiable Checklist
Run through this before creating or editing any file under `test/`: Run through this before creating or editing any file under `test/`:
+24
View File
@@ -0,0 +1,24 @@
---
name: git-workflow
description: Read before running any git command that changes branch state (checkout -b, branch, push) in this repo. The default workflow is commit-and-push directly to next_release, not a feature-branch/PR flow - never create a branch without asking first.
---
# Git Workflow
## Never create a branch without asking first
Don't run `git checkout -b <new-branch>`, `git branch <new-branch>`, or anything else that creates a new branch, unless the user has explicitly said to. This applies even when a task sounds like it implies a branch-and-PR flow (e.g. "prep a PR") — ask first rather than assuming that's the intended workflow here.
**Why:** this repo's working tree is not necessarily an isolated checkout. The user may have their own terminal open on the exact same repo (e.g. a NAS/server shell alongside this session's working directory) at the same time. Switching branches changes shared repository state — a `git checkout -b` run from one place silently changes what `git push`/`git status` does from every other place touching the same repo, which has caused real, confusing failures (a `git push` from the user's own terminal failing with "no upstream branch" because this assistant had switched branches without saying so).
## Default: commit and push directly to `next_release`
Absent other instructions, work lands on `next_release` directly — commit there, `git push` targets `origin next_release`. Don't invent a feature-branch/PR workflow unless asked for one.
## Confirm before every push
Ask for explicit confirmation immediately before running `git push`, even to the default `next_release` target. Don't fold a push into a larger task silently — surface it as its own step and wait for a go-ahead.
## Before any git command that changes shared state
Run `git status` and `git branch --show-current` first, and don't assume the branch you last left the repo on is still checked out — another process/terminal may have changed it.
+28
View File
@@ -0,0 +1,28 @@
---
name: install-scripts
description: Read before editing anything under install/ (production-filesystem/, ubuntu24/, etc.) or reviewing a PR that touches it. Covers uninstall safety, why check-*.sh scripts don't all exit the same way, and what's legacy vs. active.
---
# Install Scripts
Conventions mined from real review comments on `install/*` PRs (#1214, #1230, #1235) — not derived from reading the scripts alone, so check current behavior before relying on any of these if the surrounding code has since changed.
## Uninstall safety
`install/*/uninstall.sh` must only remove NetAlertX's own files (`/app` and its own data) — never touch system packages it depends on (PHP, nginx, avahi, etc.) or other shared system components. A hardware/bare-metal install shares the host with other software; removing a system package on uninstall can break things NetAlertX didn't install.
## `check-*.sh` scripts don't all exit the same way — by design
`install/production-filesystem/services/scripts/check-*.sh` scripts are **not uniform** in whether a failure is fatal:
- Some are fail-fast on purpose: `check-first-run-config.sh` exits non-zero on a critical failure (can't create the config dir, can't copy default config) so the container doesn't start in a broken state.
- Some are warning-only on purpose: `check-ramdisk.sh` exits 0 even when it detects a real issue, letting the app start anyway with suboptimal config.
Don't assume one check's exit-code convention applies to another — check what that specific script is actually guarding before suggesting a change to its exit behavior.
## `/back` is legacy, not the active code path
`back/cron_script.sh` (and the rest of `/back`) is legacy, kept only for compatibility with older/external components. The active, maintained version is `install/production-filesystem/services/scripts/cron_script.sh`. Changes to `/back` are out of scope for most PRs unless the PR is specifically about compatibility with whatever still depends on it.
## Build-time vs. runtime, and generated files
See the `devcontainer-configs`/`devcontainer-management` skills for the Dockerfile-generation and build-vs-runtime-file-availability rules — the same "source files aren't present at Docker build time" constraint applies to `install/production-filesystem/build/*` scripts, not just the devcontainer image.
+8
View File
@@ -5,6 +5,14 @@ description: How to analyze and respond to GitHub PR review comments in NetAlert
# PR Analysis # PR Analysis
## Standing Rule: A Repeated Comment Becomes a Skill Update
If a review comment corrects something a skill *should* already cover, don't just fix that one instance — update the relevant skill as part of addressing the comment, same PR, same turn. If no skill covers it yet, that's the signal to create one. Check the skill first, though — sometimes it already covers the point and just wasn't consulted; the fix is in *applying* it, not in the skill being incomplete.
## Code Style
- **Prefer explicit, readable logic over compact-but-opaque expressions.** When a built-in/clever expression (e.g. a `max()`-based one-liner) saves a line or two but a reader has to reverse-engineer *why* it produces the right answer, write it out as plain conditional logic instead.
## Before Writing Any Test Code — Non-Negotiable Checklist ## Before Writing Any Test Code — Non-Negotiable Checklist
Run through this before creating or editing any file under `test/`: Run through this before creating or editing any file under `test/`:
+3 -2
View File
@@ -25,8 +25,9 @@ Skills with the same purpose exist in more than one, sometimes under different n
| Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference | | Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference |
| Plugin dev | `plugin-development` | `plugin-run-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` | | Plugin dev | `plugin-development` | `plugin-run-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` |
| Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the "don't re-document settings" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit | | Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the "don't re-document settings" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit |
| Devcontainer | `devcontainer-management` | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | — | Gemini combines into one (uses `docker exec`); Copilot splits into 3 focused skills | | Devcontainer | `devcontainer-management` | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | — | Gemini combines into one (uses `docker exec`); Copilot splits into 3 focused skills. `devcontainer-configs` also covers Dockerfile generation (root `Dockerfile``generate-dockerfile.sh``.devcontainer/Dockerfile`, source files not present at build time) - mined from real review comments on PR #1184/#1230. |
| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist | | Install scripts | `install-scripts` | `install-scripts` | `install-scripts` | `install/*` conventions mined from real review comments (#1214/#1230/#1235): uninstall must only remove NetAlertX's own files, never system packages; `check-*.sh` scripts intentionally differ on fail-fast vs. warning-only; `/back` is legacy, not the active code path. |
| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist; the standing rule that a repeated review comment becomes a skill update (sourced from jokob's own real PR comments, #1739/#1744); a readability-over-cleverness code-style note from the same source. |
| Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log | | Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log |
| Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. | | Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. |
| Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. | | Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. |
@@ -26,3 +26,11 @@ Combines and merges template configurations into the final config used by VS Cod
## Note ## Note
This affects only the devcontainer configuration. It has no bearing on the production or test Docker image. This affects only the devcontainer configuration. It has no bearing on the production or test Docker image.
## Dockerfile Generation (separate from the config generation above)
`.devcontainer/Dockerfile` is itself a **generated file**`generate-dockerfile.sh` combines the root `Dockerfile` with `.devcontainer/resources/devcontainer-Dockerfile`. Never review or edit `.devcontainer/Dockerfile` directly; dependency/build-step changes belong in the root `Dockerfile`, regenerate after.
Two build-time gotchas that have caused real review confusion:
- **Source repo files are not available during the Docker build phase** — they're mounted into the container after it starts. A `COPY` referencing a source file will fail at build time; config that depends on repo content has to be handled at runtime (a setup script), not a build step.
- Under modern BuildKit, `COPY` with a glob pattern that matches nothing (e.g. `.[V]ERSION`) succeeds silently — copies nothing, doesn't fail the build. Don't assume a missing optional file will surface as a build error.
+24
View File
@@ -0,0 +1,24 @@
---
name: netalertx-git-workflow
description: Read before running any git command that changes branch state (checkout -b, branch, push) in this repo. The default workflow is commit-and-push directly to next_release, not a feature-branch/PR flow - never create a branch without asking first.
---
# Git Workflow
## Never create a branch without asking first
Don't run `git checkout -b <new-branch>`, `git branch <new-branch>`, or anything else that creates a new branch, unless the user has explicitly said to. This applies even when a task sounds like it implies a branch-and-PR flow (e.g. "prep a PR") — ask first rather than assuming that's the intended workflow here.
**Why:** this repo's working tree is not necessarily an isolated checkout. The user may have their own terminal open on the exact same repo (e.g. a NAS/server shell alongside this session's working directory) at the same time. Switching branches changes shared repository state — a `git checkout -b` run from one place silently changes what `git push`/`git status` does from every other place touching the same repo, which has caused real, confusing failures (a `git push` from the user's own terminal failing with "no upstream branch" because this assistant had switched branches without saying so).
## Default: commit and push directly to `next_release`
Absent other instructions, work lands on `next_release` directly — commit there, `git push` targets `origin next_release`. Don't invent a feature-branch/PR workflow unless asked for one.
## Confirm before every push
Ask for explicit confirmation immediately before running `git push`, even to the default `next_release` target. Don't fold a push into a larger task silently — surface it as its own step and wait for a go-ahead.
## Before any git command that changes shared state
Run `git status` and `git branch --show-current` first, and don't assume the branch you last left the repo on is still checked out — another process/terminal may have changed it.
+28
View File
@@ -0,0 +1,28 @@
---
name: netalertx-install-scripts
description: Read before editing anything under install/ (production-filesystem/, ubuntu24/, etc.) or reviewing a PR that touches it. Covers uninstall safety, why check-*.sh scripts don't all exit the same way, and what's legacy vs. active.
---
# Install Scripts
Conventions mined from real review comments on `install/*` PRs (#1214, #1230, #1235) — not derived from reading the scripts alone, so check current behavior before relying on any of these if the surrounding code has since changed.
## Uninstall safety
`install/*/uninstall.sh` must only remove NetAlertX's own files (`/app` and its own data) — never touch system packages it depends on (PHP, nginx, avahi, etc.) or other shared system components. A hardware/bare-metal install shares the host with other software; removing a system package on uninstall can break things NetAlertX didn't install.
## `check-*.sh` scripts don't all exit the same way — by design
`install/production-filesystem/services/scripts/check-*.sh` scripts are **not uniform** in whether a failure is fatal:
- Some are fail-fast on purpose: `check-first-run-config.sh` exits non-zero on a critical failure (can't create the config dir, can't copy default config) so the container doesn't start in a broken state.
- Some are warning-only on purpose: `check-ramdisk.sh` exits 0 even when it detects a real issue, letting the app start anyway with suboptimal config.
Don't assume one check's exit-code convention applies to another — check what that specific script is actually guarding before suggesting a change to its exit behavior.
## `/back` is legacy, not the active code path
`back/cron_script.sh` (and the rest of `/back`) is legacy, kept only for compatibility with older/external components. The active, maintained version is `install/production-filesystem/services/scripts/cron_script.sh`. Changes to `/back` are out of scope for most PRs unless the PR is specifically about compatibility with whatever still depends on it.
## Build-time vs. runtime, and generated files
See the `devcontainer-configs`/`devcontainer-management` skills for the Dockerfile-generation and build-vs-runtime-file-availability rules — the same "source files aren't present at Docker build time" constraint applies to `install/production-filesystem/build/*` scripts, not just the devcontainer image.
+8
View File
@@ -5,6 +5,14 @@ description: How to analyze and respond to GitHub PR review comments in NetAlert
# PR Analysis # PR Analysis
## Standing Rule: A Repeated Comment Becomes a Skill Update
If a review comment corrects something a skill *should* already cover, don't just fix that one instance — update the relevant skill as part of addressing the comment, same PR, same turn. If no skill covers it yet, that's the signal to create one. Check the skill first, though — sometimes it already covers the point and just wasn't consulted; the fix is in *applying* it, not in the skill being incomplete.
## Code Style
- **Prefer explicit, readable logic over compact-but-opaque expressions.** When a built-in/clever expression (e.g. a `max()`-based one-liner) saves a line or two but a reader has to reverse-engineer *why* it produces the right answer, write it out as plain conditional logic instead.
## Before Writing Any Test Code — Non-Negotiable Checklist ## Before Writing Any Test Code — Non-Negotiable Checklist
Run through this before creating or editing any file under `test/`: Run through this before creating or editing any file under `test/`:
+3 -2
View File
@@ -25,8 +25,9 @@ Skills with the same purpose exist in more than one, sometimes under different n
| Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference | | Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference |
| Plugin dev | `plugin-run-development` | `plugin-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` | | Plugin dev | `plugin-run-development` | `plugin-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` |
| Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the "don't re-document settings" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit | | Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the "don't re-document settings" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit |
| Devcontainer | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | `devcontainer-management` | — | Copilot splits into 3 focused skills; Gemini combines into one (uses `docker exec`) | | Devcontainer | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | `devcontainer-management` | — | Copilot splits into 3 focused skills; Gemini combines into one (uses `docker exec`). `devcontainer-configs` also covers Dockerfile generation (root `Dockerfile``generate-dockerfile.sh``.devcontainer/Dockerfile`, source files not present at build time) - mined from real review comments on PR #1184/#1230. |
| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist | | Install scripts | `install-scripts` | `install-scripts` | `install-scripts` | `install/*` conventions mined from real review comments (#1214/#1230/#1235): uninstall must only remove NetAlertX's own files, never system packages; `check-*.sh` scripts intentionally differ on fail-fast vs. warning-only; `/back` is legacy, not the active code path. |
| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist; the standing rule that a repeated review comment becomes a skill update (sourced from jokob's own real PR comments, #1739/#1744); a readability-over-cleverness code-style note from the same source. |
| Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log | | Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log |
| Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. | | Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. |
| Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. | | Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. |
+1
View File
@@ -34,6 +34,7 @@ GROUPS = [
[".gemini/skills/ux-design-patterns/SKILL.md", ".github/skills/ux-design-patterns/SKILL.md", ".claude/skills/ux-design-patterns/SKILL.md"], [".gemini/skills/ux-design-patterns/SKILL.md", ".github/skills/ux-design-patterns/SKILL.md", ".claude/skills/ux-design-patterns/SKILL.md"],
[".gemini/skills/skill-hygiene/SKILL.md", ".github/skills/skill-hygiene/SKILL.md", ".claude/skills/skill-hygiene/SKILL.md"], [".gemini/skills/skill-hygiene/SKILL.md", ".github/skills/skill-hygiene/SKILL.md", ".claude/skills/skill-hygiene/SKILL.md"],
[".gemini/skills/plugin-review/SKILL.md", ".github/skills/plugin-review/SKILL.md", ".claude/skills/plugin-review/SKILL.md"], [".gemini/skills/plugin-review/SKILL.md", ".github/skills/plugin-review/SKILL.md", ".claude/skills/plugin-review/SKILL.md"],
[".gemini/skills/install-scripts/SKILL.md", ".github/skills/install-scripts/SKILL.md", ".claude/skills/install-scripts/SKILL.md"],
[".gemini/skills/settings/SKILL.md", ".github/skills/settings-management/SKILL.md"], [".gemini/skills/settings/SKILL.md", ".github/skills/settings-management/SKILL.md"],
[".gemini/skills/mcp-activation/SKILL.md", ".github/skills/mcp-activation/SKILL.md"], [".gemini/skills/mcp-activation/SKILL.md", ".github/skills/mcp-activation/SKILL.md"],
[".gemini/skills/project-navigation/SKILL.md", ".github/skills/project-navigation/SKILL.md"], [".gemini/skills/project-navigation/SKILL.md", ".github/skills/project-navigation/SKILL.md"],
+5 -9
View File
@@ -1909,15 +1909,11 @@ def metrics(payload=None):
tags=["messaging"], tags=["messaging"],
auth_callable=is_authorized auth_callable=is_authorized
) )
def api_write_notification(payload=None): def api_write_notification(payload: CreateNotificationRequest = None):
data = request.json or {} # Use the validated payload, not the raw request body - CreateNotificationRequest's
content = data.get("content") # truncate_content validator runs against payload.content; re-reading request.json
level = data.get("level", "alert") # directly would silently bypass it and store the untruncated original.
write_notification(payload.content, payload.level)
if not content:
return jsonify({"success": False, "message": "ERROR: Missing parameters", "error": "Missing content"}), 400
write_notification(content, level)
return jsonify({"success": True}) return jsonify({"success": True})
+14 -2
View File
@@ -912,14 +912,26 @@ class CreateNotificationRequest(BaseModel):
content: str = Field( content: str = Field(
..., ...,
min_length=1, min_length=1,
max_length=1024, max_length=4096,
description="Notification content" description="Notification content"
) )
level: NOTIFICATION_LEVELS = Field( level: NOTIFICATION_LEVELS = Field(
"info", "alert",
description="Notification severity level" description="Notification severity level"
) )
@field_validator("content", mode="before")
@classmethod
def truncate_content(cls, v):
"""Truncate instead of rejecting - content is often a generated
summary (e.g. a bulk multi-edit's affected MAC list) with no natural
length cap, and the notification store is a JSON file with no
underlying column limit that 4096 corresponds to. Losing the whole
write on overlength input would be worse than a truncated one."""
if isinstance(v, str) and len(v) > 4096:
return v[:4093] + "..."
return v
# ============================================================================= # =============================================================================
# SYNC SCHEMAS # SYNC SCHEMAS
@@ -107,6 +107,46 @@ def test_delete_single_notification(client, api_token, notification_guid):
assert resp.json.get("success") is True assert resp.json.get("success") is True
def test_write_notification_truncates_overlong_content(client, api_token):
"""A notification whose content exceeds 4096 chars (e.g. a bulk multi-edit's
generated MAC-list summary, selecting hundreds of devices) must be
truncated, not rejected outright - silently dropping the whole
notification loses the audit trail of what the bulk action did."""
overlong = "[Multi edit] Executed \"update\" matching \"" + ",".join(
f"{i:04x}:{i:04x}:{i:04x}" for i in range(300)
) + "\""
assert len(overlong) > 4096
resp = client.post(
"/messaging/in-app/write",
json={"content": overlong, "level": "info"},
headers=auth_headers(api_token)
)
assert resp.status_code == 200
assert resp.json.get("success") is True
resp = client.get("/messaging/in-app/unread", headers=auth_headers(api_token))
stored = next((n["content"] for n in resp.json if n["content"].startswith("[Multi edit]")), None)
assert stored is not None
assert len(stored) == 4096
assert stored.endswith("...")
assert stored.startswith(overlong[:100])
def test_write_notification_at_exact_limit_unchanged(client, api_token):
exactly_4096 = "x" * 4096
resp = client.post(
"/messaging/in-app/write",
json={"content": exactly_4096, "level": "info"},
headers=auth_headers(api_token)
)
assert resp.status_code == 200
resp = client.get("/messaging/in-app/unread", headers=auth_headers(api_token))
stored = next((n["content"] for n in resp.json if n["content"].startswith("xxx")), None)
assert stored == exactly_4096 # untouched - no trailing "..."
def test_delete_all_notifications(client, api_token, random_content): def test_delete_all_notifications(client, api_token, random_content):
# Add a notification first # Add a notification first
client.post("/messaging/in-app/write", json={"content": random_content}, headers=auth_headers(api_token)) client.post("/messaging/in-app/write", json={"content": random_content}, headers=auth_headers(api_token))