Recent Activities
This page shows what are we working on.
-
head
-
feat(skills): a query-scoped skill contract, so a search skill can exist (#189)
SkillInterface::execute() takes an `object $entity`. A product-discovery
skill has none: it is handed a phrase and the storefront scope, and the
entities are its result. #190 and #192 both stop here.
Adds, without touching anything that exists:
* Api\QuerySkillInterface — getCode/getLabel/execute(string $query,
array $history, array $options). Scope travels in $options, as it
already does through GenerateService, AiRequest and every entity
skill; a typed context object would have to be assembled by each of
the planned callers for no consumer that exists yet.
* Api\Data\QueryResultInterface + Model\Data\QueryResult — answer text
plus the entities it refers to. A query skill returns structure, not
a string: BuildPage shows the cost of the alternative, its caller
json_decodes the result and finds a malformed answer three frames
from where it was produced (BulkConsumer.php:348). Cost and latency
stay out — the usage ledger (#171) already records both per request.
* Model\QuerySkillPool — a second pool, not a widened SkillPool.
Every consumer of SkillPool assumes entity semantics: SkillSupport
and BulkAction\ViewData call getSupportedEntityTypes(), McpServer
gives each pooled skill a tool whose schema requires entityType and
entityId. One pool of a common parent type does not stay typed; it
becomes a parent type plus an instanceof in four places, and a query
skill nobody filtered out is published as a tool no client can call.
QuerySkillInterface deliberately does not share a parent interface with
SkillInterface. Declaring getCode() and getLabel() twice keeps this
change strictly additive, so no implementor — ours or a merchant's —
can break on upgrade; extracting the parent later is itself backwards
compatible, so the option costs nothing to defer.
The five existing skills, SkillPool, BulkConsumer and the MCP tool list
are untouched. Unit tests cover resolution of both kinds and both
cross-kind mistakes.
Checked on 2.4.7 as well as 2.4.8 (#197): ConstructorIntegrity,
TypeDuplication and ArgumentSequence against a standalone
magento/framework 103.0.7.0-patch10 and 103.0.8.0-patch1 — clean on
both, with the #197 widening re-introduced as a positive control to
prove the gate was live.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 013f50 -
fix(content-builder): keep the failed payload and name the output limit (#188)
Review of #199 found five things the structured-output move left behind.
The providers carry no logger, so a payload that is not put on the
exception is gone: StructuredResponseException now holds the raw reply
and BuildPage logs it, which is what master's logUnreadableReply() was
written to keep. Null on a truncation or a refusal, where the stop
reason settles it before a body exists.
A document cut off inside the "components" string is the one shape of
#143 the stop reason cannot catch — the envelope closes normally and
the provider reports an ordinary stop — so the sentence for an
unreadable document names the output limit itself.
Also: a negative "bands" is read as no count rather than as the number
it says, because `minimum` is outside the subset Schema\Validator
accepts and BandAudit would otherwise report "read as -2 band(s)"; and
a reply that missed the schema no longer borrows the document's
sentence, in the same words and punctuation on both skills.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 8568ec -
chore(content-builder): move BuildPage and PlanPage to structured output (#188)
Both skills asked for JSON in the prompt and dug the answer out of the reply
text afterwards. That cost us the one fact that separates the failures: a
document the provider cut off at the output limit and a document the model
mangled both reached the merchant as json_decode's "Syntax error" (#143), and
they want opposite answers — raise a number, or try again.
Both now call AiRequest::sendStructured() with a response schema (#187), and a
call that produced no usable answer arrives as StructuredResponseException with
a reason. Each skill turns the three reasons into three named sentences,
carrying the provider's own message — the one that names "Max Tokens" — whole.
PlanPage is fully structured: PlanSchema describes {"sections": [...]} and the
skill parses nothing at all. extractJson() and its json_decode are gone.
BuildPage gets as far as the schema subset reaches. A Content Builder node has
free-form "props" and recursive "children", and neither is expressible under
OpenAI strict mode — nor, therefore, under the intersection Schema\Validator
holds. So DocumentSchema schematises the envelope ("bands" an integer or null,
"unsupported" a list of strings, both checked on the way out) and the document
travels inside it as a JSON string, read by the new ContentBuilder\DocumentReader.
DocumentSchema's class comment sets out why, and what finishing the job would
take: a flat node list with parent references, which is a measurement to run
rather than a refactor to slip in here.
What that removes from Model/Skill: the fenced-block regex, the widest-bracket-run
candidates, the lone-backslash repair pass, the hand-written bracket scanner that
sliced a broken list element by element, and the envelope scraper. Around 630
lines of BuildPage. The partial-page salvage goes with them — a truncation is now
reported as a truncation before any document exists, instead of handing back a
prefix nobody could verify.
StopReason stays and is unchanged; it is read off the response field by
Provider\StructuredResponse, which is where it already was.
Checked: no json_decode or preg_match over a model reply is left in Model/Skill;
the two schemas pass Schema\Validator and lower onto all three dialects
(SkillSchemaTest); truncation, refusal and a reply that missed the shape each
produce a different named error in both skills' tests. 2185 unit tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> e506b3
-
-
1.8.22
-
Version 1.8.22 9893b7
-
fix(providers): StructuredResponseException cause type breaks di:compile on 2.4.7 (#197) (#198)
* fix(providers): StructuredResponseException cause type breaks di:compile on 2.4.7 (#197)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* style(providers): wrap the cause-type comment (#197)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> 1d4842 -
style(providers): wrap the cause-type comment (#197)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 602747 -
fix(providers): StructuredResponseException cause type breaks di:compile on 2.4.7 (#197)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> a961ae
-
-
0.7.15
-
Version 0.7.15 cd67bf
-
fix(smtp): reset STARTTLS state when the handshake fails (#44)
A 220 reply to STARTTLS marked the connection as upgraded before the
TLS handshake ran; if the handshake failed, the flag stayed set on a
plain socket until the next start(). Clear it when start() fails and
drop the redundant stop() override.
Tests now also drive start() against a scripted SMTP server in a child
process: a real STARTTLS handshake followed by AUTH, a server that
does not offer STARTTLS, and a failed handshake.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 8b8e10 -
fix(smtp): allow mail after a STARTTLS upgrade (#44)
SocketStream::isTLS() reports implicit TLS (port 465) only and stays
false after STARTTLS, so RequireTlsSmtp refused AUTH on ports 587/2525
even though the connection was encrypted. Track the accepted STARTTLS
in the transport and reset it on reconnect.
Fixes #44
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 622063 -
fix(smtp): reset STARTTLS state when the handshake fails (#44)
A 220 reply to STARTTLS marked the connection as upgraded before the
TLS handshake ran; if the handshake failed, the flag stayed set on a
plain socket until the next start(). Clear it when start() fails and
drop the redundant stop() override.
Tests now also drive start() against a scripted SMTP server in a child
process: a real STARTTLS handshake followed by AUTH, a server that
does not offer STARTTLS, and a failed handshake.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 4c4769 -
fix(smtp): allow mail after a STARTTLS upgrade (#44)
SocketStream::isTLS() reports implicit TLS (port 465) only and stays
false after STARTTLS, so RequireTlsSmtp refused AUTH on ports 587/2525
even though the connection was encrypted. Track the accepted STARTTLS
in the transport and reset it on reconnect.
Fixes #44
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> f5e969
-
-
0.7.14
-
Version 0.7.14 d58265
-
feat(log): delete logged emails after a configurable retention period (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> e84db5 -
fix(acl): use matching ACL resources and ignore redirect and token fields in save (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> e3a5f5 -
fix(service): stop exposing the stored password in the form and URL (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> e1d05d -
fix(smtp): enforce the SSL and TLS options of mail services (#42)
SSL now uses implicit TLS (smtps). TLS now requires STARTTLS instead of silently continuing in clear text.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 61be08 -
fix(grid): check ACL and stop returning bodies and passwords in grid data (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> a0da7f -
fix(acl): require ACL resources for inline edit and mass actions (#42)
These actions fell back to Magento_Backend::admin, so any admin role could change or delete mail services.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> f2baec -
test: add PHPUnit setup (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 4ee61c -
feat(log): delete logged emails after a configurable retention period (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 355c80 -
fix(acl): use matching ACL resources and ignore redirect and token fields in save (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 72377f -
fix(service): stop exposing the stored password in the form and URL (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> f0a25a -
fix(smtp): enforce the SSL and TLS options of mail services (#42)
SSL now uses implicit TLS (smtps). TLS now requires STARTTLS instead of silently continuing in clear text.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> b914b0 -
fix(acl): use matching ACL resources and ignore redirect and token fields in save (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 5d4124 -
fix(service): stop exposing the stored password in the form and URL (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 354f53 -
fix(smtp): enforce the SSL and TLS options of mail services (#42)
SSL now uses implicit TLS (smtps). TLS now requires STARTTLS instead of silently continuing in clear text.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 342801 -
fix(grid): check ACL and stop returning bodies and passwords in grid data (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 8a3b83 -
fix(acl): require ACL resources for inline edit and mass actions (#42)
These actions fell back to Magento_Backend::admin, so any admin role could change or delete mail services.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 6d5242 -
test: add PHPUnit setup (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> dec9a8
-
-
1.19.5
-
1.19.4
-
Version 1.19.4 f97cb2
-
Security: harden {{navpro}} directive, admin preview, admin actions (CSRF), GraphQL (#37)
* Security: restrict {{navpro}} method calls, sanitize admin HTML preview
- {{navpro data="..."}} called any get* method on any reachable object,
bypassing Magento's template strict mode. An admin with only item_save
could read DB credentials via
remote_entity.getResource.getConnection.getConfig.password.
Only allowlisted methods (getProductCount, getUrl, getName, getImageUrl)
are now callable; other segments read DataObject data only.
- Dropdown layout HTML preview parsed stored content with jQuery in the
live admin document, so <img/src=x onerror> or <svg onload> executed
for any admin opening the menu. Parse in an inert DOMParser document and
strip all attributes except data-src/data-content-type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Security: make state-changing admin actions POST-only (CSRF)
None of the admin controllers implemented HttpPostActionInterface, so
delete/duplicate/create/move/save worked over GET, protected only by the
admin URL secret key. With "Add Secret Key to URLs" disabled, e.g.
<img src=".../navigationpro/menu/delete/menu_id/1"> deleted a menu.
POST requests always require a valid form_key.
- Item: Delete, Import, MassSave, Move, Save
- Menu: Create, Delete, Duplicate, MassDelete, Save
Callers switched to POST: item Delete button (deleteConfirm postData),
grid row delete ('post' => true), Duplicate button (mage/dataPost),
item drag-and-drop move ajax (form_key added by backend ajaxSetup).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Security: stop GraphQL leaking exception messages and raw item html
- getNavproMenu wrapped every exception into a client-aware
GraphQlNoSuchEntityException with the original message, exposing
internal errors (e.g. SQL) to anonymous clients. Only
NoSuchEntityException is converted now; anything else goes through
Magento's GraphQL error handler (logged, masked as "Internal server
error" outside developer mode).
- Node::getFlatArray() discarded the filtered result, so the GraphQL
`html` field returned raw template source ({{block}}, {{widget}},
{{navpro}}) instead of rendered html.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> fe9760 -
Security: stop GraphQL leaking exception messages and raw item html
- getNavproMenu wrapped every exception into a client-aware
GraphQlNoSuchEntityException with the original message, exposing
internal errors (e.g. SQL) to anonymous clients. Only
NoSuchEntityException is converted now; anything else goes through
Magento's GraphQL error handler (logged, masked as "Internal server
error" outside developer mode).
- Node::getFlatArray() discarded the filtered result, so the GraphQL
`html` field returned raw template source ({{block}}, {{widget}},
{{navpro}}) instead of rendered html.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 8387a0 -
Security: make state-changing admin actions POST-only (CSRF)
None of the admin controllers implemented HttpPostActionInterface, so
delete/duplicate/create/move/save worked over GET, protected only by the
admin URL secret key. With "Add Secret Key to URLs" disabled, e.g.
<img src=".../navigationpro/menu/delete/menu_id/1"> deleted a menu.
POST requests always require a valid form_key.
- Item: Delete, Import, MassSave, Move, Save
- Menu: Create, Delete, Duplicate, MassDelete, Save
Callers switched to POST: item Delete button (deleteConfirm postData),
grid row delete ('post' => true), Duplicate button (mage/dataPost),
item drag-and-drop move ajax (form_key added by backend ajaxSetup).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 88dd36 -
Security: restrict {{navpro}} method calls, sanitize admin HTML preview
- {{navpro data="..."}} called any get* method on any reachable object,
bypassing Magento's template strict mode. An admin with only item_save
could read DB credentials via
remote_entity.getResource.getConnection.getConfig.password.
Only allowlisted methods (getProductCount, getUrl, getName, getImageUrl)
are now callable; other segments read DataObject data only.
- Dropdown layout HTML preview parsed stored content with jQuery in the
live admin document, so <img/src=x onerror> or <svg onload> executed
for any admin opening the menu. Parse in an inert DOMParser document and
strip all attributes except data-src/data-content-type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> ad87a1 -
Move styles to non-critical. Remove search styles. Cleanup grid. e50a2b
-
Do not build the tree in prepareLayout 52e1ba
-
-
head
-
Add icons to customer menu #44 cf113b
-
Subscription product page: add product-subscription body class ae2732
-
Redirect sales/order/reorder to the order page e19f99
-
Stores Using attribute: use wysiwyg/stores in field note example c49caf
-
Add product atribute stores_using 4f2b4a
-
Show subscription expired message on Activation and My Tickets pages e1d992
-
-
1.0.0
-
Use category path in breadcrumbs #50 317f00
-
-
head
-
Add icons to customer menu #44 a6a11e
-
Subscription product page: show Buy button on mobile #43 ec0d18
-
Improve deployer page #48 d87605
-
Subscription archive: link Reorder to order page instead of sales/order/reorder #53 c42992
-
Stores using: add srcset with PageSpeed 0.5x/0.75x variants and sizes for store images #49 d8a112
-
Product Page: Add section Stores using ceed62
-
Fix Red Hat Display font preload 70dfce
-
Product page: keep product name from jumping when ProLabels label is inserted ed8eef
-
Fix listing grid columns for 1column layout: use current Breeze variable names #52 4c81d9
-
Blog: replace FontAwesome list icon with SVG, style wp-block-code like Prism 7febcd
-
Use category path in breadcrumbs #50 4b10c4
-
-
1.3.4
-
1.3.3
-
Version 1.3.3 0f8d9f
-
Security and robustness fixes (#9)
- Restrict update-config to developer mode and validate values
- Censor sensitive request data in Flare reports
- Open the error modal only for Ignition error responses
- Keep the store's error reporting level
- Fix Magento coding standard errors in copy-buttons.js
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> d86e96 -
Security and robustness fixes
- Restrict update-config to developer mode and validate values
- Censor sensitive request data in Flare reports
- Open the error modal only for Ignition error responses
- Keep the store's error reporting level
- Fix Magento coding standard errors in copy-buttons.js
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> e15a70 -
Strip query from Referer header in Flare reports
The Referer header could contain tokens, e.g. a password reset link.
Also use explode() instead of strtok(), which skips a leading "?".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 09d9c8 -
Fix Magento coding standard errors in copy-buttons.js
Avoid self-closing tags with non-void elements in the SVG icons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 20f4b7 -
Keep the store's error reporting level
Ignition::register() without arguments forces error_reporting(-1), which
overrode the level set in app/bootstrap.php or by the store. Stores that
exclude deprecations (E_ALL & ~E_DEPRECATED) got 500 errors on every
deprecation once the module was installed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> d98949 -
Open error modal only for responses marked by Plugin\App
The modal opened for any failed ajax response containing the
"window.ignite(window.data)" marker, including cross-origin responses
and error responses that reflect user input, which allowed XSS.
Plugin\App now sends an X-Ignition-Error-Page header with the error page
and the modal requires it. Also fixes a JS error on Breeze network errors,
where the fail callback receives no xhr.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> d733c5 -
Censor cookies in Flare JS error reports
The Flare JS client sent all cookies readable by JS (form_key and others)
with every error report. Replace their values before submitting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 22f466 -
Censor sensitive data in Flare reports
Production reports sent to Flare included the full request: cookies,
Authorization header, passwords and card data from the POST body, tokens
in the URL, and stack frame arguments.
- Add CensorSensitiveData middleware: censors cookies, sensitive headers,
and sensitive keys in body and query string (nested keys included),
and strips the query from the request URL.
- Disable stack frame arguments in production.
- Keep user provided context when copying the report, otherwise
middleware changes were lost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> c5a9d0 -
Fix stored XSS via unauthenticated update-config endpoint
The endpoint was available in all modes without authentication and saved
arbitrary values, which the error page renders unescaped (theme). Now it
works in developer mode only and accepts known values only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> abcc71
-
-
head
-
Add AJAX controller for fetching product labels and update label handling logic 33072b
-
-
1.0.10
-
Version 1.0.10 b8b88d
-
Fix child labels ignoring schedule, store and product status
- Skip expired labels and pass active period to frontend
- Filter index by store view, skip disabled and out-of-website children
- Validate $mode before using it in SQL column names
- Require swissup/module-pro-labels ^1.10.0 (active period columns) 3074b5
-
-
0.1.2
-
0.1.1
-
1.21.0
-
Version 1.21.0 28f0bf
-
fix(installer): do not enable Advanced JS Bundling by default 19fc09
-
Merge pull request #134 from swissup/feat/extract-advanced-js-bundling
refactor: move Advanced JS Bundling to swissup/module-advanced-js-bundling 343655 -
fix: keep deprecated config path constants, skip the order test without the new module
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 9d53ff -
Merge master into feat/extract-advanced-js-bundling
# Conflicts:
# composer.json 1714b2
-
-
1.20.1
-
Version 1.20.1 de757e
-
Merge pull request #136 from swissup/fix/security-audit-135
fix(security): audit findings (#135) 8aa502 -
fix(critical-css): reject any markup in critical CSS and share the POST script (#135)
- assertSafeCss rejects every markup start (<link>, <svg>, <img>, ...), not only
style/script/comment; saveConfig() runs the same check
- move the duplicated getPostScript() into PostScriptTrait
- DebugModeDefaultTest asserts config.xml loaded
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 406f33 -
fix(csp): whitelist www.googleapis.com for the admin area only (#135)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> bab7ee -
fix(critical-css): generate actions accept POST with form key only (#135)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 46e02b -
fix(critical-css): use https and reject markup in the API response (#135)
The response is printed unescaped into a <style> tag, so a response that
closes the tag or opens a script is refused.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> aecc4f -
fix(debug): disable debug mode by default (#135)
The ?pagespeed= switch and the debug console output were open to every
visitor on a fresh install. They stay available once debug mode is
enabled on purpose.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 933453 -
test: run ProductionHeadOrderTest against the Bundles block of the new module
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> cb52e3 -
refactor: move Advanced JS Bundling to swissup/module-advanced-js-bundling
Config keys and the bundle path are unchanged; the new package is required.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> b964d6
-