gina 0.6.25 → 0.6.26
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 +7 -0
- package/README.md +16 -19
- package/ROADMAP.md +1 -0
- package/framework/v0.6.26/VERSION +1 -0
- package/framework/{v0.6.25 → v0.6.26}/core/controller/controller.js +51 -6
- package/framework/{v0.6.25 → v0.6.26}/core/gna.js +1 -0
- package/framework/{v0.6.25 → v0.6.26}/core/server.js +136 -0
- package/framework/{v0.6.25 → v0.6.26}/lib/job/src/main.js +159 -6
- package/framework/{v0.6.25 → v0.6.26}/package.json +1 -1
- package/gna.js +4 -4
- package/llms.txt +3 -3
- package/package.json +3 -3
- package/schema/app.json +51 -0
- package/schema/settings.json +1 -1
- package/framework/v0.6.25/VERSION +0 -1
- /package/framework/{v0.6.25 → v0.6.26}/AUTHORS +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/LICENSE +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/html/nolayout.html +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/html/static.html +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/img/android-chrome-192x192.png +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/img/android-chrome-512x512.png +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/img/apple-touch-icon.png +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/img/favicon-16x16.png +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/img/favicon-32x32.png +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/img/favicon.ico +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.css +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/beemaster/index.html +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/css/gina.min.css +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.br +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.gz +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/html/statusbar.html +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.br +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.gz +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/inspector/have_heart_one-webfont.woff2 +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/inspector/index.html +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/inspector/inspector.css +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/inspector/inspector.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/inspector/logo.svg +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/js/gina.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/js/gina.min.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.br +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.gz +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/config.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/ai/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/ai/lib/connector.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/couchbase/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/couchbase/lib/connector.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/couchbase/lib/connector.v3.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/couchbase/lib/connector.v4.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/couchbase/lib/n1ql.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/couchbase/lib/session-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/couchbase/lib/session-store.v3.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/couchbase/lib/session-store.v4.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/couchbase/lib/storage-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/duckdb/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/duckdb/lib/connector.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/mongodb/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/mongodb/lib/connector.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/mongodb/lib/job-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/mongodb/lib/pipeline-loader.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/mongodb/lib/session-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/mysql/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/mysql/lib/connector.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/param-redact.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/postgresql/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/postgresql/lib/connector.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/redis/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/redis/lib/connector.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/redis/lib/job-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/redis/lib/kv-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/redis/lib/render-cache-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/redis/lib/session-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/scylladb/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/scylladb/lib/connector.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/scylladb/lib/session-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/settle-once.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/sql-parser.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/sqlite/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/sqlite/lib/connector.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/sqlite/lib/job-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/sqlite/lib/kv-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/connectors/sqlite/lib/session-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/content.encoding +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/controller.framework.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/controller.render-json.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/controller.render-nunjucks-async.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/controller.render-nunjucks.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/controller.render-stream.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/controller.render-swig-async.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/controller.render-swig.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/controller.render-v1.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/controller.render-xml.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/inline-script.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/inspector-window-emit.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/controller/release-banner.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/dev/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/dev/lib/class.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/dev/lib/factory.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/dev/lib/tools.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/locales/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/locales/currency.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/locales/dist/language/en.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/locales/dist/language/fr.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/locales/dist/region/en.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/locales/dist/region/fr.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/locales/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/mime.types +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/model/entity.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/model/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/model/template/entityFactory.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/model/template/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/csrf/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/csrf/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/csrf/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/coep/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/coep/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/coep/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/coop/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/coop/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/coop/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/corp/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/corp/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/corp/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/csp/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/csp/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/csp/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/hide-powered-by/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/hide-powered-by/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/hide-powered-by/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/hsts/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/hsts/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/hsts/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/origin-agent-cluster/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/origin-agent-cluster/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/origin-agent-cluster/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/referrer-policy/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/referrer-policy/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/referrer-policy/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-content-type-options/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-content-type-options/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-content-type-options/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-dns-prefetch-control/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-dns-prefetch-control/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-dns-prefetch-control/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-download-options/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-download-options/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-download-options/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-frame-options/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-frame-options/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-frame-options/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-xss-protection/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-xss-protection/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/security-headers/x-xss-protection/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/session/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/session/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/session/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/storage/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/storage/build.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/storage/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/storage/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/validator/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/validator/build.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/validator/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/validator/src/form-validator.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/plugins/lib/validator/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/router.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/server.express.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/server.isaac.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/status.codes +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/_gitignore +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/config/app.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/config/connectors.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/config/routing.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/config/settings.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/config/settings.server.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/config/templates.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/config/watchers.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/controllers/controller.content.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/controllers/controller.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/controllers/setup.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle/locales/en.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_namespace/controllers/controller.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_public/css/default.css +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_public/css/home.css +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_public/css/vendor/readme.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_public/favicon.ico +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_public/js/components/x-checklist.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_public/js/vendor/readme.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_public/manifest.webmanifest +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_public/readme.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_public/sw.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_templates/handlers/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_templates/html/content/homepage.html +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_templates/html/includes/error-msg-noscript.html +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_templates/html/includes/error-msg-outdated-browser.html +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_templates/html/includes/x-checklist.html +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/boilerplate/bundle_templates/html/layouts/main.html +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/command/gina.bat.tpl +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/command/gina.tpl +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/conf/env.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/conf/manifest.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/conf/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/conf/settings.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/conf/statics.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/conf/templates.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/error/client/json/401.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/error/client/json/403.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/error/client/json/404.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/error/server/html/50x.html +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/error/server/json/500.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/error/server/json/503.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/core/template/extensions/logger/config.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/console.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/context.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/data/LICENSE +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/data/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/data/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/data/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/dateFormat.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/json/LICENSE +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/json/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/json/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/json/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/path.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/plugins/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/plugins/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/plugins/src/api-error.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/plugins/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/prototypes.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/task.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/helpers/text.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/admin/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/admin/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/archiver/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/archiver/build.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/archiver/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/archiver/src/dep/jszip.min.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/archiver/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/async/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/async/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/audit/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/audit/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/audit-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/authn/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/authn/src/lockout.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/authn/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/authn/src/totp.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/authz-gate/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/authz-gate/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cache/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cache/build.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cache/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cache/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/aliases.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/audit/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/audit/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/audit/verify.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/add.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/build.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/copy.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/cp.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/inc/name-rewrite.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/list.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/man.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/mcp-start.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/mcp.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/oas.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/openapi.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/remove.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/rename.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/restart.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/rm.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/start.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/status.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/stop.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/bundle/types.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/cache/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/cache/clear.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/cache/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/cache/stats.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/connector/add.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/connector/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/connector/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/connector/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/connector/infer.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/connector/list.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/connector/migrate.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/connector/models.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/connector/remove.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/connector/rm.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/connector/test.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/container/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/container/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/container/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/container/man.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/container/ps.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/container/stop.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/controller/add.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/controller/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/controller/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/controller/inc/args.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/controller/inc/namespace.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/controller/inc/reference-rewrite.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/controller/inc/reference-scan.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/controller/inc/scaffold.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/controller/remove.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/controller/rename.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/controller/rm.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/env/add.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/env/get.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/env/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/env/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/env/link-dev.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/env/list.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/env/remove.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/env/rm.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/env/set.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/env/unset.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/env/use.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/add.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/build.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/dot.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/get.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/init.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/link-node-modules.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/link.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/list.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/man.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/msg.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/open.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/remove.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/reset.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/restart.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/set.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/start.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/status.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/stop.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/tail.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/update.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/framework/version.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/gina-dev.1.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/gina-framework.1.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/gina.1.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/helper.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/i18n/add.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/i18n/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/i18n/export.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/i18n/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/i18n/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/i18n/import.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/i18n/scan.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/image/_host.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/image/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/image/build.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/image/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/image/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/image/list.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/image/man.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/image/rm.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/image/run.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/inspector/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/inspector/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/inspector/open.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/man-render.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/minion/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/minion/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/minion/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/minion/kill.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/minion/list.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/msg.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/port/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/port/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/port/inc/scan.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/port/list.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/port/reset.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/port/set.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/add.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/backup.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/build.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/import.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/list.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/man.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/move.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/remove.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/rename.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/restart.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/restore.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/rm.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/start.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/status.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/project/stop.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/protocol/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/protocol/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/protocol/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/protocol/list.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/protocol/remove.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/protocol/set.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/scope/add.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/scope/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/scope/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/scope/link-local.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/scope/link-production.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/scope/list.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/scope/remove.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/scope/rm.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/scope/use.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/secrets/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/secrets/check.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/secrets/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/secrets/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/secrets/scan.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/service/help.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/service/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/service/list.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/service/man.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/service/start.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/storage/arguments.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/storage/gc.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/storage/help.txt +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/storage/stats.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/storage/verify.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd/view/add.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd-status-format/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cmd-status-format/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/collection/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/collection/build.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/collection/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/collection/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/config.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/connector-config/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/connector-config/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/connector-error/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/connector-error/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/connector-registry/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/connector-registry/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cron/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cron/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/cron/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/domain/LICENSE +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/domain/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/domain/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/domain/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/dto/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/dto/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/dto-pipe/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/dto-pipe/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/dto-types/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/dto-types/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/generator/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/i18n/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/i18n/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/idempotency/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/idempotency/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/image-build/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/image-build/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/inherits/LICENSE +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/inherits/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/inherits/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/inherits/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/inspector-events/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/inspector-events/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/inspector-redact/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/inspector-redact/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/instrument/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/instrument/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/job/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/job-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/json-config-header/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/json-config-header/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/kv/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/kv/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/kv-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/loading-state/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/loading-state/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/src/containers/default/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/src/containers/file/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/src/containers/file/lib/logrotator/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/src/containers/file/lib/logrotator/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/src/containers/mq/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/src/containers/mq/listener.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/src/containers/mq/speaker.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/src/helper.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/logger/src/redact.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/maintenance/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/maintenance/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/math/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/mcp-dispatch/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/mcp-dispatch/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/mcp-http/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/mcp-http/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/mcp-server/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/mcp-server/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/merge/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/merge/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/merge/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/message-validator/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/message-validator/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/metrics/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/metrics/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/model.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/money/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/money/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/net-locality/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/net-locality/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/nunjucks-filters/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/nunjucks-filters/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/nunjucks-filters/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/nunjucks-resolver/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/nunjucks-resolver/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/proc.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/push/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/push/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/rate-limit/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/rate-limit/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/release-watch/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/release-watch/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/render-cache/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/render-cache/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/render-cache-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/routing/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/routing/build.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/routing/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/routing/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/routing/src/radix.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/routing-introspect/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/routing-introspect/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/secrets/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/secrets/src/backends/env.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/secrets/src/backends/exec.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/secrets/src/backends/file.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/secrets/src/declaration.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/secrets/src/env-file.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/secrets/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/secrets/src/sources.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/security-headers-emitter/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/security-headers-emitter/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/session-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/shell.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/sqlite-driver.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/sri/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/sri/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/state.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/storage/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/storage/src/local-cas.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/storage/src/local-stream.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/storage/src/local.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/storage/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/storage/src/meta-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/storage/src/s3.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/storage/src/util.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/storage-store.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/swig-filters/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/swig-filters/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/swig-filters/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/swig-resolver/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/swig-resolver/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/template-loaders/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/template-loaders/src/loaders/http.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/template-loaders/src/loaders/memory.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/template-loaders/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/url/README.md +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/url/index.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/url/routing.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/uuid/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/uuid/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/validator.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/watcher/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/watcher/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/ws-framing/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/ws-framing/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/ws-query/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/ws-query/src/main.js +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/ws-session/package.json +0 -0
- /package/framework/{v0.6.25 → v0.6.26}/lib/ws-session/src/main.js +0 -0
package/gna.js
CHANGED
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
17
|
// Framework core — the main gna module (lifecycle hooks, lib, etc.)
|
|
18
|
-
var _gna = require('./framework/v0.6.
|
|
18
|
+
var _gna = require('./framework/v0.6.26/core/gna');
|
|
19
19
|
|
|
20
20
|
// SuperController and EntitySuper — loaded from their source modules
|
|
21
|
-
var SuperController = require('./framework/v0.6.
|
|
22
|
-
var EntitySuper = require('./framework/v0.6.
|
|
21
|
+
var SuperController = require('./framework/v0.6.26/core/controller');
|
|
22
|
+
var EntitySuper = require('./framework/v0.6.26/core/model/entity');
|
|
23
23
|
|
|
24
24
|
// uuid — from the lib registry
|
|
25
|
-
var uuid = require('./framework/v0.6.
|
|
25
|
+
var uuid = require('./framework/v0.6.26/lib/uuid');
|
|
26
26
|
|
|
27
27
|
module.exports = {
|
|
28
28
|
|
package/llms.txt
CHANGED
|
@@ -333,7 +333,7 @@ ai.client.chat.completions.create({ ... });
|
|
|
333
333
|
|
|
334
334
|
## Async jobs (#AI6)
|
|
335
335
|
|
|
336
|
-
Run slow work (e.g. a 1–30s `.infer()`) out-of-band so it doesn't tie up the request pipeline. `lib.job.create(fn, opts)` enqueues a deferred async function on a concurrency-limited worker (default 4) and returns a `jobId` immediately; state moves `pending → running → completed | failed`. Records live behind a callback-shaped `set/get/remove/list/sweep` store seam — in-memory by default, or connector-backed via `app.json`'s `jobs.store` (SQLite for single-host durability, MongoDB or Redis for multi-pod visibility: records survive bundle restarts and are readable cross-process/pod — a job created on one pod is pollable from another — while the deferred function still runs only in the creating process) — with a self-contained unref'd `setInterval` TTL sweep — `lib/cron` is NOT involved.
|
|
336
|
+
Run slow work (e.g. a 1–30s `.infer()`) out-of-band so it doesn't tie up the request pipeline. `lib.job.create(fn, opts)` enqueues a deferred async function on a concurrency-limited worker (default 4) and returns a `jobId` immediately; state moves `pending → running → completed | failed`. Records live behind a callback-shaped `set/get/remove/list/sweep` store seam — in-memory by default, or connector-backed via `app.json`'s `jobs.store` (SQLite for single-host durability, MongoDB or Redis for multi-pod visibility: records survive bundle restarts and are readable cross-process/pod — a job created on one pod is pollable from another — while the deferred function still runs only in the creating process) — with a self-contained unref'd `setInterval` TTL sweep — `lib/cron` is NOT involved. **Orphan reclaim (#B471):** the sweep is preceded by a reclaim pass on DURABLE stores — a non-terminal record stranded by a dead process (the deferred fn is a closure and dies with its process, so such a record can never settle, and the TTL sweep's `expires_at IS NOT NULL` predicate excludes it forever) is terminalized as `failed` with error name `JobOrphanedError` once older than `jobs.orphanTimeout` (SECONDS, default 86400/24h, `0`/`false` disables, floored at 60), after which normal `ttl` retention removes it; gates: `running` on last-transition age, `pending` with a scheduled retry on `nextRetryAt` + ceiling (a record merely waiting on backoff is never touched). The built-in memory store is exempt (an in-process store cannot hold another process's orphans — the shipped default therefore has zero false-positive exposure); a record reclaimed while its origin was merely slow SELF-CORRECTS on the origin's next write (every origin write is unguarded last-write-wins, measured); reclaim fires NO completion webhook (concurrent multi-process reclaim + a self-healing job would produce contradictory deliveries — the record carries the truth); a store lacking `list` skips the pass; never throws. Rule: a sweep whose predicate keys on a field only TERMINAL transitions set will strand every record a dead process leaves non-terminal — pair such a sweep with a reclaim pass, or stamp the field at creation.
|
|
337
337
|
|
|
338
338
|
```javascript
|
|
339
339
|
// In a controller action — return immediately, work runs out-of-band:
|
|
@@ -882,7 +882,7 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
882
882
|
|
|
883
883
|
151. **`templates.json` pre-process pass in `core/config.js` (right after the `hasViews` line, BEFORE the routing↔template GET auto-vivify) expands two additive section-key shapes once per bundle — gina-io/gina#8 comma-separated keys + #10 `_common.config`.** #8: a comma-separated section key (`"a, b": {…}`) is split on `/\s*,\s*/` (each name `.trim()`med, empty segments skipped) and the block is replicated under each named section, MERGING into any section that already exists so a section's own keys win — `merge(existing, JSON.clone(block))`, and `lib/merge` keeps its FIRST argument on a leaf collision (`override=false` default). #10: an optional `_common.config` block is `merge(_common, _common.config)`-flattened back into `_common` then deleted, so the existing `_common.*` read sites are unchanged and a direct `_common.X` overrides `_common.config.X`. **Placement is load-bearing:** the pass must run before the GET auto-vivify `files['templates'][rule.toLowerCase()] = {}` (which keys off CLEAN route names from routing.json) — otherwise a comma key leaves the real route names "missing" → empty `{}` sections get minted AND the comma key survives to produce a dead `"a, b@bundle"` route. **Both are no-ops when absent** (no comma → single-element split → identical; no `_common.config` → untouched), so existing bundles are byte-identical — verified zero comma keys across known consumer + gina fixture `templates.json` before shipping. **#7 (a Swig-like `{ "inherit": … }` directive) was closed un-shipped**, so #8 is NOT redundant: `_common` shares to ALL routes, #8 shares a chosen SUBSET — a gap nothing else fills. Collect-then-mutate (gather comma keys first) avoids changing the object mid-`for…in`. Tests: `test/core/config-templates-preprocess.test.js` (source pins incl. placement-before-auto-vivify + a real-`merge` pure-logic replica: split / union-merge / own-keys-win / trim / empty-segment / flatten / no-op). Established 2026-06-05.
|
|
884
884
|
|
|
885
|
-
174. **`throwError` — call signatures, status-code preservation, render-error interception, and fail-closed error-response `stack` hygiene** (replaces individual entries #10, #21, #26, #105, #110, #132) — `self.throwError` accepts four shapes: `(errorObj|Error)` 1-arg; `(code, Error|string)` 2-arg, dispatched via a function-top normalization shift that preserves the explicit code (`throwError(404, new Error('not found'))` sends 404 — earlier releases fell back to 500, and the `new Error(...)` wrapping workaround from that era still works unchanged); `(code, errorObj)` 2-arg, intentionally NOT shifted — it flows through the `arguments.length < 3` branch whose `code = res || 500` reaches the explicit code, and including errorObj in the shift detection would break exactly that case; `(res, code, string|Error)` 3-arg (explicit code preserved). Always `return self.throwError(...)` immediately in a controller action — it is a terminal response, and any later response-API call runs against a released response (guarded to warn + no-op since 0.5.1-alpha.2 instead of crashing the bundle). Calling `self.render(err)` with a non-2xx `data.page.data.status` and a defined `data.page.data.error` is intercepted before template rendering and routed through `throwError` automatically; object-valued `error`/`message` fields are normalised to strings first (no `[object Object]`), and the same normalized string feeds the server-side `console.error('[render] ...')` log, so wire and log carry identical readable text. Error responses are scope-gated FAIL-CLOSED: unless `NODE_SCOPE_IS_LOCAL` is explicitly `true`, the server-side `stack` is stripped from BOTH the JSON error body AND the fallback HTML error page's `<pre class="stack">` block — the gate is strip-unless-local, not strip-only-if-prod, so an unset scope on a fresh deployment still strips and internals (file paths, frames, library versions) never leak; local scope keeps the stack on the wire for the dev toolbar's data-xhr panel. Custom error templates remain consumer-owned (a view rendering the error object's `stack` is the consumer's call), and passing `err.stack` as the message argument surfaces it in the un-gated `error` STRING — sanitize that at the call site. **The sibling `renderJSON()` status surface had an HTTP/2-only hole (#B172, fixed 2026-07-30): the `status` key in the payload correctly resolves onto `response.statusCode`, but the HTTP/2 body path hand-builds its header frame for the raw `stream.respond()` and hardcoded `':status': 200` — and the pending-header merge below can never supply the real code, since `setHeader(':status', …)` throws ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED — so every `renderJSON({status: 4xx})` on a genuine HTTP/2 stream was served as 200 with the error payload in the body (HTTP/1.1, the HEAD branch, and the HTML delegates were already correct). Fixed to `':status': response.statusCode || 200`, matching the HEAD branch. The `errno` half of the status branch is now guarded like the swig/v1 delegates (enter only when `statusCodes[jsonObj.status]` is defined): an `errno`-only payload used to assign `statusCode = undefined`, which HTTP/1.1's `response.end()` rejects with the throw swallowed by an empty catch (the response was never sent — the client hangs) and HTTP/2's compat setter rejects at assignment (→ 500); guarded, such a payload is served as a normal 200 with the payload in the body — measured wire-identical to dropping the errno clause on every input. Rule: any hand-built HTTP/2 header frame must carry `response.statusCode || 200`, never a literal — the compat layer's pending-header merge structurally cannot repair a pseudo-header. Tests: `render-json.test.js §07-§08` (extract-and-execute on the real branch bytes).** **Body field semantics — which key carries the human sentence (docs-corrected 2026-07-30): on a known status code `error` carries the STATUS TEXT (`statusCodes[code]`) and the caller's text lands in `message` (the Error argument's `.message`, or the trailing string), so a client wanting the sentence reads `message`, NOT `error`; only for an unknown status code (warned) can the caller's text land in `error` instead. The sibling `ApiError` + `renderJSON` path cannot carry a message on the wire AT ALL: the server-error forms (`new ApiError(msg)`, `new ApiError(msg, code)`) return a real `Error` whose `message` is a non-enumerable own property that `renderJSON`'s single `JSON.stringify` skips (readable server-side, absent from the body — and reassigning `e.message` does NOT make it enumerable, only `defineProperty` would), while the field forms (`new ApiError(msg, field)`, `new ApiError(msg, field, code)`, the array form) return a PLAIN OBJECT built by `merge({tag,fields,path}, e)`, which copies enumerable properties only — so `message` is absent outright there and the text travels in `fields[field]` (those bodies also carry `tag` + `path`). To place a sentence in the body use `throwError` or hand-build the payload.** **The custom-error-PAGE path had the same inert-status class (#B190, fixed 2026-08-01): `renderCustomError` — the `req.routing.param.error` / `errorFiles` path — never set the response status; the swig and v1 delegates recompute it downstream from `data.page.data.status`, but the nunjucks and both async delegates only read the already-set `res.statusCode || 200` at their write sites, so a configured custom error page was served as HTTP 200 there (live-measured on the same fixture: nunjucks 200 vs swig 500 pre-fix; nunjucks 500 post-fix). Fixed with a guarded stamp in `renderCustomError` immediately before the render dispatch — `res.statusCode = data.status`, gated on `!res.headersSent` + `statusCodes[data.status]` membership, mirroring the swig delegate's own downstream semantics — so every delegate serves the configured page with its real status, and a transient-failure 503 upgrade (#CE1) rides this path with a meaningful `Retry-After`. Tests: `test/core/controller-custom-error-status.test.js` (source pins + behavioral drive of the real bytes via `createTestInstance` with a stubbed `render()`; red-first).** **The same path could DISCARD its resolved template outright (#B191, fixed 2026-08-01, consumer-reported against 0.6.0): `renderCustomError` built its `errOptions` only under the `isLocalOptionResetNeeded` flag, but the server-side throwError twin sets `param.file` WITHOUT that flag (and the flag rode a shared routing object the dispatch mutated per error), so a falsy read left `errOptions` null and the swig delegate fell back to the FAILING route's own `file` — a bare, un-rooted `<file>.html` ENOENT plus a "check the following rule in your routing.json" dump naming a rule that was correct by construction (an upstream outage misread as a routing misconfiguration; the built-in fallback page still served, so end users saw an error page throughout). Fixed threefold: the resolved `param.file` now reaches `errOptions` unconditionally (with `path: null` so the namespace stays ignored — heals nunjucks too, which read the same `localOptions.file`); the controller-side dispatch works on a `JSON.clone` of the injected route instead of mutating `content.routing[<rule>]` (lib/routing `getRoute()` already clones for the server twin, which is also why a clean router dispatch self-rescued); and render-swig's template-not-found diagnostic, when rendering a custom error page, names the custom error template and hands off to throwError's re-entry guard (built-in page, no loop) — never the routing-rule dump. Rule: anything the error dispatch resolves must not depend on optional dispatch flags, and the dispatch must never mutate shared routing state. Tests: `test/core/controller-custom-error-options.test.js` (source pins + real-bytes behavioral for BOTH the file channel and the shared-config non-mutation; red-first validated on pre-fix bytes in a detached worktree).**
|
|
885
|
+
174. **`throwError` — call signatures, status-code preservation, render-error interception, and fail-closed error-response `stack` hygiene** (replaces individual entries #10, #21, #26, #105, #110, #132) — `self.throwError` accepts four shapes: `(errorObj|Error)` 1-arg; `(code, Error|string)` 2-arg, dispatched via a function-top normalization shift that preserves the explicit code (`throwError(404, new Error('not found'))` sends 404 — earlier releases fell back to 500, and the `new Error(...)` wrapping workaround from that era still works unchanged); `(code, errorObj)` 2-arg, intentionally NOT shifted — it flows through the `arguments.length < 3` branch, which reaches the explicit code — that branch passes an OBJECT through untouched (so the errorObj resolution inside the JSON branch below can unpack it; collapsing it there silently disables the #CE1 transient-503 upgrade, measured) and validates a SCALAR, and including errorObj in the shift detection would break exactly that case; `(res, code, string|Error)` 3-arg (explicit code preserved). Always `return self.throwError(...)` immediately in a controller action — it is a terminal response, and any later response-API call runs against a released response (guarded to warn + no-op since 0.5.1-alpha.2 instead of crashing the bundle). Calling `self.render(err)` with a non-2xx `data.page.data.status` and a defined `data.page.data.error` is intercepted before template rendering and routed through `throwError` automatically; object-valued `error`/`message` fields are normalised to strings first (no `[object Object]`), and the same normalized string feeds the server-side `console.error('[render] ...')` log, so wire and log carry identical readable text. Error responses are scope-gated FAIL-CLOSED: unless `NODE_SCOPE_IS_LOCAL` is explicitly `true`, the server-side `stack` is stripped from BOTH the JSON error body AND the fallback HTML error page's `<pre class="stack">` block — the gate is strip-unless-local, not strip-only-if-prod, so an unset scope on a fresh deployment still strips and internals (file paths, frames, library versions) never leak; local scope keeps the stack on the wire for the dev toolbar's data-xhr panel. Custom error templates remain consumer-owned (a view rendering the error object's `stack` is the consumer's call), and passing `err.stack` as the message argument surfaces it in the un-gated `error` STRING — sanitize that at the call site. **The sibling `renderJSON()` status surface had an HTTP/2-only hole (#B172, fixed 2026-07-30): the `status` key in the payload correctly resolves onto `response.statusCode`, but the HTTP/2 body path hand-builds its header frame for the raw `stream.respond()` and hardcoded `':status': 200` — and the pending-header merge below can never supply the real code, since `setHeader(':status', …)` throws ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED — so every `renderJSON({status: 4xx})` on a genuine HTTP/2 stream was served as 200 with the error payload in the body (HTTP/1.1, the HEAD branch, and the HTML delegates were already correct). Fixed to `':status': response.statusCode || 200`, matching the HEAD branch. The `errno` half of the status branch is now guarded like the swig/v1 delegates (enter only when `statusCodes[jsonObj.status]` is defined): an `errno`-only payload used to assign `statusCode = undefined`, which HTTP/1.1's `response.end()` rejects with the throw swallowed by an empty catch (the response was never sent — the client hangs) and HTTP/2's compat setter rejects at assignment (→ 500); guarded, such a payload is served as a normal 200 with the payload in the body — measured wire-identical to dropping the errno clause on every input. Rule: any hand-built HTTP/2 header frame must carry `response.statusCode || 200`, never a literal — the compat layer's pending-header merge structurally cannot repair a pseudo-header. Tests: `render-json.test.js §07-§08` (extract-and-execute on the real branch bytes).** **Body field semantics — which key carries the human sentence (docs-corrected 2026-07-30): on a known status code `error` carries the STATUS TEXT (`statusCodes[code]`) and the caller's text lands in `message` (the Error argument's `.message`, or the trailing string), so a client wanting the sentence reads `message`, NOT `error`; only for an unknown status code (warned) can the caller's text land in `error` instead. The sibling `ApiError` + `renderJSON` path cannot carry a message on the wire AT ALL: the server-error forms (`new ApiError(msg)`, `new ApiError(msg, code)`) return a real `Error` whose `message` is a non-enumerable own property that `renderJSON`'s single `JSON.stringify` skips (readable server-side, absent from the body — and reassigning `e.message` does NOT make it enumerable, only `defineProperty` would), while the field forms (`new ApiError(msg, field)`, `new ApiError(msg, field, code)`, the array form) return a PLAIN OBJECT built by `merge({tag,fields,path}, e)`, which copies enumerable properties only — so `message` is absent outright there and the text travels in `fields[field]` (those bodies also carry `tag` + `path`). To place a sentence in the body use `throwError` or hand-build the payload.** **The custom-error-PAGE path had the same inert-status class (#B190, fixed 2026-08-01): `renderCustomError` — the `req.routing.param.error` / `errorFiles` path — never set the response status; the swig and v1 delegates recompute it downstream from `data.page.data.status`, but the nunjucks and both async delegates only read the already-set `res.statusCode || 200` at their write sites, so a configured custom error page was served as HTTP 200 there (live-measured on the same fixture: nunjucks 200 vs swig 500 pre-fix; nunjucks 500 post-fix). Fixed with a guarded stamp in `renderCustomError` immediately before the render dispatch — `res.statusCode = data.status`, gated on `!res.headersSent` + `statusCodes[data.status]` membership, mirroring the swig delegate's own downstream semantics — so every delegate serves the configured page with its real status, and a transient-failure 503 upgrade (#CE1) rides this path with a meaningful `Retry-After`. Tests: `test/core/controller-custom-error-status.test.js` (source pins + behavioral drive of the real bytes via `createTestInstance` with a stubbed `render()`; red-first).** **The same path could DISCARD its resolved template outright (#B191, fixed 2026-08-01, consumer-reported against 0.6.0): `renderCustomError` built its `errOptions` only under the `isLocalOptionResetNeeded` flag, but the server-side throwError twin sets `param.file` WITHOUT that flag (and the flag rode a shared routing object the dispatch mutated per error), so a falsy read left `errOptions` null and the swig delegate fell back to the FAILING route's own `file` — a bare, un-rooted `<file>.html` ENOENT plus a "check the following rule in your routing.json" dump naming a rule that was correct by construction (an upstream outage misread as a routing misconfiguration; the built-in fallback page still served, so end users saw an error page throughout). Fixed threefold: the resolved `param.file` now reaches `errOptions` unconditionally (with `path: null` so the namespace stays ignored — heals nunjucks too, which read the same `localOptions.file`); the controller-side dispatch works on a `JSON.clone` of the injected route instead of mutating `content.routing[<rule>]` (lib/routing `getRoute()` already clones for the server twin, which is also why a clean router dispatch self-rescued); and render-swig's template-not-found diagnostic, when rendering a custom error page, names the custom error template and hands off to throwError's re-entry guard (built-in page, no loop) — never the routing-rule dump. Rule: anything the error dispatch resolves must not depend on optional dispatch flags, and the dispatch must never mutate shared routing state. Tests: `test/core/controller-custom-error-options.test.js` (source pins + real-bytes behavioral for BOTH the file channel and the shared-config non-mutation; red-first validated on pre-fix bytes in a detached worktree).** **Every status the method resolves is VALIDATED before it can reach `writeHead` (#B466, 2026-09-04): an unvalidated status previously reached `res.writeHead()` verbatim, so a payload whose top-level `status` was a domain string (`{status:'draft', error:'…'}` — a field name any page vocabulary may legitimately own) threw `RangeError [ERR_HTTP_INVALID_STATUS_CODE]` and replaced the intended error page with an unhandled 500. The failure was invisible on every SUCCESS render (the swig success path already guarded with a 200 fallback) and fatal only on the error path, i.e. exactly when the error page was needed. The guard is a module-level `_isValidHttpStatus` predicate encoding node's OWN rule — integer 100-999 — deliberately NOT `statusCodes` membership (that table carries a `_comment` key, so a membership test accepts the status `"_comment"`) and NOT `/^\d{3}$/` (it accepts `"099"`, which node rejects; both measured). The `statusCodes` lookup stays as the `[ ApiValidator ]` diagnostic warn — table for diagnosis, range for correctness. A numeric `status` still sets the HTTP status exactly as documented; an invalid one now degrades to a 500 error page instead of throwing. Tests: `test/core/throwerror-status-validation.test.js` (15; red-first 6/13 → green, driven through `createTestInstance` against real bytes).** **The same method's five `writeHead` EGRESS sites had a headers-argument twin (#B467, 2026-09-04): two of them — the MSIE override and the no-`user-agent` else branch — passed the literal string `"content-type"` as node's statusMessage (`writeHead(statusCode, statusMessage, headers)`), so the MIME string landed in the headers slot, while the two sibling branches beside them already used the correct 2-arg object form. Node does NOT throw on this — measured on both transports: over HTTP/1.1 it iterates `Object.keys()` of the MIME STRING, so the response carries ~10-31 junk headers named `"0"`,`"1"`,… , a reason phrase reading `content-type`, and NO content-type at all; over HTTP/2 the headers argument is dropped and a one-time `UnsupportedWarning` (status message unsupported, RFC 7540 §8.1.2.4) is emitted. So the defect is SILENT corruption of the error response, never a crash — nothing appears in logs. Reachability was measured by driving the real method, not inferred: the else branch fires for any caller that sends no `user-agent` header at all, the common shape for machine callers. Both sites now pass an object, and the MSIE branch declares `'text/plain; charset='+ bundleConf.encoding` rather than a bare `text/plain` — every branch here ends at `res.end(JSON.stringify(errorObject))`, whose caller-supplied `error`/`message` text goes out as UTF-8 (`JSON.stringify` does not escape non-ASCII), so a charset-less `text/plain` invites a single-byte guess that corrupts it (`refusé` → `refusé`, measured); this matches the `'text/plain' + '; charset='+ conf.encoding` form the same file already builds on its text-render path. Rule: `writeHead`'s 2nd POSITIONAL argument is a statusMessage, never a header name — headers go in an object. Tests: `test/core/throwerror-writehead-headers.test.js` (13; red-first 6/13 → green; both defective branches driven through `createTestInstance`, both correct siblings pinned as controls, and every source pin validated red against the pre-fix bytes).**
|
|
886
886
|
|
|
887
887
|
175. **Dev-mode hot-reload & module lifecycle — what reloads, what doesn't, and the `require.cache` poisoning antipattern** (replaces individual entries #22, #25, #28, #34, #104) — in dev mode (`NODE_ENV_IS_DEV` set; `isCacheless()` true) the framework hot-reloads code via two functions with DIFFERENT triggers: `refreshCoreDependencies()` (`core/router.js`) evicts and re-requires the controller pair ONLY when the dev watcher has marked a watched file dirty (`__hotReload` flags; watched: `controller.js`, `controller.render-swig.js`, and the bundle's `controllers/` directory; falls back to per-request eviction when the watcher context is absent), while `refreshCore()` (`core/server.isaac.js`, isaac engine only) re-exports core-path modules and re-requires `lib/index.js` + `plugins/index.js` on EVERY request. Consequence for controllers: module-level state (`var store = {}`) resets whenever a watched file changes (not per request) — for in-process state that survives hot-reloads (but resets on `bundle:restart`) attach to `global` (`if (!global.__myStore) global.__myStore = {}; var store = global.__myStore;`); for durable state use a database or file. NOT hot-reloaded (a full `gina bundle:stop` + `bundle:start` or `docker restart` is required): `server.js` / `server.isaac.js` / `server.express.js` (loaded once at process start, never evicted); connector code (`core/connectors/*/index.js`, loaded once via entity registration, outside the refresh scope); and bundle-registered plugin middleware — the `onInitialize` → `app.use(gina.plugins.X(...))` factories run ONCE at bootstrap, so a plugin config change needs a bundle restart in BOTH dev and prod, despite the misleading per-request refresh cue. Correctness invariant for any eviction code: `require.cache[path]` must hold a `Module` instance — `require.cache[path] = require(path)` poisons the slot by storing the bare exports object (no `.exports` key), so the next plain `require()` of that path returns `undefined`, surfacing as `Cannot read properties of undefined (reading '<X>')` after a hot reload; use `delete require.cache[require.resolve(path)]` + the `require()` return value, or swap `require.cache[c].exports = require(path)` on the existing Module — never the bare assignment. **The eviction cycles also leaked the whole module graph (#B32, folds former #173):** Node pushes every cache-miss require's fresh Module onto the REQUIRING module's `children` array and dedupes only on cache hits, so the per-request delete-and-re-require cycles accumulated one dead Module per eviction on long-lived parents — each pinning its entire evaluated exports graph (~1.8 MB post-GC live heap per request on a minimal dev bundle; heap-limit OOM/SIGABRT at ~2400 requests, presenting upstream as HTTP/2 PING timeouts, then ECONNREFUSED, then a supervisor respawn loop that keeps every process cold). Fixed by a `pruneDeadModuleChildren()` sweep at the end of BOTH eviction cycles — `children` is diagnostic metadata (nothing in Node resolution reads it), so pruning never unloads a module still referenced elsewhere; prod was never affected (no eviction + cache-hit dedup). The sweep walks `require.cache` keys ONLY, so a second residual of the same class existed OFF-cache: hot-evicted leaf/singleton libs captured at gen-0 by load-once modules and re-required per request pushed dead children onto the evicted-but-retained gen-0 parent, prune-blind — those libs are plain-`require`d now (never evicted → cache-hit → children deduped), and a completeness audit closed the gen-0-binding class. Rules: any new delete-`require.cache` + re-require cycle in a long-lived process must end with the prune sweep; a hot-evicted lib captured as a gen-0 binding by a load-once module leaks PAST the prune — plain-require leaf/singleton libs that don't need hot reload.
|
|
888
888
|
|
|
@@ -902,7 +902,7 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
902
902
|
|
|
903
903
|
209. **FormValidator — engine disambiguation, string inputs, a11y reflection, and live-check message visibility (consolidates former #42/#130/#150/#186/#197).** The live form/data rule engine is `core/plugins/lib/validator/src/form-validator.js` (single source, `isGFFCtx`-branched: runs server-side via `backendInit` AND compiled into the browser bundle) with the client orchestration in `validator/src/main.js` — `framework/v*/lib/validator.js` is a DEAD standalone fluent validator with overlapping `is*` rule names; never edit it for form-rule work (tell them apart fast: the live engine's `isRequired` rejects whitespace-only input, the dead one passes it). Rule bodies must handle STRING inputs — the two DECLARATIVE contexts feed strings (`.value` + urlencoded bodies) — so a typed/numeric rule coerces or parses explicit components, never assumes a typed JS value; but "always a string" is NOT true of every path, and reading it that way shipped #B198 (see below): a JSON request body keeps real Numbers (`JSON.parse` → `req.body`/`req.post`, which the `validator::{}` routing path MERGES into the validated data before spreading array bounds through `apply()`), and `toInteger` leaves `Math.round()`'s real Number on `this.value`, so a `toInteger` → `is*` chain hands the next rule a Number even in the browser — a rule must therefore be correct for a typed value too, not merely tolerant of strings: `isFloat` coerces via `Number()` (#B46); `isDate` builds from explicit mask components + a round-trip check so non-ISO slash masks aren't US-misparsed and impossible dates still reject (#B47); `isDate` returns the FIELD again on its valid path (#B48, 0.5.4 — parsed `Date` preserved on the field's `.value`, the `isDate(mask).format(...)` idiom unchanged), so rule chaining works. An empty value is adjudicated by `isRequired` ALONE (#B78): the per-rule empty-bypass became unconditional on empty (`if (this.value == '')`, its old `!errors['isRequired']` gate dropped) for `isEmail`/`isJsonWebToken`/`isFloat`/`isInList` — each regating `this.valid = isValid && !errors['isRequired']` — and `isString` keeps the field invalid without recording a second message, so a required-empty field shows ONE message (`is required`) not two, optional empty fields still pass, a filled-but-invalid value still reports its own error, and custom `is` was deliberately excluded by #B78 and re-declined by #B82 — an exclusion REVERSED by #B233 (2026-08-03, 0.6.3, `307721f2`): `is` now carries the same canonical strict bypass (`if ( this.value === '' ) { isValid = true; }`) and the same regate, taking the Shape-A population from four rules to FIVE, so a required+EMPTY field carrying an `is` condition records `isRequired` ALONE instead of also collecting a second `Condition not satisfied`. `isBoolean` joined the same contract at #B235 (2026-08-03, 0.6.3, `aa1c2035`), taking that population to SIX: its pre-switch rescue `errors['isRequired'] && this.value == false` was LOOSE (`'' == false`), so a required+EMPTY boolean field LOST its isRequired error and reported `Must be a valid boolean` instead of `Cannot be left empty`; the rule now takes the canonical strict `=== ''` self-pass, and the rescue moves AFTER the accept-set switch gated on the value having been ACCEPTED (`val !== null`) — which keeps the documented unchecked-but-required-toggle case working, since a recognized `false`/`0` is a present answer, while emptiness returns to `isRequired` alone. Paired in the same commit with #B236, the SERVER-side half: the plugin's `getCastedValue` funneled EVERY value on an isBoolean-ruled field through `/^true$/i ? true : false` BEFORE the engine ran (client AND server — `validate` calls `formatFields` unconditionally), so on the server auto path junk validated CLEAN and PERSISTED as `false` — `nope`, the HTML checkbox default `on` (a CHECKED box storing UNchecked), the strings `1`/`0`, `TRUE`/`True` — and the NUMBER 1 stored `false` where the engine reads it as `true`. The pre-cast now survives ONLY in dynamised-rules mode, where a referenced boolean field must splice into a stringified `is` condition as an unquoted operand (measured NECESSARY: deleting it outright breaks a server `$flag === true` condition); the ENGINE is the single adjudicator on every surface, which is what the routing `validator::` surface always enforced and what the published reference already promised. Disclosed both directions: values that silently stored `false` now ERROR, the number 1 flips its stored value `false`→`true` on a verdict that was already valid, an optional blank boolean field now PASSES instead of erroring, and a required blank field's message changes from isBoolean to isRequired. A sibling server-path crash in the same plugin is fixed by #B234 (2026-08-03, 0.6.3, `7c56565d`): `getDynamisedRules` substitutes in two passes, and the SECOND is a DOM fallback re-deriving each splice value from the live element (`$fields[...].value`) — which `backendInit` calls with `$fields = null`, so it threw `TypeError: Cannot read properties of null` on its FIRST iteration for ANY `$` surviving pass 1: a regex end-anchor in an `is` condition, a `$` inside a human-readable message string, or a `$` in any array-rule element after the first. Plain cross-field `$peer === $me` never crashed, because pass 1 consumes tokens that NAME fields. The loop is now gated `$fields && ...`, joining the #B127 precedent one function later; `validate`'s same-text gate is deliberately left UNGUARDED, being reachable only with a live DOM. Residual, disclosed — and since FIXED (#B239): a `$` token in an ARRAY rule's FIRST argument that names no field (`isInList: ['$100']`) threw one site later at `checkFieldAgainstRules`' `d[<token>].value` — NOT DOM-dependent, so it reached the client too. The substitution is now gated on the token resolving to a REAL field (an existing `d` key with a defined `.value` — two clauses, both load-bearing: an engine-METHOD-name collision like `'$isValid'` resolves to a defined key with no `.value`, and pre-fix spliced the string "undefined" into the rule for a silent wrong verdict rather than a crash); anything else stays LITERAL so strict comparison applies (`'$100'` matches its own literal, rejects non-members with the rule's own error; bare-`$` and mixed elements covered). `$` is therefore the engine's RESERVED cross-field sigil: whether an authored `$` stays literal depends on a runtime field-name collision — a token naming a sibling field is consumed UPSTREAM by getDynamisedRules loop 1, substituted with quoting fit for `is`-condition splices, not array elements (`"yes"` with quotes can never match `yes`), so real cross-field refs in array-rule elements are always-invalid, fail-closed, never-worked, undocumented (the reference scopes `$name` to `is` expressions) — tracked as #B240 (demand-gated; the fix is relocating array-element substitution into checkFieldAgainstRules, whose guarded loop is deliberately preserved as the substrate). This reserved-sigil model is also the #DTO2 `$` guard's CURRENT rationale (the crash rationale is retired — deterministic literal semantics are impossible for any `$`, so toRules() refuses at boot rather than validate collision-dependently). The reversal is measured rather than re-argued: the old bypass was gated on `!errors['isRequired']` — off exactly when #B78 wants it on — beside a two-disjunct guard that was DEAD CODE (`x == '' && x != 0` has no witness), optional+empty ALREADY self-passed through the live else-if, and on required+empty the condition is VERDICT-IRRELEVANT (form validity is `getErrors().count()` and `isRequired` has already errored), so form validity and the request payload are identical in both directions and only the message list changes; the "coercion-sensitive" premise had already been retired by #B199's strict test, which leaves `0`/`false`/`null` as operands that still evaluate the condition. #B82 is neither regressed nor retired — its root `getCastedValue` quoting and its `is()` grammar guard stay necessary and reachable with a FILLED host; #B233 only closes that crash path a second time for an empty HOST, whose condition is no longer compiled at all. Same commit drops the dead `_defaultErrorLabels['isApiError']` entry (zero consult sites: the API path assigns the server's message directly and never calls `replace()`) - but a cross-field `is` (`"$a === $b"`) no longer THROWS when the referenced field is empty (#B82): the client dynamised-rules substitution (`getCastedValue` in `main.js`) now renders an empty referenced operand as a quoted `""` (it was spliced RAW, leaving a dangling `"7654321" === ` that `is()`'s binary-comparison grammar `_SCS_BINARY_RE` rejected -> an uncaught throw that aborted the whole-form validity pass and left the submit trigger ungated on an invalid form, breaking the documented `is`+`isRequired` value-confirmation pattern while the confirm field was blank), mirroring `getDynamisedRules`' own sibling substitution default (`: '\"\"'`); `null`/`undefined` stay raw (already valid operands). Hardening: `is()`'s grammar mismatch now FAILS-THE-FIELD (`console.warn`+`isValid=false`) instead of throwing, so a per-keystroke live check can never abort the gate on a residually-unparseable condition (e.g. a field literally valued `"NaN"`, which the root fix leaves raw). Browser-bundled -> prod dist rebuilt; the `#SCS1e`/`#SCS1h` eval-safety pins target the untouched `_SCS_BINARY_RE`/`_scsParseOperand`/regex-literal constructs, so the hardening flips none of them. The form's validity comes from `getErrors().count()` (the surviving `isRequired` error), never the per-field `.valid` flag (whose only error-dropping reader, `setErrors`, is dead). Length bounds are ARITY-sensitive, and the source JSDoc was WRONG about it until 0.6.3: `"isString": [N]` (same for `isInteger`/`isNumber`) supplies `minLength` ONLY — identical in effect to the scalar `N` — because the exact-length branch fires only when `minLength === maxLength`, so an exact length needs `[N, N]`; the stale comment had propagated verbatim into the published reference page, so correct BOTH surfaces when one is found. Those bounds measure the value's STRING FORM (`val.toString().length`) — until 0.6.3 `isInteger` alone measured a bare `val.length`, which is `undefined` on a real Number, so BOTH its bounds were silently inert on every numeric value: no error, no warn, field left `valid` (#B198, a fail-OPEN bypass reachable from a JSON body, a `validator::{}` requirement, or a preceding `toInteger` — the browser included). `isString` reads the same bare `val.length` at two sites and is CORRECT there because a `typeof(val) == 'string'` guard precedes it, so this class of fix is line-scoped: a whole-file replace of the bound expression hits four sites, two of which must not change. One consequence of measuring the string form, intended: a negative number counts its sign toward the length (parity with the same value arriving as a string). The zero-swallow residual #B198 initially left open is CLOSED by #B199 (0.6.3): loose `== ''` emptiness tests conflated `0`/`-0`/`false`/`[]` with the empty string at FIVE sites — the isInteger/isNumber bounds gates AND the isEmail/isJsonWebToken/isFloat empty-bypasses, where a JSON body's `{"email": 0}` validated as a correct email — all five now compare strictly, so only the literal `''` bypasses (the designed empty-is-adjudicated-by-isRequired contract, preserved byte-exactly); `isString` stays loose behind its typeof guard (operators identical for strings), `isInList` was already strict, and `isDate`'s broader `!val` swallow (a silent half-state: `valid` false, NO error recorded, so the form passes) is deliberately untouched. STILL OPEN sibling (#B200): a TRUTHY non-string in an isEmail/isJsonWebToken field (`{"email": 123}`) hits an unguarded `.toLowerCase()` and the rule driver RE-THROWS, killing the whole validation run — the falsy/truthy non-string space is partitioned between the fixed bug and this one. Custom validators (`bundle/validators/<name>/main.js`) are a BROWSER-ONLY affordance, NEVER a server-side guarantee: the server gate reads `getContext('gina').forms` while the loop it guards reads a bare `gina` that is undefined in Node, so a custom rule never attaches server-side and the engine then silently skips the unknown rule name with no warn — re-validate such constraints in the action. (Publishing that context without also fixing the loop would make EVERY validator construction throw, including for bundles shipping no custom validators.) A rule-body edit needs a prod dist rebuild AND flips the section-locked characterization tests by design. Editing trap: `form-validator.js` embeds hidden NO-BREAK SPACE bytes (U+00A0) where a normal space appears inside several `||`/ternary sequences, so a literal-space find/replace spanning one silently fails — patch such regions with a byte-scoped script over clean-ASCII substrings, not a space-spanning match. The blur-time global validation pass sets submit-button state but renders errors ONLY for the touched field — untouched invalid fields stay quiet until interacted-with or submit. Accessibility (#A11Y1): the rule-agnostic chokepoint `handleErrorsDisplay` reflects committed errors into `aria-invalid="true"` (gated on committed-not-warning; `"false"` on clear mirrors native `ValidityState` so it agrees with `:user-invalid`; hidden fields skipped), auto-wires `aria-errormessage` to a gina-owned message div UNLESS the consumer provided their own, focuses the first DOM-order invalid field on a failed submit, and announces blur-time errors via a per-form visually-hidden `aria-live="polite"` region; the per-field aria passes fire only under live-check — the always-on submit pass covers every bound form regardless. Live-region LIFECYCLE (#A11Y2): creation is split out of the announcer into `ensureA11yLiveRegion($form)` and called from `bindForm` — the chokepoint every registration path funnels through — so the region is in the a11y tree from BIND time. It previously created, inserted AND populated the region in one synchronous tick, which reaches assistive tech as a single mutation batch on a node it has never observed, so the FIRST announcement per form (the one that matters most) was the one least likely to be spoken while every later one worked. The region is a CHILD OF THE FORM on purpose: a popin renders its form inside a native `<dialog>` opened with `showModal()`, which leaves everything outside the top layer inert, so a body-level region would go unspoken for exactly the forms that live in popins — the placement is an accessibility constraint, not a convenience. The price of that choice is that a subtree replacement (`$el.innerHTML =` on a popin re-render, a nav fragment swap) destroys it, so `ensureA11yLiveRegion` is create-OR-RECOVER and also re-homes a region whose form node was replaced; any region created or re-homed at announce time is marked fresh and defers its first write one macrotask, so insertion and mutation land in different ticks — that deferral is what stops a recovery from silently repeating the defect, and it is load-bearing because a re-render does NOT always re-bind (`validateFormById` early-returns on an already-registered id, and a multi-form popin's teardown loop splices while iterating so it skips every odd-indexed form). An already-bound region writes synchronously, unchanged; while a deferred write is pending a newer error replaces the pending text so the LATEST message wins, and the timer re-enters the announcer, keeping exactly ONE `textContent` write site. Rule: a live region must be observable BEFORE it is written — separate creation from announcement, and when the same call must do both, put a tick between them. **Submit LIFECYCLE exposure (#A11Y4):** the accessibility signals for an in-flight submit hang off the request window inside `send()` — the only place reached once an XHR genuinely exists — and that placement is the fix for a timing trap, not an accident: the loading state (`data-gina-loading`) is armed at CLICK time, BEFORE validation runs, so a signal hung there would arm-then-disarm on every rejected submit and announce a spurious busy/not-busy pair. Three effects, all scoped to that window. (1) The trigger's focus is captured before gina natively disables it and restored once the request settles: a natively `disabled` control cannot hold focus, so the browser drops focus to `<body>` and re-enabling does NOT bring it back (both measured), which silently cost every keyboard submit its place. The restore is deliberately conservative — only when focus is still on `<body>` and the trigger is still in the document — so a response that opened a popin, redirected, or focused the first invalid field keeps its own focus decision; gina restores what gina took, never more. The `<a>` branch is excluded from the capture: an anchor gets `aria-disabled` from the in-flight lock (since #B312 the framework's only `aria-disabled` write), which does not blur (measured control). (2) The trigger carries `aria-busy` for the request's duration — on the TRIGGER, never the form, because the live region is a CHILD of the form and an ancestor marked busy MAY be treated as "defer announcements in this subtree", which would silence the very channel used to announce; ARIA 1.2 defines no normative behaviour here, so that is a cheap hedge, and the same reading is why `aria-busy` can never substitute for an announcement (it produces none of its own). ⚠️ **Do NOT restate this as "assistive tech commonly defers"** — that wording shipped once and was WRONG: measured 2026-08-05 on VoiceOver/Chrome, an announcement made from inside an `aria-busy="true"` ancestor was spoken normally, so at least that pairing does not defer. The placement costs nothing and still guards ATs that might, but it is a precaution, never a claim about implementations. (3) The start is announced ONCE through the #A11Y2 region; completion announces NOTHING by design, because an errored response is already announced field-by-field by `handleErrorsDisplay` and a second status write over the same polite region in the same beat can truncate it. Announced strings are the framework's own, so they resolve through `gina.config.a11y` with English defaults (e.g. `{ submitting: 'Envoi…' }`) — deliberately separate from `setErrorLabels`, which is keyed by RULE name and owns rule messages. Rule: put a state signal where the state actually begins; a signal armed on intent rather than on the operation announces work that may never happen. **Error-association integrity (#A11Y5):** three fixes sharing one theme — an ARIA assertion is only worth what its target is worth. (a) Hiding the message div used gina's `.hidden` helper (`display: none !important`), which removes it from the accessibility tree entirely — fine for a soft warning, wrong at the TWO hide paths that coexist with an asserted `aria-invalid` (`refreshWarning`'s focus-driven hide, and the refresh re-create), where the field was announced invalid while `aria-errormessage` pointed at an unreachable target. Both now CLIP instead: inline declarations that hide visually but keep the node in the tree, with `display` carrying `!important` because that is what outranks the class's own `!important` (measured — a plain inline `display:block` loses to it); the class is deliberately left in place so consumer CSS keyed on `.hidden` keeps matching. (b) A polite region is announced on CHANGE, so re-writing byte-identical text is commonly not spoken — which is precisely the repeat-error case (blur a field that still fails the same rule). A trailing no-break space now makes the content differ without altering what is read out, and it self-cancels on the next write. NOTE this half is source-derived: the string demonstrably changes, but no assistive-technology pass has confirmed the re-announcement. ⚠️ The same caveat NO LONGER applies to #A11Y2's first-announcement fix — **V1 was CONFIRMED 2026-08-05 on VoiceOver/Chrome** by an A/B whose two arms differ only by a `setTimeout(0)`: the same-tick create-and-write was NOT spoken, the deferred write WAS. So the deferral is load-bearing in practice, not merely defensible in theory; the region-lifecycle discipline above is measured, not inferred. (c) `focusFirstInvalidField` (and its deliberately-duplicated inline twin) gated focusability on `typeof $field.focus == 'function'`, which is TRUE for every HTMLElement — a custom element with neither `tabindex` nor `delegatesFocus` passes it and its `focus()` is a silent no-op (measured), so a failed submit whose first invalid control was such a host focused nothing AND stopped searching. Both loops now confirm `document.activeElement` actually moved before stopping, which is also what makes the JSDoc's "skips unfocusable controls" claim true rather than aspirational. Rule: a capability probe (`typeof x.focus == 'function'`, `'foo' in el`) tests the API's PRESENCE, never its EFFECT — when the effect is what matters, assert the resulting state. Error-MESSAGE visibility has THREE write paths (create / refreshWarning's un-hide / the refresh re-create) and the re-create runs LAST in the live-check pass, so it owns the steady state: it is focus-aware — message hidden while the edited field is the active element, revealed on blur (soft warning border while typing). Rule: when an element is written by multiple paths in a single validation pass, guard the LAST writer — an earlier-writer fix is silently overridden. #B319 (0.6.5-alpha.2): the focus-driven hide is EXEMPT during the framework's own ANSWER focus — both refused-submit paths (the #B246/#B308 display-only reveal via `focusFirstInvalidField`, and the `validate.<id>` failure branch's inline focus twin) render errors then focus the first invalid field, and that focus's synchronous `focusin` re-entered the live-check listener and hid the just-rendered message: a refused submit explained itself only to a screen reader (the #A11Y5 clip kept the node resolvable) while sighted users saw nothing — despite the render itself being VISIBLE (no fieldName ⇒ the live-check branch is skipped, and activeElement is still the trigger/BODY at render time), which is also why an async `query` rule in a repro is incidental (the suppressing dispatch is synchronous inside `focus()`). Fix: a one-shot module flag raised around BOTH focus loops (try/finally, cleared on every exit) gates the focusin arm's `refreshWarning` call; the message stays visible with the hard `form-item-error` styling, aria state untouched, and the first later keystroke re-engages the mid-typing suppression unchanged (in that answered-then-typed configuration the border stays `form-item-error` — the answer focus re-registered the field so `lastFocused` reads it twice and the isWarning heuristic keeps the hard border while the active-element ternary hides the message; the hidden message is the contract, the border there is heuristic). Behavioral lock: `test/e2e/validator-submit-answer-visibility.spec.js` (4 arms, red-first against the pre-fix bundle); browserless pins: `test/core/validator-answer-focus.test.js`. #B387 (shipped in 0.6.11): the one-shot flag covers only that synchronous window — on an async-`query` form with a committed error, a refused submit whose click lands inside the UNDRAINED completion tail of the previous settle had its answer delivered, focused, then re-hidden ~0.2ms later: a stale live-check waiter (woken inside the click's cascade by the reveal pass's deferred release) or the trailing silent global re-validation ran the display-refresh pair AFTER the flag's `finally` had cleared it, and BOTH hide sites key on the same heuristic — "the field is the active element, so the user is editing it" — which cannot tell answer-placed focus from user-placed focus (`refreshWarning`'s error→warning downgrade appends ` hidden`; `handleErrorsDisplay`'s refresh branch re-creates the message born-hidden via its active-element ternary; the occurrence gate is click-inside-the-tail, which is why full-suite/CI runs flaked ~1/20 while standalone runs stayed green). Fix: focus PROVENANCE — a single module slot `answerFocusHold` ({formId, elName}; one slot is exact, only one active element exists) recorded at both confirmed answer-focus points (inside the #B319 windows), consulted by BOTH hide sites for ANY caller however late (provenance beats a pass-staleness latch: the trailing re-validation is a FRESH pass spawned inside the click cascade and would sail through any staleness check), and released on the first genuine user interaction — any TRUSTED native event reaching one of the seven form proxy handlers while the one-shot flag is down (framework `triggerEvent` dispatches are untrusted and cannot release it; the answer's own trusted synchronous focusin is excluded by the flag) — so the deliberate mid-typing suppression re-engages the moment the user actually edits; no timers. Locked by `test/core/validator-answer-focus-hold.test.js` (17 — source pins on every edit site, comment-stripped extracted-real-bytes behavioral arms for both hide sites + the release helper, red-first against the pre-fix bytes; gina.js dist pins) and the §02 e2e arm (25/25 post-fix vs the ~1/20 pre-fix CI red whose signature was `Received: 1` + msgClass `hidden` — a recurrence of that signature is a NEW defect, not #B387). #B348 (shipped in 0.6.11): `revealValidationState`'s completion STARVED on any form whose async `query` field was not declared last — the reveal pass is un-latched, writes neither `isSubmitting` nor `isValidating`, and on a valid form its verdict is clean, so its waiter completion matched NO dispatch branch (terminal errors>0 / the latched dispatch / last-field / the display-only live-check arm): `onDisabledTriggerReveal` never ran, the stale `data-gina-form-submit-gated` marker never re-synced, and a fully valid form ate every later click until reload (measured live: 2 post-settle clicks, 0 POSTs — field declaration order decided whether the documented self-heal worked). Fix: the reveal's callback carries a completion identity (`onDisabledTriggerReveal.isRevealCompletion = true`, the engine's own `cb._data`/`cb._errors` property idiom) and the waiter chain gains ONE else-if chained after the display-only arm, gated on the SAME terminal condition the errors>0 block uses (`hasParsedAllRules && asyncCount <= 0` — the guard that stops a multi-query-field early wake, where the first waiter fires at asyncCount 1): a terminal reveal completion that matched no other branch dispatches `validated.<formId>` with its own cb. Every previously-working shape is byte-identical (errored reveals keep completing via the terminal branch, query-last via last-field, latched submits via the latched dispatch, live-check stays display-only), and the un-latched programmatic-submit starve (#B347) is deliberately untouched — its cb carries no marker and its fix is gated on its own repro. Known residual, pre-existing on EVERY branch: a query field whose own rule object continues past `query` never sets the terminal flag and still starves — same guard as the existing dispatch, no new asymmetry. Locked by `test/e2e/validator-reveal-starve.spec.js` (red-first: the starve arm failed on the pre-fix bundle while the query-LAST control arm passed on the same bytes, pinning the defect to field order; the scene manufactures [valid values + stale gate + stale committed error] deterministically via a prototype-setter silent fill after an errored reveal, with every precondition an explicit expect so an impossible scene voids loudly) and `test/core/validator-reveal-completion.test.js` (9 — stamp/consult/placement pins, extracted-real-bytes reveal arm asserting the stamp as a runtime value, gina.js verbatim pins AND a gina.min.js exact-count pin — unlike a local flag, the property name survives Closure). The not-ready submit trigger is marked `data-gina-form-submit-gated="true"` + the class `gina-form-submit-disabled` (#B312 retired `aria-disabled` from this marker — its contract says not-operable while the #B246 gate deliberately answers the click with the error reveal; authored `aria-disabled` remains enforced by the gates and is never auto-cleared), NEVER native `disabled` (#B76 — a natively-disabled button emits no click, so the validate-render-focus guard could never run); `isValid()` is the real send gate, and **the framework now ships a default not-ready look (cursor `not-allowed` + dim; deliberately no `pointer-events`, which would swallow the click the reveal answers) that consumer CSS overrides**. Form-associated custom elements (FACEs) participate in binding + live-check (#CC2 — hyphenated members of `form.elements`; their own `.value` accessor is honoured, live-check rides the composed bubbling `change`; author contract: `static formAssociated`, a `name` attribute, a `.value` getter, composed `change` on commit). **Radio-group collection (#B221):** an unchecked non-boolean radio group whose rule declares a truthy `isRequired` is collected as an EMPTY value by BOTH collectors (`getFormValidationInfos` + the native-submit inline copy) so `isRequired` adjudicates it via the standard emptiness test — pre-fix no collection arm admitted the shape (each required `.checked`, a `true|false`-shaped value, or an `isBoolean` rule), the DOM handle was held in `$fields` but the VALUE never entered `fields`, so no rule ran against the group and a radio-group-only form short-circuited BOTH submit guards (field count 0 reads as nothing-to-validate → synthetic `isValid() === true`) and submitted its XHR with zero client-side validation. Other unchecked groups stay absent-when-unchecked (native parity: no rule / `isRequired: false` unchanged; `isBoolean`-declared groups keep the force-false arm), checked members post exactly as before, and on the auto path a required-empty form is invalid and never sends — so the wire only changes for the newly-gated shape. Enforcement-tightening: forms that silently submitted with nothing picked now gate on the pick (trigger marked not-ready at bind under default-on live-check — `data-gina-form-submit-gated` + class since #B312 — message on submit attempt, re-enabled after picking). **The re-enable is real only since #B228:** the radio live-check listener was registered under a `changed.<id>` event name nothing dispatches on a user pick — the form-level click proxy short-circuits into the radio state updater (which never dispatches any gina event), and the change proxy dispatches ONLY names present in the event registry, which radios never registered (checkboxes have that registration via their state-updater relay; radios' equivalent relay is keyed on the bare element id, which nothing triggers) — so the whole-form silent pass never re-ran after a pick and the trigger kept its bind-time disabled state indefinitely, while submit-time validation (a separate call chain) accepted the checked group and let the click-guard send: flows completed, only the trigger state was wrong (announced disabled to assistive tech; automation actionability checks refuse `aria-disabled`). Radios now ALSO register the proxy-dispatched `change.<id>` name alongside `changed.<id>` — the handler's radio arm accepted `change.`-typed events all along, so one registration line closes the loop: mouse, label and keyboard picks all re-run the field + whole-form passes (single delivery per pick — native `change` fires only on real state changes; the legacy `changed.<id>` name stays registered for the relay/programmatic path; checkboxes byte-identical). Latent since the live-check's introduction, invisible until #B221 armed it. **A field that DRIVES its own conditional block lost its BASE rules until #B229:** `forEachField`'s per-field tail read `if (isInCase || caseName == field) continue;`, and `caseName` is assigned inside the `_case_` scan loop that re-runs in full on EVERY field iteration, so it always held the LAST scanned `_case_` key's driver name — when the iterated field WAS that driver the `continue` skipped the rest of the iteration, base-rule check included. A rule shape `{ "group": { "isRequired": true }, "_case_group": { "conditions": [...] } }` therefore never adjudicated `group`'s own `isRequired` on the bind pass, the live-check global pass OR the submit pass: the form never gated and an empty submit went out with zero client-side validation — the silent-submit class above, resurfacing for the self-driving shape and structurally DOWNSTREAM of the collection fix (the group IS collected as `''`; only adjudication was missing). The tail is now split: `isInCase` keeps its own `continue` (it is dead code — never assigned truthy — and is preserved as such), and the `caseName == field` arm runs the base-rule check before continuing, restoring the driver's collected value around the call (the check deletes the field from the object it is handed, and that object is where the scan block re-reads the case VALUE on every later field iteration; a deleted entry re-seeds from the DOM, which for a radio group is the FIRST member's value regardless of `.checked`). Which conditions apply is unchanged — the direct-case block is never entered for a self-driving case, pre- or post-fix (measured) — and the fix is order-independent: a driver declared BEFORE another `_case_` block was already adjudicated (the tail's comparison never matched it), so the post-fix union is every driver carrying base rules. Client-only: the server form-body path throws earlier on any `_case_`-bearing rule set (conditional rules are unsupported there). Enforcement-tightening: a form built on this shape starts gating where it silently submitted. Known interplay, pre-existing: on a rule set with NO `$` tokens, a pick whose value matches a `_case_` condition lets the case machinery PERSISTENTLY replace injected fields' rules in the live store (the site-B replacement), so a later `reBind()` can re-arm the gate from the mutated store — `$`-bearing rule sets are immune (the dynamised-rules path clones). **A conditional driver's collected VALUE survives every full-form pass since #B230:** the base-rule check deletes each adjudicated field from the object it is handed, and until #B230 only the last-declared driver's entry was restored (the #B229 arm above) — any OTHER field that both carries base rules and drives a `_case_` lost its stored case value the moment its own rules were adjudicated, so later field iterations re-read it from the DOM (a radio group's FIRST member regardless of `.checked`) and matched conditions against a value the user never picked — spuriously requiring the wrong flow's fields (a correctly-completed picked flow could not submit), or with excluding condition rules under-validating the picked flow — while the driver's own direct-case block read `undefined` in the same pass and matched nothing. The entry is now backed up and restored around the base-rule check for any field driving a `_case_` in the live rules OR the pass-entry rule clone; the union matters because inside a direct-case recursion the pass's rule set is the condition's own rules, which carry no `_case_` keys, so the live-rules test alone is blind there. Non-driver fields keep the deletion untouched (the condition pull-in gate, the direct-case exclude injection and the async-`query` re-validation input all read those absences today), and a driver with no rules of its own is byte-identical — including the legitimate first-scan DOM seed for rule-less unchecked groups, which is preserved. **`setFlash` `[null, "message"]` works client-side since #B226 (the form the reference documents):** it previously lost its custom message in the browser ONLY — `lib/merge` classified a `null` array element as an object (`typeof null`) and dropped it on every no-override merge, and the client rules path re-merges the whispered rules (the `data-gina-form-rule` bind merge, the `gina.hasValidator` instance re-merge, the `_case_` merges), so the engine received a one-element array, bound the message to the ignored first `regex` argument, and rendered the built-in label; `["", "message"]` always survived (empty strings, `false` and `0` are primitives — `null` was the only casualty), and the server was unaffected (it reads the boot-loaded rules without those hops). The fix is in `lib/merge` itself, so no-override merges now preserve `null` array elements as VALUES framework-wide (and the index-merge branch stops manufacturing `{}` from a `null` source element) — a merge consumer relying on the silent compaction sees the `null` slots preserved. **Bracket-notation and nested-authored rule KEYS enforce on the SERVER form-body path since #B241:** the rule parser canonicalizes every rule key to a dotted path (`account[username]` becomes `account.username`; a nested rule tree flattens to its dotted leaves) while the server's fields map kept the RAW posted keys, so such rules never joined — the field was silently skipped with no warning, fail-open for every rule-keyed directive alike: checks (`isRequired`, `isEmail`, ...), the `exclude` drop, and value transforms — on BOTH production wire shapes (flat bracket keys: the client posts its name-keyed data as JSON and the JSON body path deliberately does no bracket expansion; and nested objects: the multipart and urlencoded parsers expand bracket names). The server now synthesizes dotted-canon field aliases ALONGSIDE the raw keys (originals kept, so `$name` cross-field tokens keep resolving off the raw posted names, and an all-flat payload synthesizes nothing — byte-identical behaviour), then folds alias outcomes back at egress: error keys return under the DOM-name bracket form the client renders against, and the validated data output keeps its materialized shape with exclusions and transforms applied (a parent object emptied by an exclusion is pruned along that alias's path only — a posted empty object survives). The client join was always bracket-on-both-sides (a named rule set passes through with its authored keys; nested-authored sets are reconstructed to bracket names at bind time), so this brings the server to parity — quirks included: a caller that posts the dotted key form keeps its own addressing, and the no-rules path still returns the payload verbatim. Behaviour change by design: a bracket-keyed or nested-authored rule that never fired before now enforces — anything relying on the old silent skip starts rejecting or dropping those fields. **Upload previews carry a text alternative (#A11Y7/U1, 0.6.4).** The staged-upload client layer builds its preview `<img>` in two MUTUALLY EXCLUSIVE branches of `onUpload` — one for a file with no server-side `preview` object, one for a returned preview variant — and neither set `alt` at all (not even `alt=""`), so assistive tech fell back to reading the temp URI aloud, once per staged file (WCAG 1.1.1). Both now set `alt` from `files[f].originalFilename`: the name the USER chose, deliberately NOT the sibling `files[f][key].originalFilename` of the preview variant, which is a server-generated artefact that means nothing to the person listening — the same distinction the adjacent `data-upload-original-filename` / `data-upload-preview-original-filename` pair already encodes. The fallback is `''` (a properly ignored image) rather than letting a missing name be spoken as a placeholder. The preview is INFORMATIVE, not decorative: it is the only signal telling a user which file is staged, and everything else in the upload layer is still silent — progress is attribute+`textContent` with no `role="progressbar"`/`aria-value*`, upload errors are an `innerHTML` write with no `role="alert"` that never calls the polite region the plugin already owns, and the reset control is an `<a href="#">` (U2/U3/U5, tracked in the accessibility audit, not fixed here). Maintainer gotcha: the two branches are per-file exclusive, so a one-site fix silently misses every upload whose server returns a preview object — fix both or neither. **A not-ready submit trigger really refuses the send — and its marker is aria-free (#B246 + #B312, 0.6.5).** `updateSubmitTriggerState()` marks an invalid form's trigger with `data-gina-form-submit-gated="true"` + the `gina-form-submit-disabled` class and deliberately never native-`disabled` (a natively-disabled button emits no click at all, so nothing could tell the user WHY it is dead) — and, since #B312, never `aria-disabled`: the gate ANSWERS the click with the error reveal, which that contract forbids for a control announced disabled, so the attribute belongs to consumers (authored marks the gates enforce and never auto-clear) and to the anchor in-flight lock. #B246's origin: NOTHING READ the marker — a click ran the entire submit cycle (collect → validate → `validate.<id>`) and only the `isValid()` gate stopped the send: the trigger was inert in appearance ONLY. `clickProxyHandler` now intercepts a disabled-or-gated trigger BEFORE the `submit.<id>` dispatch — so `bindSubmitEl`'s handler never runs, `isSubmitting` is never latched, and no send path is reachable by construction — and answers the click with a display-only `revealValidationState()` pass that renders every invalid field, focuses the first, and re-syncs the trigger state so a STALE not-ready marker on a form that has since become valid heals itself — the heal touches only the marker + class; authored `aria-disabled` and the in-flight lock survive it (#B313 closed by construction). The predicate `isTriggerDisabled()` reads the CLICKED element rather than `$formInstance.submitTrigger` (a form may carry several submit buttons while only one registers) and mirrors the popin plugin's existing trigger gate on the shared channels (authored `aria-disabled` + native `disabled`), so both subsystems agree there; since #B312 it ALSO fires on the validator-owned not-ready marker `data-gina-form-submit-gated="true"`. **#B293 (0.6.5) NARROWED that predicate: the native `disabled` ATTRIBUTE now counts only where `disabled` is not a real IDL property (`!('disabled' in $el)`).** As first shipped it accepted native `disabled` on ANY element, which silently killed the near-universal double-submit guard: a click listener on the submit button that sets `disabled` to block a second submit is bound to the button and therefore runs BEFORE gina's delegated form-level proxy reaches the gate, so the gate saw the attribute, cancelled the click, and `send()` never ran — then the consumer's own handler cleared the attribute again, leaving NOTHING marked, a normal-looking button, and every subsequent click equally dead. Measured: a natively-disabled `<button>` delivers **no click to JS at all** (the browser suppresses it), so on a real form control that arm could only ever fire on an attribute set DURING the dispatch — it protected nothing and cost everything; on an `<a>` or a custom element the browser enforces nothing, so there the attribute is still the only honest signal and is still read (`'disabled' in $el` measured: button/input `true`, anchor/custom-element/span `false`). Regression cover is an **e2e** (`test/e2e/validator-submit-native-disabled.spec.js`, 4 arms), deliberately NOT a replica: the defect is an ordering interaction between a consumer's own listener and gina's delegated proxy, which only the real bundle handling a real click can exhibit — `test/core/validator-submit-trigger-state.test.js` covers the same gate with replicas and stayed GREEN throughout, including two tests named as controls for the working case. Gotcha for maintainers: `focusFirstInvalidField()` deliberately DUPLICATES the inline focus loop inside the `validate.<id>` guard — that inline shape (`_a11yErrs` / `_aField`) is locked by source pins, so collapsing the two would break tests unrelated to this fix; keep them in step by hand if the focus rule ever changes. **Staged uploads speak (#A11Y7/U2+U3+U5, 0.6.4).** Three defects in one client layer, fixed together because they share one plumbing change. (a) `updateUploadProgressIndicator` wrote `textContent` plus two `data-gina-upload-progress*` attributes and NO ARIA, so any indicator that is not a native `<progress>` was unlabelled text; it now carries `role="progressbar"` + `aria-valuemin="0"` / `aria-valuemax="100"` / `aria-valuenow`, with `aria-valuenow` tracking the percent attribute EXACTLY — absent while `preparing`/`indeterminate` and on `error`, because an absent value is how a progressbar signals "unknown" while a stale or zeroed one reads as real, stalled progress. `reset` strips the ARIA too (it strips everything this layer ever set); `processing` advances the state only, preserving the last value so a determinate bar stays full through the server-side window. A native `<progress>` is deliberately untouched — it already exposes all of this, and an explicit role risks overriding the implicit one. (b) The TRANSITIONS are announced through the polite region `bindForm` already stands up: `uploadStarted` at the selection kickoff — deliberately NOT gated on the indicator being present, since opt-in-by-presence is right for a visual and wrong for an announcement — and `uploadComplete` in the success branch only. Per-tick progress is NEVER announced: `aria-live="polite"` coalesces but does not throttle, so one announcement per `onprogress` event would bury every other message on the page. There is deliberately NO generic `uploadError` key, because the error path announces the server's own message and a generic second write in the same beat would clobber it (the region is latest-wins — the same reasoning that keeps `releaseSubmitA11y` silent). (c) The upload error container is written via `innerHTML` while `display:none` and revealed only by a fade-in — exactly where a `role="alert"` on it is unreliable — so the error routes through that same region, announcing `$error.textContent` (the RENDERED text: a message can carry markup, and the url-action checker substitutes `<br>`). Note the container being consumer-owned is NOT the discriminator: the progress indicator is equally consumer-supplied and gina already writes attributes into both; the hidden-then-revealed shape is what rules out `alert`. (d) The generated reset control was `<a href="#">Reset</a>` — announced as a link, not activatable with Space, and identical across every staged file. It now carries `role="button"` plus an `aria-label` of `<visible label> <filename>`, the visible label kept as a PREFIX so the accessible name still contains it (WCAG 2.5.3 Label in Name) and reusing the consumer's own `data-gina-form-upload-reset-label` so it is translated wherever that already is. The role is stamped ONLY on an anchor (strict `/^a$/i`, unlike the loose `/a/i` guarding `href` a few lines above, which also matches TEXTAREA/CANVAS/LABEL), so a consumer supplying their own `<button>` keeps its implicit role. A `role="button"` that cannot be operated by Space would be worse than no role at all, so the binder adds a `keydown` handler mapping Space to the removal — anchors only, since a native button already fires click on Space and would otherwise run the removal twice. (e) That control is REMOVED from the DOM while holding focus (`$resetLink.remove()`, which its own comment requires be last so the click listener dies with the node). The finding described this as a `display:none`; both happen, and the removal is the decisive one — a fix aimed only at the hide would not have worked. Focus now moves to the file input BEFORE the removal, guarded on the control actually holding focus (a programmatic reset must not steal focus from elsewhere) and CONFIRMED afterwards rather than assumed, since `typeof focus == 'function'` is true for every HTMLElement and `focus()` is a silent no-op on one that cannot take it. The removal is then announced (`fileRemoved`, default `'%s removed'`) using a FUNCTION replacer — never a string — because a file name may contain `$` and a string replacement would expand `$&`/`$1` patterns inside it. All three new strings live in `A11Y_LABELS`, so `gina.config.a11y` overrides them. **Maintainer gotcha worth the line:** `test/core/validator-upload-reset-delete.test.js`'s `fnSlice` takes a FIXED 12000-char window from its declaration anchor, and this fix grew `onUploadResetOrDelete` by ~2.6k chars — pushing the `#R8` strip and the callback dispatch outside it and reddening four pins whose code was present and correctly ordered the whole time. Widened to 16000 (the function ends ~14.2k past its anchor), but it is still a fixed window and will bite again; the durable fix is to brace-walk to the real function end, as the sibling `validator-upload-progress.test.js` already does. **Not verified against assistive technology** — source-derived plus a served-bytes check, like the rest of this arc. **#B295 (2026-08-06) — an async `query` rule left the form's validity state STALE once it settled.** `onasyncCompleted` derives its verdict from `d.getErrors(field)`, which `form-validator.js` scopes to a SINGLE field, so its `isFormValid` means "this field passed", never "the form is valid" — and the entire update block sits behind `if (!isFormValid && …)`. A form that had just become valid therefore received NO update: `updateSubmitTriggerState` was never called, so the bind-time not-ready mark (then `aria-disabled="true"`; the `data-gina-form-submit-gated` marker since #B312) survived on a valid form and `$forms[id].errors` kept listing the field. Cosmetic until #B246 made the click gate read that marker — then the first click after the query settled was silently eaten, and only the second went through (`revealValidationState`'s re-sync self-heals it). Fixed by handing the verdict to the fresh whole-form pass the same function ALREADY runs for the not-last-field case (`needsGlobalReValidation`), whose unconditional `updateSubmitTriggerState($currentForm, gResult.isValid())` settles the marker while its `handleErrorsDisplay` empty-errors branch clears the stale record; measured to add NO extra round-trip, because the query rule's already-registered-listener guard short-circuits the re-run. **The measurement that CHOSE the fix, and the reason the two cheaper shapes are wrong:** with a second required field left invalid, that fresh pass reports its error while BOTH the field-scoped `cb._errors` AND the in-pass `d.getErrors()` report none — so un-gating the existing call, or letting `handleErrorsDisplay`'s empty-errors branch auto-enable via its own `updateSubmitTriggerState(..., true)`, would each have ENABLED submit on an invalid form and re-opened #B246's hole. Both were rejected on that arm, not on reading. Covered by `test/e2e/validator-async-query-revalidation.spec.js` (4 arms, red-first: 01+02 RED on the pre-fix dist, 03 "another field invalid ⇒ still blocked" and 04 "no query rule ⇒ sends" GREEN throughout); arm 01 deliberately waits on the QUERY settling rather than polling the trigger's not-ready marker, because polling the guard is exactly what let the pre-existing FACE e2e stay green through #B293. Browser-bundled ⇒ consumer pickup is restart AND re-bake. **#B294 (2026-08-06) — a submit trigger whose DOM NODE is REPLACED after binding (the AJAX / popin re-render shape) was PERMANENTLY and SILENTLY dead.** Submit binding is **two-stage and only the first stage is delegated**: the native click proxy is registered on the FORM (`:8090`, "Form-level proxies: capture bubbled events from in-tree controls") and survives any re-render, but the listener that does the work is attached to the trigger NODE by `bindSubmitEl` (`:8231`). `gina.events` is a **name → id-STRING** registry (`utils/events.js:42`), so the `submit.<id>` key outlives the node: after a `cloneNode(true)`+`replaceChild` the `:8037` dispatch gate still passes, `cancelEvent` suppresses the native submit, and `triggerEvent` fires at a node with no listener — so the form does not even fall back to a normal submit. Nothing self-heals (`bindForm` latches `binded` at `:8580` behind `:585`, cleared only by `unbindForm`; there is no `MutationObserver` and no `isConnected` check in the bind/dispatch path). **Pre-existing, NOT a 0.6.4 regression** — measured identically on published 0.6.3. Fixed by marking the bound node with a **JS expando** (`__ginaSubmitBoundFor`) and re-binding the live node at the dispatch gate when the marker is absent — measured: `cloneNode(true)` copies `data-*` attributes but NOT expandos, which is exactly why the inherited `dataset.ginaFormSubmitTriggerFor` is useless as a fresh-node test and was a red herring in the original filing. **The rejected alternative is worth recording:** delegating the custom `submit.<id>` event to the form also fixes it (measured), but was measured to RETAIN the `gina.events` key past `unbind` (`registryKeyDeleted:false` vs stock `true`), which would force an edit inside `unbindForm`'s removal loop — six guard variants all keyed on `gina.events[name] == element.id`. Covered by `test/e2e/validator-submit-trigger-rebind.spec.js` (4 arms, red-first: 01 single replacement and 03 double replacement RED pre-fix; 02 untouched-sends and 04 replaced-but-invalid-still-refuses GREEN throughout, so re-binding provably does not bypass validation). **Diagnostic reflex for any "it stopped working after we re-rendered" report: the question is not whether the click listener survived — it did, it is on the form — but whether the node carrying the stage-2 listener is still the node in the document.** Browser-bundled ⇒ restart AND re-bake. **The submit proxy's gesture gate reads the LIVE trigger (#B308, 0.6.5).** The per-form `submit` proxy enforced its disabled gate on a DOMParser copy of the form's innerHTML, testing only native `.disabled` on the copy — blind to the gate's own marker (then `aria-disabled`, `data-gina-form-submit-gated` since #B312, written by `updateSubmitTriggerState`) while SIGHTED on a native `disabled` written mid-dispatch by a double-submit guard (the exact attribute #B293 ruled must not count on a real control — so wrapped-label + double-submit guard was a dead submit, the #B293 signature alive on the submit path). A gated trigger's trusted gesture therefore ran the full collect → validate cycle and SENT whenever the values validated. Now an `e.isTrusted` submit on a form whose REGISTERED trigger is `isTriggerDisabled()` is cancelled and answered with the same display-only `revealValidationState()` as the #B246 click path (errors rendered, first invalid field focused, stale marker re-synced); programmatic `$forms[id].submit()` arrives untrusted (triggerEvent CustomEvent) and deliberately keeps the fresh-validate path — a programmatic submit is not operating the control, the fresh pass is the honest gate there, and cancelling on a cached marker would break fill-then-submit flows. Two gesture shapes reach this gate (probe-measured, Chromium): a wrapped-label click (`<button type="submit"><span>` — the span has no `.type`, so the click proxy never fires and the gesture surfaces as a native submit event), and Enter in a form with NO native submit button (e.g. an `<a data-gina-form-submit="true">` trigger — implicit submission fires a DIRECT trusted submit; with a native button present, Enter instead synthesizes a click on the default button that the #B246 CLICK guard already refuses while the marker stands, so that shape never needed this gate). Covered by `test/e2e/validator-submit-proxy-disabled-gate.spec.js` (5 arms, red-first BOTH directions via the committed `B308_PREFIX_BUNDLE` route-swap hook: label-click and A-tag-Enter RED pre-fix on an EVENT discriminator — the pre-fix cycle fires `validate.<id>` from the no-`on('submit')` branch, while the post-fix reveal is display-only and fires nothing; POSTs cannot discriminate, since an invalid form never sends on either side). Scene lesson for e2e authors: stale-marker scenes are racy BY CONSTRUCTION — a page-level `.value =` write on a bound field is heard SYNCHRONOUSLY (the value property is instrumented and the silent live validation repairs the marker inside the write), and a delayed global silent pass can heal a protocol-level fill's stale marker mid-arm — so pin gate behaviour on a genuinely-invalid form and discriminate on event emission. Browser-bundled ⇒ restart AND re-bake. **The anchor in-flight lock owns `aria-disabled` alone; the settle releases remove it unconditionally (#B309 → #B312, 0.6.5).** On an `<a data-gina-form-submit="true">` trigger send()'s in-flight lock and the not-ready gate historically SHARED `aria-disabled` with opposite lifecycles — the settle releases (the `loadend` fail-safe + the readyState-4 twin) removed it unconditionally, so a mid-flight gate re-mark was silently erased at settle (#B309), and the reveal's valid-form heal erased the LOCK mid-flight the same way (#B313). #B312's single-writer split dissolves the class structurally: the gate writes only `data-gina-form-submit-gated` + the class, the lock is the sole framework `aria-disabled` writer on anchors (buttons lock via native `disabled`), both settle releases are back to the plain unconditional `removeAttribute` (the interim #B309 verdict-stamp replay retired unreleased, its 18-test file with it), and the reveal's show branch never touches aria — so authored `aria-disabled` survives every framework pass (enforced by the gates, never auto-cleared: consumers relying on the old auto-clear-on-valid must migrate) and #B313 is closed by construction. Scene fact that shaped the design: live-check is DELIBERATELY quiet during a real in-flight submit (the #B192 valid branch holds the submit latch until settle), so keystrokes cannot re-mark mid-flight — the reachable mid-flight writer was the #B246/#B308 reveal itself. Covered by `test/core/validator-submit-gated-marker.test.js` (17 — block-scoped single-writer negatives immune to right-extension, authored-aria + lock survival behaviour, the shipped `isTriggerDisabled` EXTRACTED and EXECUTED, terminator-anchored release-shape pins, dist-fidelity pins validated red-first across the rebuild). Browser-bundled ⇒ restart AND re-bake. **isRequired's emptiness is anchored at both ends, and trim strips both sides (#B245, 0.6.5).** The emptiness conjunct was `!/^\s+/.test(value)` — leading-anchor only — so ANY leading-whitespace-padded value read as empty (" x" rejected, "x " accepted), and under the documented isRequired-first ordering a paired `trim` healed `local.data` AFTER the error was recorded: a rejected field carried the trimmed non-empty value in the same result. Now `/^\s+$/`: whitespace-ONLY is empty, padded values pass, all-whitespace and the empty string still fail. Same fix: `trim`'s replace gained its missing `g` flag (first-match-only rewriting left the trailing run whenever a leading run matched — " x " → "x "). The validation-rules reference is corrected (it had documented the leading-space rejection as intended while prescribing the isRequired-then-trim pairing that could not work under it). Covered by `test/core/validator-required-whitespace-trim.test.js` (block-scoped comment-stripped source pins + the heal matrix on the REAL plugin over the server auto path + dist-fidelity counts red-first across the rebuild). Browser-bundled ⇒ restart AND re-bake. #B345 (issue #59, shipped in 0.6.7): the #SCS1e paren/`return` strip used to run BEFORE the regex-vs-comparison branch split, so a parenthesized `is` regex literal compiled from MANGLED text — groups destroyed and anchors rebound (`/^(a|b)$/` behaved as `/^a|b$/`: substring-permissive for middle alternatives), quantified groups requantified (`(#TAG)?` → literal `#TA` + optional `G`), and a literal `return` inside a pattern deleted — all silent, since the stripped text still compiled as a valid regex (the `Invalid regex literal` throw structurally cannot catch it). The strip now lives inside the binary-comparison branch only (grammar lock + authored-paren tolerance unchanged — `("a") === ("a")` still strips to a valid comparison); a regex literal compiles exactly as authored, that branch never evaluating the condition as JS. Behavior change (changelog ACTION REQUIRED): paren-carrying `is` patterns now match as authored, i.e. stricter; and the paren-wrapped-regex edge `(/foo/)` now fails closed in the comparison branch instead of being unwrapped into the regex branch. Locked by `test/lib/validator-is-regex-parens.test.js` (14 — red-first source ORDER pins, the reported reproduction, anchors/quantifier/`return` arms, comparison-branch controls, gina.js + wrap-immune gina.min.js order pins). #B389 (gh issue #63, shipped in 0.6.11): the Safari-autocomplete keydown interception (`handleAutoComplete` — REAL-Safari-only per #B135; preventDefault + programmatic value rebuild behind a transient readonly) restored the caret only two setTimeout(0) hops after each rebuild, while a `.value` assignment parks the selection at the END of the field (measured on WebKit AND Chromium) — so a fast second keystroke read a stale `selectionStart` and composed scrambled text ("AXB" where the user typed "ABX"; deterministic whenever two keydowns share one task, which live-check work between keystrokes makes routine). Fixed with a synchronous caret commit after every rebuild (`commitCaret` — measured to stick, and to survive the readonly toggle, on both engines) plus an element-recorded desired-caret tracker (`_ginaAcCaret`/`_ginaAcPending`) the handler trusts while a restore is in flight; the deferred restore (`queueCaretRestore` — the autofill-suppression readonly dance, mechanism-identical) re-asserts the LATEST committed position instead of its own stale capture, and arrows commit through the same tracker so a pending restore cannot undo them. By-catches in the same switch, all fixed to native behavior: #B390 Backspace at position 0 deleted the FIRST character (native no-op), #B391 Delete with a selection starting at 0 ate one char MORE than the selection, #B392 ArrowLeft at position 0 wrapped `setSelectionRange(-1)` to the unsigned maximum and teleported the caret to the END (now floored at 0). Browser-bundled ⇒ pickup is bundle restart AND re-bake. Tests: `test/core/validator-autocomplete-caret.test.js` (red-first: extraction controls, source pins, extracted-real-bytes behavioral arms on a caret-to-end element model, dist pins) + the webkit-only e2e `test/e2e/validator-autocomplete-caret.spec.js` (full-stack real-WebKit reproduction, red-first "AXB" → green "ABX"; chromium/firefox skip — #B135 gates them out; runs in the on-demand cross-engine job). **#B200 (shipped in 0.6.12): a NON-STRING field value no longer aborts the whole validation pass.** `isEmail` and `isJsonWebToken` opened with a TRUTHY-only coercion (`(this.value) ? this.value.toLowerCase() : this.value`), so a truthy non-string — `123` / `true` / `[]` from a JSON body, or a checkbox boolean on the client — reached `.toLowerCase()` and threw; `trim` was WIDER still, its type guard present but COMMENTED OUT and no truthy guard either, so EVERY non-string threw, a falsy `0` included. In all three the rule driver's catch RE-THROWS (`[ ginaFormValidator ] could not evaluate …`), so one bad field kills every remaining one: server-side the request goes unvalidated, client-side the boot-time binding loop dies and later forms silently lose validation AND CSRF injection. Fixed by type-guarding each site against a precedent already in the file — the #B87 `query` coercion for the two rules, isFloat's identical guarded `.replace()` for `trim`. **Measured, and the load-bearing check: the guard does NOT open a #B199-style silent bypass** — a non-string passes through untouched and the rule's own regex then rejects it, so `isEmail`/`isJsonWebToken` record a normal rule-keyed error (`isValid === false`), while `trim`, being a transform, leaves it untransformed (the #B245 both-ends strip on real strings is unchanged). **Reusable rule: type-guard BEFORE a string coercion, never truthy-guard** — a truthy check admits every non-string except the falsy ones (the authn.md §4 lesson, and the same split that sent falsy values to #B199 and truthy ones here). Deliberately NOT fixed in that pass: `isDate` (its throw is a purpose-built catch — an error-contract design call, #B397). `toFloat`/`format` — plus the same-family `set` — were later fixed by #B398 (context-safe rules; `test/core/validator-context-safe-rules.test.js`): `toFloat` reads the live DOM value only when one is reachable and otherwise uses the submitted `this.value` (server-side that IS the raw value, so the rule is fully functional in both contexts; the missing comma that leaked `isFloatingWithCommas` as an implicit global is fixed with it); `set` assigns the value everywhere and guards its DOM write (`isGFFCtx && target`); `format` always worked in BOTH contexts after `isDate` — the filed "dateFormat prototype extension absent server-side" mechanism was REFUTED by measurement (helpers/index installs `Date.prototype.format` unconditionally, server included) — and a non-Date value now throws a NAMED authoring error (`apply isDate(mask) before format(mask)`) instead of the opaque `val.format is not a function` (`toFloat`/`format` were first misread as this same class by a probe whose STRING CONTROL also threw, which is what exposed them). Browser-bundled ⇒ pickup is bundle restart AND re-bake. Tests: `test/core/validator-nonstring-value-guards.test.js` (comment-stripped source pins red-first vs the pre-fix blob, behavioural arms asserting the VERDICT not merely the absence of a throw, wrap-agnostic dist pins red-first vs the pre-fix artifact, plus untouched-rule controls incl. one labelled INVARIANT because it cannot go red). **The form-level keydown proxy defers the native-cancel decision to the namespaced handler (#B444, gh issue #67):** `addListener(gina, $el, 'keydown.<id>', fn)` registers a CUSTOM event type a native keydown can never fire; the form-level `keydownProxyHandler` bridges the two by re-dispatching - and it used to `cancelEvent()` the NATIVE keydown unconditionally BEFORE that dispatch, so on real-Safari UAs (where the autocomplete interception registers exactly such a handler) EVERY modifier chord on a live-checked autocomplete-suppressed field was dead - paste, select-all, copy, cut, undo - with NO paste/beforeinput event observable anywhere (preventDefault on a native keydown suppresses the browser editing command itself), while the interception handler's own chord bail sat one layer too low to help. The proxy now dispatches FIRST and cancels the native keydown only when the handler prevented the synthetic event (which the interception already does on the paths it re-implements and already does not on chords); `triggerEvent` returns the dispatched event to make that decision readable, undefined on the element-less path - treated fail-open to native. The keyup proxy is untouched (no `keyup.<id>` registrar exists). Measured live on WebKit: chords restored, typing interception + caret integrity + autofill suppression unchanged. Tests: `test/core/validator-keydown-proxy.test.js` (extract-and-execute on both changed functions) + `test/e2e/validator-autocomplete-paste.spec.js` (webkit project, cross-engine job). **#FIN4 (`isIban` / `isBic`, 0.6.x):** IBAN and BIC join the built-in rule set. `isIban` validates ISO 13616 in three ordered checks - shape (`^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$`), official per-country length via the constructor-scope `_ibanLengths` registry (87 countries; unknown country codes deliberately pass on shape + checksum so a registry gap never hard-fails a well-formed IBAN), and the ISO 7064 MOD 97-10 checksum folded modulo 97 in <=7-digit chunks of plain Number math (no BigInt in bundled source - build-chain constraint). Tolerant read: validation runs on an uppercased copy with spaces/hyphens stripped; the stored value and the DOM are NEVER mutated (unlike `isEmail`'s lowercase write-back). The type guard folds into `isValid` (`typeof(candidate) == 'string' && rgx.test(...)`) so a toString-spoofing object records an invalid instead of throwing into the rule driver (#B200 posture, tightened). `isBic` validates the ISO 9362 8/11-char shape case-insensitively by regex - no case mutation. Both follow Shape A (`this.valid = isValid && !errors['isRequired']`), the strict #B199 empty bypass, and register English defaults in `_defaultErrorLabels` ('A valid IBAN is required' / 'A valid BIC is required'), culture-localisable like every built-in label. Behavioral + pin coverage: `test/lib/validator-isiban-isbic.test.js` (real-engine; fixtures verified against an independent BigInt oracle).
|
|
904
904
|
|
|
905
|
-
210. **Multipart upload config is ENFORCED — groups, destination, limits, text-field capture + caps, terminal states (consolidates former #187/#188/#228/#230's server half; #B49/#B50/#B51/#B92-adjacent/#B93/#B97).** Every uploaded file must map to a CONFIGURED upload group: the resolved group (no/empty → the default `untagged`) must exist in `settings.json upload.groups` or the request is rejected 400 BEFORE the temp file is created, and `untagged` obeys its own config like any named group (pre-#B50 the checks ran only for defined non-`untagged` groups — an allow-list bypass; the shipped `untagged` default is `allowedExtensions: '*'` + `isMultipleAllowed: true`, so configure `untagged` restrictively or a client can route around a named group's allow-list). Destination + limits honoured: files stream to `uploadDir || tmpPath || os.tmpdir()` with a per-group `path` override and mkdir-if-missing before `createWriteStream` (the mkdir itself crash-guarded → 500, #B145); `maxFields` caps the per-request file COUNT (400 past the cap; 0/unset disables); `maxFieldsSize` parses its unit suffix (B/KB/MB/GB; bare number = MB) as the whole-body cap (431). **The staged on-disk FILENAME is server-generated and opaque, never the client's (#B419, 0.6.16):** `crypto.randomBytes(16).toString('hex') + '.part'`, minted once per part and used at BOTH path-construction sites — the write stream AND `req.files[].path`, which are built independently and must never diverge. Pre-fix the destination was `<fileUploadDir>/<client basename>` with NO per-part component, so two parts sharing a name — within ONE request or across concurrent requests — opened two write streams on ONE path with independent file offsets and INTERLEAVED their bytes into a single hybrid matching neither source, while the framework answered `storeErr:false` + 200 (measured live pre-fix: 12 concurrent same-named uploads → 0/12 clean, one hybrid file; one request with two same-named parts → one 500000-byte file of {B:417353,A:82647}; post-fix 12/12 clean, each byte-exact). The parse precedes routing and all middleware, so an UNAUTHENTICATED POST to ANY url reaches it — an unrouted one answers 404 to the client and still writes the file. A CSPRNG rather than the `Math.random()` used by `movefiles` (V8's is xorshift128+, so staging paths would be predictable, and a group `path` is operator-configurable into a served root); a RANDOM name rather than `filename + <suffix>` (keeps attacker-controlled bytes off the filesystem — RTL-override display spoofing, control chars, reserved device names — and cannot overflow NAME_MAX=255 into an ENAMETOOLONG 500 for a long-but-legal client filename); no interpretable extension, so a misconfigured static-serve of the staging dir cannot hand back an executable type. This is the multer ("a random name that doesn't include any file extension") and formidable (`newFilename` hexoid) convention. `req.files[].originalFilename` still carries the client's name and is what `store()` publishes the file under (`controller.js:4061`) and what a storage driver receives as `originalName`, so the documented "keeping each file's original name" contract is UNCHANGED — only `req.files[].path`'s BASENAME differs, and that field is documented as the temporary file's path. Server-side ⇒ pickup is a bundle restart, no re-bake. **Multipart TEXT fields are captured** — a real busboy `'field'` listener (busboy silently SKIPS all non-file parts when none is registered) exposes them on `req.body` + `req[method]` (POST/PUT/PATCH only), values VERBATIM (no url-decode, no `"true"/"false"/"on"/"null"` coercion — the JSON body contract, deliberately: otherwise the same client `send(fd)` call would change value TYPES with file presence), bracket-notation names nested through the urlencoded path's own layer, duplicate plain names last-wins; caps `upload.maxTextFields` (default 1000) + `upload.maxTextFieldSize` (default 1MB, unit-aware, explicit 0 = no limit) answer 400 on breach instead of busboy's silent skip/truncation. **A multipart request with no successfully-parsed file part reaches a TERMINAL state:** fields-only → the request resumes; malformed/empty → 400 via `busboy.on('error')` with a double-response guard (busboy terminates in exactly `finish` XOR `error`; pre-#B93/#B97 both were unauthenticated pre-routing DoS holes — an eternal hang, and an uncaughtException → SIGTERM bundle kill). Rules: a per-group restriction is only a control if the UNCONFIGURED/default case is denied or constrained, never waved through; a documented config key is only real if a code path reads it — grep the consumer before assuming a setting works; any stream/parser whose SUCCESS path drives a request's continuation must ALSO handle its error/empty terminals. Since #STO1 slice 1 a group may also carry driver: '<name>', routing its self.store() step through settings.storage (entry #300; the parse/staging path above is unchanged). Server-side only. Tests: `test/core/upload-groups.test.js` + `upload-config.test.js` + `multipart-nonfile-terminal.test.js` + `multipart-field-capture.test.js` + `upload-concurrent-staging.test.js` (+ the client `send(FormData)` half: `validator-send-formdata-multipart-fields.test.js`). **Server-path integrity + terminal-state hardening (former #263/#267/#268/#270 — #B103/#B142/#B143/#B144/#B145/#B223, 0.5.22-0.5.24):** a multipart body stays RAW end-to-end — `request.isMultipart` is computed ONCE at the request prologue and gates the request-stream `setEncoding` (a multipart stream reaches busboy as raw Buffers, its documented input; the decode remains for the text-body branches), write-pipeline chunks pass through VERBATIM, and `req.files[].size` counts BYTES, never a decoded string's length (#B103 — pure-ASCII surviving both old decode layers is why text uploads always worked while every real binary corrupted on every native multipart client: a PNG's 0x89 magic landed as 0xFD). The per-file `size` is finalized in the liner Transform's `_flush` (#B142 — the source-'end' record ran one pipe hop UPSTREAM of the byte counter with 16-object high-water marks still queued, under-counting ~25% on a 1.5MB file while md5 stayed perfect: the bytes were intact, only the number lied; `_flush` runs after the last `_transform` and strictly before the write stream can emit 'finish', both interleavings measured exact), and BOTH write-stream terminal listeners arm AT STREAM CREATION in the 'file' handler (#B143 — arming inside `busboy.on('finish')` lost the race for any early-finishing small file: Node never replays 'finish' for a late listener, so a throttled two-file upload hung deterministically, ~1 in 13 unthrottled; unauthenticated, both engines). Resume fires on `busboyDone && pending === 0`, whichever event lands last (the zero-pending branch still covers the fields-only #B93 terminal), and a mid-stream write error (missing dir, disk full) gets a guarded 500 instead of the historical unhandled-'error' uncaughtException SIGTERM. Downstream, `Controller.store()`'s mover streams each file to a temp sibling and publishes with an ATOMIC rename (a reader never observes a partial file; a pre-existing destination is replaced only on success), propagates the REAL filesystem Error (previously masked as `No file to upload`), and never settles the callback twice (#B223). Since #B227 the SAME semantics govern the general-purpose byte-writer in `helpers/path.js` behind `_().cp()` / `PathObject.mv()` (which CLI copy/build/rename paths ride): temp sibling in the destination's own directory + atomic rename (a pre-existing destination is no longer unlinked BEFORE the copy — its content survives a failed copy), a source-stream error listener (was an unhandled `error` event → process kill), a settled latch (a destination-side failure previously settled twice, the second time as a success, forking `browseCopy`'s directory recursion), and a real `Error` instead of the former plain string (`Error on Path.cp(...): Not found ...`) — so caller `err.stack` prints stop logging `undefined`. The destination mkdir is crash-guarded (#B145): a group whose custom `path` has a read-only/EACCES/EROFS parent answers a guarded 500 naming group + path — a server CONFIG problem is 500 (the #B50 unconfigured-group 400 stays upstream and unchanged), and pre-fix `fs.mkdirSync` threw SYNCHRONOUSLY inside the parser callback → uncaughtException → SIGTERM, an unauthenticated single-request bundle kill (the multipart parse precedes routing and all middleware, both engines). A per-group `simulateWriteError: true` flag (#B144) lets a consumer deterministically fire the guarded-500 write-error path OUTSIDE production scope only (`!NODE_SCOPE_IS_PRODUCTION`): the 'file' handler creates the REAL write stream, arms the REAL terminal listeners, then synthetically `destroy()`s it — the exact terminal semantics of a real ENOSPC/EIO (an errored stream never emits 'finish', so the request stays terminal at the 500), N destroyed parts collapsing to exactly ONE 500 via throwError's `!res.headersSent` guard; a boot warn scans `upload.groups` for the flag in both scopes (production: "IGNORED — remove before shipping"; else "PROBE active"); nothing ships active. The `group="…"` tag rides a Content-Disposition PARAMETER that `curl -F` / browser FormData cannot emit (the `@rhinostone/busboy` fork parses it into `info.dispositionParams.group`), so a faithful probe HAND-BUILDS the multipart body. **The staged upload client layer (`data-gina-form-upload-*`, lives in the validator plugin — browser-bundled ⇒ consumers RE-BAKE at pickup; former #269/#271/#272/#274 — #R8 slices 1-2, #B146/#B147/#B148/#B149):** the staging POST body is assembled as a `Blob` with each File object embedded RAW (#B148 — the historical `FileReader`/`ab2str` DOMString concatenation UTF-8-inflated every byte >= 0x80 on the wire, ×1.49 measured, a PNG's 0x89 arriving as 0xC2 0x89; the corruption only STARTED at 0.5.22 because the pre-#B103 server's two since-removed decode layers EXACTLY reversed the inflation — two wrongs cancelling — so the #B103 server fix is what exposed the client defect). Multipart FRAMING is byte-identical (same boundary delimiters, same `name=`/`group=`/`filename=` disposition-parameter set, values percent-escaped for CR/LF/double-quote per RFC 7578 §5.1.1); a fixed client must pair with a server >= 0.5.22 (an older server's decode layers would corrupt the now-raw bytes — the #B103 corruption in reverse); files corrupted by the defect are LOSSLESSLY recoverable (the stored bytes are exactly the UTF-8 encoding of the originals: decode utf8 → re-encode latin1). **Upload progress (#R8 slice 1):** `send()` assigns `xhr.upload.onprogress` FRESH on every send (the module-scoped XHR was reused across sends — a stale handler replays the previous send's closure with the wrong id), dispatching the REGISTERED event `uploadProgress.<uploadFormId>` (the events-array registration is required — `on()` validates names against the plugin registry) with `{ status: 100, progress: <int 0-100 | null when lengthComputable is false>, loaded, total, lengthComputable, files: [names] }` — a per-REQUEST aggregate (ONE staging XHR carries every file of a selection; per-file wire progress is not separable). Consumer surfaces: `data-gina-form-upload-on-progress` (bare window identifier, the -on-success convention) + a declarative indicator `data-gina-form-upload-progress="<elId>"` defaulting to `<fieldId>-progress`, opt-in by element presence. The updater feature-detects the target: a native `<progress>` tracks value=loaded/max=total (indeterminate = the value attribute REMOVED for the native animation; error = value 0, NEVER indeterminate — that animation would read as still working); any other element gets percent textContent + `data-gina-upload-progress`/`data-gina-upload-progress-state` styling hooks (preparing|uploading|indeterminate|processing|complete|error) with NO hardcoded wording (i18n-neutral — label via CSS on the state attribute). Lifecycle: `preparing` at selection (covers the FileReader/assembly phase), `uploading` per frame, `processing` at `xhr.upload.onloadend` (the browser finished SENDING — advances the state attribute ONLY, leaving value/percent as the last frame left them so a determinate bar stays visually full), `complete`/`error` finalized in onUpload (one chokepoint covers success and every error/timeout path), reset/delete strips the indicator. The response-side download channel `progress.<id>` is byte-untouched (it also serves attachment downloads). **Drag-and-drop (#R8 slice 2):** a file input carrying `data-gina-form-upload-dropzone="<elementId>"` gets drag listeners bound on the named element at form-bind time; dropped files are assigned to the input (`input.files = dataTransfer.files`) and the input's `change` is re-fired synthetically, so the ENTIRE staging pipeline — group tagging, virtual form, staging POST, previews, hidden metadata fields, reset/delete, upload progress — runs with zero duplicated logic (the change handler reads only `currentTarget`, so a synthetic dispatch is indistinguishable from a trusted one on this path). Contract: EXPLICIT-id-only (deliberately NO `<fieldId>-dropzone` default — auto-binding a coincidentally-named element would attach drag semantics to markup that may carry its own drop handling; absent attribute = inert, missing element = console.warn + inert); the zone is stamped `data-gina-upload-dropzone` (value = owner input id — the first-wins guard: one zone serves one input) and `data-gina-upload-dropzone-state` (`idle` → `over` on a file-drag hover, DEPTH-COUNTED so child-boundary crossings never flicker → `dropped` → back to `idle` at the same onUpload chokepoint that finalizes progress, and at reset/delete) — pure CSS hooks, no hardcoded wording. Only FILE drags react (`dataTransfer.types` must carry `Files`; text/link drags fall through untouched, never preventDefault'd); a multi-file drop on a non-`multiple` input keeps the FIRST file only (console.warn) — configured groups still enforce `isMultipleAllowed` server-side; a bare file input already accepts native browser drops through the same change handler — the attribute exists to delegate a larger/styled element. **Action/preview/bind fixes (#B146/#B147/#B149):** `checkUploadUrlActions`'s default-route fallback writes the attribute ACTUALLY being checked (`setAttribute(action, …)` — it used to hardcode the staging attribute and silently repoint a staging POST at the resolved reset/delete default route, compounding to a SILENT failure when `toUrl()`'s absolute origin tripped a CORS preflight → XHR status-0 → the commented-out status-0 branch); the preview-container guard uses the typeof-null pattern (`&& previewContainer` — `getElementById` returns element|null and `typeof null === 'object'`, so a preview-element MISS used to TypeError the success handler at `.id`); an upload-only input binds QUIETLY — `-delete-action` deliberately has no framework default (it removes an already-saved file, an app-specific endpoint), so a no-default absent action is a single `console.debug` + early return (a WITH-default action that genuinely fails to resolve still errors; the delete requirement stays enforced lazily at `onUploadResetOrDelete`). Tests: `upload-binary-integrity.test.js` / `upload-size-accuracy.test.js` (11) / `multipart-multifile-resume.test.js` (15) / `upload-write-error-probe.test.js` / `upload-config.test.js §07` / `validator-upload-progress.test.js` (46) / `validator-upload-binary-wire.test.js` / `validator-upload-action-guard.test.js` — all red-first-validated with control-gated extractions of the shipped bytes. **The store() fluent form is restored (#B420, 0.6.16):** `self.store(target).onComplete(cb)` returns its `{onComplete}` handle synchronously as documented -- an accidental `async` on the declaration had wrapped the handle in a Promise on every stable from v0.6.0 to v0.6.15, so the fluent form (the upload guide entry-point example) threw `TypeError` while the 3-arg callback form worked throughout; the body was await-free, nothing anywhere awaits `store()`, and dropping the keyword is the whole fix (types now declare the fluent return shape).
|
|
905
|
+
210. **Multipart upload config is ENFORCED — groups, destination, limits, text-field capture + caps, terminal states (consolidates former #187/#188/#228/#230's server half; #B49/#B50/#B51/#B92-adjacent/#B93/#B97).** Every uploaded file must map to a CONFIGURED upload group: the resolved group (no/empty → the default `untagged`) must exist in `settings.json upload.groups` or the request is rejected 400 BEFORE the temp file is created, and `untagged` obeys its own config like any named group (pre-#B50 the checks ran only for defined non-`untagged` groups — an allow-list bypass; the shipped `untagged` default is `allowedExtensions: '*'` + `isMultipleAllowed: true`, so configure `untagged` restrictively or a client can route around a named group's allow-list). Destination + limits honoured: files stream to `uploadDir || tmpPath || os.tmpdir()` with a per-group `path` override and mkdir-if-missing before `createWriteStream` (the mkdir itself crash-guarded → 500, #B145); `maxFields` caps the per-request file COUNT (400 past the cap; 0/unset disables); `maxFieldsSize` parses its unit suffix (B/KB/MB/GB; bare number = MB) as the whole-body cap (431). **The staged on-disk FILENAME is server-generated and opaque, never the client's (#B419, 0.6.16):** `crypto.randomBytes(16).toString('hex') + '.part'`, minted once per part and used at BOTH path-construction sites — the write stream AND `req.files[].path`, which are built independently and must never diverge. Pre-fix the destination was `<fileUploadDir>/<client basename>` with NO per-part component, so two parts sharing a name — within ONE request or across concurrent requests — opened two write streams on ONE path with independent file offsets and INTERLEAVED their bytes into a single hybrid matching neither source, while the framework answered `storeErr:false` + 200 (measured live pre-fix: 12 concurrent same-named uploads → 0/12 clean, one hybrid file; one request with two same-named parts → one 500000-byte file of {B:417353,A:82647}; post-fix 12/12 clean, each byte-exact). The parse precedes routing and all middleware, so an UNAUTHENTICATED POST to ANY url reaches it — an unrouted one answers 404 to the client and still writes the file. A CSPRNG rather than the `Math.random()` used by `movefiles` (V8's is xorshift128+, so staging paths would be predictable, and a group `path` is operator-configurable into a served root); a RANDOM name rather than `filename + <suffix>` (keeps attacker-controlled bytes off the filesystem — RTL-override display spoofing, control chars, reserved device names — and cannot overflow NAME_MAX=255 into an ENAMETOOLONG 500 for a long-but-legal client filename); no interpretable extension, so a misconfigured static-serve of the staging dir cannot hand back an executable type. This is the multer ("a random name that doesn't include any file extension") and formidable (`newFilename` hexoid) convention. `req.files[].originalFilename` still carries the client's name and is what `store()` publishes the file under (`controller.js:4061`) and what a storage driver receives as `originalName`, so the documented "keeping each file's original name" contract is UNCHANGED — only `req.files[].path`'s BASENAME differs, and that field is documented as the temporary file's path. Server-side ⇒ pickup is a bundle restart, no re-bake. **Staged-part lifecycle + orphan reclaim (#B469):** a staged part has exactly TWO reclaim paths — the `movefiles()` unlink when `self.store()` publishes it, and the per-upload `autoTmpCleanupTimeout` deletion timer (`false`/0/empty disables it — the shipped default, and removing staged files is then the operator's own job). The timer is IN-PROCESS: a restart strands every part it was holding, and a part that never reaches `store()` (a request failing AFTER the multipart parse — the parse precedes routing, so auth/validation/quota failures have already staged; or an app consuming the staged file without `store()`) then has NO reclaim path at all. So an ARMED timer also arms a boot-time sweep at server init: each configured landing dir (the resolved global + every group `path`, deduped) is swept NON-recursively for `<32 lowercase hex>.part` REGULAR files (lstat, symlinks never followed; a client-named orphan predating the opaque staged naming is indistinguishable from an application file and is left alone) older than max(timeout, 1h) — the floor keeps a SIBLING live process's in-flight part on a shared staging dir safe, since its mtime stays fresh while chunks land; ENOENT at any step is not an error (a sibling bundle sweeping the same dir, or the app unlinking its own part). Disabled timer = NO sweep — the documented operator-owns-cleanup contract is unchanged. An application MAY unlink `req.files[].path` itself: `movefiles()` tolerates a vanished source (ENOENT) by design, and the armed timer already requires that tolerance. Rule: a group `path` is the PARSE-TIME LANDING (staging) directory, never where published files live — `store(targetDir)` or the group's storage `driver` owns final placement, and one key must never carry both meanings. **Multipart TEXT fields are captured** — a real busboy `'field'` listener (busboy silently SKIPS all non-file parts when none is registered) exposes them on `req.body` + `req[method]` (POST/PUT/PATCH only), values VERBATIM (no url-decode, no `"true"/"false"/"on"/"null"` coercion — the JSON body contract, deliberately: otherwise the same client `send(fd)` call would change value TYPES with file presence), bracket-notation names nested through the urlencoded path's own layer, duplicate plain names last-wins; caps `upload.maxTextFields` (default 1000) + `upload.maxTextFieldSize` (default 1MB, unit-aware, explicit 0 = no limit) answer 400 on breach instead of busboy's silent skip/truncation. **A multipart request with no successfully-parsed file part reaches a TERMINAL state:** fields-only → the request resumes; malformed/empty → 400 via `busboy.on('error')` with a double-response guard (busboy terminates in exactly `finish` XOR `error`; pre-#B93/#B97 both were unauthenticated pre-routing DoS holes — an eternal hang, and an uncaughtException → SIGTERM bundle kill). Rules: a per-group restriction is only a control if the UNCONFIGURED/default case is denied or constrained, never waved through; a documented config key is only real if a code path reads it — grep the consumer before assuming a setting works; any stream/parser whose SUCCESS path drives a request's continuation must ALSO handle its error/empty terminals. Since #STO1 slice 1 a group may also carry driver: '<name>', routing its self.store() step through settings.storage (entry #300; the parse/staging path above is unchanged). Server-side only. Tests: `test/core/upload-groups.test.js` + `upload-config.test.js` + `multipart-nonfile-terminal.test.js` + `multipart-field-capture.test.js` + `upload-concurrent-staging.test.js` + `upload-orphan-sweep.test.js` (+ the client `send(FormData)` half: `validator-send-formdata-multipart-fields.test.js`). **Server-path integrity + terminal-state hardening (former #263/#267/#268/#270 — #B103/#B142/#B143/#B144/#B145/#B223, 0.5.22-0.5.24):** a multipart body stays RAW end-to-end — `request.isMultipart` is computed ONCE at the request prologue and gates the request-stream `setEncoding` (a multipart stream reaches busboy as raw Buffers, its documented input; the decode remains for the text-body branches), write-pipeline chunks pass through VERBATIM, and `req.files[].size` counts BYTES, never a decoded string's length (#B103 — pure-ASCII surviving both old decode layers is why text uploads always worked while every real binary corrupted on every native multipart client: a PNG's 0x89 magic landed as 0xFD). The per-file `size` is finalized in the liner Transform's `_flush` (#B142 — the source-'end' record ran one pipe hop UPSTREAM of the byte counter with 16-object high-water marks still queued, under-counting ~25% on a 1.5MB file while md5 stayed perfect: the bytes were intact, only the number lied; `_flush` runs after the last `_transform` and strictly before the write stream can emit 'finish', both interleavings measured exact), and BOTH write-stream terminal listeners arm AT STREAM CREATION in the 'file' handler (#B143 — arming inside `busboy.on('finish')` lost the race for any early-finishing small file: Node never replays 'finish' for a late listener, so a throttled two-file upload hung deterministically, ~1 in 13 unthrottled; unauthenticated, both engines). Resume fires on `busboyDone && pending === 0`, whichever event lands last (the zero-pending branch still covers the fields-only #B93 terminal), and a mid-stream write error (missing dir, disk full) gets a guarded 500 instead of the historical unhandled-'error' uncaughtException SIGTERM. Downstream, `Controller.store()`'s mover streams each file to a temp sibling and publishes with an ATOMIC rename (a reader never observes a partial file; a pre-existing destination is replaced only on success), propagates the REAL filesystem Error (previously masked as `No file to upload`), and never settles the callback twice (#B223). Since #B227 the SAME semantics govern the general-purpose byte-writer in `helpers/path.js` behind `_().cp()` / `PathObject.mv()` (which CLI copy/build/rename paths ride): temp sibling in the destination's own directory + atomic rename (a pre-existing destination is no longer unlinked BEFORE the copy — its content survives a failed copy), a source-stream error listener (was an unhandled `error` event → process kill), a settled latch (a destination-side failure previously settled twice, the second time as a success, forking `browseCopy`'s directory recursion), and a real `Error` instead of the former plain string (`Error on Path.cp(...): Not found ...`) — so caller `err.stack` prints stop logging `undefined`. The destination mkdir is crash-guarded (#B145): a group whose custom `path` has a read-only/EACCES/EROFS parent answers a guarded 500 naming group + path — a server CONFIG problem is 500 (the #B50 unconfigured-group 400 stays upstream and unchanged), and pre-fix `fs.mkdirSync` threw SYNCHRONOUSLY inside the parser callback → uncaughtException → SIGTERM, an unauthenticated single-request bundle kill (the multipart parse precedes routing and all middleware, both engines). A per-group `simulateWriteError: true` flag (#B144) lets a consumer deterministically fire the guarded-500 write-error path OUTSIDE production scope only (`!NODE_SCOPE_IS_PRODUCTION`): the 'file' handler creates the REAL write stream, arms the REAL terminal listeners, then synthetically `destroy()`s it — the exact terminal semantics of a real ENOSPC/EIO (an errored stream never emits 'finish', so the request stays terminal at the 500), N destroyed parts collapsing to exactly ONE 500 via throwError's `!res.headersSent` guard; a boot warn scans `upload.groups` for the flag in both scopes (production: "IGNORED — remove before shipping"; else "PROBE active"); nothing ships active. The `group="…"` tag rides a Content-Disposition PARAMETER that `curl -F` / browser FormData cannot emit (the `@rhinostone/busboy` fork parses it into `info.dispositionParams.group`), so a faithful probe HAND-BUILDS the multipart body. **The staged upload client layer (`data-gina-form-upload-*`, lives in the validator plugin — browser-bundled ⇒ consumers RE-BAKE at pickup; former #269/#271/#272/#274 — #R8 slices 1-2, #B146/#B147/#B148/#B149):** the staging POST body is assembled as a `Blob` with each File object embedded RAW (#B148 — the historical `FileReader`/`ab2str` DOMString concatenation UTF-8-inflated every byte >= 0x80 on the wire, ×1.49 measured, a PNG's 0x89 arriving as 0xC2 0x89; the corruption only STARTED at 0.5.22 because the pre-#B103 server's two since-removed decode layers EXACTLY reversed the inflation — two wrongs cancelling — so the #B103 server fix is what exposed the client defect). Multipart FRAMING is byte-identical (same boundary delimiters, same `name=`/`group=`/`filename=` disposition-parameter set, values percent-escaped for CR/LF/double-quote per RFC 7578 §5.1.1); a fixed client must pair with a server >= 0.5.22 (an older server's decode layers would corrupt the now-raw bytes — the #B103 corruption in reverse); files corrupted by the defect are LOSSLESSLY recoverable (the stored bytes are exactly the UTF-8 encoding of the originals: decode utf8 → re-encode latin1). **Upload progress (#R8 slice 1):** `send()` assigns `xhr.upload.onprogress` FRESH on every send (the module-scoped XHR was reused across sends — a stale handler replays the previous send's closure with the wrong id), dispatching the REGISTERED event `uploadProgress.<uploadFormId>` (the events-array registration is required — `on()` validates names against the plugin registry) with `{ status: 100, progress: <int 0-100 | null when lengthComputable is false>, loaded, total, lengthComputable, files: [names] }` — a per-REQUEST aggregate (ONE staging XHR carries every file of a selection; per-file wire progress is not separable). Consumer surfaces: `data-gina-form-upload-on-progress` (bare window identifier, the -on-success convention) + a declarative indicator `data-gina-form-upload-progress="<elId>"` defaulting to `<fieldId>-progress`, opt-in by element presence. The updater feature-detects the target: a native `<progress>` tracks value=loaded/max=total (indeterminate = the value attribute REMOVED for the native animation; error = value 0, NEVER indeterminate — that animation would read as still working); any other element gets percent textContent + `data-gina-upload-progress`/`data-gina-upload-progress-state` styling hooks (preparing|uploading|indeterminate|processing|complete|error) with NO hardcoded wording (i18n-neutral — label via CSS on the state attribute). Lifecycle: `preparing` at selection (covers the FileReader/assembly phase), `uploading` per frame, `processing` at `xhr.upload.onloadend` (the browser finished SENDING — advances the state attribute ONLY, leaving value/percent as the last frame left them so a determinate bar stays visually full), `complete`/`error` finalized in onUpload (one chokepoint covers success and every error/timeout path), reset/delete strips the indicator. The response-side download channel `progress.<id>` is byte-untouched (it also serves attachment downloads). **Drag-and-drop (#R8 slice 2):** a file input carrying `data-gina-form-upload-dropzone="<elementId>"` gets drag listeners bound on the named element at form-bind time; dropped files are assigned to the input (`input.files = dataTransfer.files`) and the input's `change` is re-fired synthetically, so the ENTIRE staging pipeline — group tagging, virtual form, staging POST, previews, hidden metadata fields, reset/delete, upload progress — runs with zero duplicated logic (the change handler reads only `currentTarget`, so a synthetic dispatch is indistinguishable from a trusted one on this path). Contract: EXPLICIT-id-only (deliberately NO `<fieldId>-dropzone` default — auto-binding a coincidentally-named element would attach drag semantics to markup that may carry its own drop handling; absent attribute = inert, missing element = console.warn + inert); the zone is stamped `data-gina-upload-dropzone` (value = owner input id — the first-wins guard: one zone serves one input) and `data-gina-upload-dropzone-state` (`idle` → `over` on a file-drag hover, DEPTH-COUNTED so child-boundary crossings never flicker → `dropped` → back to `idle` at the same onUpload chokepoint that finalizes progress, and at reset/delete) — pure CSS hooks, no hardcoded wording. Only FILE drags react (`dataTransfer.types` must carry `Files`; text/link drags fall through untouched, never preventDefault'd); a multi-file drop on a non-`multiple` input keeps the FIRST file only (console.warn) — configured groups still enforce `isMultipleAllowed` server-side; a bare file input already accepts native browser drops through the same change handler — the attribute exists to delegate a larger/styled element. **Action/preview/bind fixes (#B146/#B147/#B149):** `checkUploadUrlActions`'s default-route fallback writes the attribute ACTUALLY being checked (`setAttribute(action, …)` — it used to hardcode the staging attribute and silently repoint a staging POST at the resolved reset/delete default route, compounding to a SILENT failure when `toUrl()`'s absolute origin tripped a CORS preflight → XHR status-0 → the commented-out status-0 branch); the preview-container guard uses the typeof-null pattern (`&& previewContainer` — `getElementById` returns element|null and `typeof null === 'object'`, so a preview-element MISS used to TypeError the success handler at `.id`); an upload-only input binds QUIETLY — `-delete-action` deliberately has no framework default (it removes an already-saved file, an app-specific endpoint), so a no-default absent action is a single `console.debug` + early return (a WITH-default action that genuinely fails to resolve still errors; the delete requirement stays enforced lazily at `onUploadResetOrDelete`). Tests: `upload-binary-integrity.test.js` / `upload-size-accuracy.test.js` (11) / `multipart-multifile-resume.test.js` (15) / `upload-write-error-probe.test.js` / `upload-config.test.js §07` / `validator-upload-progress.test.js` (46) / `validator-upload-binary-wire.test.js` / `validator-upload-action-guard.test.js` — all red-first-validated with control-gated extractions of the shipped bytes. **The store() fluent form is restored (#B420, 0.6.16):** `self.store(target).onComplete(cb)` returns its `{onComplete}` handle synchronously as documented -- an accidental `async` on the declaration had wrapped the handle in a Promise on every stable from v0.6.0 to v0.6.15, so the fluent form (the upload guide entry-point example) threw `TypeError` while the 3-arg callback form worked throughout; the body was await-free, nothing anywhere awaits `store()`, and dropping the keyword is the whole fix (types now declare the fluent return shape).
|
|
906
906
|
|
|
907
907
|
211. **Request-body parsing contract — verbatim JSON, `req.rawBody`, and crash-safe decodes (consolidates former #162/#163/#164; #B28/#B30/#B64).** `application/json` bodies (POST/PUT/PATCH) parse VERBATIM first — `JSON.parse(request.body)`, with a `decodeURIComponent` fallback ONLY when that throws — so the client's exact types and string contents survive (no double-decode, no `"true"/"false"/"on"/"null"` coercion, no bracket-key expansion; urlencoded/absent content-types keep the legacy decode+coerce path; error disposition unchanged — POST/PATCH 500 only when both attempts fail, PUT warns). **XML bodies pass through VERBATIM (#FIN1).** `application/xml`, `text/xml` and the `application/*+xml` suffix family fork off the legacy path on POST/PUT/PATCH before any decode: `request.body` stays the exact document (the same string `rawBody` carries) and the method slot is left unset — the routing loop normalises it to `{}` further down, so a controller still reads an object from `req.post`. The framework never PARSES XML, so it takes on no XML-parser surface and no XXE exposure; consumers bring their own library. Pre-#FIN1 such a body was silently DESTROYED rather than rejected: the bracket-notation data helper splits on `&` then on `=` and keeps only the first pair, so an XML declaration alone collapsed a whole document to a single bogus key which — having a key — then REPLACED `request.body` at the shared tail, while the `"true"`/`"on"` coercion separately rewrote quoted attribute values. The matched set is the convergent one — ASP.NET Core registers ApplicationXml/TextXml/ApplicationAnyXmlSyntax (`application/*+xml`), Spring's `MimeType.includes()` documents `application/*+xml` as including `application/soap+xml`, and type-is matches structured suffixes generically — with the wildcard scoped to `application/` in all three, so `image/svg+xml` is deliberately NOT matched and RFC 7303's non-document types (`application/xml-dtd` §9.5, both `xml-external-parsed-entity` §9.3/§9.4) are excluded. RFC 7303 §9.6.1 registers the suffix but RFC 6838 §4.2.8 makes it a naming convention that does NOT mandate generic processing, so this set is convergent implementation practice, not a normative requirement. ONE flag computed at the request prologue feeds all three branches (the #B103 single-site lesson, after those same three sites drifted); each arm seeds `obj = {}` because the shared POST/PATCH tail reads `typeof(obj) == 'object' && obj.count()` and `typeof null === 'object'`. Both engines share the chokepoint; server-side only, no dist rebuild. The framework's own server-to-server client (`self.query()`) sends raw `JSON.stringify(data)` for JSON bodies (RFC5987 value-encoding is for header values, not request bodies), and the inter-bundle HTTP/2 proxy no longer copies the INCOMING request's Content-Type onto the outbound JSON body it serialized itself — a urlencoded label re-routed raw JSON through the receiving parser's urlencoded branch and corrupted string values (the only incoming-CT→outbound copy framework-wide; kept for non-JSON bodies). `request.rawBody` snapshots the exact unparsed body (reference assignment, always-on, non-multipart only, `''` for empty) immediately BEFORE parsing mutates `request.body` — inbound-webhook HMAC verification needs the raw bytes, which middleware cannot recover after the stream drains; both engines share this single `core/server.js` pipeline. Malformed percent-escapes (`%`, `%zz`, truncated `%E0%A`) in a URL or query no longer kill the bundle (#B30 — an unauthenticated single-request DoS: any unguarded `decodeURIComponent` OR `decodeURI` on the request path threw `URIError` → uncaughtException → SIGTERM): redundant SECOND decodes on the GET/HEAD branches were DROPPED (the engine query parser + the data helper each already decode once, guarded), and every genuine first decode of attacker-controllable input routes through `safeDecodeURIComponent`/`safeDecodeURI` (try/decode/fallback-to-raw) — including the ERROR paths, where a malformed-% URL to a missing asset previously crashed FROM the error handler, turning a would-be 404 into a bundle kill. Sweep rule: `decodeURIComponent` and `decodeURI` are the same throwing family — sweep BOTH; fix the throw sites, never widen the uncaughtException net. Sibling #B64 (0.5.7): the two static-file resolvers confine the resolved filename to the matched mapping target (`path.resolve` both sides + separator-aware containment), so `../` / `%2F` / `%2e%2e` cannot escape to sibling files — an escape 404s exactly like a missing file. Second site, same guard (#B179, Security/CWE-22): the dev-mode Inspector SPA handler (`/_gina/inspector/*`) built its filename the same way — request target minus the prefix, joined onto the asset root, passed through the `_()` path helper — and `_()` calls `Path.normalize`, which **RESOLVES** `..` rather than rejecting it, so a literal `../` read any absolute path the bundle process could reach (verified live: 200 on `/etc/passwd`; encoded `%2e%2e` unaffected, since this handler never decodes — and decoding was deliberately not added). Node does not normalise the request-target (only clients do), so a raw HTTP client reaches it where a browser cannot. Unlike the static resolvers, this handler is **DUPLICATED per engine** (server.js + server.isaac.js) and `confineToBase` is scoped inside server.js's `Server()` closure — unreachable from isaac, which therefore declares an engine-local twin, pinned against drift by a test asserting both extracted copies agree. **Rule: any handler joining request-derived segments onto a base MUST confine the RESOLVED path; path normalisation is not a traversal defence, it is the mechanism that makes the escape land.** Corollary for endpoint families: check the SIBLING handlers' gating too — the Inspector endpoint carries no IP allowlist while its `/_gina/*` neighbours (`info`, `cache/stats`, `cache/clear`) all gate on `lib.admin.isClientAllowed`. **GET/HEAD query serialization no longer destroys escapes (#B407, consumer-reported):** both branches round-trip `request.query` through JSON.stringify + a re-parse, and the former textual nested-JSON unwrap ended with a blanket backslash strip over the WHOLE serialized document — so `%0A` in a value became the letter `n`, a backslash vanished, and a double quote OR a JSON-ARRAY value produced invalid JSON that silently dropped EVERY query param on the request (`parseBody` logs `[365]` and returns undefined). Both sites now serialize via `serializeQueryForReparse` (a shared inner helper in `processRequestData`): each own STRING value leaning `{`/`[` is unwrapped by PARSING it (kept verbatim when it does not parse — previously that shape also dropped the whole query), then a clean stringify with no post-processing; the whole-value `"true"/"false"/"on"/"null"` coercion is unchanged and, with escapes intact, can no longer match an escaped occurrence embedded inside a longer value. Intended behaviours preserved byte-for-byte (nested-object unwrap — now also correct for nested content carrying escapes, which the textual unwrap corrupted as well — coercion, plain values); array-valued and unparsable-`{`-leading params are delivered instead of killing the query. **PROTOTYPE-POLLUTION GUARD (#B446).** Every parse path that nests a bracket-notation key routes through `parseLocalObj` in `helpers/data` (aliased `nestBracketNotationKey`, and reached from `formatDataFromString`), which assigned `obj[key[k]]` from a CLIENT-SUPPLIED field name with no key filtering: `__proto__[x]=y`, `constructor[prototype][x]=y` and the percent-encoded `%5F%5Fproto%5F%5F[x]=y` each wrote to `Object.prototype` process-wide while the parsed body still read `{}` — i.e. SILENTLY. Reachable from the GET query string, urlencoded bodies, `inheritedData`, and multipart text-field names. ✅ **CONFIRMED LIVE over real HTTP on the DEFAULT `isaac` engine** (daemonless throwaway bundle, pre-fix worktree vs fixed tree, same fixture): a single unauthenticated `GET /?__proto__[polluted]=OWNED` — no body, no interaction — returned `polluted:"OWNED"` with `hasOwn:false` (the prototype-pollution signature, not an own property) and `arrayToo:"OWNED"`, and **the NEXT, innocent request inherited it at the same pid**, proving cross-request persistence. All three vectors reproduced; the fixed tree answered clean on all three with benign bracket nesting still HTTP 200. ⚠️ Isaac does NOT reference these functions at all — its own query parser is flat (`request.query[a[0]] = a[1]`); the attack survives because `server.js` re-serialises isaac's flat query (`serializeQueryForReparse`) and re-parses it through `formatDataFromString`, which restores the bracket branch. ⚠️ Instrument trap that nearly produced a FALSE NEGATIVE: `curl` treats `[...]` as a URL glob range, so every bracket request fails inside curl and never reaches the server — use `curl -g`. A second, independent source: PUT merges `JSON.parse` output as the merge SOURCE, and `JSON.parse` produces an OWN `__proto__` key, so an own-property check alone does NOT stop it — see #321. Measured gadgets before the fix: outbound-request options fully controllable (host/hostname/port/method/auth, `rejectUnauthorized` forced false, `protocol` feeding a dynamic transport require), `SAFE_HTTP_METHODS[m] === true` classifying POST as safe, and `req.routing.csrfExempt` reading true — the last two ONLY via the JSON source, since the string source yields `"true"` and is correctly rejected by the strict compare. ⚠️ The `csrfExempt` one is UNRESOLVED, not confirmed on a live route: what was measured is that a routing object LACKING an own `csrfExempt` reads the polluted value, but `core/server.js:7674` builds routes as `csrfExempt: routing[name].csrfExempt || false`, creating an OWN key that shadows the prototype — while `lib/routing/src/main.js:220` sets it only when already defined, so a route built there can lack it. Which construction path a live `req.routing` takes was NOT determined. Both sources now reject `__proto__` / `constructor` / `prototype` path segments and DROP the field rather than throwing (a throw on the parse path would turn one bad field into a 500). The guard is kept INLINE in `parseLocalObj`, not a closure helper, because `test/core/validator-send-formdata-nesting.test.js` extracts that function's source text and evals it standalone to pin client/server parity — a helper call is a ReferenceError there. `helpers/data` and the validator copy are both browser-bundled, so pickup is a bundle restart AND a re-bake. Regression suite: `test/lib/prototype-pollution-b446.test.js` (attack arms each paired with a benign control, pre/post A/B validated on extracted HEAD source).
|
|
908
908
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gina",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.26",
|
|
4
4
|
"description": "MVC framework for Node.js and Bun with built-in HTTP/2, multi-bundle architecture, and scope-based data isolation — no Express dependency",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nodejs",
|
|
@@ -76,12 +76,12 @@
|
|
|
76
76
|
"gina-container": "bin/gina-container",
|
|
77
77
|
"gina-init": "bin/gina-init"
|
|
78
78
|
},
|
|
79
|
-
"main": "./framework/v0.6.
|
|
79
|
+
"main": "./framework/v0.6.26/core/gna",
|
|
80
80
|
"exports": {
|
|
81
81
|
".": {
|
|
82
82
|
"types": "./types/index.d.ts",
|
|
83
83
|
"import": "./index.mjs",
|
|
84
|
-
"require": "./framework/v0.6.
|
|
84
|
+
"require": "./framework/v0.6.26/core/gna.js"
|
|
85
85
|
},
|
|
86
86
|
"./gna": {
|
|
87
87
|
"types": "./types/gna.d.ts",
|
package/schema/app.json
CHANGED
|
@@ -80,6 +80,57 @@
|
|
|
80
80
|
"default": ["127.0.0.1", "::1"]
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
+
},
|
|
84
|
+
"jobs": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"description": "Async-job primitive tuning (`lib/job`, #AI6). The primitive is always-on; an absent block means sane defaults with the in-memory record store. Read once at bundle start (gna.js) — changes need a bundle restart.",
|
|
87
|
+
"additionalProperties": false,
|
|
88
|
+
"properties": {
|
|
89
|
+
"store": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"description": "Name of a connectors.json entry providing a durable record store (sqlite, redis, mongodb ship implementations). Records then survive a bundle restart and are readable cross-process; the deferred FUNCTION always lives in the creating process only. Fail-fast: a configured store that cannot be built aborts the boot rather than silently falling back to memory. Absent = the in-memory store."
|
|
92
|
+
},
|
|
93
|
+
"maxConcurrency": {
|
|
94
|
+
"type": "number",
|
|
95
|
+
"description": "Max deferred functions running at once in this process; the rest queue. Default 4."
|
|
96
|
+
},
|
|
97
|
+
"ttl": {
|
|
98
|
+
"type": "number",
|
|
99
|
+
"description": "Terminal-record retention, SECONDS: a completed/failed record is swept this long after finishing. Default 3600."
|
|
100
|
+
},
|
|
101
|
+
"sweepInterval": {
|
|
102
|
+
"type": "number",
|
|
103
|
+
"description": "Internal sweep cadence, SECONDS (an unref'd timer — never holds the process open). 0 disables the internal timer (drive lib.job.sweep() yourself). Default 300."
|
|
104
|
+
},
|
|
105
|
+
"idSize": {
|
|
106
|
+
"type": "number",
|
|
107
|
+
"description": "Job id length, characters. Default 21."
|
|
108
|
+
},
|
|
109
|
+
"retryBackoffMs": {
|
|
110
|
+
"type": "number",
|
|
111
|
+
"description": "Base delay (ms) between attempts of a failed job created with maxAttempts > 1; doubles each attempt. Retries run on the creating process only. Default 1000."
|
|
112
|
+
},
|
|
113
|
+
"orphanTimeout": {
|
|
114
|
+
"type": ["number", "boolean"],
|
|
115
|
+
"description": "#B471 — orphan-reclaim ceiling, SECONDS. A non-terminal record whose last transition is older than this (or whose scheduled retry is this far overdue) is presumed stranded by a dead process and terminalized as `failed` with error name `JobOrphanedError`; normal ttl retention then applies. Durable stores only — the built-in memory store is exempt (it cannot hold another process's orphans). A record reclaimed while its origin was merely slow self-corrects on the origin's next write; no completion webhook fires for a reclaim. Default 86400 (24h); 0 or false disables; positive values are floored at 60."
|
|
116
|
+
},
|
|
117
|
+
"webhookMaxAttempts": {
|
|
118
|
+
"type": "number",
|
|
119
|
+
"description": "Completion-webhook delivery retry ceiling. Default 3."
|
|
120
|
+
},
|
|
121
|
+
"webhookBackoffMs": {
|
|
122
|
+
"type": "number",
|
|
123
|
+
"description": "Webhook retry backoff base (ms); doubles each retry. Default 500."
|
|
124
|
+
},
|
|
125
|
+
"webhookTimeoutMs": {
|
|
126
|
+
"type": "number",
|
|
127
|
+
"description": "Per-attempt webhook request timeout (ms). Default 5000."
|
|
128
|
+
},
|
|
129
|
+
"webhookSecret": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"description": "HMAC secret for signing completion-webhook payloads (X-Gina-Signature). Omit to disable signing."
|
|
132
|
+
}
|
|
133
|
+
}
|
|
83
134
|
}
|
|
84
135
|
},
|
|
85
136
|
"definitions": {
|
package/schema/settings.json
CHANGED
|
@@ -677,7 +677,7 @@
|
|
|
677
677
|
},
|
|
678
678
|
"autoTmpCleanupTimeout": {
|
|
679
679
|
"type": ["string", "number", "boolean"],
|
|
680
|
-
"description": "Arms a per-file deletion timer at parse time for each landed file (e.g. \"30s\", \"10m\", \"1h\", \"500ms\"; a bare number is milliseconds). false, 0 or empty disables it — the shipped default. Block-level only."
|
|
680
|
+
"description": "Arms a per-file deletion timer at parse time for each landed file (e.g. \"30s\", \"10m\", \"1h\", \"500ms\"; a bare number is milliseconds). false, 0 or empty disables it — the shipped default. Block-level only. When armed, a boot-time sweep also reclaims staged parts (<32 hex>.part) a previous process stranded — parts older than this timeout (floored at 1h) in the configured landing directories; disabled means no sweep, and removing staged files is then the operator's own job."
|
|
681
681
|
},
|
|
682
682
|
"groups": {
|
|
683
683
|
"type": "object",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.6.25
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.css
RENAMED
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.js
RENAMED
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/beemaster/index.html
RENAMED
|
File without changes
|
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.br
RENAMED
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.gz
RENAMED
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/html/statusbar.html
RENAMED
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.br
RENAMED
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.gz
RENAMED
|
File without changes
|
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/inspector/index.html
RENAMED
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/inspector/inspector.css
RENAMED
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/inspector/inspector.js
RENAMED
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/inspector/logo.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js
RENAMED
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.br
RENAMED
|
File without changes
|
/package/framework/{v0.6.25 → v0.6.26}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.gz
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|