gina 0.4.5 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/CONTRIBUTING.md +2 -2
- package/README.md +10 -8
- package/ROADMAP.md +2 -2
- package/framework/v0.4.6/VERSION +1 -0
- package/framework/v0.4.6/core/asset/plugin/dist/vendor/gina/css/gina.min.css +1 -0
- package/framework/v0.4.6/core/asset/plugin/dist/vendor/gina/css/gina.min.css.br +0 -0
- package/framework/v0.4.6/core/asset/plugin/dist/vendor/gina/css/gina.min.css.gz +0 -0
- package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/inspector/inspector.js +62 -0
- package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/js/gina.js +934 -42
- package/framework/v0.4.6/core/asset/plugin/dist/vendor/gina/js/gina.min.js +571 -0
- package/framework/v0.4.6/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
- package/framework/v0.4.6/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
- package/framework/{v0.4.5 → v0.4.6}/core/controller/controller.js +62 -6
- package/framework/v0.4.6/core/controller/controller.render-nunjucks-async.js +788 -0
- package/framework/v0.4.6/core/controller/controller.render-swig-async.js +664 -0
- package/framework/{v0.4.5 → v0.4.6}/core/controller/controller.render-swig.js +52 -5
- package/framework/{v0.4.5 → v0.4.6}/core/gna.js +24 -0
- package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/csp/README.md +35 -0
- package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/csp/src/main.js +136 -2
- package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/storage/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/validator/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/validator/src/main.js +17 -4
- package/framework/{v0.4.5 → v0.4.6}/core/server.isaac.js +2 -2
- package/framework/{v0.4.5 → v0.4.6}/core/server.js +273 -97
- package/framework/{v0.4.5 → v0.4.6}/helpers/data/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/helpers/data/src/main.js +58 -3
- package/framework/{v0.4.5 → v0.4.6}/helpers/json/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/helpers/plugins/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/archiver/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/cache/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/link.js +16 -5
- package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/msg.json +1 -1
- package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/restart.js +11 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/start.js +5 -1
- package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/version.js +24 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/cmd/helper.js +28 -9
- package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/add.js +29 -13
- package/framework/{v0.4.5 → v0.4.6}/lib/collection/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/cron/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/domain/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/index.js +7 -0
- package/framework/{v0.4.5 → v0.4.6}/lib/inherits/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/logger/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/merge/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/model.js +17 -0
- package/framework/{v0.4.5 → v0.4.6}/lib/nunjucks-filters/src/main.js +37 -11
- package/framework/{v0.4.5 → v0.4.6}/lib/routing/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/routing/src/main.js +4 -4
- package/framework/{v0.4.5 → v0.4.6}/lib/swig-filters/package.json +0 -3
- package/framework/{v0.4.5 → v0.4.6}/lib/swig-filters/src/main.js +45 -6
- package/framework/v0.4.6/lib/template-loaders/package.json +17 -0
- package/framework/v0.4.6/lib/template-loaders/src/loaders/http.js +272 -0
- package/framework/v0.4.6/lib/template-loaders/src/loaders/memory.js +75 -0
- package/framework/v0.4.6/lib/template-loaders/src/main.js +134 -0
- package/framework/{v0.4.5 → v0.4.6}/package.json +2 -2
- package/gna.js +4 -4
- package/llms.txt +32 -44
- package/package.json +6 -3
- package/playwright.config.js +35 -0
- package/schema/settings.json +105 -0
- package/types/gna.d.ts +1 -1
- package/framework/v0.4.5/VERSION +0 -1
- package/framework/v0.4.5/core/asset/plugin/dist/vendor/gina/css/gina.min.css +0 -1
- package/framework/v0.4.5/core/asset/plugin/dist/vendor/gina/css/gina.min.css.br +0 -0
- package/framework/v0.4.5/core/asset/plugin/dist/vendor/gina/css/gina.min.css.gz +0 -0
- package/framework/v0.4.5/core/asset/plugin/dist/vendor/gina/js/gina.min.js +0 -555
- package/framework/v0.4.5/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
- package/framework/v0.4.5/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/AUTHORS +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/LICENSE +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/html/nolayout.html +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/html/static.html +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/img/android-chrome-192x192.png +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/img/android-chrome-512x512.png +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/img/apple-touch-icon.png +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/img/favicon-16x16.png +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/img/favicon-32x32.png +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/img/favicon.ico +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.css +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/beemaster/index.html +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/html/statusbar.html +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.br +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.gz +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/inspector/have_heart_one-webfont.woff2 +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/inspector/index.html +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/inspector/inspector.css +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/inspector/logo.svg +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.br +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.gz +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/config.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/ai/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/ai/lib/connector.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/couchbase/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/couchbase/lib/connector.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/couchbase/lib/connector.v3.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/couchbase/lib/connector.v4.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/couchbase/lib/n1ql.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/couchbase/lib/session-store.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/couchbase/lib/session-store.v3.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/couchbase/lib/session-store.v4.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/mongodb/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/mongodb/lib/connector.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/mongodb/lib/pipeline-loader.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/mongodb/lib/session-store.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/mysql/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/mysql/lib/connector.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/postgresql/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/postgresql/lib/connector.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/redis/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/redis/lib/session-store.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/scylladb/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/scylladb/lib/connector.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/scylladb/lib/session-store.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/sql-parser.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/sqlite/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/sqlite/lib/connector.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/connectors/sqlite/lib/session-store.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/content.encoding +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/controller/controller.framework.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/controller/controller.render-json.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/controller/controller.render-nunjucks.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/controller/controller.render-stream.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/controller/controller.render-v1.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/controller/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/controller/inspector-window-emit.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/deps/busboy-1.6.0/LICENSE +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/deps/busboy-1.6.0/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/deps/busboy-1.6.0/lib/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/deps/busboy-1.6.0/lib/types/multipart.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/deps/busboy-1.6.0/lib/types/urlencoded.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/deps/busboy-1.6.0/lib/utils.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/deps/busboy-1.6.0/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/deps/streamsearch-1.1.0/LICENSE +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/deps/streamsearch-1.1.0/lib/sbmh.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/deps/streamsearch-1.1.0/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/dev/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/dev/lib/class.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/dev/lib/factory.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/dev/lib/tools.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/locales/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/locales/currency.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/locales/dist/language/en.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/locales/dist/language/fr.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/locales/dist/region/en.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/locales/dist/region/fr.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/locales/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/mime.types +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/model/entity.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/model/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/model/template/entityFactory.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/model/template/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/csrf/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/csrf/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/csrf/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/coep/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/coep/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/coep/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/coop/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/coop/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/coop/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/corp/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/corp/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/corp/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/csp/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/hide-powered-by/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/hide-powered-by/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/hide-powered-by/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/hsts/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/hsts/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/hsts/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/origin-agent-cluster/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/origin-agent-cluster/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/origin-agent-cluster/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/referrer-policy/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/referrer-policy/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/referrer-policy/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-content-type-options/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-content-type-options/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-content-type-options/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-dns-prefetch-control/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-dns-prefetch-control/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-dns-prefetch-control/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-download-options/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-download-options/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-download-options/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-frame-options/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-frame-options/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-frame-options/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-xss-protection/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-xss-protection/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/security-headers/x-xss-protection/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/session/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/session/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/session/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/storage/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/storage/build.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/storage/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/validator/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/validator/build.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/plugins/lib/validator/src/form-validator.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/router.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/server.express.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/status.codes +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/_gitignore +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/config/app.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/config/connectors.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/config/routing.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/config/settings.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/config/settings.server.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/config/templates.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/config/watchers.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/controllers/controller.content.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/controllers/controller.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/controllers/setup.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle/locales/en.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_namespace/controllers/controller.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_public/css/default.css +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_public/css/home.css +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_public/css/vendor/readme.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_public/favicon.ico +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_public/js/vendor/readme.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_public/manifest.webmanifest +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_public/readme.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_public/sw.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_templates/handlers/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_templates/html/content/homepage.html +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_templates/html/includes/error-msg-noscript.html +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_templates/html/includes/error-msg-outdated-browser.html +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/boilerplate/bundle_templates/html/layouts/main.html +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/command/gina.bat.tpl +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/command/gina.tpl +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/conf/env.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/conf/manifest.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/conf/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/conf/settings.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/conf/statics.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/conf/templates.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/error/client/json/401.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/error/client/json/403.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/error/client/json/404.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/error/server/html/50x.html +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/error/server/json/500.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/error/server/json/503.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/core/template/extensions/logger/config.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/console.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/context.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/data/LICENSE +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/data/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/dateFormat.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/json/LICENSE +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/json/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/json/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/path.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/plugins/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/plugins/src/api-error.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/plugins/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/prototypes.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/task.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/helpers/text.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/archiver/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/archiver/build.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/archiver/src/dep/jszip.min.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/archiver/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/async/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/async/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cache/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cache/build.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cache/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/aliases.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/add.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/arguments.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/build.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/copy.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/cp.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/inc/name-rewrite.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/list.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/mcp-start.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/mcp.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/oas.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/openapi.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/remove.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/rename.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/restart.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/rm.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/start.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/status.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/bundle/stop.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/cache/stats.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/connector/add.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/connector/arguments.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/connector/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/connector/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/connector/list.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/connector/migrate.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/connector/remove.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/connector/rm.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/env/add.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/env/get.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/env/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/env/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/env/link-dev.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/env/list.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/env/remove.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/env/rm.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/env/set.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/env/unset.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/env/use.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/arguments.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/build.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/dot.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/get.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/init.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/link-node-modules.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/open.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/set.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/status.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/stop.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/tail.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/framework/update.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/gina-dev.1.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/gina-framework.1.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/gina.1.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/i18n/add.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/i18n/arguments.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/i18n/export.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/i18n/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/i18n/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/i18n/import.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/i18n/scan.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/inspector/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/inspector/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/inspector/open.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/minion/arguments.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/minion/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/minion/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/minion/kill.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/minion/list.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/msg.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/port/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/port/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/port/inc/scan.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/port/list.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/port/reset.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/port/set.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/arguments.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/build.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/import.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/list.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/move.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/remove.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/rename.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/restart.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/rm.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/start.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/status.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/project/stop.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/protocol/arguments.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/protocol/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/protocol/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/protocol/list.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/protocol/remove.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/protocol/set.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/scope/add.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/scope/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/scope/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/scope/link-local.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/scope/link-production.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/scope/list.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/scope/remove.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/scope/rm.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/scope/use.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/secrets/arguments.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/secrets/check.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/secrets/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/secrets/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/secrets/scan.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/service/help.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/service/help.txt +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/service/list.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/service/start.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd/view/add.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd-status-format/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cmd-status-format/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/collection/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/collection/build.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/collection/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/config.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/connector-registry/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/connector-registry/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cron/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/cron/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/domain/LICENSE +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/domain/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/domain/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/generator/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/i18n/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/i18n/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/inherits/LICENSE +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/inherits/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/inherits/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/inspector-redact/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/inspector-redact/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/instrument/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/instrument/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/job/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/job/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/logger/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/logger/src/containers/default/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/logger/src/containers/file/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/logger/src/containers/file/lib/logrotator/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/logger/src/containers/file/lib/logrotator/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/logger/src/containers/mq/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/logger/src/containers/mq/listener.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/logger/src/containers/mq/speaker.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/logger/src/helper.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/logger/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/math/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/mcp-dispatch/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/mcp-dispatch/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/mcp-http/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/mcp-http/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/mcp-server/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/mcp-server/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/merge/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/merge/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/metrics/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/metrics/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/nunjucks-filters/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/nunjucks-filters/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/nunjucks-resolver/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/nunjucks-resolver/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/proc.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/routing/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/routing/build.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/routing/src/radix.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/routing-introspect/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/routing-introspect/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/secrets/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/secrets/src/backends/env.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/secrets/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/session-store.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/shell.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/state.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/swig-filters/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/swig-resolver/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/swig-resolver/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/url/README.md +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/url/index.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/url/routing.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/uuid/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/uuid/src/main.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/validator.js +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/watcher/package.json +0 -0
- /package/framework/{v0.4.5 → v0.4.6}/lib/watcher/src/main.js +0 -0
package/llms.txt
CHANGED
|
@@ -51,7 +51,7 @@ framework/v${version}/
|
|
|
51
51
|
|
|
52
52
|
### Swig — `@rhinostone/swig` (npm dependency)
|
|
53
53
|
|
|
54
|
-
Maintained fork of the abandoned swig 1.4.2 template engine. Published to npm as `@rhinostone/swig@2.
|
|
54
|
+
Maintained fork of the abandoned swig 1.4.2 template engine. Published to npm as `@rhinostone/swig@2.7.2` (npm org: `rhinostone`, maintainer: `beemaster`). Repo: `https://github.com/gina-io/swig`. Declared in `framework/v*/package.json` — installed via `npm install` inside the framework directory. Requires `npm install` after clone, merge, or worktree switch (the framework `node_modules/` is gitignored).
|
|
55
55
|
|
|
56
56
|
Previously vendored at `core/deps/swig-1.4.2/` — removed in the migration to the npm dep. CVE-2023-25345 parse-time blocklist (`__proto__`/`constructor`/`prototype`) is patched in the fork's `parser.js` and `tags/set.js`. The path traversal boundary check in `controller.render-swig.js` is Gina's own code and is unaffected by this migration.
|
|
57
57
|
|
|
@@ -656,9 +656,9 @@ The Inspector is a dev-mode SPA embedded in every bundle at `/_gina/inspector/`.
|
|
|
656
656
|
|
|
657
657
|
**Data channels:**
|
|
658
658
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
659
|
+
- **`/_gina/agent` SSE** — remote/standalone mode. Activated by `?target=<bundle_url>` query param. Streams `event: data` (ginaData updates) + `event: log` (server log entries) over a single SSE connection. When active, all other channels are skipped. The "No source" overlay provides a manual connect form that navigates to `?target=<url>` — users can type a bundle URL (scheme auto-prefixed) instead of editing the address bar. Outside dev mode the endpoint is closed unless `inspector.agent.enabled` + `inspector.agent.key` are set in settings.json (#INS9b): the key is presented via the `x-gina-inspector-key` header or a `?key=` query param and compared in constant time (the SPA connect form has an optional key field), production-safe toggle; in dev it stays open and keyless, and enabled-but-bad-key returns 401. In production this authenticates server-log streaming + connection identity only — per-request data/query/flow stays dev-gated (#INS10).
|
|
660
|
+
- **`window.opener.__ginaData`** — same-origin polling every 2 s (primary). Always works since the Inspector and the monitored page share the same origin.
|
|
661
|
+
- **engine.io** — real-time push when the monitored bundle has `ioServer` configured. The Inspector connects to the bundle's own port via `window.location.port`.
|
|
662
662
|
|
|
663
663
|
**Log channels:**
|
|
664
664
|
|
|
@@ -727,7 +727,7 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
727
727
|
|
|
728
728
|
**Binding** (`src/vendor/gina/helpers/binding.js`) — processes binding descriptor arrays (call/handler/payload). AMD module.
|
|
729
729
|
|
|
730
|
-
**Unit tests:** `test/core/popin.test.js` (
|
|
730
|
+
**Unit tests:** `test/core/popin.test.js` (65 tests — perf optimizations, DOM injection, dedup guard, per-load XHR, popinDestroy, registeredPopins, events.js regex/typo, binding.js precedence, dist verification, CSP-safe close, showModal dev/prod parity, opt-in `preOpen` skeleton pre-open).
|
|
731
731
|
|
|
732
732
|
---
|
|
733
733
|
|
|
@@ -759,7 +759,6 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
759
759
|
8. **`${variable}` convention in all docs** — Every path, filename pattern, and inline variable reference in Gina documentation uses `${variable}` (with the leading `$`), never bare `{variable}`. This matches the `whisper()` interpolation syntax. Breaking this convention makes variables look like plain text. Applies to paths (`~/.gina/${shortVersion}/`), filename patterns (`connectors.${env}.json`), framework paths (`${core}/controller/`), and cache paths (`${cache.path}/${bundle}/`). Always scan existing docs before writing new content to verify consistency.
|
|
760
760
|
9. **`gina_version` in `manifest.json`** — optional per-bundle pin. When set, that bundle's spawned process gets `GINA_VERSION`, `GINA_FRAMEWORK_DIR`, and `GINA_CORE` overridden in its context. The socket server is unaffected. CLI flag `--gina-version` takes priority over the manifest declaration.
|
|
761
761
|
10. **`self.render(err)` with object errors**: `render-swig.js` normalises `data.page.data.error` and `data.page.data.message` to strings before building the error response — object fields (e.g. `ApiError` instances, `{message, code}` objects) no longer render as `[object Object]` (#Q2). The normalized string is also used in the `console.error` log that fires before `throwError`.
|
|
762
|
-
11. **Couchbase session store `touch()`/`destroy()` MutationResult leak**: In `session-store.v3.js` and `session-store.v4.js`, `.then(fn.apply(this,arguments))` in `touch()` passed the Couchbase `MutationResult` (`{cas, token}`) as the first argument of the express-session callback. express-session v1.18.1 treats a truthy first arg as an error and calls `defer(next, err)`, routing the raw MutationResult through the framework error handler as a 500 response body. Same issue in `destroy()` via `.then(fn)`. Fixed (#CB-BUG-4): both methods now call `fn(null)` explicitly on success.
|
|
763
762
|
12. **Static file caching — production vs dev**: In production (`NODE_ENV_IS_DEV` not set), `handleStatics` sends `ETag` (`"<size>-<mtime>"`) and `Last-Modified` on every 200 response. If the browser resends `If-None-Match` or `If-Modified-Since`, a **304 Not Modified** (no body) is returned. `If-None-Match` takes precedence. In dev mode (`isCacheless=true`), 304 is never sent — all statics are served fresh with `cache-control: no-cache, no-store, must-revalidate`. The ETag format matches Express/serve-static: `"<byteSize>-<mtimeMs>"`. Both HTTP/1.x and HTTP/2 paths implement the same logic.
|
|
764
763
|
13. **`docs/repo` is a separate git repository** — `~/Sites/gina/docs/repo` cannot be staged or committed from within `gina-dev`. Always `cd ~/Sites/gina/docs/repo` before adding/committing docs changes. `git add ~/Sites/gina/docs/repo/...` from inside `gina-dev` throws "outside repository".
|
|
765
764
|
14. **Sidebar icon key = `sidebar_label.toLowerCase()`** — `Root.js` `NAV_ICONS` map in the Docusaurus site uses the **lowercase** value of the page's `sidebar_label` frontmatter as the map key. If an icon doesn't render, verify the key matches exactly (e.g. `sidebar_label: Security` → key `'security'`).
|
|
@@ -787,22 +786,14 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
787
786
|
|
|
788
787
|
54. **Bundle `config/*.json` shared → bundle overlay is key-level deep merge, not whole-file replace** — `core/config.js:1752-1757` runs `merge(sharedMain, jsonFile, true)` AFTER the bundle file loads. Shared-only keys are preserved; bundle wins on conflicting keys. Applies to `connectors.json`, `env.json`, `settings.json`, and every other per-bundle config file that also has a `shared/config/<same-file>.json` sibling. Reading only the bundle file for config inspection is a silent half-picture — any CLI or tool that inspects per-bundle config must reproduce the overlay merge.
|
|
789
788
|
|
|
790
|
-
|
|
791
|
-
|
|
792
789
|
57. **`schema/connectors.json` — `connector` field is the driver-type identifier, not the logical key** — entries are keyed by logical name (e.g. `"sessionStore"`, `"cache"`, `"primary"`); the driver is identified by the `connector` enum field inside each entry (`couchbase` / `mysql` / `postgresql` / `sqlite` / `redis` / `ai`). When the logical key name differs from the driver, the `connector` field is the authoritative signal. Do not introduce a separate `driver` field — it would be redundant. The `version` field (added 2026-04-21, #CN10 session 2) is optional; holds a semver range or exact version used by `connector:add --driver-version=…` for the `npm install` hint and the future `--install` flag.
|
|
793
790
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
791
|
61. **Negative-invariant tests enforce "we did NOT do X"** — source-inspection unit tests usually assert presence (this function exists, this regex fires). The mirror form — `assert.ok(!/require\(.*connector\/migrate/.test(src))` against `core/config.js` and `bin/cli` — locks in architectural decisions that must not be silently reversed. Use when a session deliberately does NOT ship a feature, does NOT add an integration point, or a file MUST NOT import from a specific path. The test message should name the future release where the change is expected (e.g. "deferred to 0.4.0 alongside #CN8") so a future session removing the negative invariant does so consciously. Don't enforce stylistic preferences as negative invariants — only real architectural rules. Precedent: `test/lib/connector-migrate.test.js` "framework hook absent" block (2026-04-21).
|
|
798
792
|
|
|
799
|
-
|
|
800
|
-
|
|
801
793
|
64. **MCP Streamable HTTP default security posture — loopback bind, Origin allowlist, bearer optional, OAuth out of scope** — when `bundle:mcp-start --transport=http` lands, the canonical security model is (1) bind `127.0.0.1` by default via the framework's existing `host_v4` convention (same chain as the MQ listener and CLI daemon socket — `GINA_HOST_V4` env from `~/.gina/<shortVersion>/settings.json > host_v4`); (2) `Origin` allowlist with a built-in loopback set (`http(s)://localhost`, `127.0.0.1`, `[::1]` on any port) + DNS-rebinding-mitigation 403 on mismatch; (3) static `--auth-token` bearer optional (constant-time via `crypto.timingSafeEqual` with length-mismatch short-circuit — `timingSafeEqual` throws on unequal-length buffers); (4) **OAuth 2.1 is deliberately out of scope** — the community pattern is a reverse proxy (oauth2-proxy, Traefik ForwardAuth, nginx `auth_request`) that handles the OAuth dance and forwards a static-bearer or no-auth request to gina. Traps: (a) WHATWG `URL` returns IPv6 hostnames bracketed (`[::1]`, not `::1`) — loopback check must accept both forms or `http://[::1]:8080` silently 403s; (b) `res.writeHead(status, {...})` passes headers via object — use `setHeader` if you want setHeader + writeHead to merge, or merge manually in one writeHead call; (c) 403 on disallowed Origin must NOT echo CORS (would defeat the check) but 401 on missing/invalid bearer MUST echo CORS (the client is legitimate, it just needs to auth — without ACAO the browser hides the error body); (d) preflight bypasses bearer (browsers cannot carry `Authorization` on an OPTIONS preflight); (e) `http.Server.close()` waits for keep-alive timeout (Node default ~5s) even when no request is in flight — set `keepAliveTimeout = 1` + call `closeIdleConnections()` on stop so SIGTERM shutdowns drain promptly. Library: `lib/mcp-http/`. CLI surface: `--transport=stdio|http`, `--http-host`, `--http-port`, `--max-in-flight`, `--auth-token`, `--cors-origin`. Each flag has a `mcp.json > server > <field>` manifest fallback. Precedent: `#AI8 Phase 2b` (sessions 1–4, commits `79ed1fec5`, `13f767cff`, `1c6d39de3`, `<S4>`, 2026-04-22).
|
|
802
794
|
|
|
803
795
|
65. **Template engines — `lib/swig-resolver` + `lib/nunjucks-resolver` + per-request env mutation invariants** — process-cached resolver-based opt-in for both engines: `core/server.js:initSwigEngine` / `initNunjucksEngine` load via `lib.swigResolver.load()` / `lib.nunjucksResolver.load()` during bundle init; `controller.js this.render()` reads `local.options.conf.content.settings.render.engine` and dispatches to `controller.render-swig.js` (default) or `controller.render-nunjucks.js`. Both delegates receive the same `deps` object (`self`, `local`, `getData`, `hasViews`, `headersSent`, `setResources`, etc.); `process.gina._swig` / `process.gina._nunjucks` survive `refreshCoreDependencies()` evictions. Both: dev-mode mtime hot-swap on project's `package.json`, first-wins standalone-mode contract, `[<engine>-resolver]` warning logged on safety-gate fallback. **Swig**: framework fallback enabled (always-installable), version floor `DEFAULT_MIN = '2.0.0'` (bump in same commit as fork-API dependence — see global "Swig version sync" rule), three safety gates (package-name allowlist preventing CVE-2023-25345 re-entry, same-major rule, min-version floor), Phase 7 build copies upstream esbuild output to `dist/swig.min.js` (no longer Closure-compiles `bin/swig.js` — swig 2.0.0's `swig-core` lazy require breaks Closure's static analysis). **Nunjucks**: NEVER framework-bundled (resolver only looks in `<projectPath>/node_modules/nunjucks/`), `load()` throws `NUNJUCKS_NOT_INSTALLED` with no fallback (bundle startup fails loud, not mid-render). `new nunjucks.Environment(loader, ...)` cached per template root on `process.gina._nunjucksEnvs`; dev-mode `FileSystemLoader({ noCache: true })` for `.njk` template hot-edit. **Render-nunjucks.js implementation invariants**: per-request `addFilter → render` mutation of cached env is safe ONLY because `env.render()` is synchronous (Node's event loop serialises requests; an async-render port would need a per-request env, no cache); pre-render `setResources()` + post-render `injectAssets()` cooperate via exact-substring user-placement detection (#NJ2) — pre-only loses auto-injection, post-only double-injects on opt-in templates; both wrap bodies in try/catch (must NOT 500 the page); `injectAssets` runs BEFORE `injectInspectorScripts` (Inspector payload stays last `<script>` before `</body>`); `isWithoutLayout` filters via `Collection.find({isCommon:false},{isCommon:true,name:'gina'})` on a `JSON.clone(localTemplateConf)` to keep common-gina assets while dropping common-other. Test-author traps (both resolvers): mtime collision within same wall-clock ms requires monotonic counter; macOS `os.tmpdir()` symlink requires `fs.realpathSync` for `require.cache` key parity.
|
|
804
796
|
|
|
805
|
-
|
|
806
797
|
72. **Vendored-deps + Socket / supply-chain visibility — enumeration, peerDependencies, vendored sub-`package.json`, build-bundle orphans** — Socket's category flags are boolean per package (one site fires for the whole tarball); enumerate via `npm view <pkg>@<ver> dist.tarball` + `curl` + `tar tzf` (NOT `npm pack --dry-run` — triggers `prepare_version.js`'s "Prerelease update" commit). Post-#SCS1+SCS1b+SCS1d+SCS1e live count: 12 eval sites in `framework/v*/` (15 of 24 cleared); load-bearing circular-require workarounds in `lib/logger`/`file/index.js`/`mq/index.js` are FIRST suspects for any `eval`/`new Function` site wearing a "publishing hack" or "needed for unit tests" comment. **`peerDependencies` aggregate into the dep graph regardless of `optional: true`** — Socket / Dependabot / `npm audit` all read peerDeps unconditionally; fix pattern is to remove from `peerDependencies` entirely and move version ranges to a lib-local registry (gina's `lib/connector-registry/`). **Vendored sub-`package.json` files leak declared externals through dep-graph edges** (`busboy → streamsearch`) — but this is BY DESIGN and load-bearing for CVE visibility; do NOT strip (incorrect strip in `4a29ca0c` reverted in `e5d5d0a2` alpha.6). **Reachability probe before adding eval sites to a session scope**: confirm file path matches one of the 5 bundled-source patterns enforced by `.githooks/pre-commit`. **Security-tagged source commits MUST rebuild the bundle in the same commit** (`.githooks/pre-commit` + `.github/workflows/bundle-freshness.yml` enforce). **SASS auto-discovery** in `core/asset/plugin/build` is independent of `build.json` JS alias map — three-probe orphan check (a/b in JS aliases vs c in `sass/` dir) catches CSS-only orphans like the toolbar 22.5KB stale CSS shipped pre-`69fb32fc`. **"Kept in sync by hand" anti-pattern** across N≥2 files always drifts; cure is a shared lib + supply-chain cleanup as forcing function.
|
|
807
798
|
|
|
808
799
|
76. **Docusaurus pages with `id: <slug>` frontmatter are served at `/<ns>/<id>`, not `/<ns>/<filename>` — external references using filename-form land on 404.** Diagnostic: if a doc URL 404s, probe sibling pages in the same subsection — all siblings 404 + section root (`/<ns>/`) 200 = the `id:` trap, not a missing page. Section-index pages (`index.md`) are immune (no `id:` override). Fix (minimum blast radius): add `slug: /<ns>/<filename>` to each page's frontmatter — keeps the `id:` that other pages reference in cross-links, overrides only the URL. Dropping `id:` also works but requires sweeping every `prereqs`/doc-id reference across the site. Precedent: 2026-04-24 link-health audit surfaced 11 × 404 across `/docs/cli/*` at filename-form URLs (all pages resolve at `/docs/cli/cli-<filename>` because of `id: cli-<name>`) — filed as gina-io/docs#11.
|
|
@@ -811,15 +802,10 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
811
802
|
|
|
812
803
|
80. **CSRF — three-phase protection (`#CSRF1` Session plugin / `#CSRF2` signed double-submit token / `#CSRF3` Origin pre-filter)** — Session plugin hardens `express-session` cookie defaults from `settings.json > session.cookie.{sameSite, httpOnly, secure}` (opt-in, one-line bundle bootstrap swap, never transparent because bundles legitimately set `httpOnly: false`); signed token middleware uses `crypto.timingSafeEqual` HMAC-SHA256 bound to `req.session.id` with per-route `routing.json > "csrfExempt": true` opt-out (see `routing-and-http2.md § "Per-route flags"` for `req.routing.csrfExempt` two-step propagation: `lib/routing` extracts → `core/server.js` hoists, top-level on `req.routing` not under `param.*`); Origin pre-filter folded INSIDE `gina.plugins.Csrf()` BEFORE token verify on mutating methods, allowlist via `settings.json > csrf.allowedOrigins` (empty defaults to `[bundleHostname]` auto-derived). Negative-invariant lock: matching token + mismatching Origin still 403s — token layer ≠ Origin layer. Implementation traps from the trilogy: plan-vs-shipped attribute drift (downstream commits must `grep -n '<attribute>' <upstream-source-file>` before referencing — source on develop wins, not the plan); CI flake-vs-regression triage (same-message-different-SHA pairs need `git diff --stat A..B` first); source-inspection `indexOf` matches the function DEFINITION before the call site (use unique assignment LHS like `requestOrigin = parseRequestOrigin(req)`); `Origin: "null"` is a real browser value (sandboxed iframes / `file://`) and needs an explicit `s === 'null'` guard so the parser falls through to Referer instead of 403'ing for "origin not allowed".
|
|
813
804
|
|
|
814
|
-
|
|
815
|
-
89. **Lead-side hedging via "open follow-up questions" in design recommendations is a distinct failure mode of the measure-first / no-static-analogy rule — even AFTER doing the measurement, the output shape can still punt the synthesis the recommender owes.** Pattern: investigation done (gap analysis, regex inspection, git-log windows, code-path verification), recommendation written, but it ends with "(i) do you want strict X or relaxed Y? (ii) for the secondary check, signal A or signal B?" — two-options framing that looks measured but is actually pushing the synthesis back to the user. **Detection**: open follow-up questions of the form "X vs Y — which do you want?" at the end of a recommendation are almost always hedging. The recommender has the data; if the data does not discriminate, the answer is "not enough data, would need observation Z to resolve" — not "you pick". **The fix shape**: pick the loosest-but-sufficient assertion the data supports, name explicitly what would change your mind (a falsifying observation, a constraint shift), and commit. If a tradeoff genuinely needs user input (a policy choice between strict-and-friction vs lenient-and-flexible), frame it as "I recommend X for these reasons; the only reason to pick Y is if [explicit constraint] applies — does it?" — that is still committed reasoning, with the user's input narrowed to the one variable that actually moves the answer. **Generalises to any agent or lead recommendation in any project.** This trap operates one layer up from the lead-side static-analogy reasoning rule (already documented as a session rule): static-analogy is "I skipped measurement and reasoned by analogy"; this is "I did measure, but my output shape still hedges". The user's reflex "what's your measured take?" catches both. Rule of thumb: a measured recommendation that ends with two open questions about which design knob to turn has not finished the work. Precedent: 2026-05-05 README freshness gate session — first recommendation flagged for static-analogy reasoning (proposed extending the existing `.local-sync-targets.json` mechanism to README.md, by analogy from the local-doc auto-bump pattern, without measuring whether the regex actually applied to README's bare-version mentions). After the user's "is this measured?" prompt, the second recommendation did the measurement (read README.md, ran git log against the tag pairs, inspected the regex, reviewed the existing checklist) but ended with "(i) strict heading-match vs lag-one-minor — which? (ii) compare swig against npm-resolved or against framework dep — which?". The user's "what's your measured take?" prompted a corrected version that dropped both questions: primary gate is touch-since-tag (catches every form of forgetfulness), secondary heading/swig assertions skipped as redundant. Both layers of the same trap surfaced in one session — useful worked example for the next one.
|
|
816
|
-
|
|
817
805
|
90. **Socket's "Obfuscated code" alert on `gina.min.js` is a verified false positive (Mozilla Public Suffix List data table from `psl@^1.15.0`, byte-identical-data-shape to the standalone `psl@1.15.0` package which Socket scores 100/100) — dismiss per-version on Socket UI with the standing justification text in the local-tool playbook's Socket section. DO NOT attempt the build-pipeline lazy-load (r.js `exclude` of `vendor/gina/psl` + standalone PSL chunk + runtime AMD path config in `core.js`) — it breaks the consumer sync-require contract: gina-consuming bundles routinely call `require('gina/link')` synchronously inside their `onGinaReady` callback, and that contract cannot be satisfied once `lib/domain → vendor/gina/psl` becomes an async XHR (`lib/domain` never resolves → `gina/link` never resolves → consumer hits `Module name "gina/link" has not been loaded yet for context: _`). Verified failure during browser smoke against a representative consumer bundle on 2026-05-05.** A source-level lazy-load (modify `lib/domain/src/main.js` to drop `vendor/gina/psl` as an AMD dep and defer `require(['vendor/gina/psl'])` into the `Domain(cb)` constructor's already-async callback path) would work but the engineering cost (4-8 hrs + cross-route smoke across each of the consumer's nginx static docroots + ongoing standalone-chunk deploy sync) doesn't pay back the per-version dismiss friction (~5-25 min/year). The standing decision is to dismiss per-version. The post-publish dismissal step is wired into both Alpha and Stable Release Checklists in the local-tool playbook.
|
|
818
806
|
93. **Reverse-proxy path-prefix awareness via `X-Forwarded-Prefix` — the bundle's internal `server.webroot` stays `/`, but the value templated into `gina.config.webroot` carries the proxy's mount path so client-side root-relative URLs (`/_gina/assets/routing.json`, the `gina.min.css` link injection, etc.) target the correct upstream through the proxy instead of routing to whichever bundle answered bare `/`.** Standard header used by Spring Boot, Traefik, FastAPI, ASP.NET Core, NestJS, Quarkus. Capture site: `core/server.isaac.js` proxy-detection block (sibling to the `X-Forwarded-Host` / `X-Forwarded-Proto` reads); normalisation rules — leading slash added if missing, trailing slashes stripped, empty / `"/"` dropped; stored on `process.gina.PROXY_PREFIX`. Composition site: `core/controller/controller.js` at the `set('page.environment.webroot', ...)` call (~line 513) — when `PROXY_PREFIX` is set, public webroot becomes `prefix + bundle.server.webroot` (slash-normalised), then templated into `gina.onload.min.js`. Internal disk-path resolution and asset-rewrite call sites (e.g. `controller.js:927,956`) still use the bundle's native `server.webroot` — only the browser-facing `gina.config.webroot` carries the prefix; the proxy strips the prefix when forwarding upstream so the bundle's static handlers receive native paths. Symptom shape that surfaces this gap: browser fetches `/_gina/*` URLs that nginx routes to the wrong upstream (the bundle that owns bare `/`, not the bundle that rendered the page); inspector indicator names one bundle but `routing.json` contents come from another. Server-side asset URL rewriting at `controller.js:927,956` and the `linkTo` family also write URLs intended for browser consumption — same prefix-awareness gap, deferred follow-up. The bare `/_gina/assets/routing.json` handler exists only in `server.isaac.js` with no engine-agnostic equivalent in `server.js`; bundles using the Express engine don't have this endpoint at all — independent of this slice but worth noting on the next `/_gina/*` parity sweep. Established 2026-05-06 after the symptom surfaced from a multi-bundle nginx deployment where a sub-path bundle's pages were fetching the root bundle's `routing.json`.
|
|
819
807
|
|
|
820
|
-
94. **ScyllaDB / Cassandra connector — `cassandra-driver` wrapper + fictitious `@scylladb/scylla-driver` registry placeholder + CQL ergonomics + `USING TTL` session store** — The Node.js ecosystem has no first-party shard-aware ScyllaDB driver (Python / Java / Go / Rust drivers are first-party shard-aware; Node.js is not), so the framework `scylladb` connector wraps `cassandra-driver` (Apache Software Foundation, `>=4.0.0` requires Node >=20, eval-safe per #SCS1 posture). Pre-#CN5 `lib/connector-registry/src/main.js` pinned `@scylladb/scylla-driver@>=1.0.0` as a placeholder — that npm package does not exist (404), which would have failed at install time the moment `connector:add primary --connector=scylladb --driver-version=...` printed its install hint. **Always `npm view <pkg>` to confirm the package exists** when populating registry-style mappings — same shape as the #I18N2 `intl-messageformat@9.13.0` non-existent-version trap. **CQL ORM** mirrors the SQL connector shape: entity files at `models/<keyspace>/cql/<Entity>/*.sql` (distinct dir like Couchbase's `n1ql/`, uniform `.sql` extension), `?` positional placeholders, `client.execute(query, args, { prepare: true })` returning Promise + `.onComplete()` shim, `@param` casting for CQL types (text/int/bigint/uuid/timeuuid/timestamp/boolean/blob/list/set/map/decimal/double/inet), `@return` coercion handling SELECT rows / LWT `[applied]` / write defaults. **CQL session store** has no Redis-EXPIRE equivalent — `set()` issues `INSERT … USING TTL <ttl>` (atomic write + expiry); `touch()` issues `UPDATE … USING TTL <ttl> SET sess = ? WHERE sid = ?` (rewrites data with fresh TTL); `clear()` uses CQL `TRUNCATE` (heavy cluster op — the only full-table delete CQL allows). Required schema: `CREATE TABLE sessions (sid TEXT PRIMARY KEY, sess TEXT) WITH default_time_to_live = 86400`. Promise → callback safety mirrors post-#CB-BUG-4: write methods MUST call `fn(null)` explicitly inside `.then()`, never `.then(fn)` directly. Established 2026-05-09 (#CN5).
|
|
821
|
-
|
|
822
|
-
95. **MongoDB connector — `mongodb` (official driver) wrapper + JSON `pipelines/` files + TTL-index session store** — Document store connector targeting `mongodb >=7.0.0` (current GA). The pre-existing `lib/connector-registry/src/main.js:68` entry pinned an abandoned `>=5.0.0` from the #CN9 placeholder; #CN6 bumped to `>=7.0.0` (latest mongodb is `7.1.1` released March 2026 — three majors past `5.0.0`). Same `npm view <pkg> version` discipline as #CN5's stale-pin trap (#94). **JSON pipeline files at `models/<db>/pipelines/<Entity>/*.json`** describe one Mongo operation each (op + filter / pipeline / doc / etc.) with optional leading JSDoc header carrying `@param {<bson-type>}` and `@return` annotations. Argument placeholders use `{"$arg": N}` (positional, EJSON-like); ObjectId literals use `{"$oid": "<hex>"}`; the literal string `"$scope"` is substituted at load time with the bundle's data isolation scope (`substituteScope` tree-walk in `pipeline-loader.js`). Supported ops: `findOne | find | aggregate | countDocuments | insertOne | insertMany | updateOne | updateMany | replaceOne | deleteOne | deleteMany`. **`castParam`** handles BSON shapes: `objectid` (wraps via `new ObjectId(value)`), `int/int32`, `long/int64` (preserves bigint), `double/number` (parses comma decimal), `boolean` (`/^true$/i`), `string/text`, `date/timestamp`. `Decimal128` and `Binary` deferred to a follow-up. **Session store** auto-creates `createIndex({expiresAt: 1}, {expireAfterSeconds: 0, name: 'sessionsExpiresTTL'})` on first `set()` with `_ttlReady` one-shot guard plus `_ttlPromise` dedupe for concurrent first calls. `IndexOptionsConflict` warn-and-continue (operator intent wins). Document shape `{_id: sid, sess: <JSON string>, expiresAt: Date}`. `get()`, `length()`, and `all()` filter `{expiresAt: {$gt: new Date()}}` to protect against MongoDB's TTL monitor 60-second interval — substantive divergence from ScyllaDB where CQL `USING TTL` reaps server-side and "expired" rows literally don't exist (same intent: return active sessions only, different mechanism). Self-contained — builds its own `MongoClient` (does NOT reuse the `MongodbConnector` entity manager), matching every other gina session store (scylladb / sqlite / redis / couchbase). Promise → callback safety mirrors §13.6 / #CB-BUG-4: every write method calls `fn(null)` explicitly inside `.then()`, never `.then(fn)` directly. Established 2026-05-09 (#CN6).
|
|
808
|
+
94. **ScyllaDB / Cassandra connector — `cassandra-driver` wrapper + fictitious `@scylladb/scylla-driver` registry placeholder + CQL ergonomics + `USING TTL` session store** — The Node.js ecosystem has no first-party shard-aware ScyllaDB driver (Python / Java / Go / Rust drivers are first-party shard-aware; Node.js is not), so the framework `scylladb` connector wraps `cassandra-driver` (Apache Software Foundation, `>=4.0.0` requires Node >=20, eval-safe per #SCS1 posture). Pre-#CN5 `lib/connector-registry/src/main.js` pinned `@scylladb/scylla-driver@>=1.0.0` as a placeholder — that npm package does not exist (404), which would have failed at install time the moment `connector:add primary --connector=scylladb --driver-version=...` printed its install hint. **Always `npm view <pkg>` to confirm the package exists** when populating registry-style mappings — same shape as the #I18N2 `intl-messageformat@9.13.0` non-existent-version trap. **CQL ORM** mirrors the SQL connector shape: entity files at `models/<keyspace>/cql/<Entity>/*.sql` (distinct dir like Couchbase's `n1ql/`, uniform `.sql` extension), `?` positional placeholders, `client.execute(query, args, { prepare: true })` returning Promise + `.onComplete()` shim, `@param` casting for CQL types (text/int/bigint/uuid/timeuuid/timestamp/boolean/blob/list/set/map/decimal/double/inet), `@return` coercion handling SELECT rows / LWT `[applied]` / write defaults. **CQL session store** has no Redis-EXPIRE equivalent — `set()` issues `INSERT … USING TTL <ttl>` (atomic write + expiry); `touch()` issues `UPDATE … USING TTL <ttl> SET sess = ? WHERE sid = ?` (rewrites data with fresh TTL); `clear()` uses CQL `TRUNCATE` (heavy cluster op — the only full-table delete CQL allows). Required schema: `CREATE TABLE sessions (sid TEXT PRIMARY KEY, sess TEXT) WITH default_time_to_live = 86400`. Promise → callback safety mirrors post-#CB-BUG-4: write methods MUST call `fn(null)` explicitly inside `.then()`, never `.then(fn)` directly. Established 2026-05-09 (#CN5). **MongoDB connector (#CN6)** — wraps the official `mongodb` driver (`>=7.0.0`); JSON op files at `models/<db>/pipelines/<Entity>/*.json` with `{"$arg":N}` / `{"$oid":hex}` / `"$scope"` placeholders + `castParam` BSON coercion (`objectid`/`int`/`long`/`double`/`date`/etc.); self-contained TTL-index session store auto-creates `{expiresAt:1}` (`expireAfterSeconds:0`, one-shot `_ttlReady` guard, `IndexOptionsConflict` warn-and-continue) and filters `{expiresAt:{$gt:now}}` because Mongo's TTL monitor lags up to 60s (vs Scylla's server-side `USING TTL` reap — same intent, different mechanism). Both connectors' registry pins must EXIST and be CURRENT (`npm view <pkg>` — the `@scylladb/scylla-driver` 404 + stale `mongodb>=5` traps). Established 2026-05-09 (#CN6).
|
|
823
809
|
|
|
824
810
|
96. **Layout cache concurrent-render ENOENT race — atomic temp+rename closes the gap window between `rmSync` and the post-priming read.** `controller.render-swig.js` primes a per-template layout cache file at `<cachePath>/<bundle>/swig/<subFolder>/layout.html` from the `{% extends "..." %}` directive, then reads it back ~340 sync-code lines below to inline assets. In dev mode (`_cacheIsEnabled !== 'true'`) the priming block USED to delete-then-rewrite (`fs.rmSync(newLayoutFilename)` followed by `await fs.promises.writeFile(newLayoutFilename, buffer)`), which opened a gap window: a concurrent render of the same URL could `rmSync` the file AFTER the first render's `writeFile` completed but BEFORE its later `readFile` syscall opened the file, leaving the first render with `Error: ENOENT: no such file or directory, open '<cache>/<bundle>/swig/.../layout.html'`. Symptom shape: intermittent 500s on parallel POST/GET bursts to the same URL in dev mode (production unaffected — cached mode skips the rmSync entirely). **Fix** — collapse `rmSync + writeFile(target)` into `writeFile(temp) + rename(temp → target)` at BOTH cache-write sites (the priming block AT THE TOP and the post-asset-injection rewrite ~640 lines below). POSIX `rename(2)` is atomic on the same filesystem, so concurrent readers always observe either the prior content or the new content, never an absent file. Per-process unique temp names (`<target>.tmp.<pid>.<Date.now()>.<random>`) avoid TOCTOU on the temp itself. The CVE-2023-25345 `{% extends %}` path-traversal boundary check at the priming block is preserved verbatim. **Reproduction** — a tight in-process Promise.all(50) misses the race because all attempts march in lockstep through awaits; the natural staggering needed for the race to fire comes from the pre-block request-lifecycle awaits in a real HTTP server (routing/middleware/controller setup). A unit-level harness can amplify the race by adding `for (k=0; k<5; k++) await Promise.resolve()` BEFORE the racy block plus a ~10ms sync CPU sink between writeFile and readFile and 0-50ms setTimeout stagger across attempts — that recipe surfaced 205/1000 ENOENT failures pre-fix, 0/1000 post-fix. **Test surface** — `test/core/render-swig.test.js` § 11: four source pins (rmSync removed, writeFile+rename pair at both cache-write sites, temp name embeds pid/time/random) + CVE-2023-25345 preservation pin + a behavior test that exercises 200 concurrent atomic writes and asserts 0 ENOENT. Generalises to any "delete then rewrite a shared file under concurrent renders" pattern — atomic temp+rename is the canonical resolution wherever the read window can outlive the rewrite cycle. Established 2026-05-11.
|
|
825
811
|
|
|
@@ -829,10 +815,8 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
829
815
|
|
|
830
816
|
99. **Probe-first protocol for removing load-bearing fallback code** — when a defensive fallback (eval fallback, try/catch shim, recovery branch) has documented history of being load-bearing despite "looking like dead code", apply the structural fix at the cause-side FIRST, then instrument the fallback with a stderr probe BEFORE removing it, then run the full test suite to observe whether the probe fires. Only remove the fallback (and the probe) AFTER observing zero probe fires across all sites. This turns "I reasoned the structural fix breaks the trigger condition" into "the test suite empirically confirms the fallback never executes after the structural fix". **Worked example** — #M22 logger circular-require (2026-05-14). The 3 eval fallbacks at `lib/logger/src/main.js:69` + `containers/file/index.js:16` + `containers/mq/index.js:10` had bitten a prior session (#SCS1c, commit `ae932a5e`, 2026-04-23) — the evals were removed WITHOUT the structural fix and every logger-consuming test crashed with `TypeError: merge is not a function at init`. The 2026-05-14 protocol: (1) apply ONLY the structural fix in `lib/merge/src/main.js` (`require('../../../helpers')` → direct `require(__dirname + '/../../../../../utils/prototypes.json_clone')`, same `JSON.clone` population without going through the for-loop helper loader). (2) Add `process.stderr.write('[m22-probe] eval fallback fired at <file>:<line>\n')` at each fallback site, KEEPING the eval intact. (3) Run full local suite, capture stderr, `grep -c '\[m22-probe\]'`. **Zero fires** = empirical evidence the structural fix worked. (4) Only THEN remove the probes + evals. (5) Re-run suite to verify clean. ~10 minutes of work; the equivalent "reason about it and hope" approach was the failure mode of #SCS1c. **Detection signal that the protocol is warranted**: any session prompt or proposal phrased as "X is load-bearing because the prior attempt failed when X was removed". When you see that shape, surface the probe-first protocol BEFORE any code deletion. **Reflex check**: if your proposed protocol is "apply X subset first, then the rest" without a measurement justification for the subset choice, you're hedging, not measuring. The user's "is this your measured recommendation?" reflex catches this — answer is to surface positive evidence (probe never fires) rather than just smaller-blast-radius framing. Sister rule: the global "No fix without measurement" + the "Verification has THREE outcomes" expansion. Established 2026-05-14 (#M22, commit `2247e22e`).
|
|
831
817
|
|
|
832
|
-
|
|
833
818
|
101. **Docs-site `.md` JSX-leak + `plugin-client-redirects` vs `onBrokenLinks` non-composition** — Docusaurus' `markdown.format: 'detect'` does NOT reliably promote a `.md` file to MDX from JSX usage alone, so manual `import` / `<JSX>` blocks in `.md` source leak as visible body text on the rendered page (e.g. `import DocMeta from '@site/src/components/DocMeta';` literally rendered as a `<p>` paragraph above body content). Convention: doc authors put metadata in frontmatter, the `readingTimePlugin` remark plugin auto-injects `<DocMeta>` into every `/docs/` page at build time; never write `import` / JSX in `.md` source (rename to `.mdx` if you need genuine JSX). Separately, `@docusaurus/plugin-client-redirects` does NOT satisfy `onBrokenLinks: 'throw'` at build time — redirect pages generate at build-OUTPUT stage, AFTER the broken-link checker runs against the source-derived doc tree, so internal links must use the canonical URL form (e.g. `/cli/cli-bundle` for CLI pages with `id: cli-<file>` frontmatter) not redirect aliases (`/cli/bundle`). Audit methodology lesson: body-link sweep extends prereq-frontmatter sweep — body links use the same anti-patterns and break the build identically. Detection greps: `grep -rln "^import " ~/Sites/gina/docs/repo/docs --include="*.md"` (JSX-in-md leaks) and `grep -rnE "\]\(/cli/(bundle|cache|connector|env|framework|port|project|protocol|scope|service|view)([#)/]|$)" ~/Sites/gina/docs/repo/docs/` (filename-form CLI body-link anti-pattern). Established 2026-05-14 (#D9 audit, commits `e3e21b6` secrets.md broken-link fix + `57a19d3` connector.md/service.md DocMeta cleanup).
|
|
834
819
|
|
|
835
|
-
|
|
836
820
|
103. **A `gina start` framework server left running during a session can hang the full test suite AND bundle boot — shared root is `~/.gina/procs.json` staleness forcing the server onto a non-canonical port.** Two symptoms observed 2026-05-14 (0.3.13 #R6/#H9 session): (1) the full `node --test` suite hung ~40 minutes on `test/core/entity-arguments.test.js` — that test connects to the MQSpeaker on `:8125`, and a *degraded* framework server (alive but not on the canonical `:8124`) leaves the connect pending forever because the host's node flags carry `--test-timeout=0` (no per-test timeout → infinite hang, the run never completes, no green/red verdict). Killing the framework server makes the same test pass in ~67ms — a fast `ECONNREFUSED` on `:8125` instead of a hang, the test logs a warn and proceeds. (2) `gina bundle:start` AND `bin/gina-container` both hang during bundle boot right after the MQSpeaker connect, before binding the HTTP port. **Root cause for both:** `~/.gina/procs.json` accumulates stale entries from long-dead gina versions (pids that no longer exist still claiming `:8124`); `gina start` for the current version then binds a *different* port (observed `:3199`), and anything expecting the framework server at the canonical `:8124` hangs or misbehaves. **Practical rules:** before running the full local suite, ensure no `gina start` server is alive (`lsof -nP -iTCP:8124 -sTCP:LISTEN`) and add `--test-timeout=120000` to the `node --test` invocation as a safety net so a single hung test fails fast instead of stalling the whole run (healthy full-suite runtime is ~6s). If bundles will not boot on a dev host, suspect stale `procs.json` first — and before assuming a code change is at fault, run a zero-content subtract test: scaffold a bundle with `bundle:add` only (no `view:add`) and confirm it hangs identically. Established 2026-05-14 during the gina 0.3.13 #R6/#H9 value-add session.
|
|
837
821
|
|
|
838
822
|
104. **`require.cache[path]` must hold a `Module` instance — `require.cache[path] = require(path)` poisons the slot.** Node reads `.exports` off each `require.cache` entry on every subsequent `require()`; assigning the bare exports object into the slot (no `.exports` key) makes the next plain `require()` return `undefined`, surfacing downstream as `Cannot read properties of undefined (reading '<X>')`. Correct hot-reload shapes: `delete require.cache[require.resolve(path)]` then use the `require()` return value directly, **or** `require.cache[c].exports = require(path)` (keeps the `Module`, swaps only `.exports`) — never `require.cache[path] = require(path)`. Fixed in `refreshCore()` (`core/server.isaac.js`, `add6655e`); the 13 `|| require.cache[require.resolve(...)]` workaround sites across the controller layer + connectors are now redundant dead code. **`router.js` had the same antipattern at two sites** (`refreshCoreDependencies()` ~L116-127 and the controller-require path ~L617-627) — latent because router.js was the slot's only consumer and read it back self-consistently. Fixed 2026-05-16 (#B18) by dropping the `require.cache[path] = require(path)` line at both sites and reading `SuperController = require(path)` directly; the preceding `delete require.cache[require.resolve(path)]` already evicts the slot so the next `require()` builds a fresh `Module`. Test coverage in `test/core/router.test.js § 10` (8 tests: source pins enforcing the antipattern is gone, both fix sites preserve the delete-before-require shape, plus a pure-logic replica showing why poisoning breaks plain `require()`). Established 2026-05-14; fix closed 2026-05-16.
|
|
@@ -843,19 +827,10 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
843
827
|
|
|
844
828
|
111. **#S7 — `/_gina/info` and `/_gina/cache/stats` IP-allowlist gate (admin axis, mirrors #OBS1 `/_gina/metrics`).** Both endpoints expose process state (`process.memoryUsage` / `uptime` / `version` / HTTP/2 session counters at `/_gina/info`, full cache contents at `/_gina/cache/stats`) — admin-grade information that should not reach public callers. The gate lives at `server.isaac.js` module scope as `isAdminClientAllowed(req)` and is invoked at the top of each handler (~L664 for `/_gina/info`, ~L707 for `/_gina/cache/stats`) returning a 403 JSON `{error:'forbidden',message:'...client IP not in app.json admin.allowFrom'}` with cache-control headers on deny. Allowlist is configured via the new `admin.allowFrom` block in `app.json` (schema/app.json declares it as a sibling of `metrics`); defaults to `['127.0.0.1', '::1']` (loopback) when omitted; empty array `[]` is explicit deny-everyone. `core/gna.js` reads the block in the existing `server.on('started')` callback (right after the metrics init block) and caches it on `process.gina._adminAllowList` so `server.isaac.js` can read it without a per-request `gna.getConfig` call. Five invariants mirror the `lib.metrics.isClientAllowed` pattern: (1) IP is read from `req.socket.remoteAddress` only — `X-Forwarded-For` is NEVER trusted because reverse proxies could spoof it; (2) `::ffff:IPv4` is normalised to `IPv4` so listing `127.0.0.1` matches both forms; (3) `req.connection.remoteAddress` is a fallback for older Node versions; (4) empty allowlist denies everyone (explicit lockdown); (5) missing `process.gina._adminAllowList` falls back to loopback (defensive against init order). `/_gina/health/check` is intentionally NOT gated (k8s liveness probes need it open). `/_gina/metrics` keeps its own separate `metrics.allowFrom` gate — separate axis from `admin.allowFrom`, so bundles that disable metrics still get the admin lockdown. Test coverage in `test/core/server.isaac.test.js § 08` (source structure pins on the helper + both handler invocations + gna.js init + schema declaration) + `§ 08b` (10-case pure-logic replica covering loopback/non-loopback × IPv4/IPv6/IPv6-mapped × empty-allowlist × `req.connection` fallback × missing-socket × X-Forwarded-For spoof). The shape is reusable for any future admin-grade `/_gina/*` endpoint — copy the gate check + add to the handler. Established 2026-05-16.
|
|
845
829
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
830
|
117. **Mermaid sequence-diagram parser (current version in Docusaurus) treats `;` inside message bodies as a statement separator → parse error on the next line.** Symptoms: diagram fails to render with `Parse error on line N: ..., 'xxx'); next() end App->>C` in the browser console; page shows a "Try again" placeholder where the diagram should have been. The `;` in a message like `Plugin->>App: res.setHeader('x-content-type-options', 'nosniff'); next()` gets parsed as a statement separator, splitting the line; mermaid then misreads the following `end` (alt/else block close) and `App->>Ctrl:` (next arrow) as malformed continuations of the prior arrow. Fix: replace `; next()` (or any `; <next-statement>` pattern) with `, then next()` (or any non-`;` separator). Same visual meaning, no `;` to confuse the parser. Generalises to: never use `;` in sequence-diagram message bodies (or note bodies). Use `,` / `then` / `→` / `+` instead. Precedent: 2026-05-17 — `docs/guides/security-headers.md:34` + `docs/guides/csp.md:44` both had the bug; pre-existing on Phase 1 docs but only surfaced after a Mermaid library upgrade in Docusaurus made the parser stricter. Older Mermaid was more permissive about `;` in messages; current is not. The bug doesn't trip per-file Mermaid linters since `;` is technically a valid character in messages — only the runtime parser rejects. Established 2026-05-17.
|
|
852
831
|
|
|
853
|
-
|
|
854
832
|
119. **Test count estimation drift — never write an "N unit tests" claim before running the test; count from `node --test` output, don't estimate from the section count.** Estimating from section count (e.g. "6 sections × ~5 tests = ~30") consistently undercounts because behaviour sections (token validation, factory + middleware end-to-end) tend to be 10-15 tests, not 5. The corrective is mechanical: write the test file → run `node --test test/core/<plugin>.test.js 2>&1 | tail -10` → copy the `ℹ tests N` line → write that exact N in the roadmap docs (public + internal) + the changie YAML. Substitute `TODO_test_count` placeholders in drafts and fill from the actual output before staging. Precedent: 2026-05-17 #HDR8-12 implementation — claimed counts 30 / 41 / 33 / 32 in roadmap + changie drafts; actual `node --test` results were 37 / 57 / 37 / 35. Required a corrective edit pass on 5 plugin commits before staging — 3 Edit tool calls per plugin × 5 plugins = 15 unnecessary edits at ~10 sec each. Cost of the fix: 1 Bash call per plugin (the actual test run that would have happened anyway). Generalises to any feature-shipping work where the roadmap entry includes a test count (every #HDR / #CSRF / #CN / #SCS slice). Established 2026-05-17.
|
|
855
833
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
834
|
123. **Framework-attribution leak sweeps must cover Mermaid `participant` declarations as a separate surface from prose blocks and inline code examples.** When correcting the standalone-Express idiom across a docs family (per llms.txt #118), the prose-sweep grep targets `require('express')` / `var app = express();` in JS code blocks. That grep does NOT match Mermaid `participant App as express()` lines — diagram syntax is structurally different from prose patterns. The canonical sibling cross-check (csrf.md, the gate prescribed by #118) also fails to reveal the gap because csrf.md's own diagram doesn't have an "App" participant at all (it uses middleware-component participants — `Browser`, `Csrf middleware`, `Controller action` — directly). So a prose-only sweep can leave Mermaid diagrams carrying the wrong adoption-shape indefinitely. Mitigation: when running the #118 sweep, add a SECOND grep over the docs tree: `grep -rn 'participant.*express\|App as express' docs/`. Fix shape: drop the `App` participant entirely and route arrows Client → Plugin → Ctrl directly, matching csrf.md's convention (one less intermediate participant; the `next()` call doesn't bounce through a separate App layer at the diagram level). For diagrams that carry a `Note over App,Plugin: ...` caption, reattach to `Note over Plugin: ...` since App is removed. Precedent: 2026-05-17 — `docs/guides/security-headers.md:24` + `docs/guides/csp.md:32` both had `participant App as express()` (carried forward from helmet's docs since HDR1 + HDR5 first shipped). Fixed in `gina-io/docs` commit `b4095ec` (2 files, 7 ins / 13 del). User-caught via "Why are we still talking about express in the diagrams" mid-session — the #118 sweep had landed earlier the same day and missed both. Generalises: any future framework-attribution correction (rename, vocabulary shift, adoption-convention update) should sweep BOTH prose patterns AND Mermaid `participant` declarations, since they're separate grep surfaces with separate idioms. Cost of the extra Mermaid sweep: 1 grep call. Cost of skipping: visible bug carries forward into the next docs reader's view. Established 2026-05-17.
|
|
860
835
|
|
|
861
836
|
125. **CLI handler authoring & operations — 12 lessons consolidated** (replaces individual entries #17, #23, #24, #55, #56, #58, #59, #60, #62, #63, #102, #112):
|
|
@@ -885,8 +860,10 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
885
860
|
- **`gh run list --commit <sha>` returns empty intermittently — use `--branch` instead** — observed 2026-05-14: `gh run list --commit <sha>` returned empty even after CI completed green; `--branch <branch>` showed the runs immediately for the same commit. Detection signal: an `until` poll loop on `--commit` hangs indefinitely (empty input never matches success regex). Workaround: poll `--branch <branch> --limit <N>` or skip the poll entirely (CI typically runs 15-60s after push).
|
|
886
861
|
- **Stable cut friction cluster — three recovery patterns (v0.3.14, 2026-05-16)** — (A) `npm publish` aborts on prerelease without `--tag`: runbook step "manual `package.json version + main` bump from `0.3.X-alpha.N` to `0.3.X`" is hard-required (`prepare_version.js` reads `version` but doesn't mutate `package.json`; npm reads `package.json` BEFORE `prepare` runs). Recovery: bump manually, leave UNCOMMITTED, re-run `npm publish`. (B) `syncDocs git merge develop` (no `--ff-only`) conflicts on `docusaurus.config.js` when docs `main` has any divergent commits since last cut (git's line-based ancestry-aware conflict detection flags the `ginaVersion` line even when both sides converge to the same value). Recovery: `git merge --abort`, `git pull --ff-only main`, re-merge, resolve to develop-side, complete merge, push, `git checkout develop`, re-run `node script/post_publish.js`. (C) `tagAndMerge git tag` not idempotent: after recovering from a mid-chain halt, re-run hits `fatal: tag 'v0.3.X' already exists`. Recovery: `git tag -d v0.3.X` LOCAL only, re-run post_publish.js. Deferred code-side fixes for all three.
|
|
887
862
|
|
|
888
|
-
127. **Web Security Headers (`#HDR` plugin family) —
|
|
863
|
+
127. **Web Security Headers (`#HDR` plugin family) — 10 lessons consolidated** (replaces individual entries #114, #115, #116, #118, #120, #121, #122, #124, #141, #157):
|
|
889
864
|
- **HTTP security response-header plugins follow the Csrf middleware-return shape, NOT the Session factory-wrap shape** — they emit a header on the response rather than wrapping an upstream module factory. Csrf returns an express middleware directly; each HDR plugin does the same. Settings.json convention stays flat top-level (each plugin gets its own top-level key, sibling of `session.cookie.*` / `csrf.*`). Idempotent-by-default: every header plugin checks `res.getHeader(name)` first and skips writing if a value is already present. No `enabled: false` shortcut — registration IS opt-in; not registering IS opt-out. Throw-on-invalid at factory call time (NOT request time). Multi-field cross-invariant validation in `_resolveOptions` helpers (e.g. #HDR4 `preload=true` requires `includeSubDomains=true` AND `maxAge>=31536000`). Spec-deviation documentation pattern: name the spec section + offsetting receiver behaviour + design tradeoff + operator escape hatch (e.g. #HDR4 RFC 6797 §7.2 vs §8.1). Per-plugin commit shape: 9 touchpoints on gina (src + package.json + README + registry entry + settings template + boilerplate adoption block + ROADMAP row + changie YAML + tests) + 3 on docs-repo (guide + migration + roadmap) in a SEPARATE commit per session.
|
|
865
|
+
- **CSP per-response nonce (#HDR16)** — `gina.plugins.Csp({ useNonce: true })` (opt-in, default off) generates a fresh per-response nonce on the per-request carrier `req._ginaCspNonce` (NOT `res.locals` / `process.gina.*` — the latter would leak across concurrent requests), appends `'nonce-<v>'` to `script-src` (fallback `default-src`; the factory THROWS at call time if neither directive is present), and the swig/nunjucks render delegates stamp it on framework-injected inline scripts and expose `{{ page.cspNonce }}` / `{{ cspNonce }}` so application templates can mark their own inline `<script>`s. The static (no-nonce) path precomputes the header once and writes no `req` slot — byte-identical + zero per-request allocation for non-opt-in bundles.
|
|
866
|
+
- **CSP report-only-inert directive omission (#HDR5)** — in `reportOnly` mode `Csp` omits directives browsers ignore there (`REPORT_ONLY_IGNORED_DIRECTIVES`, currently just `sandbox` — the only directive ignored in report-only by every engine: CSP2 spec text, MDN's Report-Only page, Chromium source, WebKit) and warns once naming what was dropped; `frame-ancestors` is deliberately KEPT — its report-only behaviour is engine-divergent (the CSP3 spec, Gecko and Blink evaluate it and send violation reports; WebKit alone ignores it with a console warning and no report, retaining the CSP2 ignore-when-monitoring rule that CSP3 dropped), so omitting it would lose the Chrome + Firefox observation-phase signal — WebKit-heavy consumers can leave it out of their own report-only set; the configured `directives` keep `sandbox`, so an enforcing factory from the same config still emits it; throws if every directive is report-only-inert.
|
|
890
867
|
- **Helmet-parity audit pattern: WebFetch the upstream README before claiming feature parity** — memory of vendor surfaces goes stale fast. When assessing parity, `WebFetch` the canonical README URL before claiming. Triage gaps on 4 axes: (a) modern-and-default-on-in-upstream → strong candidate to add; (b) modern-and-opt-in → moderate; (c) legacy-but-default-on → defense-in-depth-only; (d) legacy-and-opt-in-everywhere → likely skip. Audit cost: 1 WebFetch + ~5 min triage. Cost of skipping: parity-narrative gap a consumer will notice. Precedent: 2026-05-17 #HDR7 (`Origin-Agent-Cluster`) caught via helmet README audit; would have been silently missed otherwise.
|
|
891
868
|
- **Wrapper / orchestrator plugins inherit "batteries-included" default behaviour from sibling wrappers (Session, Csrf, SecurityHeaders), NOT the "register to opt in" rule that single-concern per-feature plugins follow** — `gina.plugins.SecurityHeaders()` with no opts mounts the 7 non-footgun headers (HDR1/2/3/4/7/13/14) with per-plugin defaults. CSP (#HDR5) and COEP (#HDR6) are opt-in-only WITHIN the orchestrator because of known footguns (CSP throws on missing directives; COEP `require-corp` BREAKS embeds without CORP). Default to batteries-included with the "safe" subset of children mounted-by-default; opt-in-only the children with known footguns; preserve the individual-plugin escape hatch via the idempotent first-writer-wins pattern.
|
|
892
869
|
- **gina-bundle plugin adoption ALWAYS uses `myapp.onInitialize(function(event, app){ app.use(<plugin>); event.emit('complete', app); })` — NEVER the standalone-Express idiom** — bundle authors do NOT `require('express')` or call `express()` themselves; gina builds the Express app and passes it via the `onInitialize` callback's `app` parameter. The standalone-Express idiom (`var express = require('express'); var app = express();`) is the recurrence-prone failure mode — universally familiar so it propagates by static-analogy when new docs are added mirroring wrong examples. When adding to an existing docs guide family, cross-check the adoption convention against sibling guides (`csrf.md`, `sessions.md`) AND against the boilerplate (`core/template/boilerplate/bundle/index.js`) BEFORE mirroring an existing example. Sibling sweep needed for Mermaid `participant` declarations: `grep -rn 'participant.*express\|App as express' docs/` — prose-only sweeps miss diagram syntax.
|
|
@@ -922,13 +899,7 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
922
899
|
|
|
923
900
|
137. **HTTP/2 response trailers (#H10) — opt-in via `self.sendTrailers(fields)` → stashed on `local._trailers`, wired in every render delegate's HTTP/2 BODY path as `stream.respond(headers, _trailers ? { waitForTrailers: true } : undefined)` + `stream.once('wantTrailers', () => stream.sendTrailers(_trailers))`.** `self.sendTrailers(fields)` (controller.js) only RECORDS fields — strips `:`-prefixed pseudo-headers (forbidden in a trailing HEADERS frame), stashes the clean object on `local._trailers`, returns self for chaining. Each delegate captures `var _trailers = (local._trailers && typeof === 'object') ? local._trailers : null` near its other per-request captures and arms the flow only when set. **Node trailer ordering contract**: with `waitForTrailers: true`, `stream.end()` no longer auto-closes — Node fires `wantTrailers`, and `stream.sendTrailers()` sends the trailing HEADERS frame AND closes the stream; call it exactly once (`.once`), inside the event, in a try/catch (best-effort — never fail the response). If `waitForTrailers` is ever set without a `wantTrailers`→`sendTrailers` handler the stream hangs forever, so gate both on the same `if (_trailers)`. **Skip**: HEAD branches (no body), HTTP/1.1 (the `res.*` path has no HTTP/2 trailer mechanism — silent no-op), destroyed/closed streams, and the framework 500 fallthrough (not an app response). **Single-conditional-arg form, NOT an if/else with two respond calls**: passing the options object conditionally as the 2nd arg keeps exactly one `stream.respond(` per path, so render-swig's source-pin counts (`5 active stream.respond()`, 5 `getHeaders`/`headersSent` paths, 4 dynamic `:status`) stay stable — an if/else (2 respond calls per body site) would trip them. (render-stream, the first slice, uses the if/else form since it has no count pin; the buffered delegates — render-json, render-swig cache-hit + cache-miss, render-nunjucks Case 3 — use the single-arg form.) **Gotcha for future body-path edits**: adding lines to a delegate body path shifts byte-offset lookback test windows (render-swig.test.js cache-miss HEAD window needed 3000→3400) and the `wantTrailers`-callback `stream.destroyed` guards add to source-pin counts (render-swig `stream.destroyed` 3→5; render-engine-dispatch.test.js `stream.respond(_streamHeaders)` regex widened to `_streamHeaders,\s*_trailers`) — recalibrate the affected #H8 pins. Tests: render-stream.test.js §10 (execution: waitForTrailers set + sendTrailers fires with fields + opt-out no-op), controller.test.js §15-16 (sendTrailers source pin + pure-logic replica), render-json §02 / render-nunjucks §03 / render-swig §13 (source pins). Established 2026-05-22.
|
|
924
901
|
|
|
925
|
-
138. **#INS9b — opt-in API-key auth gate on the dev-mode `/_gina/agent` Inspector SSE endpoint, reachable outside dev mode behind a key (production-safe toggle); plus the `$`-anchored-gate-regex gotcha caught in cold-read.** The agent stream (combined `event: data` + `event: log` SSE, #INS9a) was dev-only (`NODE_ENV_IS_DEV` in server.js / `isCacheless` in server.isaac.js). #INS9b widens the gate to `(dev || process.gina._inspectorAgentEnabled) && GET && <agent-url>` and, when NOT in dev, requires a valid key before opening the stream (else `401`). Config: `settings.json > inspector.agent.{enabled,key}` (default disabled; `key` is `${secret:KEY}`-capable, resolved at config-load). Capture: `gna.js` reads `getConfig('settings').inspector.agent` at `server.on('started')` onto `process.gina._inspectorAgent{Enabled,Key}` (mirrors the #S7 admin-allowlist capture — both engines read one `process.gina` slot because Isaac's handler `options` lacks `content.settings`). Key check: a file-scope `_agentKeyValid(req)` per engine reads the `x-gina-inspector-key` header OR a `?key=` query param (browser `EventSource` can't set headers, so the SPA's `tryAgent()` + connect form thread `?key=`), compared with `crypto.timingSafeEqual` + a length guard; **fail-closed** — no configured key returns false even when `enabled`. The Isaac 401 deny goes through `_setPoweredByHeader` (16th #HDR8 wrapped site). Dev stays open + keyless (#INS9a unchanged). **Value boundary**: in production this authenticates remote server-log streaming + connection identity ONLY — the render-side `inspector#data` emit (per-request data/query/flow) stays `isCacheless`-gated and never fires in prod, so enabling data capture in prod remains #INS10's separate concern. Stayed on SSE; the WebSocket transport in #INS9b's original wording is deferred (only needed for bidirectional commands). **Gate-regex gotcha (caught in a cold-read review, NOT by the 27 new tests):** `/_gina/*` gate regexes are `$`-anchored (`/\/_gina\/agent$/`); the instant the endpoint accepts a query param, `/_gina/agent?key=…` stops matching (the `$` sees `…key=abc`, not `agent`) and the handler silently never fires — auth path dead. Fix: widen to `/\/_gina\/agent(?:\?|$)/` (still rejects `/_gina/agentx`, `/_gina/agent/`, `/_gina/agent/foo`). Source-structure pins AND an isolated `_agentKeyValid` replica both passed while the integration was dead; only a cold read against the cumulative diff surfaced it. **Rule: when a previously-query-less endpoint gains a query param, exercise the GATE regex against the query'd URL, not just the helper that parses the param** — and keep the access condition (dev/enabled) FIRST in the gate so the dev-check stays inside the source-pin locality window. Tests: `test/core/inspector.test.js` §64-67 (gate source-pins both engines, behavioral key-compare replica, client `?key=` threading, `?key=` URL-match guard). Established 2026-05-24.
|
|
926
|
-
|
|
927
|
-
139. **#INS10 — toggleable Inspector instrumentation window: query + flow capture runs OUTSIDE dev mode for a time-boxed, key-authenticated window, surfaced ONLY over the authenticated `/_gina/agent` SSE (never an externally-facing response body).** New runtime signal `process.gina._inspectorWindowUntil` (epoch ms; `0` = closed) — every capture gate now opens on `<devTerm> || (process.gina._inspectorWindowUntil > Date.now())`, a lazy per-gate check that auto-closes the instant the deadline passes. Deliberately NOT `_inspectorActive` (that flag is ANDed with a load-time dev constant at every read, so it is inert in prod; the window is a separate prod-capable signal). `lib/instrument` (module-singleton, PLAIN-required in lib/index.js like lib/job/State so the window + unref'd expiry timer survive `refreshCore()`) owns `open(ttl)`/`close()`/`status()`/`isActive()`; `HARD_CAP_SECONDS = 3600` absolute ceiling (config `inspector.instrumentation.maxWindowSeconds` may lower, never raise), 300s default; lazy auto-close + an unref'd `setTimeout` teardown emitting `inspector#instrument-closed`. Control: `POST /_gina/instrument {enable,ttlSeconds}` + `GET` status, BOTH engines, behind a SEPARATE `_instrumentKeyValid` (mirrors `_agentKeyValid` but reads `process.gina._inspectorInstrumentKey`; constant-time, `x-gina-inspector-key` header or `?key=`, required EVEN in dev — raw query capture is more sensitive than #INS9b log-streaming), gated on `inspector.instrumentation.enabled` (fail-closed; when off the block doesn't match and the request 404s through normal routing). Bounded 4KB JSON body reader with a `req.body` fast-path + 2s timeout. Capture gates broadened (all `<devTerm> || window`): ALS-entry (controller.js), both flow-create gates (server.js/isaac), 7 connector query-push gates (couchbase main+bulkInsert, mysql, postgresql, sqlite, mongodb, scylladb — only the `_devLog`-binding gate broadens; statement `console.debug` stays dev-only). Egress is remote-only: render-json `inspector#data` emit + Flow closing-bars broaden to the window (redacted SSE payload); the raw cross-bundle `__ginaQueries`/`__ginaFlow` body sidecars emit in a window ONLY to an internal caller (inbound `x-gina-inspector` header, who strips them in controller.js's extract path), never an external client; the UNAUTHENTICATED engine.io `getGinaData` reply is dev-gated (`options.isCacheless ? _lastGinaData : null`); both agent SSE handlers replay the snapshot only in dev-or-active-window (no post-window leak to a late client). **Redaction is NOT the query-log safety layer** — `lib/inspector-redact` masks only secret-NAMED keys, so a query's `statement` + positional `params` travel RAW on every path incl. the SSE; the channel AUTH is the sole protection, hence the strict separate opt-in. **Cross-bundle in a prod window needs the window open on EACH participating bundle** — the `x-gina-inspector` header path stays behind `_isDev`, so a spoofed header can't trigger prod capture (only an opened window does). **Follow-up — shipped 2026-05-26 (commit `7e7531dc`, see #140)**: a server-rendered HTML PAGE's own controller queries + flow now ALSO egress in a prod window — the render-swig/nunjucks `inspector#data` emit lives inside the dev-only cacheless render block (entangled with the cached-vs-cacheless script-injection path), so a correct prod-window HTML emit uses a NEW render-json-style HTML-free emit path wired PER-DELEGATE (render() ends in a terminal un-awaited delegate return — no controller.js seam); JSON/XHR + cross-bundle egress already cover the API-driven case. #HDR8 `_setPoweredByHeader` wrapped-site count 16→17 (the Isaac `/_gina/instrument` deny). Tests: `test/lib/instrument.test.js` + `test/core/inspector.test.js` §68-72. Established 2026-05-26.
|
|
928
|
-
|
|
929
|
-
140. **#INS10 follow-up (shipped) — a server-rendered HTML PAGE's own controller queries + flow now egress in a prod instrumentation window, via a separate HTML-free emit path (NOT a broadened dev gate, NOT a controller.js seam).** The render-swig/render-nunjucks `inspector#data` emit is dev-only and entangled with the `window.__ginaData` injection + `</body>` rewrite, so its gate can't be widened to a prod window without corrupting cached renders. New helper `core/controller/inspector-window-emit.js` (`emitInspectorWindowData(self, local)`) mirrors the render-json v1 emit — builds environment (`getContext('gina')` + `local.options.conf`) + `local._queryLog` + `local._timeline`, redacts via `lib/inspector-redact`, sets `self.serverInstance._lastGinaData`, `process.emit('inspector#data')`, touching NO HTML. Wired at THREE gated sites — render-swig cache-hit + cache-miss tails + the render-nunjucks fresh-render tail — each `(process.gina._inspectorWindowUntil > Date.now()) && !self.isCacheless()`: **window-open AND not-dev**, mutually exclusive with every dev block (all require `isCacheless()`/`displayInspector === true`), so no double emit + no prod HTML mutation. **Payload is minimal — environment + queries + flow, NO `user.data`** (unlike render-json which carries the response body): an HTML page's server-side story is its queries+flow; omitting the template context keeps the authenticated-but-key-name-only-redacted SSE surface minimal. No `_lastGinaDataUnredacted` write (a prod window is never `local` scope). SSE handlers unchanged (already gate replay/forward on `dev || lib.instrument.isActive()`). **Correction to #139's "cleanest in controller.js" note:** controller.js `this.render()` ends in a terminal un-awaited `return require(_delegate)(...)` — no post-delegate seam without making render() async+await for every request; per-delegate matches render-json's own delegate-located emit (`render-json.js:252`). **Known limitation:** render-swig reads `self`/`local` at MODULE scope (the #M1 retrofit captured only req/res/next), so the swig emit inherits render-swig's pre-existing module-level-`local` cross-request exposure under concurrent prod-window renders — low severity (authenticated operator-only + time-boxed; same exposure as the existing dev emit); render-nunjucks uses function-scoped captures (race-clean). Tests: `inspector.test.js` §73. Established 2026-05-26.
|
|
930
|
-
|
|
931
|
-
141. **CSP per-response nonce — `gina.plugins.Csp({ useNonce: true })` (opt-in, default off) lets a bundle drop `'unsafe-inline'` from `script-src` without breaking the framework's injected inline scripts.** When `useNonce` is true the `ginaCsp` middleware generates a fresh `crypto.randomBytes(16).toString('base64')` nonce PER RESPONSE (128-bit W3C CSP3 floor), stamps it on the per-request carrier `req._ginaCspNonce`, and rebuilds the header per response appending `'nonce-<value>'` to `script-src` (fallback `default-src`; the factory THROWS at call time if neither directive is present, since the nonce has nowhere to attach). **`req._ginaCspNonce` is the carrier — NOT `res.locals`** (gina has no such slot in EITHER engine — verified: zero hits across server.js / server.isaac.js / server.express.js / controller.js; the issue's `res.locals.cspNonce` assumption does not hold) **and NOT `process.gina.*`** (process-global → would leak across concurrent requests, the PROXY_PREFIX bug class; `req._ginaCspNonce` mirrors the `req._ginaProxyPrefix` precedent, written by a middleware + read by the renderer on the same `req` that flows middleware→routing→controller→render in both engines). The static path (`useNonce:false`) precomputes the header once at factory time and writes no `req` slot — **zero behaviour change + zero per-request allocation** for bundles that don't opt in. The swig + nunjucks render delegates capture `_cspNonce = req._ginaCspNonce` alongside the #M1 req/res capture and stamp `nonce="<value>"` on the `onGinaLoaded` bootstrap `<script>`: swig via a function-local `_nonceLoader()` applied at all 3 ginaLoader injection sites; nunjucks by threading the nonce into `injectAssets()`. The bootstrap `<script type="text/javascript">` tag is built ONCE out-of-band in `config.js` and cached, so the nonce is injected at render time via a `.replace()` into a fresh string (never mutating the shared cache). The nonce is generated ONLY when gina is the header writer (the idempotent first-writer-wins `res.getHeader` guard): if a proxy/ingress already set CSP, no nonce is generated and none lands on the tags, keeping header and tags consistent. **Scope:** the always-on `onGinaLoaded` bootstrap (the production blocker — the only inline script emitted in prod) PLUS the dev-only Inspector (`__ginaData` + console-capture) and metrics-patch inline blocks in BOTH engines, all via the same `_cspNonce` capture + a `_cspNonceAttr` fragment (`nonce ? ' nonce="'+nonce+'"' : ''`) spliced into each `'<script' + _cspNonceAttr + '>'` opening. **nunjucks `injectInspectorScripts` reads the STABLE `local._cspNonce` slot** (stamped from the captured req in the main render scope), NOT volatile `local.req` — `render-nunjucks.test.js` §01 enforces "only one `local.req` read (the capture line)" per the #M1 race-discipline, so a `local.req._ginaCspNonce` read inside the helper trips it; the stable-`local._cspNonce` channel mirrors how that helper already reads `local._timeline`/`local._queryLog`. **Follow-up (shipped) — statusbar + application-template nonce helper.** The render delegates also expose the per-request nonce to templates: swig sets `data.page.cspNonce = _cspNonce` before BOTH `compiledTemplate(data)` sites (cache-hit + cache-miss — the swig var re-evaluates per execute, and the app-template helper must resolve on the cache-hit path too); nunjucks sets top-level `data.cspNonce` in the `env.render`/`env.renderString` context (its native top-level-promotion idiom). Both guard `if (_cspNonce)` so the key is ABSENT when no nonce (`{{ page.cspNonce }}` / `{{ cspNonce }}` then render empty + the `{% if %}` wrapper is falsy → no attribute → byte-for-byte back-compat). The Inspector `statusbar.html` (swig-only, dev-only, `{%- include -%}`d from the layout at the **dist** path) opens with `<script{% if page.cspNonce %} nonce="{{ page.cspNonce }}"{% endif %}>` — src + dist kept BYTE-IDENTICAL (dist is the runtime include artifact, not built from src via build.json; its JS body has zero swig delimiters so the line-1 conditional is the only template construct). This doubles as the **application-template nonce helper**: a bundle marks its OWN inline `<script>`s the same way (swig `{{ page.cspNonce }}`, nunjucks `{{ cspNonce }}`). Verified end-to-end with real execution at both ends — `csp-plugin.test.js §13` (middleware emits the header + stamps `req._ginaCspNonce`) and a real-`@rhinostone/swig` harness that `{%- include -%}`s the actual dist statusbar inside a parent template and confirms `page` inherits into the include (nonce attribute carries the exact value; bare tag without). Tests: `csp-plugin.test.js` §10-13, `render-swig.test.js` §14-16, `render-nunjucks.test.js` §04-06. Established 2026-05-27.
|
|
902
|
+
138. **#INS9b — opt-in API-key auth gate on the dev-mode `/_gina/agent` Inspector SSE endpoint, reachable outside dev mode behind a key (production-safe toggle); plus the `$`-anchored-gate-regex gotcha caught in cold-read.** The agent stream (combined `event: data` + `event: log` SSE, #INS9a) was dev-only (`NODE_ENV_IS_DEV` in server.js / `isCacheless` in server.isaac.js). #INS9b widens the gate to `(dev || process.gina._inspectorAgentEnabled) && GET && <agent-url>` and, when NOT in dev, requires a valid key before opening the stream (else `401`). Config: `settings.json > inspector.agent.{enabled,key}` (default disabled; `key` is `${secret:KEY}`-capable, resolved at config-load). Capture: `gna.js` reads `getConfig('settings').inspector.agent` at `server.on('started')` onto `process.gina._inspectorAgent{Enabled,Key}` (mirrors the #S7 admin-allowlist capture — both engines read one `process.gina` slot because Isaac's handler `options` lacks `content.settings`). Key check: a file-scope `_agentKeyValid(req)` per engine reads the `x-gina-inspector-key` header OR a `?key=` query param (browser `EventSource` can't set headers, so the SPA's `tryAgent()` + connect form thread `?key=`), compared with `crypto.timingSafeEqual` + a length guard; **fail-closed** — no configured key returns false even when `enabled`. The Isaac 401 deny goes through `_setPoweredByHeader` (16th #HDR8 wrapped site). Dev stays open + keyless (#INS9a unchanged). **Value boundary**: in production this authenticates remote server-log streaming + connection identity ONLY — the render-side `inspector#data` emit (per-request data/query/flow) stays `isCacheless`-gated and never fires in prod, so enabling data capture in prod remains #INS10's separate concern. Stayed on SSE; the WebSocket transport in #INS9b's original wording is deferred (only needed for bidirectional commands). **Gate-regex gotcha (caught in a cold-read review, NOT by the 27 new tests):** `/_gina/*` gate regexes are `$`-anchored (`/\/_gina\/agent$/`); the instant the endpoint accepts a query param, `/_gina/agent?key=…` stops matching (the `$` sees `…key=abc`, not `agent`) and the handler silently never fires — auth path dead. Fix: widen to `/\/_gina\/agent(?:\?|$)/` (still rejects `/_gina/agentx`, `/_gina/agent/`, `/_gina/agent/foo`). Source-structure pins AND an isolated `_agentKeyValid` replica both passed while the integration was dead; only a cold read against the cumulative diff surfaced it. **Rule: when a previously-query-less endpoint gains a query param, exercise the GATE regex against the query'd URL, not just the helper that parses the param** — and keep the access condition (dev/enabled) FIRST in the gate so the dev-check stays inside the source-pin locality window. Tests: `test/core/inspector.test.js` §64-67 (gate source-pins both engines, behavioral key-compare replica, client `?key=` threading, `?key=` URL-match guard). Established 2026-05-24. **Built on this gate:** **#INS8** adds a WebSocket transport over the same `/_gina/agent` (`ws@^8.20.1`, `noServer:true`, ONE `upgrade` listener on the unified raw server — both engines, no isaac mirror; client `tryAgentWS()` is the DEFAULT transport, SSE the fallback) + optional `inspector.agent.allowedOrigins` + dev auto-start (`gina service:start <service>` and a `gna.js server.on('started')` hook gated `isDev && projectName !== 'gina'` — the self-spawn guard). **#INS10** opens per-request query+flow capture in a time-boxed, SEPARATELY-keyed PROD window (`POST /_gina/instrument`, `process.gina._inspectorWindowUntil`, `lib/instrument`, `HARD_CAP_SECONDS=3600`); egress is remote-only over the authenticated SSE — channel AUTH, not redaction, protects raw query text — including a separate HTML-free per-delegate emit (`core/controller/inspector-window-emit.js`) for server-rendered pages. Established 2026-05-26 / 2026-06-02.
|
|
932
903
|
|
|
933
904
|
142. **`bin/cli` resolves the framework dir from `GINA_VERSION` (env/persisted) falling back to `package.json` version, then `require()`s `framework/v<version>/lib/generator` — now guarded by `fs.existsSync(frameworkPath)` BEFORE that require.** A `GINA_VERSION` pointing at a non-installed version (a stale pin, or a bind-mounted dev tree at a different version) otherwise throws `MODULE_NOT_FOUND` that the surrounding `try/catch` mislabels as `gina: could not load [ package.json ]` (package.json actually loaded fine), and the legitimate `existsSync` guard sits AFTER the require so it never fires — every CLI command then fails opaquely (in a container, `project:import` silently fails, so bundles report "not registered" on start). The guard now fails fast with a clear `gina framework:install <version>` message via `process.stderr.write` (`console` is not reassigned to the framework logger until later, so `console.alert` is undefined there). Diagnostic tell: `could not load package.json` followed by `Cannot find module '…/framework/v<X>/lib/generator'` means framework version `<X>` is not installed, not that package.json is broken. (`8fa9c278`, 2026-05-30)
|
|
934
905
|
|
|
@@ -942,8 +913,6 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
942
913
|
|
|
943
914
|
147. **Layoutless (`renderWithoutLayout`) renders expose controller `userData` BOTH at top level AND under `page.data`; the stash-before-merge order is circular-ref-critical (shipped 0.4.2).** The swig `isWithoutLayout` branch in `controller.render-swig.js` copies each `userData` key into `data.page.data` BEFORE `data = merge(userData, data)`, so a layoutless fragment template resolves bare `{{ var }}` (top level) AND `data.X` / `page.data.X` (via `{% set data = page.data %}` — the data path popins rely on). **The copy MUST precede the merge:** `lib/merge` returns its first argument, so after `merge(userData, data)` `data === userData`; writing `data.page.data = merge(userData, data.page.data)` afterwards would make `data.page.data` alias `data` → a circular structure that throws in the layoutless XHR-data `JSON.stringify` (the `gina-without-layout-xhr-data` hidden input) → HTTP 500. 0.4.1's 'top-level variables' fix had populated ONLY the top level (locked by a negative-invariant test FORBIDDING `page.data`); 0.4.2 restored the `page.data` copy to reach parity with the nunjucks engine (which already populated both) and flipped that test to the both-contract. **Process lesson:** a patch framed as a 'regression fix' that makes a FAILING test pass is suspect — a test that FORBIDS the patched behaviour means the prior choice was deliberate + test-locked; verify the prior decision's intent and treat the test flip as a sanctioned (approval-gated) test change, not a silent restore. Backed by a real-`merge` behavioural replica + a SUBTRACT proving the stash-after-merge cycle in `render-swig.test.js`. Commit `cd690698`. Established 2026-06-02.
|
|
944
915
|
|
|
945
|
-
148. **#INS8 — authenticated WebSocket transport for `/_gina/agent`, alongside the existing SSE channel (the substrate for future bidirectional Inspector features #INS11/#INS12; today still server→client push only).** One file-scope `attachInspectorAgentWs(rawServer, ctx)` in `core/server.js` attaches a single `upgrade` listener to the raw server captured at the unified `listen()` point — covering BOTH engines (the isaac instance IS the raw server; express's `app.listen()` returns it), so there is deliberately NO isaac-specific mirror; the isaac engine.io `server.on('upgrade')` handler (registered only in `ioServer`-attach mode) gains a `/_gina/agent(?:\?|$)` skip-guard so it defers to the WS handler. `ws@^8.20.1` (already transitive via engine.io; now declared in BOTH the root + `framework/v*` manifests) provides `new WebSocketServer({ noServer: true })` + `handleUpgrade`, lazy-required in try/catch so a missing `ws` disables WS and leaves SSE as the transport. **Auth = SSE parity on the upgrade socket:** gate `(dev || process.gina._inspectorAgentEnabled)`; outside dev a valid key via the SHARED `_agentKeyValid(req)` (it already parses `?key=` from `req.url` — browsers can't set WS handshake headers, same constraint that forced `?key=` for EventSource), fail-closed. Denials are RAW status lines written on the upgrade socket (`socket.write('HTTP/1.1 401/404/403 …'); socket.destroy()`) — there is no `ServerResponse` at upgrade time, so the #HDR8 `_setPoweredByHeader` wrapper does NOT apply (deliberate deviation). 404 when not-dev-and-not-enabled (endpoint invisible), 401 on bad key, 403 on Origin mismatch. **Origin allowlist** `inspector.agent.allowedOrigins` (captured to `process.gina._inspectorAgentAllowedOrigins` in `gna.js` alongside the agent key; default `[]` ⇒ allow any origin, parity with the SSE CORS `*`; enforced ONLY when non-empty — cross-origin is the norm, Inspector on :4101 → target on another port). Frames are `{event:'data'|'log', data}` JSON envelopes mirroring the SSE named-event shape; snapshot replay + the `inspector#data`/`logger#default` listener wiring reuse the dev-or-active-window gate (#INS10), cleaned up on ws close/error; inbound frames are reserved (only a `{type:'ping'}` heartbeat answered). **Client `tryAgentWS()`** (Inspector SPA `inspector.js`, the DEFAULT agent transport since #INS9b — `?target=<url>`; `?transport=sse` forces SSE): converts the scheme `http→ws` (`https→wss` preserved), threads `?key=`, parses the envelopes (same data/log apply logic as `tryAgent()`), falls back to SSE ONCE if the socket never opens (e.g. an HTTP/2-only target that can't upgrade). **Anchor-preservation trick:** the existing `var isAgent = tryAgent()` init line is UNCHANGED — `tryAgentWS()` runs before it and claims `source='agent'` only AFTER the socket constructs, so `tryAgent()` short-circuits (a new `if (source === 'agent') return true` guard) on WS success and opens SSE on a constructor throw; this kept the §39/§55 source-pins green with ZERO existing-test changes (the alternative — editing the over-specific `var isAgent = tryAgent()` literal that four pre-existing tests anchor on — would have needed the approval-gated test-change path). **Dev auto-start SHIPPED** (deferral lifted): `gina service:start <service>` (`lib/cmd/service/start.js`) starts a framework-internal `@gina` service (e.g. the standalone Inspector) via the daemon-free `bin/gina-container` launcher — spawned detached+unref'd with the caller's `NODE_ENV`/`NODE_SCOPE`/`NODE_PORT`/`NODE_BUNDLE`/`NODE_PROJECT` scrubbed so it resolves `inspector@gina` from `@gina`'s own dev/local defaults — guarded on srcExists + a `lib.cmdStatusFormat.readPidfile` not-running check (already-running → no-op exit 0), with `--dry-run` + `--format=json`. AND a `gna.js server.on('started')` hook auto-starts the Inspector when a USER bundle boots in dev: gated `isDev && projectName !== 'gina'` (the timing-proof self-spawn guard — a `@gina` service must never re-trigger the auto-start, else the Inspector would re-spawn itself on boot) + srcExists (the local-only no-op — `services/` is gitignored+npmignored so the hook does nothing in a fresh `npm install gina`) + the same readPidfile check, fail-closed; the spawn shape is kept in sync with the command. The correct hook IS `gna.js server.on('started')` per-bundle (NOT the daemon `framework/start.js`, which lacks a per-bundle dev signal). The embedded `/_gina/inspector/` SPA remains for zero-start dev access. Tests: `inspector.test.js` §74-77 (WS) + `service-start.test.js` (command + hook gate, source-pins + pure-logic replicas). Commits `b8b74fc2` (server) + `bc25e328` (SPA). Established 2026-06-02.
|
|
946
|
-
|
|
947
916
|
149. **Client-bundle plugins must NOT inject inline event-handler attributes (`setAttribute('onclick', …)`, `el.onX = …`) — they trip CSP `script-src-attr` under nonce-based policies; suppress the default action with an `addEventListener('click', e => e.preventDefault())` listener instead (popin close + link + validator submit-triggers fixed in `0.4.3-alpha.2`).** Setting an inline event-handler attribute compiles an inline handler governed by CSP `script-src-attr` (which falls back to `script-src`); once a `'nonce-…'` source is present in `script-src`, `'unsafe-inline'` is disabled for inline handlers (CSP Level 3), so the injection is reported in report-only mode and blocked in enforce mode — exactly what stops a nonce-adopting bundle from flipping its CSP to enforce. Three plugins injected `onclick="return false;"` at bind time: the popin close-binding (`popin/main.js`), the link binding (`link/main.js`), and the form-validator anchor submit-trigger binding (`core/plugins/lib/validator/src/main.js`). **The CSP-safe replacement is element-shape-dependent:** (a) popin already had a DIRECT `addEventListener('click', …)` whose `cancelEvent()` (preventDefault + stopPropagation) fires unconditionally, so removing the inline onclick was a pure, behaviour-neutral removal (the "click scrolls to #" worst-case never materialises — the existing listener's `preventDefault` runs regardless); (b) link + validator default-prevention runs through an id-gated DELEGATION listener (link: document-level; validator: form-level `clickProxyHandler`) that misses child-clicks, so each needed a NEW per-element listener — and it MUST be `preventDefault`-only, NOT `cancelEvent`, because `cancelEvent`'s `stopPropagation` would stop the event reaching the delegation listener and break the link AJAX trigger / validator submit. A `preventDefault`-only listener sets `event.defaultPrevented` exactly as the inline handler did (so the validator's `clickProxyHandler` `if (event.defaultPrevented) return` short-circuit still fires identically) and covers direct AND child clicks. In all three, the dead/live `else if` append branch was dropped and the injection gates preserved. **Rule:** replace an inline `on*` attribute with an `addEventListener` listener; use `preventDefault`-only (NOT `cancelEvent`/`stopPropagation`) whenever the element participates in event delegation or bubbling that other handlers depend on. **Browser-verified** (gina-starter harness, real `Content-Security-Policy-Report-Only: script-src-attr 'none'`): the inline-onclick mechanism raises a `script-src-attr` report violation on execution; the `addEventListener`+`preventDefault` mechanism raises none; both prevent default; gina loads clean. Tests: `popin.test.js` §14 + `csp-inline-handler.test.js` (source pins: no `setAttribute('onclick'` in any of the three + the preventDefault listeners + the rebuilt-dist whole-bundle zero-injection invariant). Established 2026-06-03.
|
|
948
917
|
|
|
949
918
|
150. **#A11Y1 — `FormValidator` reflects each managed field's validity into `aria-invalid`, so `aria-errormessage` associations actually reach assistive technology; done at the rule-agnostic error-display chokepoint `handleErrorsDisplay` (`core/plugins/lib/validator/src/main.js`).** Per WAI-ARIA an `aria-errormessage` (or error `aria-describedby`) association is inert unless the field also carries `aria-invalid="true"`, which the validator never set. Now the chokepoint sets `aria-invalid="true"` on a committed error (error-set + refresh branches, gated `!isWarning` so soft live-check warnings while typing are NOT asserted) and `"false"` on clear — but on clear it MIRRORS the native `ValidityState` (`$el.willValidate && $el.validity && !$el.validity.valid` ⇒ keep `"true"`) so it never disagrees with the `:user-invalid` styling already shown for natively-constrained fields. Hidden fields (`$el.type=='hidden'`) are skipped. **No-duplicate-message + auto-wire:** a `_hasConsumerErrMsg` probe hoisted before the branch chain suppresses the injected `form-item-error-message` div when the field already references its own error element via `aria-errormessage`; legacy forms with no association keep the injected div and gain an `aria-errormessage` wire to it — a deterministic `gina-errormessage-<form>-<field>` id (sanitised), marked `data-gina-aria-errormessage` so a gina-owned wire is distinguished from a consumer's, removed on clear, and the refresh branch preserves the owned id. **Submit + blur announcement:** on a failed submit (`onValidate`) focus moves to the first DOM-order invalid field (hidden / unfocusable / zero-count skipped) so AT announces it; blur-time committed errors are announced through a lazily-created per-form visually-hidden `aria-live="polite"` region (`announceA11yError`), gated on the per-field path (`fieldName` set) once focus has LEFT the field — a CSS `:user-invalid` toggle is not reliably announced and on blur the field's own aria state isn't re-read; submit-time errors use focus, not the region. **Scope nuance:** the per-field blur/`input` passes fire only when the form opts into `data-gina-form-live-check-enabled` (the warning branch is gated on it); the always-on submit pass covers `aria-invalid` + focus regardless, so a non-live form still gets submit-time exposure. Rule-agnostic (the chokepoint operates on `errors[name]`, not per rule — `isRequired` / `isEmail` / `isInList` / every rule covered). No public API change; existing `form-item-error` / `form-item-warning` / `form-item-error-message` / `data-gina-form-errors` classes + submit-button toggling unchanged. Real-AT smoke (VoiceOver / NVDA announcing the field + message) is the manual acceptance step — the automated coverage exercises the DOM attributes that drive AT. Shipped as four atomic commits. Tests: `test/core/validator-aria-invalid.test.js` (42 — jsdom DOM-behaviour replicas + source pins). Established 2026-06-03.
|
|
@@ -952,8 +921,27 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
952
921
|
|
|
953
922
|
152. **#M12a — opt-in structured (JSON) logging resolved once into `opt.format` (env `GINA_LOG_FORMAT=json`; `text` default).** `lib/logger/src/main.js` init resolves the render format with precedence `GINA_LOG_FORMAT=json|text` > `GINA_LOG_STDOUT` truthy ⇒ json (back-compat alias for the #K8s3 flag) > `text`, BEFORE `loadContainers()` `JSON.clone()`s `opt` into each container — so `containers/default/index.js` reads `opt.format` (falling back to the `GINA_LOG_STDOUT` env when `opt.format` is absent) instead of re-testing env. JSON line = `{ts, level, bundle, message, group, msg}`: `bundle`/`message` are the canonical #M12 keys; `group`/`msg` are retained as back-compat aliases for the shipped #K8s3 `{ts,level,group,msg}` shape — ADDITIVE, non-breaking (a rename would have failed the existing `logger-log-integrity.test.js §17.07` subprocess test, which asserts `typeof entry.{group,msg}==='string'`; the additive choice kept it green). The raw `console.log` path (`self.log`, `main.js` ~L814) bypasses the container dispatch, so it ALSO honours `opt.format==='json'` (wraps as `{ts, level:'info', bundle:opt.name, message, group, msg}`) — without this, JSON mode would interleave JSON + plain lines and break a collector. Default `text` keeps `docker`/OrbStack `docker logs` output byte-identical (the chokepoint for levelled output is the default container; `self.log` is the only other stdout writer — both covered). **M12b deferred** — automatic per-request `requestId`/`durationMs` stamping needs an always-on per-request `AsyncLocalStorage` context = #M14 (the Inspector `_queryALS` store at `controller.js:282-283` is dev/window-gated and carries no id; `durationMs` is computable from the metrics `response.on('finish')` hook but only when metrics is enabled). Tests: `test/lib/logger-render.test.js` (9, behavioural + source-pin). Established 2026-06-05.
|
|
954
923
|
|
|
955
|
-
153. **Couchbase entities expose a public `getCluster()` accessor (wired onto both the model-entity and N1QL-entity prototypes in `core/connectors/couchbase/index.js`) returning the underlying SDK Cluster handle, so applications can use SDK-level features the entity layer does not wrap — notably multi-document ACID transactions via `cluster.transactions().run(...)` — without reaching into private `_*` connection internals.** The dual connection-shape lookup (`conn._cluster` for the entity-query path vs `conn._scope._bucket._cluster` for the bulk-insert/scope-collection path) is now centralised in ONE internal `resolveCluster(conn)` helper shared by `explainForIndexes`, `bulkInsert` and `getCluster()` — previously each path re-implemented it; it throws a clearly-coded `GINA_COUCHBASE_CLUSTER_UNRESOLVED` error (not a cryptic `undefined.query()` TypeError) when neither shape resolves. `getCluster()` calls `this.getConnection()` then `resolveCluster()`; it is Couchbase-specific so it lives on the connector's entity decoration (reachable wherever `bulkInsert` is), NOT on the generic `EntitySuper`/`getModel()` surface (a SQL/Redis/Mongo entity has no cluster). `explainForIndexes` consumes the helper in a try/catch to stay fire-and-forget non-fatal (warn + skip — it must never break the live query). gina does not pin the `couchbase` driver (resolved from the project's `node_modules`), so `.transactions()` availability depends on the project's installed SDK (3.2+/4.x); `getCluster()` guarantees only the handle. Tests: `test/core/couchbase-connector.test.js` (source pins + pure-logic replicas, neutral fixtures); the pre-existing `inspector.test.js` block-49 cluster-resolution pins were re-pointed from `explainForIndexes` to the extracted `resolveCluster()`. Established 2026-06-05.
|
|
956
|
-
|
|
957
|
-
154. **MongoDB entities expose a public `getClient()` accessor (wired onto the entity prototype in `core/connectors/mongodb/index.js`) returning the underlying driver `MongoClient`, so applications can use driver-level features the entity layer does not wrap — notably multi-document transactions via `client.startSession()` → `session.withTransaction(...)`, change streams and `bulkWrite` — without reaching into private connection internals.** Unlike Couchbase's `getCluster()` (which derives the handle from the connection the entity already holds), Mongo's accessor needs a one-line connector plumbing step first: the entity is handed the `Db` (what `getConnection()` returns), but the `MongoClient` lives one level up and was previously closure-private — reachable through NO supported path — so `MongodbConnector.onReady` now decorates the yielded `Db` with a `_client` back-reference (mirroring the existing `_db._name` decoration) before `fn(null, _db)`. A private `resolveClient(conn)` helper reads `conn._client`, guards that it exposes a `startSession()` method, and throws a clearly-coded `GINA_MONGODB_CLIENT_UNRESOLVED` error (not a cryptic `undefined.startSession()` TypeError) when it cannot resolve; `getClient()` calls `this.getConnection()` then `resolveClient()`. It is Mongo-specific so it lives on the connector's own entity decoration, NOT on the generic `EntitySuper` surface — and on a SINGLE entity surface (Mongo pipelines attach only to existing entity classes, so there is no second N1QL-style prototype to mirror onto, unlike Couchbase). gina does not pin the `mongodb` driver (resolved from the project's `node_modules`); multi-document transactions additionally require a replica-set or sharded deployment, so `getClient()` guarantees only the handle. Tests: `test/core/mongodb-connector.test.js` (source pins + pure-logic replicas, neutral fixtures). Established 2026-06-06.
|
|
924
|
+
153. **Couchbase entities expose a public `getCluster()` accessor (wired onto both the model-entity and N1QL-entity prototypes in `core/connectors/couchbase/index.js`) returning the underlying SDK Cluster handle, so applications can use SDK-level features the entity layer does not wrap — notably multi-document ACID transactions via `cluster.transactions().run(...)` — without reaching into private `_*` connection internals.** The dual connection-shape lookup (`conn._cluster` for the entity-query path vs `conn._scope._bucket._cluster` for the bulk-insert/scope-collection path) is now centralised in ONE internal `resolveCluster(conn)` helper shared by `explainForIndexes`, `bulkInsert` and `getCluster()` — previously each path re-implemented it; it throws a clearly-coded `GINA_COUCHBASE_CLUSTER_UNRESOLVED` error (not a cryptic `undefined.query()` TypeError) when neither shape resolves. `getCluster()` calls `this.getConnection()` then `resolveCluster()`; it is Couchbase-specific so it lives on the connector's entity decoration (reachable wherever `bulkInsert` is), NOT on the generic `EntitySuper`/`getModel()` surface (a SQL/Redis/Mongo entity has no cluster). `explainForIndexes` consumes the helper in a try/catch to stay fire-and-forget non-fatal (warn + skip — it must never break the live query). gina does not pin the `couchbase` driver (resolved from the project's `node_modules`), so `.transactions()` availability depends on the project's installed SDK (3.2+/4.x); `getCluster()` guarantees only the handle. Tests: `test/core/couchbase-connector.test.js` (source pins + pure-logic replicas, neutral fixtures); the pre-existing `inspector.test.js` block-49 cluster-resolution pins were re-pointed from `explainForIndexes` to the extracted `resolveCluster()`. Established 2026-06-05. **MongoDB `getClient()`** is the sibling accessor (`core/connectors/mongodb/index.js`) returning the driver `MongoClient` for `client.startSession().withTransaction(...)` / change streams / `bulkWrite`; unlike Couchbase it needed a one-line plumbing step — `onReady` decorates the yielded `Db` with a `_client` back-reference (the `MongoClient` lived a level up, closure-private) — read via `resolveClient()` (throws coded `GINA_MONGODB_CLIENT_UNRESOLVED`). Both accessors are connector-specific (NOT on generic `EntitySuper`) and guarantee only the handle (gina pins neither driver; transactions need Couchbase SDK 3.2+/4.x, and Mongo a replica-set/sharded deployment). Design test: add an accessor only when `getConnection()` returns a CHILD of the needed handle, not when it already returns the handle itself. Established 2026-06-06.
|
|
958
925
|
|
|
959
926
|
155. **#M12b — opt-in per-request `requestId` + `durationMs` in JSON logs, built on a narrow `AsyncLocalStorage` (`process.gina._reqALS`, parked like `_queryALS` so it survives dev `require.cache` busting).** Gated on JSON logging ONLY: `_reqCtxLogging` in `core/server.js` mirrors the logger's `opt.format` env precedence (`GINA_LOG_FORMAT=json` > `GINA_LOG_STDOUT`). Text mode renders no id field, so running the ALS there would be pure overhead with no reader — gating on JSON ties the cost to the operator's existing structured-logging opt-in and keeps the default text path byte-identical/zero-cost (this also sidesteps the unmeasured always-on throughput question). `onInstance` (`server.js`) stamps `request._ginaReqStartMs` + `request._ginaReqId` at request entry; the id resolver honours a SANITISED inbound `X-Request-Id` (charset `/^[\w.\-]{1,128}$/`, regenerate-on-violation to kill log-forging/injection) else `crypto.randomUUID()`. The `.run({requestId,startMs})` wrap is at `handle()` — NOT `onInstance` — because the `request.on('end')` boundary between them loses async context whereas `handle()`'s awaits preserve it (original body renamed `_handleDispatch`; `handle` became a thin wrapper; call sites untouched). HTTP/2 gets per-stream scoping for free: streams flow through the same `handle()` via Node's per-stream compat `'request'` event, NOT `session.on('stream')`. The logger's two JSON-assembly sites (default container + the raw `self.log`/`console.log` path) optionally read `process.gina._reqALS.getStore()` and add `requestId` + `durationMs = Date.now() - startMs` (per-line elapsed) when a store is active; absent for CLI/boot/off-request logs (graceful guard). `.run()` NOT `enterWith()` (enterWith bleeds sideways across siblings). The always-on-regardless-of-format throughput PoC (#M14's go/no-go for un-gating) stays deferred — needs a bootable env. Tests: `test/lib/logger-render.test.js §06` (behavioural store→fields + source pins) + `test/core/server.test.js #M12b` (resolver sanitisation, env precedence, ALS-propagation-through-await + concurrent isolation, source pins). Established 2026-06-06.
|
|
927
|
+
|
|
928
|
+
156. **#TPL1 Slice 1 — opt-in async template-loader extension point for swig, implemented as a SEPARATE render delegate (`controller.render-swig-async.js`), NOT an inline branch in `render-swig.js`.** A bundle configures `settings.template.swig.loader` (connector-style: a named `type` + type-specific flat keys; Slice 1 ships `type:"memory"` with an inline `templates` map — `lib/template-loaders`). `core/server.js initSwigEngine` builds + validates the loader at bundle startup via `lib.templateLoaders.build(cfg)` — fail-fast on bad config, NO network probe at boot (mirroring `initNunjucksEngine`/`NUNJUCKS_NOT_INSTALLED`) — and stashes `{loader, autoescape}` on `process.gina._swigLoaders[dir]` keyed by `conf.content.templates._common.html`. `controller.js this.render` dispatch routes to the async delegate when `process.gina._swigLoaders[<templateRoot>].loader.async === true` (the SAME key expression on all three sides), else the byte-identical filesystem `render-swig.js`. **Why a separate delegate, not an inline `isAsyncLoader` fork:** render-swig's FS coupling is far deeper than the page self-read + `{% extends %}` pre-resolution — it ALSO `fs.existsSync(path)`-404s a missing template, FS-resolves + reads the layout, and `getAssets()`-scans that layout — so an inline fork would thread ~8-10 guards through ~1825 lines + two duplicated send blocks and break the byte-unchanged guarantee; a sibling delegate (the proven render-nunjucks pattern) leaves render-swig.js untouched. **Per-bundle isolation crux:** swig is a process-singleton (`lib/swig-resolver` caches ONE module on `process.gina._swig`), so a per-bundle `swig.setDefaults({loader})` would collide (last bundle wins). The delegate instead builds an ISOLATED `new swigMod.Swig({loader, autoescape, cache:false})` per bundle in `process.gina._swigEngines[templateRoot]`, owner-guarded on `_swigEnginesOwner !== swigMod` (drops the registry when dev-mode hot-swaps the swig module — mirrors render-nunjucks `_nunjucksEnvsOwner`). Verified by spike: `new Swig()` fully isolates its `options.loader`/`cache`/`filters`/`tags`. **Loader contract** (`{resolve(to,from)→id, load(id,cb), async:true}`): gina wraps the user loader with a CVE-2023-25345 segment-guard run on EVERY `resolve()` (rejects `..` segments + absolute paths → covers the whole transitive extends/include chain, stronger than render-swig's page+first-extends-only guard), re-exposing `load` via `.bind()` so `load.length` (arity ≥2) survives — load-bearing because swig's `getTemplate` picks the callback-load path on `load.length>=2`; routing-to-async is gated separately by the loader's `async===true` property. **Render path:** the delegate SKIPS the FS self-read/extends machinery and calls `await engine.getTemplate(name)` (returns `Promise<fn>`; swig forces `cache:false` for async-compiled templates) then `(await fn(data)).output` — swig's async codegen drives `resolve→load` for the page AND its transitive `extends`/`include` through the loader, so templates can live off-disk (remote/CDN/object-storage/in-memory). **MVP scope:** isolation + loader pipeline + per-request gina-filter registration (`engine.setFilter`) + render + HTTP/1.1 & HTTP/2 send (cloned from render-nunjucks `sendHtmlResponse`) + **post-render asset injection** (gina client bundle / CSS / JS injected onto the `</head>`/`</body>` anchors via a verbatim `injectAssets()` port + the gina-bootstrap `whisper()` placeholder pass; `setResources` output reaches `data` via the render-swig.js:609 `data = merge(data, getData())` "needed !!" re-fetch; per-request CSP nonce honoured on the injected bootstrap — so an off-disk full page ships the client runtime and is production-usable). Deferred to follow-up slices (mirroring render-nunjucks N2→#NJ): Inspector payload, static HTML cache writes, error-template routing, Early Hints; the Tier-2 compiled-fn cache (Slice 4). The HTTP(S)-fetch loader + Tier-1 source cache shipped in Slice 2. Default (no-loader) bundles are byte-identical to pre-#TPL1. Tests: `test/lib/template-loaders.test.js` (factory/guard/memory contract + behavioural render-through-swig: extends+include + two-instance isolation + traversal block) + `test/lib/render-engine-dispatch.test.js §03b-03e` (dispatch branch, schema/lib/server wiring, delegate shape + negatives: no FS self-read, no string `compile`; §03e asset-injection / setResources port — source pins + behavioural `injectAssets` eval). **#TPL1 Slice 3 extends the same separate-delegate architecture to nunjucks** (`controller.render-nunjucks-async.js`, opt-in `settings.template.nunjucks.loader` — a verbatim-shape sibling of `template.swig.loader`): `initNunjucksEngine` builds + stashes the loader on `process.gina._nunjucksLoaders[<templateRoot>]` keyed by the same `conf.content.templates._common.html`, and `controller.js` dispatch adds an `_njAsync` sub-check inside the `engine==='nunjucks'` branch (`_njAsync ? '/controller.render-nunjucks-async' : '/controller.render-nunjucks'`). The adapter is a `nunjucks.Loader.extend({async:true, resolve:(from,to)=>to, getSource(name,cb)})` subclass wrapping the gina loader — `resolve` overridden to IDENTITY so the gina loader's CVE-2023-25345 segment-guard is the single path authority on every transitive `extends`/`include` hop; `getSource` returns the nunjucks `{src,path,noCache}` source shape. **Two nunjucks-specific divergences from the swig-async delegate:** (1) a **per-request `new nunjucks.Environment(adapter)`** (the Slice-3 default) — NOT the cached `_nunjucksEnvs` registry the sync `render-nunjucks.js` reuses — originally believed §8.1-mandated for race-safety, but **Tier-2 found that rationale wrong** (a per-request env isolates only the filter name→fn table, NOT the process-global context singleton the filters read — so nunjucks-async raced too; see the Tier-2 close below): it is really just the cache-OFF default, and Tier-2 adds an opt-in shared env; (2) **promisified callback-form `env.render(name,ctx,cb)` is MANDATORY** — a sync `env.render` on an uncached template under an async loader returns `null` SILENTLY (empirically verified, nunjucks 3.2.4). Helpers (`resolveTemplatePath`/`registerGinaFilters`/`sendHtmlResponse`/`injectAssets`/whisper/userData a/b/c merge + the Bug-J `data.data` alias) ported verbatim from `render-nunjucks.js`; the post-await `headersSent()` re-check + the render-swig.js:609 `data = merge(data, getData())` re-fetch come from the swig-async structure. MVP scope matches swig-async (Inspector / writeCache / error-template / Early Hints deferred). nunjucks added as a **ROOT devDependency only** (never `framework/v*/package.json` — the negative invariant) to gate a behavioural §03j adapter test that renders a real transitive `extends`+`include` through the gina memory loader + asserts the CVE guard fires at the gina resolve boundary. Tests: `render-engine-dispatch.test.js §03f-03j` (dispatch, schema/lib/server wiring, delegate shape incl. the negative per-request-env lock `does NOT reference process.gina._nunjucksEnvs` + the `_njAsync` ternary, asset-injection port, gated behavioural adapter). **Tier-2 SHIPPED (2026-06-07, closes #B25):** the KEY FINDING is the per-request filter-context race was NEVER swig-only — BOTH async delegates read per-request context off a PROCESS-GLOBAL filter singleton (`SwigFilters.instance._options` / `NunjucksFilters.instance._options`), which a per-request nunjucks env does NOT isolate (only the name→fn table); the sync delegates are safe only because their render is synchronous. The fix: **context-free filters** that read `process.gina._renderALS.getStore()` at call time (an inner `getRenderCtx()`; a singleton fallback keeps the sync path byte-identical) + an **UNCONDITIONAL** `getRenderALS().run({options,isProxyHost,throwError,req,res}, …)` wrap around each async render (ALS propagates context across every await, closing #B25 whether or not the cache is opted in). Tier-2 compiled-template reuse then rides on top, opt-in via `settings.template.{swig,nunjucks}.loader.cache` (boolean, default off, dev-disabled): swig ALWAYS shares its per-bundle engine (filters registered once) + an opt-in per-template compiled-fn memo (caches the `Promise<fn>`, evicts on reject — swig-core's forced async `cache:false` limits reuse to the top-level compile, transitive children recompile); nunjucks is two-mode (shared owner-guarded `process.gina._nunjucksAsyncEnvs` env when on, fresh per-request env when off). Tests: `render-engine-dispatch.test.js §03k` (source pins + the headline #B25 concurrent-load behavioural for swig + gated nunjucks — two interleaved renders through ONE shared engine/env each read their OWN context — + a pure-logic ALS-isolates-vs-singleton-bleeds subtract; full suite 7643/7643). Established 2026-06-06; Tier-2 2026-06-07. **Slice 2 — `http` built-in loader** (`lib/template-loaders/src/loaders/http.js`, registered in `BUILTINS` alongside `memory`): fetches swig/nunjucks templates over HTTP(S) from a configured `origin`+`basePath` with a Tier-1 source cache (`process.gina._cache`, resolved LAZILY at load-time — the loader is built before the server's shared cache Map exists), absolute TTL (default 60s, `0`=until-evicted), and opt-in ETag `If-None-Match` revalidation (`revalidate:true` → 304 refresh TTL + serve cached, 200 replace, network-error serve stale rather than 500). `resolve()` containment-checks every mapped URL stays under `origin`+`basePath` — a second boundary beyond the CVE-2023-25345 segment-guard that already rejects `..`/absolute identifiers. Established 2026-06-06.
|
|
929
|
+
|
|
930
|
+
159. **Dialog popins open as native modals in EVERY env (dev/prod parity) — the dev-only non-modal downgrade was removed (`popin/main.js`, shipped `0.4.6-alpha.2` develop, commit `4d59cb86`).** `popinOpen` now calls `$el.showModal()` unconditionally (was `if (gina.config.envIsDev) $el.show(); else $el.showModal();`), and the three `.gina-popins-overlay` gates (overlay creation, `cancelOnOverlayClick` bind, activation) dropped their `|| gina.config.envIsDev` disjunct so the manual overlay survives ONLY for `!useDialogMode` (non-dialog) mode — dialog popins use the native `::backdrop` in all envs (removes the dev double-dim; matches prod, which already rendered overlay-less native modals). **Two-layer correctness rule (why the earlier "Option F" attempt was reverted):** `popinOpen` guards its `showModal()` with `!$el.getAttribute('open')`, so it SKIPS the call when the dialog is already open. A consumer that preemptively opens the dialog (skeleton-loading `MutationObserver` before the XHR returns) must therefore ALSO use `showModal()` (not `show()`); otherwise the dialog is born non-modal, gina's removed-overlay leaves nothing positioning it, and it renders inline-at-bottom with no backdrop (the exact Option-F regression — overlay removed while the consumer still pre-opened non-modally). Making BOTH layers `showModal()`-only keeps the dialog born-modal. **Trade-off accepted:** a native modal inerts the in-page dev Inspector statusbar while the popin is open (no in-dialog launcher). **Verification gotcha:** a gina-starter smoke is FALSELY GREEN (no preemptive open → `showModal()` always runs → modal looks fine); the regression manifests only in a real preemptive-open consumer, so verification MUST use one. Verified live in a real preemptive-open dev render: `:modal`=true, real form, centered, ZERO `.gina-popins-overlay` (the live causation proof that the gate change is the running code — a stale dev-gina would have created+activated the overlay), no console errors. Tests: `popin.test.js §15` (source + dist pins). Established 2026-06-07. **Opt-in skeleton pre-open (`preOpen` + `loadingShell`, commit `92eadb88`):** a popin registered with `preOpen:true` is filled with a loading skeleton and opened BEFORE the XHR returns (born-modal `showModal()` in dialog mode, active container + `.gina-popins-overlay` in div mode); `popinBind` swaps in the real content on completion and `popinOpen`'s already-open guard then skips. A closure-private `showLoadingShell()` runs at BOTH `data-gina-popin-loading` write sites, idempotent via a `hasAttribute('open') || .gina-popin-is-active` guard — NOT `getAttribute('open')`, because `showModal()` sets the `open` attribute to the EMPTY STRING (falsy), so a getAttribute-truthiness check would re-inject + re-`showModal()` and throw. Default skeleton is a gina-namespaced `.gina-popin-skeleton` shimmer (`prefers-reduced-motion` opt-out); pass `loadingShell` HTML to override. Off by default — existing popins byte-identical. Verify with a REAL preemptive-open (a no-pre-open smoke is falsely green). Tests: `popin.test.js §16`. Established 2026-06-07.
|
|
931
|
+
|
|
932
|
+
160. **#TPL2 — gina's default Swig render path keeps swig-core's CVE-2023-25345 loader confinement ON (no `allowOutsideRoot` opt-out), by eliminating the two trusted out-of-root resolutions it used to make.** swig-core 2.7.1 confines the filesystem loader to its `basepath` (= the bundle templates root) and rejects any `{% include %}`/`{% extends %}`/`{% import %}` resolving outside it. gina had opted out (`swig.loaders.fs(dir, 'utf8', true)` at `core/server.js` initSwigEngine + `core/controller/controller.js` per-request setDefaults — interim commit `b7a022e9`) because its render path produced two trusted out-of-root paths: (1) the processed-layout cache — `render-swig.js` rewrites the page's `{% extends %}` to an absolute path under a SIBLING `cache/` tree — and (2) the dev inspector statusbar, injected as an `{% include %}` of an absolute framework-core path. #TPL2 removes BOTH so both loader sites revert to the bare confined `swig.loaders.fs(dir)`: (1) the layout cache is RELOCATED IN-ROOT to `<templates.html>/.gina-layout-cache/...` (the rewritten `{% extends %}` now resolves inside the loader basepath — only the `cachePath` base assignment changes; the seed-read/write + processed-write file-I/O flow is byte-identical, just relocated), and (2) the statusbar leaf template (only `{% if page.cspNonce %}` + `{{ }}`, no nested directives) is INLINED — its body `fs.readFile`-read and spliced into the layout string in place of the `{% include %}`. The in-root cache is kept INVISIBLE to a consumer's git by an auto-dropped self-ignoring `.gitignore` (`*`) written once at the cache root (the old out-of-root cache lived under the project `cache/` dir consumers already ignored, so no migration burden); its leading-dot name also rides gina's existing dotfile-skip (the `config.js` public/errors/forms scans) and the templates html tree is never a static docroot nor recursively scanned, so the cache is never served nor enumerated. **Enumeration was the crux** (an in-root/confined fix is only correct if EVERY out-of-root resolution is found): error templates (`renderCustomError`/`errorFiles` build only from in-root `/errors` dirs; the framework `50x.html` fallback never reaches the loader) and popin/XHR partials (only string-manipulate the layout, inject no directives) were verified NOT to add out-of-root paths; the `render-swig-async.js` async delegate (#TPL1) + nunjucks are separate engines with their own loaders (out of scope). **Residual is now effectively zero** — an untrusted include cannot escape the templates root at all (a `../` traversal or out-of-root absolute is rejected by swig-core before any read), strictly stronger than the prior interim opt-out + Layer-2 page+first-extends-only guards. Tests: `test/core/swig-loader-allowoutsideroot.test.js` flipped from opt-out-present pins to confinement-active — behavioural: the confined loader ACCEPTS the in-root cache shape and REJECTS both the old out-of-root sibling-cache path and a `../` traversal, plus a real `swig.compile` extends chain (in-root renders, out-of-root rejected). Live-verified: a gina-starter dev render returns HTTP 200 with the statusbar inlined, the in-root `.gina-layout-cache` created, and `git status` clean. Established 2026-06-08. **Floor now `^2.7.2` (2026-06-09) + trustedRoots opt-out REVERTED:** swig-core 2.7.1's confinement carried a relative-basepath regression — a RELATIVE loader `basepath` made it wrongly reject EVERY in-root include/extends (the resolved template path is always absolute and can never be prefixed by a basepath that was only normalized, not resolved); swig-core 2.7.2 resolves the basepath to absolute before the root check, fixing it (absolute-basepath behaviour + the escape/prefix-bypass rejections unchanged). gina's own loader basepath is ABSOLUTE (`templates._common.html` = `${executionPath}/bundles/<bundle>/templates/html`, executionPath = the registered-absolute project root), so gina was never hit — the floor bump just ships the fix to any relative-basepath consumer. A per-bundle `trustedRoots` opt-out to this confinement (`lib/swig-trusted-loader`, develop-only, never released; introduce `24c7c851` + re-home `040b304d`) was briefly added then REVERTED (2026-06-09): out-of-root sibling includes (`{% include "../shared/x" %}`) are intentionally NOT supported — that is the exact CVE-2023-25345 traversal pattern the confinement protects against, so keep shared assets inside the templates root; the relative-basepath case that appeared to motivate the opt-out is fixed by 2.7.2 directly, not by a confinement opt-out. Verified empirically (install 2.7.2 + loader probe): under 2.7.2 a relative basepath now resolves in-root paths (rejected under 2.7.1) while `../` escapes still throw; under an absolute basepath in-root works and out-of-root `../sibling` is still rejected (unchanged 2.7.1→2.7.2). DO NOT re-add trustedRoots — the design decision is to keep confinement absolute with no out-of-root opt-out.
|
|
933
|
+
|
|
934
|
+
161. **`gina version` (and `gina framework:version`) reports the framework-bundled template engine — `Template engine: <name>@<version>` (e.g. `@rhinostone/swig@2.7.2`), beneath the middleware line.** Built in `lib/cmd/framework/version.js`: reads `framework/v<version>/node_modules/@rhinostone/swig/package.json` (name + version) and injects the line into the `msg.json` `basic[4]` banner via a `%engine%` placeholder placed between the middleware and copyright lines (`%engine%%copyright%`, so an empty token leaves no blank line). The read is wrapped in try/catch, so a framework dir without the engine package installed (e.g. a fresh clone before `npm install`) simply OMITS the line — the "when available" contract — and `version --short=true` is unaffected (prints the bare number only). **Scope constraint (durable):** `gina version` is a context-free global command — no bundle/project config is loaded — so it can only report the framework DEFAULT engine, which is always swig. Nunjucks is per-bundle, project-only and opt-in (`render.engine: 'nunjucks'` in a bundle's `settings.json`) and is therefore NOT visible to this command; reporting a bundle's actual engine (which may be nunjucks) belongs to a future bundle-scoped command, not `version`. Tests: `test/lib/framework-version.test.js` (source pins on version.js + msg.json placeholder ordering + pure-logic token-resolution and banner-assembly replicas incl. the omit-when-empty path). Shipped on develop (0.4.6-alpha.2). Established 2026-06-09.
|
|
935
|
+
|
|
936
|
+
162. **`self.query()` and the `#B28` body parser are now self-consistent for `application/json` bodies — the built-in HTTP client sends raw JSON, and the server tolerantly accepts a percent-encoded body as a fallback.** The server-to-server client (`this.query` in `core/controller/controller.js`) historically serialized PUT/POST `application/json` bodies as `encodeRFC5987ValueChars(JSON.stringify(data))` (percent-encoded), while `#B28` had switched the server parser (`processRequestData` in `core/server.js`) to read `application/json` verbatim via `JSON.parse(request.body)` — so the framework emitted bodies its own parser rejected: every internal `self.query()` POST/PATCH with a non-empty body returned HTTP 500, and PUT silently dropped the body. Empty-data calls were unaffected (no body sent). Two-sided fix. **Client:** `this.query` now sends `queryData = JSON.stringify(data)` raw for the json/text/x-www-form PUT/POST branch (the wire content-type is forced to `application/json` anyway) — RFC5987 value-encoding is defined for HTTP header values (`filename*=`), not request bodies; the empty-data short-circuit and the urlencoded-into-path GET-style branch (per-key `encodeRFC5987ValueChars`, correct for URL params) are untouched. **Server:** each of the three `application/json` branches (POST/PUT/PATCH) tries the verbatim `JSON.parse(request.body)` FIRST and falls back to `JSON.parse(decodeURIComponent(request.body))` only when that throws — so a genuine raw-JSON body (incl. a literal `%XX` inside a string value) is never double-decoded (`#B28` intent preserved) while an already-deployed encoded sender is still accepted (version-skew-safe). Error disposition unchanged: POST/PATCH `throwError(500)` only when BOTH attempts fail, PUT warn-only. No dist rebuild — `controller.js` is not in the browser bundle and `encodeRFC5987ValueChars`'s definition is untouched (only its use in `this.query` changed). Tests: `test/core/http-methods.test.js` §14 (server tolerant-parse source pins + behavioural replica: encoded body parses, raw parses, `%XX` preserved via verbatim-win, malformed throws, POST/PATCH-500-vs-PUT-warn disposition) + §15 (client raw-JSON source pins + round-trip replica, incl. the legacy-encoded-sender still round-tripping); §13's `decodeURIComponent`-absence pin was tightened to verbatim-first / decode-only-as-fallback. Established 2026-06-09.
|
|
937
|
+
|
|
938
|
+
163. **`request.rawBody` — the exact unparsed request body is snapshotted before parsing, for inbound-webhook HMAC verification.** The non-multipart end-of-stream handler in `core/server.js` (`request.on('end', onEnd)`) now sets `request.rawBody = (typeof request.body === 'string') ? request.body : ''` immediately BEFORE `processRequestData(request, response, next)` mutates `request.body` into the parsed object. Signed-webhook authentication computes an HMAC over the exact raw request bytes; by the time `app.use(...)` middlewares run the stream is drained and `request.body` is the parsed object, so a consumer middleware could not otherwise capture the raw bytes (consume-then-`next()` hangs the request; a passive `data`/`end` listener sees nothing). The snapshot is a reference assignment (zero-copy) and always-on — no opt-in flag. It is gated to the non-multipart branch only: the `multipart/form-data` path uses Busboy (`request.pipe(busboy)`) and never reaches `onEnd`, so uploads are unaffected. Empty bodies yield `''` (never the `{}` request-init object — `request.body` is initialised to `{}` and only becomes a string once the data handler appends a chunk, so the `typeof === 'string'` guard maps both empty and object cases to `''`). **Engine coverage:** `processRequestData` and this end-of-stream handler live solely in `server.js` and are the shared body pipeline for BOTH engines — `server.isaac.js` has no separate general body accumulation (its only `req.on('data')` is the bounded `/_gina/instrument` control reader), so the isaac engine is covered without an engine-specific mirror. Tests: `test/core/http-methods.test.js` §16 (source pins: snapshot-before-`processRequestData` ordering, the string guard, single-site / after-`request.pipe(busboy)` placement; behavioural replica of multi-chunk accumulation + snapshot; empty-body → `''`; an HMAC round-trip recomputed over `req.rawBody`; a subtract-my-contribution case proving a re-stringified parse breaks verification). Established 2026-06-09.
|
|
939
|
+
|
|
940
|
+
164. **#B30 — a request with a malformed percent-escape in its URL or query string crashed the bundle (unauthenticated single-request DoS); fixed by crash-safe decode helpers + dropping a redundant double-decode.** Both `decodeURIComponent` AND `decodeURI` throw `URIError: URI malformed` on a malformed escape (a bare `%`, `%zz`, a truncated `%E0%A`); `proc.js`'s `uncaughtException` handler emits an `[ FRAMEWORK ][ uncaughtException ]` emerg and SIGTERM-shuts-down the bundle on a generic uncaught error, so any unguarded decode of attacker-controllable input on the request path was a single-request crash (the production trace: `processRequestData` GET branch → `onEnd` emit → emerg → SIGTERM → forced exit). Two defect classes, two fixes. **(1) Redundant double-decode (GET/HEAD)** — `processRequestData`'s GET/HEAD branches did `formatDataFromString(decodeURIComponent(request.query.inheritedData))` / `(bodyStr)`, but `request.query` is already percent-decoded once by the engine query parser AND `formatDataFromString` self-guards its own internal decode, so the explicit decode was a redundant SECOND decode that crashed on a literal `%` surviving the first decode (e.g. inheritedData `{"x":"50%off"}` → `%of`) and silently double-decoded valid data; DROPPED at all four sites (robust regardless of engine — `formatDataFromString` supplies the single guarded decode; behaviour byte-identical for the no-`%`-in-data case). **(2) Genuine first-decode (cannot drop)** — added `safeDecodeURIComponent` / `safeDecodeURI` DataHelper globals (`try { return decode…(str) } catch { return str }`, mirroring the POST/PUT/PATCH body branches) and routed every first-decode of attacker input through them: `server.isaac.js` query parser (`a[1]` ×2), `server.js` static path (`handleStatics` filename + `getAssetFilenameFromUrl` url), `helpers/data parseBody` (`arr[i]`/`el[0]`/`el[1]`), AND — the class the first pass MISSED (caught by adversarial review) — every `decodeURI` site (a SEPARATE function that throws the same `URIError`, used with a `/// avoid %20` comment on the routing + error paths): `server.js` ×3 (trie lookup, route params, `throwError`), `lib/routing/src/main.js` ×4 (cached params, `request.routing.path` ×2, linear-scan params), `controller.js` ×1 (error path). The `throwError` / `controller.js` error-path sites were worst — a malformed-`%` URL to a missing asset (`GET /assets/%E0%A.css`) reached `throwError` via the 404 path and crashed FROM the error handler (turning a would-be 404 into a crash); the async route-matching `decodeURI` sites instead rejected the dispatch promise → `unhandledRejection` (logged) → a *hung* request, now a clean 404. **NOT done:** no broadening of `proc.js`'s handler into a swallow-everything net (only known-benign TCP/HTTP codes are continued; generic errors still SIGTERM — swallowing arbitrary uncaught exceptions masks state corruption). **Deferred:** `formatDataFromString` still single-decodes an already-decoded value, so a literal `%20` in data still decodes to a space (no crash); fully fixing needs a no-decode variant (own consumer survey). **Lesson:** when sweeping a throwing-decode crash class, sweep BOTH `decodeURIComponent` AND `decodeURI`. Tests: `http-methods.test.js` §17 (GET/HEAD drop + first-decode pins, comment-stripped negatives) + §18 (the 8 `decodeURI` guards across server.js / lib/routing / controller.js), `server.isaac.test.js` §10 (query-parser safe-decode + `?x=%` replica), `format-data-from-string.test.js` (`safeDecodeURIComponent`/`safeDecodeURI` behavioural against the real loaded helper + subtract proving the pre-fix shape throws). Empirical repro (real DataHelper) reproduces the exact production `URIError` pre-fix and the clean result post-fix. Established 2026-06-10.
|
|
941
|
+
|
|
942
|
+
165. **The dev-mode Inspector statusbar (and its launch link) vanished on content-heavy pages because `String.replace(/re/, str)` expanded dollar-patterns in the replacement STRING (`0ba469d0`, 2026-06-10).** `render-swig.js` splices its inline dev scripts before `</body>` via `layout/htmlContent.replace(/<\/body>/i, content + '...')`. With a STRING replacement, `String.prototype.replace` expands dollar-sequences found in `content`: dollar-backtick = the text BEFORE the match (prematch), dollar-quote = AFTER (postmatch), `$&` = the match, `$1`/`$2` = capture groups (empty when the regex has none). The inlined #TPL2 statusbar body legitimately carries a dollar-backtick (in a regex-explaining comment) and a dollar-quote (in a regex literal), so the prematch spliced the ENTIRE document before `</body>` INTO the statusbar `<script>` → `SyntaxError` → the statusbar IIFE never ran (no statusbar host, no `ginaToolbar` shim, no link). Volume-dependent: a near-empty page (gina-starter) has a tiny prematch and renders fine — the bug only manifests on a real content-heavy render, so a gina-starter smoke is falsely green. The two sibling flow-patch splices (cache-hit + cache-miss) had the same latent exposure via SQL `$1`/`$2` placeholders captured in the flow `detail`. **Fix: function replacers** — `html.replace(/<\/body>/i, function () { return frag + '</body>'; })` inserts the return value verbatim with no dollar-expansion (escaping `$`→`$$` works too, less clear). The popin-XHR splices were already safe — `encodeRFC5987ValueChars` percent-encodes `$`→`%24`. **General rule: any `String.replace(re, X + ...)` where X is dynamic (JSON, user data, an inlined template, captured query/flow text) must use a function replacer or escape `$`** — a string replacement is a latent injection. render-swig.js is server-side (not in the browser bundle) so the fix needs no dist rebuild; it hot-reloads per render in dev/cacheless mode (so a running dev bundle picks it up on the next render). Tests: `render-swig.test.js §18` (3 source pins locking the function replacers + a pure-logic replica proving the old string form duplicates the document while the function replacer does not + a guard that the shipped statusbar actually carries a dollar-sequence). Established 2026-06-10.
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
166. **`project:add`'s gina-symlink step (`linkGina`) no longer shells out to a PATH-resolved `gina` binary, and a failed link now fails the command instead of printing success (`2966a823`, 2026-06-10).** The project's `node_modules/gina` symlink was created by spawning `gina link @<project>` resolved from the PATH; on hosts where no `gina` is on PATH — a repo checkout where `npm install --ignore-scripts` skipped bin linking (CI), or any non-global install — the spawn failed, the failure was routed through the SUCCESS callback (`onSuccess(err)`; the `onError` parameter was dead code), `project:add` printed "Project has been added", and the scaffolded bundle later crashed at boot with `MODULE_NOT_FOUND` on its framework require (the daemonless container-boot integration test was red on CI from its very first run there — never a regression of the commits it first ran against). Three-part fix in `lib/cmd/project/add.js::linkGina`: (a) spawn the running install's OWN CLI — `[process.execPath, <gina root>/bin/cli, 'link', '@<project>']`, the root resolved from the handler's own location (the same self-resolution rationale `framework/link.js` documents for its symlink source; `bin/cli` rewrites a bare `link` task to `framework:link`, which is offline-allowed, so no daemon is needed); the ARRAY form of `Shell.run` matters because the string form is split on spaces with no shell; (b) verify the actual POSTCONDITION (`fs.existsSync` on the project's `node_modules/gina`) instead of trusting the Shell's err channel — the Shell helper reports ANY stderr output as an error even on success, so postcondition-wins is what keeps stderr noise from failing a healthy link; (c) route genuine failures through `onError` so `project:add` exits non-zero. **Two general lessons: never self-invoke your own CLI via a PATH-resolved name (resolve from your own location — PATH may carry no copy or a different version), and when a spawn wrapper's error channel is noisy, assert the operation's postcondition rather than the channel.** Repro recipe for the class: re-run the failing flow with `PATH=/usr/bin:/bin` — a green-locally/red-on-CI integration test whose CI log shows `MODULE_NOT_FOUND` from generated code is the signature. Tests: `test/lib/project-add-link.test.js` (spawn-shape + postcondition + onError source pins, comment-stripped negative on the old PATH form, resolved-CLI-exists check against the real tree, onComplete decision replica); the container-boot integration test is the end-to-end CI guard. Established 2026-06-10.
|
|
946
|
+
|
|
947
|
+
167. **Two follow-on rules from completing the PATH-resolved self-invocation sweep (`884ee7cd` + `96ca2d03`, 2026-06-10).** (a) **Never guard `execSync` with `instanceof Error`** — `execSync` THROWS on non-zero exit and never returns an Error, so the shape `err = execSync(...); if (err instanceof Error) {...}` is dead code: the "handled" branch can never fire and a real failure escapes as an uncaught throw instead of reaching the command's error path. Wrap in try/catch, and prefer `err.stderr.toString().trim()` for the operator-facing message when present (execSync attaches the child's stderr to the thrown error). Three live instances of this dead shape were removed (framework:link's stale-node_modules repair and CmdHelper's auto-link step). (b) **When self-invoking, pick the entry point that preserves spawn semantics:** plain CLI commands (`link`, `link-node-modules`) are invoked through `bin/cli` directly, but daemon-lifecycle commands (`start`, `stop`, `bundle:restart` inside framework start/restart) must go through the `bin/gina` wrapper — it is the daemonizing "fake daemon" entry point and `start` relies on its detached spawn; `bin/gina` self-locates its cli via its own directory, so an absolute-path invocation stays PATH-independent end-to-end. Deliberate exceptions kept out of the sweep: deriving an npm install PREFIX from `which gina` for project:import registry metadata (semantically "where is gina installed on this machine" — meaningless on a repo checkout, and a wrong self-derived guess would corrupt project metadata; kept with its existing try/catch), and `$(which npm)` (a third-party binary — PATH is the normal resolution). Tests: `test/lib/project-add-link.test.js` §05-07. Established 2026-06-10.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gina",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "Node.js MVC framework with built-in HTTP/2, multi-bundle architecture, and scope-based data isolation — no Express dependency",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nodejs",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"gina-container": "bin/gina-container",
|
|
73
73
|
"gina-init": "bin/gina-init"
|
|
74
74
|
},
|
|
75
|
-
"main": "./framework/v0.4.
|
|
75
|
+
"main": "./framework/v0.4.6/core/gna",
|
|
76
76
|
"types": "./types/index.d.ts",
|
|
77
77
|
"typesVersions": {
|
|
78
78
|
"*": {
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
},
|
|
84
84
|
"scripts": {
|
|
85
85
|
"test": "node --test test*.test.js",
|
|
86
|
+
"test:e2e": "playwright test test/e2e",
|
|
86
87
|
"types:gen": "node script/generate_gna_types.js",
|
|
87
88
|
"types:check": "node script/generate_gna_types.js --check",
|
|
88
89
|
"prepare": "node script/prepare_version.js",
|
|
@@ -94,15 +95,17 @@
|
|
|
94
95
|
"license": "MIT",
|
|
95
96
|
"readmeFilename": "README.md",
|
|
96
97
|
"dependencies": {
|
|
97
|
-
"@rhinostone/swig": "^2.
|
|
98
|
+
"@rhinostone/swig": "^2.7.2",
|
|
98
99
|
"engine.io": "^6.6.7",
|
|
99
100
|
"psl": "^1.15.0",
|
|
100
101
|
"ws": "^8.20.1"
|
|
101
102
|
},
|
|
102
103
|
"devDependencies": {
|
|
104
|
+
"@playwright/test": "^1.48.0",
|
|
103
105
|
"csso-cli": "4.0.2",
|
|
104
106
|
"engine.io-client": "^6.6.4",
|
|
105
107
|
"jsdom": "^29.1.1",
|
|
108
|
+
"nunjucks": "^3.2.4",
|
|
106
109
|
"requirejs": "^2.3.7",
|
|
107
110
|
"sass": "1.100.0"
|
|
108
111
|
},
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Playwright config for the gina dialog ("popin") a11y e2e spec.
|
|
5
|
+
*
|
|
6
|
+
* Scope-limited to test/e2e/ — the default `npm test` (node --test) suite is
|
|
7
|
+
* unaffected. Requires browsers: `npx playwright install chromium`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { defineConfig, devices } = require('@playwright/test');
|
|
11
|
+
|
|
12
|
+
const E2E_PORT = process.env.GINA_E2E_PORT || '3179';
|
|
13
|
+
|
|
14
|
+
module.exports = defineConfig({
|
|
15
|
+
testDir: './test/e2e',
|
|
16
|
+
testMatch: '**/*.spec.js',
|
|
17
|
+
fullyParallel: true,
|
|
18
|
+
reporter: 'list',
|
|
19
|
+
use: {
|
|
20
|
+
trace: 'on-first-retry'
|
|
21
|
+
},
|
|
22
|
+
// Starts the runtime harness (test/e2e/runtime-server.js) that serves the REAL
|
|
23
|
+
// built gina bundle for popin-dialog.runtime.spec.js. The framework-free
|
|
24
|
+
// popin-dialog.a11y.spec.js drives file:// fixtures and ignores it.
|
|
25
|
+
webServer: {
|
|
26
|
+
command: 'node test/e2e/runtime-server.js',
|
|
27
|
+
url: 'http://localhost:' + E2E_PORT + '/',
|
|
28
|
+
reuseExistingServer: !process.env.CI,
|
|
29
|
+
timeout: 30 * 1000,
|
|
30
|
+
env: { GINA_E2E_PORT: E2E_PORT }
|
|
31
|
+
},
|
|
32
|
+
projects: [
|
|
33
|
+
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }
|
|
34
|
+
]
|
|
35
|
+
});
|