Bugfix 1.5.7
All checks were successful
release-tag / release-image (push) Successful in 6m16s

This commit is contained in:
2026-08-27 12:50:24 +02:00
parent f7f3460f6c
commit 5899989228
11 changed files with 332 additions and 18 deletions

View File

@@ -1,5 +1,5 @@
###############################################################################
# GLPI NEUROFORGE MEGA v1.5.6 - VOLLSTÄNDIGE .ENV.example
# GLPI NEUROFORGE MEGA v1.5.7 - VOLLSTÄNDIGE .ENV.example
#
# Diese Datei ist die zentrale Konfiguration für docker compose.
# Sie enthält:
@@ -29,7 +29,7 @@
# 01. MEGA STACK - RELEASE / HOST PORTS / PFADE
###############################################################################
# Immutable Registry-Tag der sechs Projekt-Images. "latest" ist produktiv verboten.
IMAGE_TAG=1.5.6
IMAGE_TAG=1.5.7
CONTROL_HOST_PORT=8070
AGENT_HOST_PORT=8080

View File

@@ -1,9 +1,9 @@
27dc46be5cbb1b171deff7fbd2f28bff1be802dff403797535fd8968bb98c8eb ./.cbmignore
8ba3eb076de10287f4d3379fb95c64678148b24ed96ddb5d1572b98d8dc4db4f ./.env.example
67c5365c186a4ced2140c819ae40e7a32dbd97a15d53b56ce253beabbabb21b4 ./.env.example
ed22fda7661db8203563611dc144998161cd024e161b0471d615eaf0defeb7db ./.gitea/workflows/release-tag.yml
e1ff71187cc3411a85067b964264011db7bd109a585ef7b9ea5b08bda039d813 ./.gitignore
ccfc4c139345a69d05cd2b809b0d09b4332d98e5b4f79aace4db0b648e3ea814 ./Makefile
947c19ea4429059926fcabd51336bd8340a62bc180db3f889963cb22e82e5d85 ./README.md
1d6f45fb84bef654faf2ef691d6edaea4e2232ef7b27cfe474a792dee0186dc2 ./README.md
4858caa52c0fb6cf302a1c581d07d448e5e90e0daa797e5819610fd6223bd348 ./RELEASE-NOTES-v1.1.0.md
01163462f46314f57660677fdef407c6c2884412ea850aab13a4f650e8c29f50 ./RELEASE-NOTES-v1.2.0.md
4da388ce660aa3b7a0d8075ec066a025b5437960973397360fcb9a5d4cb58c96 ./RELEASE-NOTES-v1.3.0.md
@@ -14,7 +14,7 @@ ccfc4c139345a69d05cd2b809b0d09b4332d98e5b4f79aace4db0b648e3ea814 ./Makefile
61ceebe5a891388336795fda2fd0d1ad10373b4ebaae7d2e670f35766115c604 ./RELEASE-NOTES-v1.4.4.md
15a3defdd5bbf07ebaec1a2e8626347a027a3622f10afb508f7a21c9b0bdfa71 ./RELEASE-NOTES-v1.4.5.md
d10b1dbd87585d72144d816449800dc17b6d7f8faba2dfb4eca93ae3def2c631 ./RELEASE-NOTES-v1.5.0.md
dab64e06c0817d77b4f887261832cbb34ba023cac7c74d72d81068553652d0c5 ./VERSION
2ba0c4acefd378988cd7a510eb0ab4a45857306a5f54c3627a688d7e48240a3e ./VERSION
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./backups/.gitkeep
8127e9db5e5e0af1d88770dc8fa60b381de45dbcc843262698cf9501409b4d58 ./deploy/searxng/settings.yml
3e153ad540d6cb1b20275775f2d7c129ec0216764cc9d0d27b963c634f347b43 ./docker-compose.dev.yml
@@ -27,7 +27,7 @@ ae0c2cfc2e92ade260c82f5c27ba0ccb65cb8aab1e9c35a8bab3936fca8f0d1a ./docker-compo
323402668da4d8e1d97a29c45db753834f6c3a16739eebebf7e016f06af160bf ./docs/CONTROLLED-AUTONOMY.md
51b4a8d7daff787f099b77bd5a33ebf8038c0812bd3056f0ee6afe17e103681d ./docs/ENV-MIGRATION-FROM-LEGACY.md
7a58f9f63e8bed40e12553e3ff1418688084ead1e0e83f78167809ddb73383fc ./docs/ENVIRONMENT.md
d68a7d43a94bde26d4a6dad6b2acad512c0fd0be40c79c4f0e0d5f8f80b42db0 ./docs/GO-LIVE-v1.5.0.md
679b3bfbac8a266811a51e799a86ef6d486193003dbbbbe9bc13e67530ee1ecc ./docs/GO-LIVE-v1.5.0.md
2465c837c243ef03856ad1297b540d6df90005c74b9f4a9bba223066a612009f ./docs/IMPLEMENTED.md
9c38807cc12fba6f94cadc4694996de58e6d2d5396e55fc0c4bd404bb13f53ad ./docs/MIGRATION-CUTOVER.md
a71d28353529906cee08cf90f35e0b0cf97bbc94ccfcef830145a2d561b21a9c ./docs/MIGRATION-MANIFEST.md
@@ -42,6 +42,7 @@ c905084e03788887894e37c040492b8ce2386ecc25ac3ab2e10bfd4b0a7cd167 ./docs/MIGRATI
fd78b77e18277a8bb81cf6ad33e67c0e7fae3977dc1bbf74787b0cb0cb9a4b4e ./docs/MIGRATION-v1.5.3-to-v1.5.4.md
4e2e595c0c1b78271a971f6e039c48416114ad9916ad7afc5293c2cff4ec8f9d ./docs/MIGRATION-v1.5.4-to-v1.5.5.md
b38aaf9d352ad70a9701566fc24d73d26c50c407291fe1c38b08b1afad9435bc ./docs/MIGRATION-v1.5.5-to-v1.5.6.md
b57e66a589cff7331d547afc0e031accd1d576c46c013143f76311a84eacbcfe ./docs/MIGRATION-v1.5.6-to-v1.5.7.md
2a01fb10a3e04eae1800a7c7e0aafc31e9bbb23584cea54d849b004716ef81b4 ./docs/OBSIDIAN-EXPORT.md
040010a807178d33797106e04716822f8d147d7d5833c1ba70865a1827f8484a ./docs/OPERATIONS.md
69ea49bc76690ac489aea908f4e784a14293e92d62478a240eabea53c5f90820 ./docs/QUALITY-REPLAY-example.json
@@ -52,6 +53,7 @@ f0ab4fa6b353059acf6b41a1e5a484ebd698684102724eddf8f8b91d7ff321a3 ./docs/RELEASE
bc9067063b13788393fc3389ac60c0db50e35cc9d7d733311fca2fc6923834f1 ./docs/RELEASE-v1.5.4.md
677bf7eacb64adce080dcc1184dca0d7c088727b1c49242df14a7694eabbb593 ./docs/RELEASE-v1.5.5.md
4cde69f2875d5f97544fb85ff066eb671a40d9670302c81b2a9d9b9a95a9bee3 ./docs/RELEASE-v1.5.6.md
f7bbf78e76592762fe6e2a08eff9110a273962ae3145da287c5875355672db7f ./docs/RELEASE-v1.5.7.md
be749a09ddbcd4cf427316c6fe531f138e9bfaa6c104f4c11a08f7555231e8b2 ./docs/UNIFIED-GRAPH.md
77d91565660789c621b05bd69fecfa3c2ef736f93855eca1f58b017e61c76f90 ./docs/VALIDATION-v1.5.0.md
3ea1953b2b4eb8f966310a65a12a681fb44e4eb3857b843312e235759aacfce5 ./docs/VALIDATION.md
@@ -162,7 +164,7 @@ cb3cde795233c0d4842dc7451c5855d1c071e459efbde3be0cabac32a837ba1b ./knowledge/14
f1eab883370e0a40ef52a6b6d785a510d8ed48a19d25e0a2bc95f4f2cc8e329e ./knowledge/17_serienbriefe-und-dokumentfunktionen.json
5a0b3d5d5bc712e30a67a4de3432f69070f7e43dd99931f675e72c213006b363 ./knowledge/example-vpn.json
49c48fb45c8387b020bb6a4bde2c81e544ac82b74403e453e28a85f987052978 ./mega-project.json
29d603b4108c2e0a3cc3af768168f776b9246d7edb4c09b9e57e8dbd58d3584e ./patches/SHA256SUMS
a13876d5d6dbc2845d5c34cafae2465dfe7d4f574a16435570df05f01626f821 ./patches/SHA256SUMS
47a6fa2c79bbba0c04af86dfa65d58529f492c698060fe586456c22a4eadb877 ./patches/glpi-agent-mega.diff
9b411c90d96a86c80f088ee4637046eeefaf31c62059d11aa1a999d6eb08b5b4 ./patches/glpi-knowledge-mega.diff
f0491de3cb6201f98ca6be8e865237adbba4772471f7fc7165d030e0c045fdb6 ./patches/neuroforge-mega.diff
@@ -181,6 +183,7 @@ b031ea42356a022d7bb03e7e2bda23c4ceddb66ccc59a5d1f0ebe787a790b829 ./patches/v1.5
27fd7fe12e9d774ced67cbcbf0f2e2e385736b48d01d655a1a9cf4b14b39bdcb ./patches/v1.5.3-to-v1.5.4.diff
7d0c4c85ee6cb89c6200cf35f23ea92c93ff5fdd9f2887c179c83fd5e5657281 ./patches/v1.5.4-to-v1.5.5.diff
405b41cbbd1ec48f316b1d935e67bc8d002fa1a3bd583fb813bf0875bd30a2e0 ./patches/v1.5.5-to-v1.5.6.diff
257a78b8f6613f92086a04bf53455f12e9bd1ab94775add31819ada465582163 ./patches/v1.5.6-to-v1.5.7.diff
564817f8edabde0c4e4a1a427a3aa5418aae7bf12e9463044a7e6e0f13973657 ./platform/neuroforge/.env.example
39319b6f2058e4c8d6656a9cf01675374f81a04075b956b093a2befb5e05ada4 ./platform/neuroforge/.gitignore
189486a885c7fb78e0eb878d93cda0c70ca6d7ff9bfdfb3f5f32487cf03a9688 ./platform/neuroforge/BENCHMARK-v0.5.0.md
@@ -221,13 +224,13 @@ fc993dc95fa49802ecb62994e4140dff18a27438e8a4f3c6352229c79b041710 ./platform/neu
585f5d02cbd11a2ebdc8e9fff536b5bd2dbd46237fa3ee23414dadda4f55a73a ./platform/neuroforge/internal/brain/brain.go
976288422c0c4116d8c98af8a9b164ac670f9caf03eddc2d5e2e48747456d3b4 ./platform/neuroforge/internal/brain/consolidation_test.go
f231a9ee8ffcbea8477e828d4111760e52fd85bb027e5670830e6df065a9ec75 ./platform/neuroforge/internal/brain/goal_progress.go
3399eb0bd71d3cd71bf2891c449d2c162aad3cbb2cbf330a33c921f134cb1d2f ./platform/neuroforge/internal/brain/goal_progress_test.go
d2ca691799b7aa6e9244767d9534c14323e1e4aeaf88ad0931036e63afcd476d ./platform/neuroforge/internal/brain/goal_progress_test.go
359955653c647125559afd6dc3ebe69aa5ca19ff7e825ce801b7bc24e5fbcfcb ./platform/neuroforge/internal/brain/policy.go
27e87af473d2d71ba94ffb9bf7a70934776f8c23ce45496ca0998ad3000fc156 ./platform/neuroforge/internal/brain/policy_test.go
9022a797aa1d49af20ced5d8c3e0b903be740fbaa7251e8a80d43cd04d2f68fe ./platform/neuroforge/internal/brain/research_quality.go
0a3c8f7d149e814e091595982dbaa69467f6bf11eec631471d133a9b21585ae4 ./platform/neuroforge/internal/brain/research_trace.go
530bd1eed2a1a157b4fd4468cbc51560fc94d6047de7593ade515fde0ad4a300 ./platform/neuroforge/internal/brain/staging.go
847b16965578f1c72e505d10e1bc956d16aaf246ef2c9de75859400ebccfdc41 ./platform/neuroforge/internal/brain/staging_quality.go
7f58e1b413836e6b0fb4ed4f272d82509df1d3d9e2ab48722c6901427496e67c ./platform/neuroforge/internal/brain/staging_quality.go
d4fed8b68d31f6fbd1992bde76abd4e7b2f9d211e53bf8b234369a6a53692fe8 ./platform/neuroforge/internal/brain/v3.go
3ae13251512ecad1423a33ce09889f961130fefaed9342170b2d5cc6b3b51893 ./platform/neuroforge/internal/brain/v3_test.go
4bc58463b659bd7e51db4c7dbeba053de90fcb41f392a7d6e62a8cd84ddaa092 ./platform/neuroforge/internal/brain/v4.go

View File

@@ -1,6 +1,6 @@
# GLPI NeuroForge Mega v1.5.6
# GLPI NeuroForge Mega v1.5.7
> Release: **v1.5.6** · Structured-Output-Hardening: provider-natives JSON, strikte Schemaprüfung und deterministische Behandlung fehlerhafter Backslash-Escapes zusätzlich zu den v1.5.5 Production-Grounding-Gates.
> Release: **v1.5.7** · Identifier-Grounding-Hardening: Slash-Komposita und URL-Pfade werden nicht mehr als CLI-Switches fehlklassifiziert; echte code-markierte Command-Switches bleiben source-verifiziert.
Ein kontrolliertes Monorepo aus **GLPI AI Agent**, **GLPI AI Knowledgebase** und **NeuroForge + SQAR**. Ziel ist nicht ein untrennbarer Monolith, sondern eine gemeinsame Plattform mit klaren Zuständigkeiten, getrennten Credentials und nachvollziehbaren Failure-Modi.
@@ -155,6 +155,10 @@ Die Zielgröße `research_corroborations` zählt v1.5.5 unabhängige Source-Orig
Staging-Synthese, Claim-Verifikation und Grounding-Rewrite fordern bei Ollama jetzt provider-nativ `format: "json"` an. Zusätzlich validiert NeuroForge die erwarteten JSON-Schemata strikt, lehnt unbekannte Felder ab und repariert ausschließlich syntaktisch eindeutig ungültige Backslash-Escapes innerhalb von JSON-Strings (z. B. Windows-/Registry-Pfade). Gültige JSON-Escapes und Daten außerhalb von Strings werden nicht verändert. Andere Syntaxfehler bleiben fail-closed bzw. durchlaufen höchstens den bereits begrenzten syntax-only Repair-Pass.
### Identifier Grounding Hardening (v1.5.7)
Der deterministische Identifier-Guard unterscheidet Slash-prefixed CLI-Switches jetzt von normaler Prosa. Konstruktionen wie `BIOS-/UEFI-Konfiguration`, `Web-/Portal-Konfiguration` und URL-Pfade blockieren Staging nicht mehr als vermeintlich erfundene Command-Switches. Slash-Switches werden nur noch aus explizit als Code markierten Markdown-Spans/Fences extrahiert; dort bleiben erfundene Optionen wie `DISM /MagicRepair` weiterhin fail-closed source-verifiziert. Fehlercodes, CVEs, KB-Nummern und Versionsnummern werden unverändert global geprüft.
## Obsidian / llm-wiki Export
Die Wissensbasis kann in zwei Sichten als Obsidian-kompatibler Vault exportiert werden:

View File

@@ -1 +1 @@
1.5.6
1.5.7

View File

@@ -46,3 +46,8 @@ Für einen realen Research→Staging-Smoke-Test zusätzlich verifizieren:
2. Staging-Synthese und Claim-Verifikation bleiben bei nicht reparierbarem JSON fail-closed.
3. Der resultierende Draft enthält weiterhin `human_review_required=true` und `auto_reply=false`.
4. Source-Authority und Claim-Verifikation aus v1.5.5 bleiben bestanden; JSON-Robustheit darf diese Gates nicht umgehen.
## v1.5.7 Identifier-Grounding Zusatzgate
Bei Windows-/Vendor-Artikeln dürfen normale Slash-Komposita oder URL-Pfade kein `source-unverified identifiers` auslösen. Echte CLI-Switches in Code-Spans/Fences bleiben source-verifiziert. Vor Go-Live mindestens einen Goal-Lauf mit `BIOS-/UEFI`-ähnlicher Prosa und einen Lauf mit einem belegten Slash-Command prüfen.

View File

@@ -0,0 +1,15 @@
# Migration v1.5.6 → v1.5.7
v1.5.7 is a drop-in identifier-grounding fix. No storage migration is required and no new environment variable is mandatory. Existing goals, memories, synapses, research history and staging drafts remain intact.
1. Build/publish the v1.5.7 images through the normal Gitea pipeline.
2. Set `IMAGE_TAG=1.5.7`.
3. Pull and recreate NeuroForge and its worker:
```bash
docker compose --profile research pull neuroforge neuroforge-worker
docker compose --profile research up -d --force-recreate neuroforge neuroforge-worker
```
4. Do not delete volumes.
5. Let the previously blocked goals run again. Their existing evidence can be reused.

17
docs/RELEASE-v1.5.7.md Normal file
View File

@@ -0,0 +1,17 @@
# GLPI NeuroForge Mega v1.5.7
v1.5.7 fixes a production-grounding false positive observed during live BitLocker, DISM and FortiClient goal revalidation. The v1.5.6 critical-identifier scanner treated every slash-prefixed word as a possible CLI switch, so normal German compounds and URL-path fragments such as `BIOS-/UEFI`, `/portal-konfiguration` or `/interaktionsbereiche` could block an otherwise grounded staging draft.
## Identifier-grounding hardening
- Slash-prefixed identifiers are no longer extracted globally from prose.
- CLI-style slash switches are extracted only from Markdown code spans/fences, where the author/model explicitly marks the content as code.
- URL paths, filesystem-like fragments and hyphen/slash compounds are not treated as command switches.
- Real code-marked switches remain fail-closed: an invented option such as `DISM /MagicRepair` is rejected unless it occurs in the evidence bundle.
- Sourced switches such as `/Online`, `/Cleanup-Image` and `/RestoreHealth` continue to pass.
- Existing global checks for error codes, CVEs, KB identifiers and version numbers remain unchanged.
- The v1.5.5 source-authority/claim-verification gates and the v1.5.6 strict structured-JSON path remain fully active.
## Regression coverage
Tests reproduce `BIOS-/UEFI`, `Web-/Portal-Konfiguration`, URL-path text, an invented code-marked `/MagicRepair` switch and a sourced `/RestoreHealth` command. Test, vet, build and race gates are required before packaging.

View File

@@ -16,3 +16,4 @@ b031ea42356a022d7bb03e7e2bda23c4ceddb66ccc59a5d1f0ebe787a790b829 v1.5.1-to-v1.5
27fd7fe12e9d774ced67cbcbf0f2e2e385736b48d01d655a1a9cf4b14b39bdcb v1.5.3-to-v1.5.4.diff
7d0c4c85ee6cb89c6200cf35f23ea92c93ff5fdd9f2887c179c83fd5e5657281 v1.5.4-to-v1.5.5.diff
405b41cbbd1ec48f316b1d935e67bc8d002fa1a3bd583fb813bf0875bd30a2e0 v1.5.5-to-v1.5.6.diff
257a78b8f6613f92086a04bf53455f12e9bd1ab94775add31819ada465582163 v1.5.6-to-v1.5.7.diff

View File

@@ -0,0 +1,190 @@
diff --git a/.env.example b/.env.example
index 712f8d1..daf61f7 100644
--- a/.env.example
+++ b/.env.example
@@ -1,5 +1,5 @@
###############################################################################
-# GLPI NEUROFORGE MEGA v1.5.6 - VOLLSTÄNDIGE .ENV.example
+# GLPI NEUROFORGE MEGA v1.5.7 - VOLLSTÄNDIGE .ENV.example
#
# Diese Datei ist die zentrale Konfiguration für docker compose.
# Sie enthält:
@@ -29,7 +29,7 @@
# 01. MEGA STACK - RELEASE / HOST PORTS / PFADE
###############################################################################
# Immutable Registry-Tag der sechs Projekt-Images. "latest" ist produktiv verboten.
-IMAGE_TAG=1.5.6
+IMAGE_TAG=1.5.7
CONTROL_HOST_PORT=8070
AGENT_HOST_PORT=8080
diff --git a/README.md b/README.md
index a17a5ed..9bfff20 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# GLPI NeuroForge Mega v1.5.6
+# GLPI NeuroForge Mega v1.5.7
-> Release: **v1.5.6** · Structured-Output-Hardening: provider-natives JSON, strikte Schemaprüfung und deterministische Behandlung fehlerhafter Backslash-Escapes zusätzlich zu den v1.5.5 Production-Grounding-Gates.
+> Release: **v1.5.7** · Identifier-Grounding-Hardening: Slash-Komposita und URL-Pfade werden nicht mehr als CLI-Switches fehlklassifiziert; echte code-markierte Command-Switches bleiben source-verifiziert.
Ein kontrolliertes Monorepo aus **GLPI AI Agent**, **GLPI AI Knowledgebase** und **NeuroForge + SQAR**. Ziel ist nicht ein untrennbarer Monolith, sondern eine gemeinsame Plattform mit klaren Zuständigkeiten, getrennten Credentials und nachvollziehbaren Failure-Modi.
@@ -155,6 +155,10 @@ Die Zielgröße `research_corroborations` zählt v1.5.5 unabhängige Source-Orig
Staging-Synthese, Claim-Verifikation und Grounding-Rewrite fordern bei Ollama jetzt provider-nativ `format: "json"` an. Zusätzlich validiert NeuroForge die erwarteten JSON-Schemata strikt, lehnt unbekannte Felder ab und repariert ausschließlich syntaktisch eindeutig ungültige Backslash-Escapes innerhalb von JSON-Strings (z. B. Windows-/Registry-Pfade). Gültige JSON-Escapes und Daten außerhalb von Strings werden nicht verändert. Andere Syntaxfehler bleiben fail-closed bzw. durchlaufen höchstens den bereits begrenzten syntax-only Repair-Pass.
+### Identifier Grounding Hardening (v1.5.7)
+
+Der deterministische Identifier-Guard unterscheidet Slash-prefixed CLI-Switches jetzt von normaler Prosa. Konstruktionen wie `BIOS-/UEFI-Konfiguration`, `Web-/Portal-Konfiguration` und URL-Pfade blockieren Staging nicht mehr als vermeintlich erfundene Command-Switches. Slash-Switches werden nur noch aus explizit als Code markierten Markdown-Spans/Fences extrahiert; dort bleiben erfundene Optionen wie `DISM /MagicRepair` weiterhin fail-closed source-verifiziert. Fehlercodes, CVEs, KB-Nummern und Versionsnummern werden unverändert global geprüft.
+
## Obsidian / llm-wiki Export
Die Wissensbasis kann in zwei Sichten als Obsidian-kompatibler Vault exportiert werden:
diff --git a/VERSION b/VERSION
index eac1e0a..f01291b 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.5.6
+1.5.7
diff --git a/docs/GO-LIVE-v1.5.0.md b/docs/GO-LIVE-v1.5.0.md
index 5633a21..ce05239 100644
--- a/docs/GO-LIVE-v1.5.0.md
+++ b/docs/GO-LIVE-v1.5.0.md
@@ -46,3 +46,8 @@ Für einen realen Research→Staging-Smoke-Test zusätzlich verifizieren:
2. Staging-Synthese und Claim-Verifikation bleiben bei nicht reparierbarem JSON fail-closed.
3. Der resultierende Draft enthält weiterhin `human_review_required=true` und `auto_reply=false`.
4. Source-Authority und Claim-Verifikation aus v1.5.5 bleiben bestanden; JSON-Robustheit darf diese Gates nicht umgehen.
+
+
+## v1.5.7 Identifier-Grounding Zusatzgate
+
+Bei Windows-/Vendor-Artikeln dürfen normale Slash-Komposita oder URL-Pfade kein `source-unverified identifiers` auslösen. Echte CLI-Switches in Code-Spans/Fences bleiben source-verifiziert. Vor Go-Live mindestens einen Goal-Lauf mit `BIOS-/UEFI`-ähnlicher Prosa und einen Lauf mit einem belegten Slash-Command prüfen.
diff --git a/platform/neuroforge/internal/brain/goal_progress_test.go b/platform/neuroforge/internal/brain/goal_progress_test.go
index b5970fa..63f4ad2 100644
--- a/platform/neuroforge/internal/brain/goal_progress_test.go
+++ b/platform/neuroforge/internal/brain/goal_progress_test.go
@@ -525,6 +525,43 @@ func TestCriticalIdentifierGuardRejectsInventedVersion(t *testing.T) {
}
}
+func TestCriticalIdentifierGuardIgnoresSlashCompoundsAndURLPaths(t *testing.T) {
+ draft := stagingDraftPayload{
+ Title: "BitLocker Wiederherstellung",
+ Text: "Nach einer TPM-, BIOS-/UEFI- oder Hardwareänderung kann die Wiederherstellung erforderlich sein. Prüfen Sie die Web-/Portal-Konfiguration und dokumentieren Sie Interaktionsbereiche/-Tags.",
+ Answer: "Öffnen Sie die Herstellerdokumentation unter https://example.test/docs/portal-konfiguration/uefi-recovery.",
+ }
+ evidence := []draftEvidence{{Memory: core.Memory{Text: "BitLocker recovery can be triggered after TPM, BIOS, UEFI, or hardware changes."}, Source: &core.KnowledgeSource{Title: "Microsoft", URI: "https://learn.microsoft.com/windows/security/operating-system-security/data-protection/bitlocker/recovery-overview"}}}
+ if err := validateDraftCriticalIdentifiers(draft, evidence); err != nil {
+ t.Fatalf("slash compounds and URL paths must not be treated as CLI identifiers: %v", err)
+ }
+}
+
+func TestCriticalIdentifierGuardRejectsInventedSlashSwitchInCode(t *testing.T) {
+ draft := stagingDraftPayload{
+ Title: "DISM Reparatur",
+ Text: "Verwenden Sie nur dokumentierte Reparaturoptionen.",
+ Answer: "Führen Sie `DISM /Online /Cleanup-Image /MagicRepair` aus.",
+ }
+ evidence := []draftEvidence{{Memory: core.Memory{Text: "Run DISM /Online /Cleanup-Image /RestoreHealth to repair the image."}, Source: &core.KnowledgeSource{Title: "Microsoft", URI: "https://learn.microsoft.com/windows-hardware/manufacture/desktop/repair-a-windows-image"}}}
+ err := validateDraftCriticalIdentifiers(draft, evidence)
+ if err == nil || !strings.Contains(strings.ToLower(err.Error()), "/magicrepair") {
+ t.Fatalf("invented slash switch in code must be rejected, got %v", err)
+ }
+}
+
+func TestCriticalIdentifierGuardAcceptsSourcedSlashSwitchInCode(t *testing.T) {
+ draft := stagingDraftPayload{
+ Title: "DISM Reparatur",
+ Text: "Verwenden Sie nur dokumentierte Reparaturoptionen.",
+ Answer: "Führen Sie `DISM /Online /Cleanup-Image /RestoreHealth` aus.",
+ }
+ evidence := []draftEvidence{{Memory: core.Memory{Text: "Run DISM /Online /Cleanup-Image /RestoreHealth to repair the image."}, Source: &core.KnowledgeSource{Title: "Microsoft", URI: "https://learn.microsoft.com/windows-hardware/manufacture/desktop/repair-a-windows-image"}}}
+ if err := validateDraftCriticalIdentifiers(draft, evidence); err != nil {
+ t.Fatalf("sourced slash switches in code must pass: %v", err)
+ }
+}
+
func TestClaimVerificationRepairsUnsupportedDISMOrder(t *testing.T) {
chatCalls := 0
s, e := policyTestEngine(t, func(w http.ResponseWriter, r *http.Request) {
diff --git a/platform/neuroforge/internal/brain/staging_quality.go b/platform/neuroforge/internal/brain/staging_quality.go
index 08810a8..e137c1a 100644
--- a/platform/neuroforge/internal/brain/staging_quality.go
+++ b/platform/neuroforge/internal/brain/staging_quality.go
@@ -10,6 +10,7 @@ import (
"sort"
"strconv"
"strings"
+ "unicode"
"neuroforge/internal/core"
)
@@ -298,18 +299,59 @@ func evidencePackForPrompt(cfg StagingPublisherConfig, evidence []draftEvidence)
return b.String()
}
-var criticalIdentifierRE = regexp.MustCompile(`(?i)\b(?:0x[0-9a-f]{4,}|cve-\d{4}-\d{4,}|kb\d{5,}|v?\d+\.\d+(?:\.\d+){0,2})\b|-\d{3,}|/[A-Za-z][A-Za-z0-9-]{2,}`)
+var (
+ criticalIdentifierRE = regexp.MustCompile(`(?i)\b(?:0x[0-9a-f]{4,}|cve-\d{4}-\d{4,}|kb\d{5,}|v?\d+\.\d+(?:\.\d+){0,2})\b|-\d{3,}`)
+ slashSwitchRE = regexp.MustCompile(`/[A-Za-z][A-Za-z0-9-]{2,}`)
+ markdownCodeRE = regexp.MustCompile("(?s)```(?:[A-Za-z0-9_+.-]+)?[\t ]*\n?(.*?)```|`([^`\n]+)`")
+)
+
+// slashSwitchIdentifiers intentionally considers slash-prefixed identifiers only
+// inside Markdown code spans/fences. A bare `/word` in prose is highly ambiguous:
+// German compounds such as "BIOS-/UEFI-Konfiguration" and URL paths such as
+// "/portal-konfiguration" previously tripped the source-grounding gate even though
+// they were not CLI switches. Actionable commands are already claim-verified, so the
+// deterministic identifier guard should be conservative and only add the extra
+// slash-token check when the draft itself marks content as code.
+func slashSwitchIdentifiers(text string) []string {
+ var out []string
+ for _, match := range markdownCodeRE.FindAllStringSubmatch(text, -1) {
+ segment := ""
+ if len(match) > 1 && match[1] != "" {
+ segment = match[1]
+ } else if len(match) > 2 {
+ segment = match[2]
+ }
+ for _, loc := range slashSwitchRE.FindAllStringIndex(segment, -1) {
+ if loc[0] > 0 {
+ prev := segment[loc[0]-1]
+ // URL paths (host/path), compound prose (BIOS-/UEFI) and
+ // filesystem-like fragments are not command switches.
+ if !unicode.IsSpace(rune(prev)) && prev != '(' && prev != '[' && prev != '{' && prev != '"' && prev != '\'' {
+ continue
+ }
+ }
+ out = append(out, segment[loc[0]:loc[1]])
+ }
+ }
+ return out
+}
func criticalIdentifiers(parts ...string) []string {
seen := map[string]bool{}
var out []string
+ add := func(m string) {
+ k := strings.ToLower(strings.TrimSpace(m))
+ if k != "" && !seen[k] {
+ seen[k] = true
+ out = append(out, k)
+ }
+ }
for _, p := range parts {
for _, m := range criticalIdentifierRE.FindAllString(p, -1) {
- k := strings.ToLower(strings.TrimSpace(m))
- if k != "" && !seen[k] {
- seen[k] = true
- out = append(out, k)
- }
+ add(m)
+ }
+ for _, m := range slashSwitchIdentifiers(p) {
+ add(m)
}
}
return out

View File

@@ -525,6 +525,43 @@ func TestCriticalIdentifierGuardRejectsInventedVersion(t *testing.T) {
}
}
func TestCriticalIdentifierGuardIgnoresSlashCompoundsAndURLPaths(t *testing.T) {
draft := stagingDraftPayload{
Title: "BitLocker Wiederherstellung",
Text: "Nach einer TPM-, BIOS-/UEFI- oder Hardwareänderung kann die Wiederherstellung erforderlich sein. Prüfen Sie die Web-/Portal-Konfiguration und dokumentieren Sie Interaktionsbereiche/-Tags.",
Answer: "Öffnen Sie die Herstellerdokumentation unter https://example.test/docs/portal-konfiguration/uefi-recovery.",
}
evidence := []draftEvidence{{Memory: core.Memory{Text: "BitLocker recovery can be triggered after TPM, BIOS, UEFI, or hardware changes."}, Source: &core.KnowledgeSource{Title: "Microsoft", URI: "https://learn.microsoft.com/windows/security/operating-system-security/data-protection/bitlocker/recovery-overview"}}}
if err := validateDraftCriticalIdentifiers(draft, evidence); err != nil {
t.Fatalf("slash compounds and URL paths must not be treated as CLI identifiers: %v", err)
}
}
func TestCriticalIdentifierGuardRejectsInventedSlashSwitchInCode(t *testing.T) {
draft := stagingDraftPayload{
Title: "DISM Reparatur",
Text: "Verwenden Sie nur dokumentierte Reparaturoptionen.",
Answer: "Führen Sie `DISM /Online /Cleanup-Image /MagicRepair` aus.",
}
evidence := []draftEvidence{{Memory: core.Memory{Text: "Run DISM /Online /Cleanup-Image /RestoreHealth to repair the image."}, Source: &core.KnowledgeSource{Title: "Microsoft", URI: "https://learn.microsoft.com/windows-hardware/manufacture/desktop/repair-a-windows-image"}}}
err := validateDraftCriticalIdentifiers(draft, evidence)
if err == nil || !strings.Contains(strings.ToLower(err.Error()), "/magicrepair") {
t.Fatalf("invented slash switch in code must be rejected, got %v", err)
}
}
func TestCriticalIdentifierGuardAcceptsSourcedSlashSwitchInCode(t *testing.T) {
draft := stagingDraftPayload{
Title: "DISM Reparatur",
Text: "Verwenden Sie nur dokumentierte Reparaturoptionen.",
Answer: "Führen Sie `DISM /Online /Cleanup-Image /RestoreHealth` aus.",
}
evidence := []draftEvidence{{Memory: core.Memory{Text: "Run DISM /Online /Cleanup-Image /RestoreHealth to repair the image."}, Source: &core.KnowledgeSource{Title: "Microsoft", URI: "https://learn.microsoft.com/windows-hardware/manufacture/desktop/repair-a-windows-image"}}}
if err := validateDraftCriticalIdentifiers(draft, evidence); err != nil {
t.Fatalf("sourced slash switches in code must pass: %v", err)
}
}
func TestClaimVerificationRepairsUnsupportedDISMOrder(t *testing.T) {
chatCalls := 0
s, e := policyTestEngine(t, func(w http.ResponseWriter, r *http.Request) {

View File

@@ -10,6 +10,7 @@ import (
"sort"
"strconv"
"strings"
"unicode"
"neuroforge/internal/core"
)
@@ -298,18 +299,59 @@ func evidencePackForPrompt(cfg StagingPublisherConfig, evidence []draftEvidence)
return b.String()
}
var criticalIdentifierRE = regexp.MustCompile(`(?i)\b(?:0x[0-9a-f]{4,}|cve-\d{4}-\d{4,}|kb\d{5,}|v?\d+\.\d+(?:\.\d+){0,2})\b|-\d{3,}|/[A-Za-z][A-Za-z0-9-]{2,}`)
var (
criticalIdentifierRE = regexp.MustCompile(`(?i)\b(?:0x[0-9a-f]{4,}|cve-\d{4}-\d{4,}|kb\d{5,}|v?\d+\.\d+(?:\.\d+){0,2})\b|-\d{3,}`)
slashSwitchRE = regexp.MustCompile(`/[A-Za-z][A-Za-z0-9-]{2,}`)
markdownCodeRE = regexp.MustCompile("(?s)```(?:[A-Za-z0-9_+.-]+)?[\t ]*\n?(.*?)```|`([^`\n]+)`")
)
// slashSwitchIdentifiers intentionally considers slash-prefixed identifiers only
// inside Markdown code spans/fences. A bare `/word` in prose is highly ambiguous:
// German compounds such as "BIOS-/UEFI-Konfiguration" and URL paths such as
// "/portal-konfiguration" previously tripped the source-grounding gate even though
// they were not CLI switches. Actionable commands are already claim-verified, so the
// deterministic identifier guard should be conservative and only add the extra
// slash-token check when the draft itself marks content as code.
func slashSwitchIdentifiers(text string) []string {
var out []string
for _, match := range markdownCodeRE.FindAllStringSubmatch(text, -1) {
segment := ""
if len(match) > 1 && match[1] != "" {
segment = match[1]
} else if len(match) > 2 {
segment = match[2]
}
for _, loc := range slashSwitchRE.FindAllStringIndex(segment, -1) {
if loc[0] > 0 {
prev := segment[loc[0]-1]
// URL paths (host/path), compound prose (BIOS-/UEFI) and
// filesystem-like fragments are not command switches.
if !unicode.IsSpace(rune(prev)) && prev != '(' && prev != '[' && prev != '{' && prev != '"' && prev != '\'' {
continue
}
}
out = append(out, segment[loc[0]:loc[1]])
}
}
return out
}
func criticalIdentifiers(parts ...string) []string {
seen := map[string]bool{}
var out []string
add := func(m string) {
k := strings.ToLower(strings.TrimSpace(m))
if k != "" && !seen[k] {
seen[k] = true
out = append(out, k)
}
}
for _, p := range parts {
for _, m := range criticalIdentifierRE.FindAllString(p, -1) {
k := strings.ToLower(strings.TrimSpace(m))
if k != "" && !seen[k] {
seen[k] = true
out = append(out, k)
}
add(m)
}
for _, m := range slashSwitchIdentifiers(p) {
add(m)
}
}
return out