gina 0.6.13 → 0.6.14
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 +30 -0
- package/README.md +28 -22
- package/ROADMAP.md +3 -2
- package/framework/v0.6.14/VERSION +1 -0
- package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/js/gina.js +9 -6
- package/framework/{v0.6.13 → v0.6.14}/core/config.js +9 -4
- package/framework/{v0.6.13 → v0.6.14}/core/controller/controller.js +208 -42
- package/framework/{v0.6.13 → v0.6.14}/core/gna.js +25 -1
- package/framework/{v0.6.13 → v0.6.14}/core/server.js +66 -2
- package/framework/{v0.6.13 → v0.6.14}/lib/cmd/secrets/check.js +220 -168
- package/framework/v0.6.14/lib/cmd/secrets/scan.js +264 -0
- package/framework/{v0.6.13 → v0.6.14}/lib/collection/src/main.js +9 -6
- package/framework/{v0.6.13 → v0.6.14}/lib/secrets/src/backends/env.js +41 -1
- package/framework/v0.6.14/lib/secrets/src/backends/exec.js +253 -0
- package/framework/{v0.6.13 → v0.6.14}/lib/secrets/src/backends/file.js +5 -27
- package/framework/v0.6.14/lib/secrets/src/declaration.js +303 -0
- package/framework/{v0.6.13 → v0.6.14}/lib/secrets/src/main.js +107 -54
- package/framework/v0.6.14/lib/secrets/src/sources.js +325 -0
- package/framework/{v0.6.13 → v0.6.14}/package.json +1 -1
- package/gna.js +4 -4
- package/llms.txt +6 -4
- package/package.json +3 -3
- package/schema/settings.json +21 -0
- package/framework/v0.6.13/VERSION +0 -1
- package/framework/v0.6.13/lib/cmd/secrets/scan.js +0 -448
- /package/framework/{v0.6.13 → v0.6.14}/AUTHORS +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/LICENSE +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/html/nolayout.html +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/html/static.html +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/img/android-chrome-192x192.png +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/img/android-chrome-512x512.png +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/img/apple-touch-icon.png +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/img/favicon-16x16.png +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/img/favicon-32x32.png +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/img/favicon.ico +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.css +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/beemaster/index.html +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/css/gina.min.css +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.br +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.gz +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/html/statusbar.html +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.br +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.gz +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/inspector/have_heart_one-webfont.woff2 +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/inspector/index.html +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/inspector/inspector.css +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/inspector/inspector.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/inspector/logo.svg +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/js/gina.min.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.br +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.gz +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/ai/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/ai/lib/connector.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/couchbase/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/couchbase/lib/connector.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/couchbase/lib/connector.v3.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/couchbase/lib/connector.v4.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/couchbase/lib/n1ql.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/couchbase/lib/session-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/couchbase/lib/session-store.v3.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/couchbase/lib/session-store.v4.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/couchbase/lib/storage-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/duckdb/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/duckdb/lib/connector.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/mongodb/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/mongodb/lib/connector.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/mongodb/lib/job-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/mongodb/lib/pipeline-loader.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/mongodb/lib/session-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/mysql/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/mysql/lib/connector.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/param-redact.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/postgresql/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/postgresql/lib/connector.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/redis/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/redis/lib/connector.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/redis/lib/job-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/redis/lib/kv-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/redis/lib/render-cache-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/redis/lib/session-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/scylladb/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/scylladb/lib/connector.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/scylladb/lib/session-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/sql-parser.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/sqlite/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/sqlite/lib/connector.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/sqlite/lib/job-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/sqlite/lib/kv-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/connectors/sqlite/lib/session-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/content.encoding +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/controller/controller.framework.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/controller/controller.render-json.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/controller/controller.render-nunjucks-async.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/controller/controller.render-nunjucks.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/controller/controller.render-stream.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/controller/controller.render-swig-async.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/controller/controller.render-swig.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/controller/controller.render-v1.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/controller/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/controller/inspector-window-emit.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/controller/release-banner.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/dev/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/dev/lib/class.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/dev/lib/factory.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/dev/lib/tools.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/locales/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/locales/currency.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/locales/dist/language/en.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/locales/dist/language/fr.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/locales/dist/region/en.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/locales/dist/region/fr.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/locales/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/mime.types +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/model/entity.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/model/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/model/template/entityFactory.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/model/template/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/csrf/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/csrf/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/csrf/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/coep/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/coep/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/coep/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/coop/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/coop/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/coop/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/corp/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/corp/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/corp/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/csp/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/csp/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/csp/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/hide-powered-by/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/hide-powered-by/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/hide-powered-by/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/hsts/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/hsts/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/hsts/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/origin-agent-cluster/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/origin-agent-cluster/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/origin-agent-cluster/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/referrer-policy/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/referrer-policy/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/referrer-policy/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-content-type-options/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-content-type-options/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-content-type-options/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-dns-prefetch-control/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-dns-prefetch-control/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-dns-prefetch-control/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-download-options/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-download-options/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-download-options/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-frame-options/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-frame-options/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-frame-options/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-xss-protection/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-xss-protection/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/security-headers/x-xss-protection/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/session/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/session/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/session/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/storage/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/storage/build.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/storage/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/storage/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/validator/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/validator/build.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/validator/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/validator/src/form-validator.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/plugins/lib/validator/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/router.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/server.express.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/server.isaac.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/status.codes +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/_gitignore +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/config/app.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/config/connectors.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/config/routing.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/config/settings.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/config/settings.server.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/config/templates.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/config/watchers.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/controllers/controller.content.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/controllers/controller.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/controllers/setup.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle/locales/en.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_namespace/controllers/controller.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_public/css/default.css +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_public/css/home.css +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_public/css/vendor/readme.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_public/favicon.ico +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_public/js/components/x-checklist.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_public/js/vendor/readme.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_public/manifest.webmanifest +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_public/readme.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_public/sw.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_templates/handlers/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_templates/html/content/homepage.html +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_templates/html/includes/error-msg-noscript.html +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_templates/html/includes/error-msg-outdated-browser.html +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_templates/html/includes/x-checklist.html +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/boilerplate/bundle_templates/html/layouts/main.html +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/command/gina.bat.tpl +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/command/gina.tpl +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/conf/env.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/conf/manifest.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/conf/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/conf/settings.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/conf/statics.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/conf/templates.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/error/client/json/401.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/error/client/json/403.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/error/client/json/404.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/error/server/html/50x.html +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/error/server/json/500.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/error/server/json/503.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/core/template/extensions/logger/config.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/console.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/context.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/data/LICENSE +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/data/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/data/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/data/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/dateFormat.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/json/LICENSE +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/json/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/json/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/json/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/path.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/plugins/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/plugins/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/plugins/src/api-error.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/plugins/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/prototypes.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/task.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/helpers/text.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/admin/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/admin/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/archiver/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/archiver/build.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/archiver/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/archiver/src/dep/jszip.min.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/archiver/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/async/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/async/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/audit/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/audit/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/audit-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/authn/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/authn/src/lockout.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/authn/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/authn/src/totp.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/authz-gate/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/authz-gate/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cache/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cache/build.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cache/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cache/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/aliases.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/audit/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/audit/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/audit/verify.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/add.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/build.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/copy.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/cp.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/inc/name-rewrite.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/list.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/man.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/mcp-start.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/mcp.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/oas.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/openapi.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/remove.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/rename.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/restart.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/rm.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/start.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/status.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/stop.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/bundle/types.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/cache/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/cache/clear.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/cache/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/cache/stats.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/connector/add.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/connector/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/connector/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/connector/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/connector/infer.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/connector/list.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/connector/migrate.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/connector/models.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/connector/remove.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/connector/rm.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/connector/test.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/container/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/container/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/container/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/container/man.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/container/ps.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/container/stop.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/controller/add.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/controller/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/controller/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/controller/inc/args.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/controller/inc/namespace.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/controller/inc/reference-rewrite.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/controller/inc/reference-scan.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/controller/inc/scaffold.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/controller/remove.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/controller/rename.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/controller/rm.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/env/add.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/env/get.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/env/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/env/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/env/link-dev.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/env/list.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/env/remove.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/env/rm.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/env/set.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/env/unset.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/env/use.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/add.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/build.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/dot.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/get.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/init.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/link-node-modules.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/link.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/list.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/man.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/msg.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/open.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/remove.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/reset.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/restart.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/set.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/start.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/status.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/stop.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/tail.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/update.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/framework/version.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/gina-dev.1.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/gina-framework.1.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/gina.1.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/helper.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/i18n/add.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/i18n/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/i18n/export.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/i18n/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/i18n/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/i18n/import.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/i18n/scan.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/image/_host.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/image/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/image/build.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/image/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/image/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/image/list.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/image/man.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/image/rm.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/image/run.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/inspector/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/inspector/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/inspector/open.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/man-render.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/minion/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/minion/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/minion/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/minion/kill.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/minion/list.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/msg.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/port/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/port/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/port/inc/scan.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/port/list.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/port/reset.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/port/set.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/add.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/backup.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/build.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/import.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/list.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/man.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/move.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/remove.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/rename.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/restart.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/restore.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/rm.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/start.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/status.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/project/stop.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/protocol/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/protocol/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/protocol/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/protocol/list.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/protocol/remove.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/protocol/set.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/scope/add.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/scope/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/scope/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/scope/link-local.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/scope/link-production.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/scope/list.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/scope/remove.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/scope/rm.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/scope/use.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/secrets/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/secrets/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/secrets/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/service/help.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/service/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/service/list.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/service/man.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/service/start.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/storage/arguments.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/storage/gc.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/storage/help.txt +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/storage/stats.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/storage/verify.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd/view/add.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd-status-format/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cmd-status-format/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/collection/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/collection/build.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/collection/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/config.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/connector-config/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/connector-config/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/connector-error/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/connector-error/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/connector-registry/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/connector-registry/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cron/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cron/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/cron/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/domain/LICENSE +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/domain/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/domain/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/domain/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/dto/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/dto/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/dto-pipe/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/dto-pipe/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/dto-types/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/dto-types/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/generator/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/i18n/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/i18n/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/image-build/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/image-build/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/inherits/LICENSE +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/inherits/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/inherits/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/inherits/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/inspector-events/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/inspector-events/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/inspector-redact/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/inspector-redact/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/instrument/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/instrument/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/job/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/job/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/job-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/json-config-header/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/json-config-header/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/kv/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/kv/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/kv-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/loading-state/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/loading-state/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/logger/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/logger/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/logger/src/containers/default/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/logger/src/containers/file/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/logger/src/containers/file/lib/logrotator/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/logger/src/containers/file/lib/logrotator/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/logger/src/containers/mq/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/logger/src/containers/mq/listener.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/logger/src/containers/mq/speaker.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/logger/src/helper.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/logger/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/maintenance/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/maintenance/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/math/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/mcp-dispatch/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/mcp-dispatch/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/mcp-http/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/mcp-http/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/mcp-server/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/mcp-server/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/merge/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/merge/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/merge/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/metrics/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/metrics/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/model.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/net-locality/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/net-locality/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/nunjucks-filters/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/nunjucks-filters/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/nunjucks-filters/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/nunjucks-resolver/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/nunjucks-resolver/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/proc.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/push/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/push/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/rate-limit/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/rate-limit/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/release-watch/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/release-watch/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/render-cache/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/render-cache/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/render-cache-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/routing/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/routing/build.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/routing/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/routing/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/routing/src/radix.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/routing-introspect/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/routing-introspect/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/secrets/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/secrets/src/env-file.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/session-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/shell.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/sqlite-driver.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/state.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/storage/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/storage/src/local-cas.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/storage/src/local-stream.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/storage/src/local.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/storage/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/storage/src/meta-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/storage/src/s3.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/storage/src/util.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/storage-store.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/swig-filters/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/swig-filters/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/swig-filters/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/swig-resolver/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/swig-resolver/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/template-loaders/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/template-loaders/src/loaders/http.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/template-loaders/src/loaders/memory.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/template-loaders/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/url/README.md +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/url/index.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/url/routing.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/uuid/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/uuid/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/validator.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/watcher/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/watcher/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/ws-framing/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/ws-framing/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/ws-query/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/ws-query/src/main.js +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/ws-session/package.json +0 -0
- /package/framework/{v0.6.13 → v0.6.14}/lib/ws-session/src/main.js +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* @module lib/secrets/sources
|
|
4
|
+
* @description The config-source walk: which config files exist for a
|
|
5
|
+
* project's bundles, and which `${secret:KEY}` placeholders they require.
|
|
6
|
+
* Shared so that every consumer walks the SAME source set.
|
|
7
|
+
*
|
|
8
|
+
* Consumers today are the `gina secrets:scan` and `gina secrets:check` CLI
|
|
9
|
+
* handlers; the walk is also the enumeration a boot-time consumer (e.g. a
|
|
10
|
+
* prefetching secrets backend) would need. Before this module existed, the
|
|
11
|
+
* two CLI handlers each carried a hand-kept copy of the walk — and two
|
|
12
|
+
* hand-kept copies of "which sources does a bundle read" drifted once
|
|
13
|
+
* already (#B263: the gate and the runtime consulted different source
|
|
14
|
+
* sets, producing a false verdict on the exact command that exists to
|
|
15
|
+
* prevent surprises). One implementation makes that class of disagreement
|
|
16
|
+
* structurally impossible rather than merely unlikely.
|
|
17
|
+
*
|
|
18
|
+
* Config sources walked, matching `core/config.js::loadBundleConfig`:
|
|
19
|
+
* - `<bundleSrc>/config/` per bundle, where `bundleSrc` comes from
|
|
20
|
+
* `manifest.bundles[<name>].src` (falling back to the bundle name).
|
|
21
|
+
* - the project-level `shared/config/`, which the loader merges into
|
|
22
|
+
* every bundle — so its keys are attributed to each bundle.
|
|
23
|
+
* Every `.json` in those dirs is read (the loader globs the dir rather
|
|
24
|
+
* than using a fixed whitelist); dotfiles and `* copy` files are skipped.
|
|
25
|
+
*
|
|
26
|
+
* With a `scope`, the sibling `config_<scope>/` dir of each config dir
|
|
27
|
+
* (e.g. `shared/config_production/`) is read-only overlaid on top of the
|
|
28
|
+
* base via deep-merge (scope wins) — mirroring how a deploy applies its
|
|
29
|
+
* per-scope config — so the report shows the *effective* keys that scope's
|
|
30
|
+
* deploy will require. This is pure introspection: it does NOT change the
|
|
31
|
+
* runtime config loader, which stays scope-agnostic (the deploy owns
|
|
32
|
+
* per-scope selection).
|
|
33
|
+
*
|
|
34
|
+
* Caveat: this reports the *authored* placeholders on disk, not the merged
|
|
35
|
+
* runtime config — correct today because placeholders are always authored
|
|
36
|
+
* literals.
|
|
37
|
+
*
|
|
38
|
+
* Shape: a FACTORY taking the key-enumeration primitive. `main.js` (which
|
|
39
|
+
* owns `getRequiredKeys`) instantiates it and spreads the result into the
|
|
40
|
+
* `lib.secrets` surface — the factory form exists so this file never
|
|
41
|
+
* requires `./main` back (no load cycle) and never touches the `lib`
|
|
42
|
+
* registry (so a bare `require` of `lib/secrets` stays dependency-free).
|
|
43
|
+
*
|
|
44
|
+
* Framework global context: the walk resolves `_()` (path normalisation)
|
|
45
|
+
* and `requireJSON` (comment-tolerant JSON read) at CALL time — they are
|
|
46
|
+
* the gna.js / helpers-injected globals every runtime and CLI scope
|
|
47
|
+
* carries. Module LOAD touches neither, so requiring `lib/secrets` in a
|
|
48
|
+
* bare test process needs no global setup; only *calling* the walk does
|
|
49
|
+
* (a test installs them by requiring the framework `helpers/` tree).
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
var fs = require('fs');
|
|
53
|
+
// Deep merge for the scope overlay. Required relatively (server-side only;
|
|
54
|
+
// this module is not in the browser bundle) — and its node export path also
|
|
55
|
+
// installs `JSON.clone` when absent (#M22), which the overlay clone needs.
|
|
56
|
+
var merge = require('../../merge');
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Builds the config-source walk over the supplied key-enumeration
|
|
60
|
+
* primitive.
|
|
61
|
+
*
|
|
62
|
+
* @function module:lib/secrets/sources
|
|
63
|
+
* @param {function} getRequiredKeys - `lib/secrets`'s own `getRequiredKeys(config)` — read-only, non-throwing, backend-free
|
|
64
|
+
* @returns {{getProjectRequiredKeys: function, loadManifest: function, readJsonSafe: function, resolveBundleSrc: function}}
|
|
65
|
+
*/
|
|
66
|
+
module.exports = function sourcesFactory(getRequiredKeys) {
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Config files are JSON. The loader globs every `.json` in a config
|
|
70
|
+
* dir; this matches that, then drops dotfiles and `* copy` siblings.
|
|
71
|
+
*
|
|
72
|
+
* @inner
|
|
73
|
+
* @constant
|
|
74
|
+
* @type {RegExp}
|
|
75
|
+
*/
|
|
76
|
+
var JSON_EXT = /\.json$/;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Reads a JSON file with comment tolerance via `requireJSON`. Returns
|
|
80
|
+
* `null` when the file is absent or unreadable so callers can choose
|
|
81
|
+
* how to surface the failure. A file that exists but does not PARSE
|
|
82
|
+
* follows `requireJSON`'s own loud-failure contract instead: in a full
|
|
83
|
+
* framework context (where `console.emerg` exists) it emerg-logs and
|
|
84
|
+
* exits the process, so the catch below never sees it — stated here
|
|
85
|
+
* because the previous in-handler copies of this helper documented
|
|
86
|
+
* "null on any parse error", which that contract makes unreachable.
|
|
87
|
+
*
|
|
88
|
+
* Note `requireJSON` caches by path (require-cache semantics), so the
|
|
89
|
+
* returned object is SHARED — callers that mutate or merge it must
|
|
90
|
+
* clone first (the scope overlay below does).
|
|
91
|
+
*
|
|
92
|
+
* @memberof module:lib/secrets/sources
|
|
93
|
+
* @function readJsonSafe
|
|
94
|
+
* @param {string} filePath - Absolute path
|
|
95
|
+
* @returns {object|null}
|
|
96
|
+
* @example
|
|
97
|
+
* var pkg = readJsonSafe('/tmp/some-project/manifest.json');
|
|
98
|
+
* if (pkg === null) { console.warn('unreadable or absent'); }
|
|
99
|
+
*/
|
|
100
|
+
var readJsonSafe = function (filePath) {
|
|
101
|
+
try {
|
|
102
|
+
if ( !fs.existsSync(filePath) ) return null;
|
|
103
|
+
return requireJSON(filePath);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Loads `<projectPath>/manifest.json`. Returns `null` on failure.
|
|
111
|
+
*
|
|
112
|
+
* @memberof module:lib/secrets/sources
|
|
113
|
+
* @function loadManifest
|
|
114
|
+
* @param {string} projectPath - Absolute project root
|
|
115
|
+
* @returns {object|null}
|
|
116
|
+
* @example
|
|
117
|
+
* var manifest = loadManifest('/tmp/some-project');
|
|
118
|
+
* var names = manifest ? Object.keys(manifest.bundles) : [];
|
|
119
|
+
*/
|
|
120
|
+
var loadManifest = function (projectPath) {
|
|
121
|
+
return readJsonSafe(_(projectPath + '/manifest.json', true));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Resolves a bundle's source-dir (relative to the project root) from
|
|
126
|
+
* the manifest, mirroring `bundle:openapi`. Falls back to the bundle
|
|
127
|
+
* name when `src` is absent.
|
|
128
|
+
*
|
|
129
|
+
* @memberof module:lib/secrets/sources
|
|
130
|
+
* @function resolveBundleSrc
|
|
131
|
+
* @param {object|null} manifest
|
|
132
|
+
* @param {string} bundleName
|
|
133
|
+
* @returns {string}
|
|
134
|
+
* @example
|
|
135
|
+
* resolveBundleSrc({ bundles: { demo: { src: 'src/demo' } } }, 'demo'); // 'src/demo'
|
|
136
|
+
* resolveBundleSrc(null, 'demo'); // 'demo'
|
|
137
|
+
*/
|
|
138
|
+
var resolveBundleSrc = function (manifest, bundleName) {
|
|
139
|
+
if ( manifest && manifest.bundles && manifest.bundles[bundleName] && manifest.bundles[bundleName].src ) {
|
|
140
|
+
return manifest.bundles[bundleName].src;
|
|
141
|
+
}
|
|
142
|
+
return bundleName;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Lists the `.json` files in `dir`, skipping dotfiles and `* copy`
|
|
147
|
+
* siblings — the same filtering `loadBundleConfig` applies. Returns a
|
|
148
|
+
* sorted list of bare filenames. Empty when the dir is absent.
|
|
149
|
+
*
|
|
150
|
+
* @inner
|
|
151
|
+
* @private
|
|
152
|
+
* @param {string} dir - Absolute config directory
|
|
153
|
+
* @returns {string[]}
|
|
154
|
+
*/
|
|
155
|
+
var listJsonFiles = function (dir) {
|
|
156
|
+
if ( !fs.existsSync(dir) ) return [];
|
|
157
|
+
var entries;
|
|
158
|
+
try { entries = fs.readdirSync(dir); } catch (e) { return []; }
|
|
159
|
+
var out = [];
|
|
160
|
+
for (var i = 0; i < entries.length; i++) {
|
|
161
|
+
var name = entries[i];
|
|
162
|
+
if ( /^\./.test(name) ) continue;
|
|
163
|
+
if ( /\s+copy/i.test(name) ) continue;
|
|
164
|
+
if ( !JSON_EXT.test(name) ) continue;
|
|
165
|
+
out.push(name);
|
|
166
|
+
}
|
|
167
|
+
out.sort();
|
|
168
|
+
return out;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Reads every `.json` under `absDir`, enumerates its required secret
|
|
173
|
+
* keys via the injected `getRequiredKeys`, and records each key
|
|
174
|
+
* against its originating file (labelled `relBase + '/' + filename`).
|
|
175
|
+
* Mutates `byKey` in place.
|
|
176
|
+
*
|
|
177
|
+
* When `scopeName` is set, the sibling `<absDir>_<scope>/` directory
|
|
178
|
+
* (e.g. `shared/config_production/`) is read-only overlaid on top of
|
|
179
|
+
* the base dir per config file, mirroring how a deploy applies its
|
|
180
|
+
* per-scope config: the scope file deep-merges over the base (scope
|
|
181
|
+
* wins on collisions, base back-fills) so the keys reported are the
|
|
182
|
+
* *effective* ones that scope's deploy will require. The scope file is
|
|
183
|
+
* JSON.clone'd before merge so cached content is not mutated. This is
|
|
184
|
+
* read-only introspection — it never touches the runtime config
|
|
185
|
+
* loader.
|
|
186
|
+
*
|
|
187
|
+
* @inner
|
|
188
|
+
* @private
|
|
189
|
+
* @param {string} absDir - Absolute base config directory to read
|
|
190
|
+
* @param {string} relBase - Display prefix for file labels (e.g. `src/demo/config`)
|
|
191
|
+
* @param {Object<string, string[]>} byKey - Mutable KEY -> [files] map
|
|
192
|
+
* @param {string|null} scopeName - Scope overlay name, or `null`
|
|
193
|
+
*/
|
|
194
|
+
var collectFromConfigDir = function (absDir, relBase, byKey, scopeName) {
|
|
195
|
+
var scopeAbsDir = scopeName ? (absDir + '_' + scopeName) : null;
|
|
196
|
+
var scopeRelBase = scopeName ? (relBase + '_' + scopeName) : null;
|
|
197
|
+
|
|
198
|
+
var baseNames = listJsonFiles(absDir);
|
|
199
|
+
var scopeNames = scopeAbsDir ? listJsonFiles(scopeAbsDir) : [];
|
|
200
|
+
|
|
201
|
+
// union of config file names across base + scope-overlay dirs
|
|
202
|
+
var names = baseNames.slice();
|
|
203
|
+
for (var s = 0; s < scopeNames.length; s++) {
|
|
204
|
+
if (names.indexOf(scopeNames[s]) < 0) names.push(scopeNames[s]);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (var f = 0; f < names.length; f++) {
|
|
208
|
+
var name = names[f];
|
|
209
|
+
var baseContent = (baseNames.indexOf(name) > -1) ? readJsonSafe(_(absDir + '/' + name, true)) : null;
|
|
210
|
+
var scopeContent = (scopeAbsDir && scopeNames.indexOf(name) > -1) ? readJsonSafe(_(scopeAbsDir + '/' + name, true)) : null;
|
|
211
|
+
|
|
212
|
+
// effective config for this scope: scope deep-merges over base (scope wins).
|
|
213
|
+
// override=false is explicit (not merge's default) so scope precedence stays
|
|
214
|
+
// correct even if lib/merge's default override ever changes.
|
|
215
|
+
var effective = scopeContent ? merge(JSON.clone(scopeContent), baseContent || {}, false) : baseContent;
|
|
216
|
+
if (!effective) continue;
|
|
217
|
+
|
|
218
|
+
var keys = getRequiredKeys(effective);
|
|
219
|
+
var scopeKeys = scopeContent ? getRequiredKeys(scopeContent) : [];
|
|
220
|
+
for (var k = 0; k < keys.length; k++) {
|
|
221
|
+
// attribute the key to the layer that actually provides it in the effective config
|
|
222
|
+
var label = (scopeKeys.indexOf(keys[k]) > -1) ? (scopeRelBase + '/' + name) : (relBase + '/' + name);
|
|
223
|
+
if (!byKey[keys[k]]) byKey[keys[k]] = [];
|
|
224
|
+
if (byKey[keys[k]].indexOf(label) < 0) byKey[keys[k]].push(label);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Computes the shared-config KEY -> [files] map once per project. The
|
|
231
|
+
* loader merges `shared/config/` into every bundle, so these keys are
|
|
232
|
+
* attributed to each bundle.
|
|
233
|
+
*
|
|
234
|
+
* @inner
|
|
235
|
+
* @private
|
|
236
|
+
* @param {string} projectPath
|
|
237
|
+
* @param {string|null} scopeName
|
|
238
|
+
* @returns {Object<string, string[]>}
|
|
239
|
+
*/
|
|
240
|
+
var computeSharedByKey = function (projectPath, scopeName) {
|
|
241
|
+
var byKey = Object.create(null);
|
|
242
|
+
collectFromConfigDir(_(projectPath + '/shared/config', true), 'shared/config', byKey, scopeName);
|
|
243
|
+
return byKey;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Enumerates the `${secret:KEY}` placeholders a project's bundles
|
|
248
|
+
* require, walking the same config sources `loadBundleConfig` reads
|
|
249
|
+
* (shared/config folded into every bundle; per-bundle `config/`; the
|
|
250
|
+
* optional `config_<scope>/` overlay). Read-only and non-throwing —
|
|
251
|
+
* the introspection family contract `getRequiredKeys` set.
|
|
252
|
+
*
|
|
253
|
+
* Provenance is always on: each key maps to the project-relative
|
|
254
|
+
* config file(s) that require it, insertion-ordered and de-duplicated
|
|
255
|
+
* (shared labels first, then the bundle's own).
|
|
256
|
+
*
|
|
257
|
+
* Semantics, precisely:
|
|
258
|
+
* - a non-string or empty `projectPath` returns `null`.
|
|
259
|
+
* - with `options.bundle`, the filter is honoured VERBATIM: that one
|
|
260
|
+
* bundle is walked even when the manifest is missing or does not
|
|
261
|
+
* list it (its src falls back to the bundle name; absent dirs
|
|
262
|
+
* simply contribute nothing) — matching the CLI, which validates
|
|
263
|
+
* membership itself before calling and, historically, walked a
|
|
264
|
+
* manifest-less project on an explicit bundle name.
|
|
265
|
+
* - without `options.bundle`, the manifest IS the bundle list: a
|
|
266
|
+
* missing/unreadable manifest, or one without a `bundles` object,
|
|
267
|
+
* returns `null` (callers decide how to surface it).
|
|
268
|
+
*
|
|
269
|
+
* @memberof module:lib/secrets/sources
|
|
270
|
+
* @function getProjectRequiredKeys
|
|
271
|
+
* @param {string} projectPath - Absolute project root
|
|
272
|
+
* @param {object} [options]
|
|
273
|
+
* @param {string|null} [options.scope=null] - Scope overlay name (`config_<scope>/` siblings)
|
|
274
|
+
* @param {string|null} [options.bundle=null] - Restrict the walk to one bundle
|
|
275
|
+
* @returns {{bundles: Array<{bundle: string, byKey: Object<string, string[]>}>}|null}
|
|
276
|
+
* Bundles sorted by name; `byKey` is a null-proto KEY -> [labels] map
|
|
277
|
+
* @example
|
|
278
|
+
* // all bundles, production overlay:
|
|
279
|
+
* var r = getProjectRequiredKeys('/tmp/some-project', { scope: 'production' });
|
|
280
|
+
* // -> { bundles: [ { bundle: 'demo', byKey: { DB_PASSWORD: ['shared/config/app.json'] } } ] }
|
|
281
|
+
* @example
|
|
282
|
+
* // one bundle, no overlay — null projectPath and missing manifests are not thrown:
|
|
283
|
+
* getProjectRequiredKeys('', {}); // null
|
|
284
|
+
* getProjectRequiredKeys('/tmp/some-project', { bundle: 'demo' }); // walks only demo
|
|
285
|
+
*/
|
|
286
|
+
var getProjectRequiredKeys = function (projectPath, options) {
|
|
287
|
+
if (typeof projectPath !== 'string' || projectPath === '') {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
var opts = (options && typeof options === 'object') ? options : {};
|
|
291
|
+
var scopeName = opts.scope || null;
|
|
292
|
+
var manifest = loadManifest(projectPath);
|
|
293
|
+
|
|
294
|
+
var names;
|
|
295
|
+
if (opts.bundle) {
|
|
296
|
+
names = [opts.bundle];
|
|
297
|
+
} else {
|
|
298
|
+
if (!manifest || !manifest.bundles || typeof manifest.bundles !== 'object') {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
names = Object.keys(manifest.bundles).sort();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
var sharedByKey = computeSharedByKey(projectPath, scopeName);
|
|
305
|
+
var bundles = [];
|
|
306
|
+
for (var i = 0; i < names.length; i++) {
|
|
307
|
+
var byKey = Object.create(null);
|
|
308
|
+
for (var sk in sharedByKey) {
|
|
309
|
+
byKey[sk] = sharedByKey[sk].slice();
|
|
310
|
+
}
|
|
311
|
+
var bundleSrc = resolveBundleSrc(manifest, names[i]);
|
|
312
|
+
var rel = bundleSrc + '/config';
|
|
313
|
+
collectFromConfigDir(_(projectPath + '/' + rel, true), rel, byKey, scopeName);
|
|
314
|
+
bundles.push({ bundle: names[i], byKey: byKey });
|
|
315
|
+
}
|
|
316
|
+
return { bundles: bundles };
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
getProjectRequiredKeys : getProjectRequiredKeys,
|
|
321
|
+
loadManifest : loadManifest,
|
|
322
|
+
readJsonSafe : readJsonSafe,
|
|
323
|
+
resolveBundleSrc : resolveBundleSrc
|
|
324
|
+
};
|
|
325
|
+
};
|
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.14/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.14/core/controller');
|
|
22
|
+
var EntitySuper = require('./framework/v0.6.14/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.14/lib/uuid');
|
|
26
26
|
|
|
27
27
|
module.exports = {
|
|
28
28
|
|
package/llms.txt
CHANGED
|
@@ -135,7 +135,7 @@ module.exports = ApiContentController;
|
|
|
135
135
|
- **No built-in API versioning, and no DB schema/data migration tool — both are deliberately the application's to own** (microservices audit #MS8-adjacent, docs 2026-08-21). There is no `version` route field and no version-aware dispatch: version with a URL prefix + a per-version `namespace` (each loads its own `controller.<ns>.js`), or with separate bundles when versions need their own deps/config/cadence. ⚠️ **`gina connector:migrate` is NOT a data migration** — it lints `connectors.json` (connection CONFIG) and `--fix` rewrites that JSON; it issues no SQL and has no `up`/`down`. Use your database's own tool (Flyway/Liquibase/Sqitch/Knex/`node-pg-migrate`), and run it as a discrete pre-start step (CI job, k8s `Job`/init container), never at bundle boot — a boot-time migration races itself across replicas and turns a schema error into a crash-loop. SQL files are validated at CALL time, not boot, so make changes additive during a rolling deploy.
|
|
136
136
|
- Always null `local.req/res/next` at response exit (done automatically in render path).
|
|
137
137
|
- `createTestInstance(deps)` creates an isolated instance for unit tests without touching production state.
|
|
138
|
-
- `async` controller actions are fully supported — the router attaches `.catch()` to any thenable returned by an action and routes rejections to `throwError(response, 500, ...)
|
|
138
|
+
- `async` controller actions are fully supported — the router attaches `.catch()` to any thenable returned by an action and routes rejections to `throwError(response, 500, ...)`; `self.query()` owns an `async` callback's rejection the same way at every delivery seam (callback form and `{onComplete}` facade, both transports — a rejected callback promise answers 500 instead of hanging; #B399). Use `await entity.method()` directly. For PathObject ops and Shell use `await onCompleteCall(_(path).mkdir())`.
|
|
139
139
|
- `self.setEarlyHints(links)` — send a 103 Early Hints informational response. Call before the terminal method. `links` is a string or array of `Link` header values. HTTP/2: `stream.additionalHeaders({ ':status': 103 })`; HTTP/1.1: `res.writeEarlyHints()` (Node.js 18.11+). Silent no-op when unsupported. Returns `self`. **Also automatic**: `render()` auto-sends 103 for the bundle's CSS/JS preloads (from `h2Links`) before Swig compilation in HTTP/2 production mode — zero config required.
|
|
140
140
|
- `self.renderStream(asyncIterable, contentType)` — stream an `AsyncIterable` as a chunked HTTP response without buffering. `contentType` defaults to `text/event-stream` (SSE). Each yielded string/Buffer becomes `data: {chunk}\n\n` for SSE; raw for other content types. HTTP/2: `stream.respond()` + `stream.write()` + `stream.end()`. HTTP/1.1: automatic chunked transfer-encoding. `x-accel-buffering: no` set automatically for SSE. Fire-and-forget — do not `await`. Required for LLM token streaming via `ai.client` with `stream: true`. **Honours a caller-set `response.statusCode` on BOTH engines (#B351, 0.6.8)** — the h2 frame carries `response.statusCode || 200` and the h1 arm assigns `response.statusCode || 200`, so a controller may pre-set 206/416/404 before calling. Until 0.6.8 both arms were 200-only (the h2 frame hardcoded the pseudo-header literal, the h1 arm clobbered a pre-set code), which made `206 Partial Content` unreachable through renderStream — the reason it blocked Range serving. Same class as #B172: **a hand-built h2 frame must never carry a literal `':status'`**, because `setHeader(':status', …)` throws and no later header merge can repair a pseudo-header; the pending-header merge deliberately does not overwrite `:status`.
|
|
141
141
|
|
|
@@ -812,7 +812,7 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
812
812
|
|
|
813
813
|
80. **CSRF — three-phase protection (`#CSRF1` Session plugin / `#CSRF2` signed double-submit token / `#CSRF3` Origin pre-filter)** — Session plugin hardens `express-session` cookie defaults from `settings.json > session.cookie.{sameSite, httpOnly, secure}` (opt-in, one-line bundle bootstrap swap, never transparent because bundles legitimately set `httpOnly: false`); signed token middleware uses `crypto.timingSafeEqual` HMAC-SHA256 bound to `req.session.id` with per-route `routing.json > "csrfExempt": true` opt-out (see `routing-and-http2.md § "Per-route flags"` for `req.routing.csrfExempt` two-step propagation: `lib/routing` extracts → `core/server.js` hoists, top-level on `req.routing` not under `param.*`); Origin pre-filter folded INSIDE `gina.plugins.Csrf()` BEFORE token verify on mutating methods, allowlist via `settings.json > csrf.allowedOrigins` (empty defaults to `[bundleHostname]` auto-derived). Negative-invariant lock: matching token + mismatching Origin still 403s — token layer ≠ Origin layer. Implementation traps from the trilogy: plan-vs-shipped attribute drift (downstream commits must `grep -n '<attribute>' <upstream-source-file>` before referencing — source on develop wins, not the plan); CI flake-vs-regression triage (same-message-different-SHA pairs need `git diff --stat A..B` first); source-inspection `indexOf` matches the function DEFINITION before the call site (use unique assignment LHS like `requestOrigin = parseRequestOrigin(req)`); `Origin: "null"` is a real browser value (sandboxed iframes / `file://`) and needs an explicit `s === 'null'` guard so the parser falls through to Referer instead of 403'ing for "origin not allowed".
|
|
814
814
|
|
|
815
|
-
98. **Secrets — `${secret:KEY}` config placeholders + the `secrets:scan`/`secrets:check` introspection CLI (absorbs former #135).** `lib/secrets` substitutes `${secret:KEY}` at config-load (`core/config.js::loadBundleConfig`, post-merge per bundle), resolving each key from the FRAMEWORK environment first (`getEnvVar`) and falling back to `process.env[KEY]`. **That two-tier read is load-bearing, not defensive (0.6.3, #B156):** the CLI MOVES every `GINA_*`/`USER_*`/`VENDOR_*` key OUT of `process.env` into `process.gina` (`filterArgs`, `utils/helper.js`), so a `${secret:GINA_*}` placeholder was previously unresolvable in ANY CLI-loaded config — `mcp.json` (the published example), the connector commands, `audit:verify` — and failed closed with a refusing boot. Non-`GINA_` keys are untouched by the sweep and resolve as before. The same idiom now backs the CSRF `GINA_CSRF_SECRET` tier and the MCP transport's `GINA_BIND_HOST`/`GINA_DIR` reads (#B157). ANCHORED: the regex matches only when the ENTIRE string value is the placeholder — mixed strings (`"prefix-${secret:K}-suffix"`) pass through unchanged; non-string scalars untouched; nested objects/arrays descend. **Fail-closed:** an unset/empty env var throws a GENERIC `Secret resolution failed` — the key name rides only a non-enumerable `_ginaSecretKey`, surfaced at debug level by the config-load catch naming key + `<bundle>/<env>:<scope>` (#B42), so consumer-visible surfaces never carry key names. Secret rotation needs a process restart (the resolver re-runs on `config.refresh()`, but the env is inherited from container init). Pluggable backend interface (`{resolve(key)}` — env-only by default, env-over-file once `secrets.file` is declared). **The two tiers are read INDEPENDENTLY, never joined by `||` (0.6.9, #B270):** `filterArgs` stores swept values as REAL booleans, so a truthy boolean used to satisfy the short-circuit and `process.env` was never consulted — the file tier then beat a SET environment variable, inverting the precedence the file backend exists to enforce. Only a non-empty STRING from the framework tier wins; a boolean, number, `undefined` or `''` falls through. `getResolvedPaths()` gives the dotted paths substituted in a config, but it is **not** a general redaction substrate (0.6.9, #B274): the paths address the config object and the lookup is keyed on its IDENTITY, so a consumer must hold the very object `resolve()` mutated — which rules out the Inspector, whose payload is a structural clone of the controller's VIEW data (`{gina, user}`), a different object in a different coordinate space. A surface holding VALUES rather than the config needs a value-based pass; `core/connectors/param-redact.js` exists for exactly that reason. **`secrets.file` shape guards (0.6.9):** `[]` still disables the tier like `null` but now WARNS rather than doing it silently (#B271 — an operator emptying the array to drop one layer drops the whole tier; boot is not refused, since an empty list genuinely means "no files"); a whitespace-only entry is REFUSED (`minLength: 1` counts a space, so `[" "]` used to build a tier that could never resolve anything); and a path containing an empty segment (`//`) is refused (#B272 — such a path does not name the file it appears to, since POSIX reads `<a>//<b>` as `<a>/<b>`; the DANGEROUS cause is a `${…}` token that resolved to EMPTY, leaving no token for the unresolved-token guard and silently reading the file one directory UP — `${homedir}/${scope}/secrets.env` with an empty scope — while the BENIGN cause is a token with a trailing slash such as an operator's `GINA_HOMEDIR=/opt/gina/`, which nothing in the path chain normalises. The two are indistinguishable once assembled, so boot refuses on both and the error names both causes rather than asserting one it cannot know; the benign case is a one-character fix). The schema gains `minItems: 1` as an editor-facing hint — no runtime validator loads `schema/settings.json`, so `main.js` is the enforcement. **FILE SYNTAX — one parser (`lib/secrets/src/env-file.js::parseEnv`) serves BOTH readers (`secrets:check` + the file backend), so the gate cannot green-light a file the runtime reads differently.** It targets what a POSIX shell `source`s, since the documented entrypoint (`set -a; . secrets.env; set +a`) is a THIRD reader the module cannot unify by construction. **#B269 (0.6.4) — a trailing `#` comment is now stripped** (previously swallowed into the value): the strip fires only when the `#` is preceded by whitespace AND outside quotes, because `abc#def` is a literal hash to a shell and `"abc # def"` is a quoted one — so quoting is a COMPLETE escape hatch, and the strip runs on the RAW pre-trim value (trimming first makes `KEY= # c` and `KEY=#c` indistinguishable while the shell reads them as `''` vs `'#c'`). Unquoting applies AFTER the strip, so `KEY="abc" # note` is `abc`, not `"abc"` — previously the comment defeated the `/^".*"$/` test and the quotes became part of the credential. Consequence to expect: `KEY= # comment` is now EMPTY, and empty counts as unset in both readers (`typeof v === 'string' && v !== ''`), so it fails closed and `secrets:check` reports UNSET where it previously reported a FALSE-GREEN SET. Two divergences REMAIN by decision — `KEY = value` (accepted+trimmed here, a shell errors) and `KEY="a"b"` (`a"b` here, `ab` in a shell) — so do NOT restate the parser as "whatever a POSIX shell does". **`getRequiredKeys(config)`** is the read-only, non-throwing sibling backing the OFFLINE CLI: `gina secrets:scan [<bundle>] [@project]` (which keys each bundle requires, grouped by file — sources derived from the manifest's `src`, never a guessed path) + `gina secrets:check` (marks SET/UNSET, exits non-zero — the CI gate), with `--scope=<s>` overlaying `config_<s>/` dirs READ-ONLY (the runtime loader stays scope-agnostic about config DIRS) and `--env-file` standing in for the ENV TIER. **#B263 (0.6.4) — `check` now reads BOTH runtime tiers, in order:** env, then the bundle's declared `secrets.file` chain resolved through the SAME global `whisper` + the shared `parseEnvFile`, so it can no longer report UNSET (and exit non-zero) for a key the bundle would have booted with — that false RED, not a false green, was the dominant real-world defect, since adding a source can only turn UNSET→SET. Parity is BY CONSTRUCTION: `homedir` comes from the project's own `env.json` (`<bundle>.<env>`) else the template's `~/.<projectName>`, NEVER `projects.json`'s `homedir` (a CLI-level field `config.js` never reads). A reps key is seeded only from a NON-EMPTY string — an absent key leaves its token verbatim and is caught loudly, but an EMPTY one substitutes SILENTLY (measured: `scope:''` → `/h//f.env`, which collapses to the base path on POSIX with no guard firing; whisper's pass 2 `dict[k]||s` leaves it, pass 3 `!==undefined` rescues it). `${scope}`/`${env}` are LAUNCH-time values the CLI cannot read (runtime prefers `NODE_SCOPE`/`NODE_ENV`), so the report NAMES the values it assumed, `--env` was added to pick the env block, and an unresolvable path is reported + its tier skipped rather than statted — under-reading can only make the gate stricter than the runtime, never laxer. **#B266 (`74e52cda`) — the reps dict must carry every token a REAL chain uses, not the ones the examples use.** The first consumer config (`${homedir}/v${projectVersionMajor}/credentials/[${scope}/]secrets.env`) resolved at runtime but NOT in `check`, re-opening the #B263 false-RED through three holes: (a) **version tokens absent** — the runtime does not list them in the bundle-config dict either, it gets them because `core/template/conf/env.json` declares `projectVersion`/`projectVersionMajor` as per-bundle SCALARS which the `:2356` harvest then copies in, so **reading only the `:2296` dict literal shows no version tokens and looks complete**; now seeded from the manifest; (b) **`${scope}` had no default** — now falls back to the project default via a SEPARATE `self.scopeAssumed` slot, because defaulting `self.scopeName` would start applying the `config_<scope>/` overlay to every bare invocation (two axes, one name); (c) **`~/.undefined`** — `buildReps` used `self.projectName`, undefined on the all-projects branch, yielding a real stat-able wrong path; now derived from the project PATH. Declaration resolution is pinned (`5a80b1a1`): bundle `secrets.file` REPLACES shared's (arrays never concatenate), `secrets:{}` INHERITS, `secrets:{file:null}` is the opt-out, and `--env-file` stays TOP tier (it stands in for the environment; demoting it below the files would model something the runtime never does). Each key's report names the tier that satisfied it. ⚠️ The other `secrets.resolve()` callers pass NO backend and stay env-only (`bundle:mcp-start`, `connector:infer`/`test`/`models` — #B264). Design of record: the framework RESOLVES secrets, the deployment layer STORES them. Since 0.6.4 it may additionally READ declared plaintext files as the LOWEST-precedence tier — opt-in `settings.secrets.file` in a bundle's `settings.json`, one path or an array written with the usual config tokens (`["${homedir}/secrets.env", "${homedir}/${scope}/secrets.env"]`), later entries winning. Files layer UNDER the environment (`getEnvVar` → `process.env` → files → throw), which INVERTS the usual `.env` intuition and is deliberate: every production delivery path (K8s `secretRef`, ECS task secrets, `sops exec-env`, a CI export) arrives via the environment, so a file that won could let a stale plaintext copy shadow the real credential. ⚠️ Precisely, it is a NON-EMPTY env value that wins (#B268, `90518bec`): a SET-but-EMPTY variable counts as ABSENT and the file fills it — deliberate and kept, because `environment: ["X=${X}"]` with the outer var unset puts an empty-but-SET var in the container (measured) and refusing would break working setups — but since a failed `export X="$(fetch …)"` produces that same shape, the fall-through WARNS, naming the key and never the value, at `warn` so the default `info` hierarchy keeps it. Undeclared ⇒ byte-identical to env-only; a declared-but-ABSENT file contributes nothing, but one that EXISTS and cannot be READ is FATAL (#B267, `90518bec`) — collapsing both to `null` let a `[base, per-scope]` chain whose per-scope file lost read permission boot silently on the SHARED credential (measured: `production_password` → `shared_dev_password`), announced only by a debug line MISLABELLED `ABSENT` which `info` suppresses; `env-file` now also exports `readEnvFile` (reports the errno — ENOENT vs EACCES/EISDIR; a dangling symlink is ENOENT, correctly absent), the backend refuses on anything non-ENOENT naming path + code, and `secrets:check` reports `UNREADABLE` so the CI gate and the runtime cannot disagree about a file neither can open; resolution stays fail-closed; paths carrying an unresolved `${…}` or a `${secret:…}` are rejected at boot. Selection reads `content.settings`, NOT `settings` — historically the only copy with tokens resolved, because `config.js` bound `.settings` BEFORE the token-substitution pass and `.content` after it, and substitution returns a NEW object rather than rewriting the original. **Since #B257 (0.6.7) the `.settings` alias is re-pointed at the post-substitution copy, so for a config built by `loadBundleConfig` the two are now the SAME object** and either read would serve; `content.settings` stays the canonical one because `selectBackend` accepts any config-shaped object, including ones assembled by other paths. ⚠️ The old rationale was never accurate AS STATED (#B273) and must not be restated: an EARLIER substitution pass already resolved `${homedir}` / `${scope}` in both copies, so what actually diverged was only the tokens pass 2 is the FIRST to know — `${bundlePath}`, `${libPath}`, `${publicPath}`, `${handlersPath}`, `${mountPath}`, `${gina}`, `${project}`, `${root}`, `${source}`, `${<name>Port}`, `${templates}`/`${html}`/`${theme}` and the `:2356` scalar harvest. `${secret:…}` was never affected in either copy, since `secrets.resolve()` walks the config IN PLACE and so reaches both subtrees. Consumer-visible consequence of the fix: a `getConfig().settings.<x>` read that used to hand back a literal `'${libPath}/…'` now returns the resolved path.It does NOT decrypt: for SOPS/Vault/KMS keep decrypting at the container entrypoint — a structural constraint, not a preference, since `lib/secrets` is fully synchronous and `config.js` holds the #P33 sync-init contract, so a network-backed backend would put a KMS round-trip inside a synchronous boot path. (The scope-agnostic note above is about `config_<s>/` DIRS, which the runtime still does not overlay; a secrets FILE path may be scope-varying via `${scope}`.) Consumers: `Csrf` (`settings.csrf.secret`), `bundle:mcp-start` (re-resolves `mcp.json` post-parse), `auth.machine` caller keys.
|
|
815
|
+
98. **Secrets — `${secret:KEY}` config placeholders + the `secrets:scan`/`secrets:check` introspection CLI (absorbs former #135).** `lib/secrets` substitutes `${secret:KEY}` at config-load (`core/config.js::loadBundleConfig`, post-merge per bundle), resolving each key from the FRAMEWORK environment first (`getEnvVar`) and falling back to `process.env[KEY]`. **That two-tier read is load-bearing, not defensive (0.6.3, #B156):** the CLI MOVES every `GINA_*`/`USER_*`/`VENDOR_*` key OUT of `process.env` into `process.gina` (`filterArgs`, `utils/helper.js`), so a `${secret:GINA_*}` placeholder was previously unresolvable in ANY CLI-loaded config — `mcp.json` (the published example), the connector commands, `audit:verify` — and failed closed with a refusing boot. Non-`GINA_` keys are untouched by the sweep and resolve as before. The same idiom now backs the CSRF `GINA_CSRF_SECRET` tier and the MCP transport's `GINA_BIND_HOST`/`GINA_DIR` reads (#B157). ANCHORED: the regex matches only when the ENTIRE string value is the placeholder — mixed strings (`"prefix-${secret:K}-suffix"`) pass through unchanged; non-string scalars untouched; nested objects/arrays descend. **Fail-closed:** an unset/empty env var throws a GENERIC `Secret resolution failed` — the key name rides only a non-enumerable `_ginaSecretKey`, surfaced at debug level by the config-load catch naming key + `<bundle>/<env>:<scope>` (#B42), so consumer-visible surfaces never carry key names. Secret rotation needs a process restart (the resolver re-runs on `config.refresh()`, but the env is inherited from container init). Pluggable backend interface (`{resolve(key)}` — env-only by default, env-over-file once `secrets.file` is declared, env-over-exec once `secrets.exec` is declared; the two declared tiers are mutually exclusive by validation). **The two tiers are read INDEPENDENTLY, never joined by `||` (0.6.9, #B270):** `filterArgs` stores swept values as REAL booleans, so a truthy boolean used to satisfy the short-circuit and `process.env` was never consulted — the file tier then beat a SET environment variable, inverting the precedence the file backend exists to enforce. Only a non-empty STRING from the framework tier wins; a boolean, number, `undefined` or `''` falls through. `getResolvedPaths()` gives the dotted paths substituted in a config, but it is **not** a general redaction substrate (0.6.9, #B274): the paths address the config object and the lookup is keyed on its IDENTITY, so a consumer must hold the very object `resolve()` mutated — which rules out the Inspector, whose payload is a structural clone of the controller's VIEW data (`{gina, user}`), a different object in a different coordinate space. A surface holding VALUES rather than the config needs a value-based pass; `core/connectors/param-redact.js` exists for exactly that reason. **`secrets.file` shape guards (0.6.9):** `[]` still disables the tier like `null` but now WARNS rather than doing it silently (#B271 — an operator emptying the array to drop one layer drops the whole tier; boot is not refused, since an empty list genuinely means "no files"); a whitespace-only entry is REFUSED (`minLength: 1` counts a space, so `[" "]` used to build a tier that could never resolve anything); and a path containing an empty segment (`//`) is refused (#B272 — such a path does not name the file it appears to, since POSIX reads `<a>//<b>` as `<a>/<b>`; the DANGEROUS cause is a `${…}` token that resolved to EMPTY, leaving no token for the unresolved-token guard and silently reading the file one directory UP — `${homedir}/${scope}/secrets.env` with an empty scope — while the BENIGN cause is a token with a trailing slash such as an operator's `GINA_HOMEDIR=/opt/gina/`, which nothing in the path chain normalises. The two are indistinguishable once assembled, so boot refuses on both and the error names both causes rather than asserting one it cannot know; the benign case is a one-character fix). The schema gains `minItems: 1` as an editor-facing hint — no runtime validator loads `schema/settings.json`, so `lib/secrets` is the enforcement. **#B408 (0.6.14) — those declaration guards are ONE shared implementation for the runtime AND the gate:** `selectBackend` and `secrets:check` both consume `secrets.validateFilePaths` (`lib/secrets/src/declaration.js` — pure and require-free, first-error-wins in entry order, with the `${secret:…}` verdict outranking the generic unresolved-token one because the token regex matches a placeholder too), because the checker's hand-kept mirror had drifted twice in the LAX direction: the #B271 trim and #B272 `//` guards never reached it, so `settings.secrets.file: [" "]` and a token-collapsed `//` path passed `gina secrets:check` GREEN while boot REFUSED — the exact checker-vs-runtime disagreement the command exists to prevent, and the second #B263-class drift after the source walk. The gate now also validates BEFORE reading any layer, as boot does (an invalid declaration reports with NO layers read), and keeps its CLI-only `--scope`/`--env` hint by APPENDING to the shared message on the `file-unresolved-token` error code, never by forking the text. **FILE SYNTAX — one parser (`lib/secrets/src/env-file.js::parseEnv`) serves BOTH readers (`secrets:check` + the file backend), so the gate cannot green-light a file the runtime reads differently.** It targets what a POSIX shell `source`s, since the documented entrypoint (`set -a; . secrets.env; set +a`) is a THIRD reader the module cannot unify by construction. **#B269 (0.6.4) — a trailing `#` comment is now stripped** (previously swallowed into the value): the strip fires only when the `#` is preceded by whitespace AND outside quotes, because `abc#def` is a literal hash to a shell and `"abc # def"` is a quoted one — so quoting is a COMPLETE escape hatch, and the strip runs on the RAW pre-trim value (trimming first makes `KEY= # c` and `KEY=#c` indistinguishable while the shell reads them as `''` vs `'#c'`). Unquoting applies AFTER the strip, so `KEY="abc" # note` is `abc`, not `"abc"` — previously the comment defeated the `/^".*"$/` test and the quotes became part of the credential. Consequence to expect: `KEY= # comment` is now EMPTY, and empty counts as unset in both readers (`typeof v === 'string' && v !== ''`), so it fails closed and `secrets:check` reports UNSET where it previously reported a FALSE-GREEN SET. Two divergences REMAIN by decision — `KEY = value` (accepted+trimmed here, a shell errors) and `KEY="a"b"` (`a"b` here, `ab` in a shell) — so do NOT restate the parser as "whatever a POSIX shell does". **`getRequiredKeys(config)`** is the read-only, non-throwing sibling backing the OFFLINE CLI: `gina secrets:scan [<bundle>] [@project]` (which keys each bundle requires, grouped by file — sources derived from the manifest's `src`, never a guessed path) + `gina secrets:check` (marks SET/UNSET, exits non-zero — the CI gate), with `--scope=<s>` overlaying `config_<s>/` dirs READ-ONLY (the runtime loader stays scope-agnostic about config DIRS) and `--env-file` standing in for the ENV TIER. **The config-dir walk behind BOTH commands is ONE implementation in `lib/secrets` (`getProjectRequiredKeys(projectPath, {scope, bundle})` → `{bundles: [{bundle, byKey}]}`, provenance labels always on; the `loadManifest`/`readJsonSafe`/`resolveBundleSrc` leaves ride along for the handlers' own single-file reads):** the two handlers previously each carried a byte-near-identical copy of the walk — copies of exactly the kind #B263's drift came from — and the shared walk is also the enumeration a boot-time prefetch consumer would need. Non-throwing family: `null` for a bad `projectPath` or a missing manifest with no bundle filter; an explicit `bundle` filter is honoured VERBATIM (walks even a manifest-less project, src falling back to the bundle name — the historical CLI shape). It resolves `_()`/`requireJSON` at CALL time only, so module load needs no framework globals; and a config file that exists but does not PARSE follows requireJSON's own loud contract (emerg + exit while `console.emerg` exists) rather than the null-skip the old in-handler helper JSDoc claimed. **#B263 (0.6.4) — `check` now reads BOTH runtime tiers, in order:** env, then the bundle's declared `secrets.file` chain resolved through the SAME global `whisper` + the shared `parseEnvFile`, so it can no longer report UNSET (and exit non-zero) for a key the bundle would have booted with — that false RED, not a false green, was the dominant real-world defect, since adding a source can only turn UNSET→SET. Parity is BY CONSTRUCTION: `homedir` comes from the project's own `env.json` (`<bundle>.<env>`) else the template's `~/.<projectName>`, NEVER `projects.json`'s `homedir` (a CLI-level field `config.js` never reads). A reps key is seeded only from a NON-EMPTY string — an absent key leaves its token verbatim and is caught loudly, but an EMPTY one substitutes SILENTLY (measured: `scope:''` → `/h//f.env`, which collapses to the base path on POSIX with no guard firing; whisper's pass 2 `dict[k]||s` leaves it, pass 3 `!==undefined` rescues it). `${scope}`/`${env}` are LAUNCH-time values the CLI cannot read (runtime prefers `NODE_SCOPE`/`NODE_ENV`), so the report NAMES the values it assumed, `--env` was added to pick the env block, and an unresolvable path is reported + its tier skipped rather than statted — under-reading can only make the gate stricter than the runtime, never laxer. **#B266 (`74e52cda`) — the reps dict must carry every token a REAL chain uses, not the ones the examples use.** The first consumer config (`${homedir}/v${projectVersionMajor}/credentials/[${scope}/]secrets.env`) resolved at runtime but NOT in `check`, re-opening the #B263 false-RED through three holes: (a) **version tokens absent** — the runtime does not list them in the bundle-config dict either, it gets them because `core/template/conf/env.json` declares `projectVersion`/`projectVersionMajor` as per-bundle SCALARS which the `:2356` harvest then copies in, so **reading only the `:2296` dict literal shows no version tokens and looks complete**; now seeded from the manifest; (b) **`${scope}` had no default** — now falls back to the project default via a SEPARATE `self.scopeAssumed` slot, because defaulting `self.scopeName` would start applying the `config_<scope>/` overlay to every bare invocation (two axes, one name); (c) **`~/.undefined`** — `buildReps` used `self.projectName`, undefined on the all-projects branch, yielding a real stat-able wrong path; now derived from the project PATH. Declaration resolution is pinned (`5a80b1a1`): bundle `secrets.file` REPLACES shared's (arrays never concatenate), `secrets:{}` INHERITS, `secrets:{file:null}` is the opt-out, and `--env-file` stays TOP tier (it stands in for the environment; demoting it below the files would model something the runtime never does). Each key's report names the tier that satisfied it. ⚠️ The other `secrets.resolve()` callers pass NO backend and stay env-only (`bundle:mcp-start`, `connector:infer`/`test`/`models` — #B264). Design of record: the framework RESOLVES secrets, the deployment layer STORES them. Since 0.6.4 it may additionally READ declared plaintext files as the LOWEST-precedence tier — opt-in `settings.secrets.file` in a bundle's `settings.json`, one path or an array written with the usual config tokens (`["${homedir}/secrets.env", "${homedir}/${scope}/secrets.env"]`), later entries winning. Files layer UNDER the environment (`getEnvVar` → `process.env` → files → throw), which INVERTS the usual `.env` intuition and is deliberate: every production delivery path (K8s `secretRef`, ECS task secrets, `sops exec-env`, a CI export) arrives via the environment, so a file that won could let a stale plaintext copy shadow the real credential. ⚠️ Precisely, it is a NON-EMPTY env value that wins (#B268, `90518bec`): a SET-but-EMPTY variable counts as ABSENT and the file fills it — deliberate and kept, because `environment: ["X=${X}"]` with the outer var unset puts an empty-but-SET var in the container (measured) and refusing would break working setups — but since a failed `export X="$(fetch …)"` produces that same shape, the fall-through WARNS, naming the key and never the value, at `warn` so the default `info` hierarchy keeps it. Undeclared ⇒ byte-identical to env-only; a declared-but-ABSENT file contributes nothing, but one that EXISTS and cannot be READ is FATAL (#B267, `90518bec`) — collapsing both to `null` let a `[base, per-scope]` chain whose per-scope file lost read permission boot silently on the SHARED credential (measured: `production_password` → `shared_dev_password`), announced only by a debug line MISLABELLED `ABSENT` which `info` suppresses; `env-file` now also exports `readEnvFile` (reports the errno — ENOENT vs EACCES/EISDIR; a dangling symlink is ENOENT, correctly absent), the backend refuses on anything non-ENOENT naming path + code, and `secrets:check` reports `UNREADABLE` so the CI gate and the runtime cannot disagree about a file neither can open; resolution stays fail-closed; paths carrying an unresolved `${…}` or a `${secret:…}` are rejected at boot. Selection reads `content.settings`, NOT `settings` — historically the only copy with tokens resolved, because `config.js` bound `.settings` BEFORE the token-substitution pass and `.content` after it, and substitution returns a NEW object rather than rewriting the original. **Since #B257 (0.6.7) the `.settings` alias is re-pointed at the post-substitution copy, so for a config built by `loadBundleConfig` the two are now the SAME object** and either read would serve; `content.settings` stays the canonical one because `selectBackend` accepts any config-shaped object, including ones assembled by other paths. ⚠️ The old rationale was never accurate AS STATED (#B273) and must not be restated: an EARLIER substitution pass already resolved `${homedir}` / `${scope}` in both copies, so what actually diverged was only the tokens pass 2 is the FIRST to know — `${bundlePath}`, `${libPath}`, `${publicPath}`, `${handlersPath}`, `${mountPath}`, `${gina}`, `${project}`, `${root}`, `${source}`, `${<name>Port}`, `${templates}`/`${html}`/`${theme}` and the `:2356` scalar harvest. `${secret:…}` was never affected in either copy, since `secrets.resolve()` walks the config IN PLACE and so reaches both subtrees. Consumer-visible consequence of the fix: a `getConfig().settings.<x>` read that used to hand back a literal `'${libPath}/…'` now returns the resolved path.The FILE tier does NOT decrypt — pointed at ciphertext it yields ciphertext, silently. **Since 0.6.14, `settings.secrets.exec` is the sanctioned in-process bridge for SOPS/Vault/K8s (#SECRETS3):** ONE operator-declared command — argv array, NO shell — run once per bundle per config-load cycle at boot only, whose stdout must be a single flat JSON object of string values (`sops decrypt --output-type json` on a flat secrets file emits it directly, measured end-to-end; `vault`/`kubectl` need a 2-3 line wrapper script — see the guide's recipes), layered UNDER the environment exactly like the file tier and MUTUALLY EXCLUSIVE with it: a bundle inheriting a shared `file` chain escapes with `"file": null` beside its own `exec` block (the refusal message names it), `"exec": null` symmetrically; unknown `secrets` keys and non-object blocks now REFUSE the boot instead of silently degrading to env-only (runtime parity with the schema's `additionalProperties: false`). The boot STAYS synchronous (#P33 intact): the fetch is `spawnSync` bounded by `timeout` (default 10000ms, 2× the house probe class) and killed with **SIGKILL** on expiry — measured-mandatory, not caution: the default SIGTERM leaves `spawnSync` BLOCKED past its timeout against a signal-ignoring child and then reports `{error: ETIMEDOUT, status: 0}`, which is also why the result check reads `.error` BEFORE `.status` (a status-first read calls that shape a success). So a wedged KMS becomes a FAILED boot, never a hung one — every fetch failure (timeout, missing binary, non-zero exit with stderr tail quoted, malformed output) is the per-bundle boot refusal through `config.js`'s existing catch. stdout is the secrets payload and NEVER appears in any error, log or report (a non-string value's key rides the debug channel only); auth travels via environment variables, never argv (`ps`-visible), and the child inherits `process.env` (the #B156 sweep means `GINA_*` / `VENDOR_*` / `USER_*` keys are invisible to it in CLI/daemon processes). `secrets:check` mirrors the tier by RUNNING the declared command through the runtime's own `fetchExecMap` (same timeout, same SIGKILL bound, same output contract — a validate-only mirror would false-RED every exec-supplied key, the #B263 shape), and **#B409 (0.6.14)**: declaration/fetch errors now fail the gate's EXIT CODE too — both exit sites widened to `anyUnset || anyError`; previously a boot-refusing declaration exited 0 whenever the environment happened to carry the keys. **The entrypoint decrypt remains the BETTER pattern wherever the entrypoint can be controlled** (`sops exec-env`, K8s `envFrom` secretRef, Vault agent-injector — values land in the environment, which every tier already defers to): `exec` supports that pattern for environments that cannot, it does not replace it. (The scope-agnostic note above is about `config_<s>/` DIRS, which the runtime still does not overlay; a secrets FILE path may be scope-varying via `${scope}`.) Consumers: `Csrf` (`settings.csrf.secret`), `bundle:mcp-start` (re-resolves `mcp.json` post-parse), `auth.machine` caller keys.
|
|
816
816
|
|
|
817
817
|
125. **CLI handler authoring & operations — consolidated** (replaces individual entries #17, #23, #24, #55, #56, #58, #59, #60, #62, #63, #102, #112; plus #161, #166, #167, #181, #182, #183, #185, #199, #201, #202, #203 folded 2026-07-05):
|
|
818
818
|
- **CLI stubs** — `gina --status` / `gina -t` appear in help.txt or docs but have no handler (not in `aliases.json`); tracked in `ROADMAP.md § CLI`; never suggest to users without checking the handler file first. `bundle:status` / `project:status` / `minion:list` / `minion:kill` / `protocol:remove` / `bundle:copy` (+ `cp` alias) / `bundle:rename` shipped 0.4.1-alpha.2; `project:move` / `project:backup` / `project:restore` / `framework:update` / `framework:man` (+ `project:man` / `bundle:man` / `service:man`) — the **CLI Tier 3** finals — shipped 0.5.x (full coverage in the two CLI Tier 3 sub-bullets below). Both minion commands are run-dir-driven process-truth (the "minion" abstraction is half-wired — nothing sets `process.isMinion` or writes `*minion*.pid`, so a minion == any running bundle child-process): `minion:list` lists every live `<bundle>@<project>.pid` grouped by project via `lib.cmdStatusFormat`; `minion:kill @<project>` reaps them (hybrid kill-set = run-dir pidfiles + a `ps -ef | grep 'gina: ...@<project>'` sweep for pidfile-less orphans bundle:stop misses), SIGTERM→grace→SIGKILL escalation, `--dry-run` preview, unlinks stale/killed pidfiles, never touches mount symlinks or its own PID. `protocol:remove <bundle> @<project>` reverts a bundle to the project default protocol by deleting ONLY its `server.protocol/scheme/allowHTTP1` override from the bundle's `settings.json` (config.js `:1014/:1020` auto-defaults an absent protocol to `def_protocol`/`def_scheme`); it deliberately does NOT mutate the shared `ports*.json` — `project:add` pre-allocates the full protocol×scheme×env matrix, so the default-protocol port already exists and pruning the set's port would be wrong; a per-env port-presence guard refuses (unless `--force`) when the default-protocol port is missing; `--dry-run` preview, header-preserving JSON rewrite (connector:rm pattern). `bundle:copy <source> <new> @<project>` (+ `cp`) duplicates a bundle under a new name in the SAME project: copies the `src/<source>` tree, then word-boundary-rewrites the name footprint (PascalCase `<Src>`→`<Dst>` for controller class names + lowercase whole-word `<src>`→`<new>` for the gina require-var / `app.json` name / webroot path, `.js`/`.json` files only — embedded tokens like `apiClient` are untouched; a first-bundle webroot `/` is repointed to `/<new>`), allocates a fresh FULL protocol×scheme×env port matrix via the shared `setPorts` (a single-port insert would later emerg in `config.js`, which expects the complete matrix), and clones+repoints the source's manifest entry (`src`/`link`/`releases` target paths). `--dry-run` previews every rewrite site before writing; `--force` overwrites an existing target (its `removeDest` mirrors `bundle:remove`'s deletions). TWO positionals leave `self.name` null (CmdHelper sets it only for a single positional), so the handler reads `self.bundles[0]`/`[1]` directly — which also slips past the `cmd.name`-gated existence guard so the not-yet-registered new name isn't rejected. `bundle:rename <old> <new> @<project>` is the move-sibling — it renames a bundle IN PLACE in the same project (`fs.renameSync` move, NOT a copy) reusing the same `inc/name-rewrite.js` engine but with `fixWebroot:false` (rename moves the only bundle, so there's no first-bundle/collision case; a name-derived `/<old>` webroot is still rewritten by the lowercase pass). Its ports are REKEYED, not reallocated — port NUMBERS are preserved: `ports.json` rewrites the `<old>@<project>/` owner prefix back into the SAME `[protocol][scheme][portKey]` slot (avoiding the two `project/rename.js` bugs: the wrong `[protocol][portKey]` slot, and a project-wide owner replace), then `ports.reverse.json` is rekeyed (`pr[new]=pr[old]; delete pr[old]`) and flipped LAST as the canonical existence record. It REFUSES a running bundle with NO `--force` bypass (`--force` only overwrites an existing dest); the whole multi-surface mutation (symlink → renameSync dir → rewrite tree → env → manifest → ports → ports.reverse) is snapshot-guarded (the `bundle:add` rollback model) so any post-move failure reverses the dir move and restores env/manifest/ports/ports.reverse from in-memory snapshots. NOTE: the ROADMAP's "fix the help.txt remouve typo" item was stale — no such typo existed.
|
|
@@ -899,7 +899,7 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
899
899
|
|
|
900
900
|
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). **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` (+ 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.
|
|
901
901
|
|
|
902
|
-
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). 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`.
|
|
902
|
+
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). 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.
|
|
903
903
|
|
|
904
904
|
212. **Structured (JSON) logging — `GINA_LOG_FORMAT=json` + per-request `requestId`/`durationMs` (consolidates former #152/#155; #M12a/#M12b).** The logger resolves its render format ONCE at init into `opt.format` (precedence `GINA_LOG_FORMAT=json|text` > `GINA_LOG_STDOUT` truthy ⇒ json > `text` default) BEFORE containers are cloned; a JSON line is `{ts, level, bundle, message, group, msg}` — `bundle`/`message` canonical, `group`/`msg` retained as additive back-compat aliases — and the raw `console.log` path honours the format too (otherwise JSON mode would interleave plain lines and break a collector); the `text` default keeps container logs byte-identical. Per-request `requestId` + `durationMs` ride JSON logs via a NARROW `AsyncLocalStorage` (`process.gina._reqALS`, parked on `process.gina` so it survives dev require-cache busting), gated on JSON logging ONLY (text renders no id field, so the ALS would be pure overhead — the text path stays byte-identical/zero-cost): the id resolver honours a SANITISED inbound `X-Request-Id` (`/^[\w.\-]{1,128}$/`, regenerate-on-violation kills log forging) else `crypto.randomUUID()`; the `.run({requestId, startMs})` wrap sits at `handle()` — NOT the request-entry handler — because the `request.on('end')` boundary between them loses async context while `handle()`'s awaits preserve it (the original body became `_handleDispatch`; `handle` is a thin wrapper); HTTP/2 gets per-stream scoping free via Node's per-stream compat `'request'` event, NOT `session.on('stream')`; the JSON-assembly sites read `getStore()` and add `requestId` + per-line `durationMs`, gracefully absent for CLI/boot/off-request logs. `.run()`, never `enterWith()` (enterWith bleeds sideways across siblings). **MS1 (2026-07-24, 0.5.25):** the same id is now PROPAGATED beyond the process — every `self.query()` outbound path (the file-download proxy + both the HTTP/1 and HTTP/2 inter-bundle clients) forwards it as `x-request-id` (sourced from the resolved `req._ginaReqId`, never a raw inbound header; a caller-set value always wins), and `server.js onRequest` ECHOES `X-Request-Id` back on every response — ungated / independent of `GINA_LOG_FORMAT` (the id is always-on even when the JSON-log field isn't) and guarded against an already-sent response — so one logical request stays correlatable as it fans out across bundles and a caller/LB/APM reads the id off the wire. `controller.js`/`server.js` are server-side → no dist rebuild; restart to apply. Tests: `test/core/request-id-propagation.test.js`. **The contextless `require('gina')` boundary + MQ-speaker transport resilience (moved here from the DTO entry that surfaced it; #B276/#B277 0.6.4, #B318 0.6.5, #B323 fixed post-0.6.5 — shipped 0.6.6 with the reconnect).** Importing gina outside a spawned bundle child has ALWAYS been an intended boundary (`index.mjs`'s own docblock states it; both published entries throw alike), but two defects sat on top of it. **(1) The throw was uncatchable and could HANG the process.** The logger is a load-time singleton whose default flows are `['default','mq']`, so `speaker.js` opens a TCP socket BEFORE the boot reaches its throw — and that socket kept the event loop alive (listener-CONTINGENT, measured both ways: nothing bound to the MQ port ⇒ ECONNREFUSED ⇒ exit 0, so the bug is invisible on a quiet machine and hangs with no output on one running bundles or in CI). Fixed by `client.unref()`: a logging transport must never be why a process stays alive. ⚠️ #B318: a THIRD state exists beyond connected/REFUSED — an UNREACHABLE host, where `unref()` covers the socket HANDLE but not the pending `TCPConnectWrap` REQUEST, which holds the loop by itself for the OS connect timeout (~75s on macOS); fixed with an unref'd connect deadline (2s default, `opt.mqConnectTimeout`) that `destroy(err)`s only while `client.connecting`. ⚠️ **That safety claim — "only while connecting" — is MEASURED FALSE and shipped a HIGH regression for one release (#B323):** `client.connecting` does NOT flip when the kernel completes the connect; it flips when the POLL phase runs `afterConnect` — and a timer fires in the TIMERS phase, which precedes poll. On any boot that blocks the event loop past the deadline (a bundle mount off a network filesystem — every Kubernetes-class start) the loop resumes, runs the OVERDUE deadline first, reads `connecting === true` on a connection the kernel established seconds ago, and destroys it; the bundle then logged NOWHERE for the rest of its life (the speaker dialled exactly once, ever). Measured both directions: a kernel-completed dial reads `connecting` TRUE at the timers phase and FALSE at the check phase; a black-holed one reads TRUE at both. The fix defers the verdict one phase — `setImmediate` INSIDE the deadline, so the read happens in the CHECK phase, after poll — a completed connect survives while a genuinely pending one still dies on schedule (#B318's no-hang contract intact). **Generalisable: a state flag written by an event-loop callback cannot be read from an EARLIER phase and treated as current** — `connecting`, `destroyed`, `readyState` and every `pending*` counter share the property, and a blocked loop is precisely what makes the stale read reachable; a timers-phase guard over I/O state wants a `setImmediate` (or the event itself), never a direct read. **The speaker also RECONNECTS (0.6.6):** `startMQSpeaker` returns a stable `{write}` transport FACADE (`mq/index.js` captures the return ONCE for the life of the process — without the indirection a replaced socket is unreachable); each dial builds its OWN `clientOptions` (the listener mints a fresh `sessionId` per connection and the handshake only fires while `clientOptions.sessionId` is unset — a shared object would make every reconnect skip its acknowledgement); `close` on the CURRENT socket (a `current !== client` guard keeps a superseded one from stacking a second connection) arms a capped unref'd backoff (`min(500 * 2^n, 30000)`, reset on connect). Unref'd is load-bearing BOTH ways: a short-lived CLI still exits while a long-lived bundle keeps retrying and HEALS if a listener appears later. The caller callback settles ONCE, the warn fires once per OUTAGE (not per retry), and frames are DROPPED while down — an unbounded queue behind an outage of unknown length is a memory leak, and the `default`/stdout flow carries the same lines regardless. ⚠️ The socket still CONNECTS — `unref` changes only whether it votes on process lifetime (consumer-verified against a live listener: the connected line still logs; "unref'd" is not "suppressed"). ⚠️ The workaround `GINA_LOG_STDOUT=true` does splice `mq` out of the flows but ALSO flips log format to JSON (measured) — a container logging-mode flag pressed into service as a don't-open-a-socket switch. **(2) The boundary's error now names the boundary** — the old message named an internal call (`setPath("gina.home", path): path cannot be empty`) because `getEnvVar` reads `process.gina` ONLY and is empty outside the CLI; it now routes the reader to `SuperController.createTestInstance()`, the supported way to exercise controller code without booting. ⛔ **Do NOT "fix" the boundary by extending the `getEnvVar → process.env` ladder to `gna.js`, nor by skipping the empty `setPath`** — REFUTED BY MEASUREMENT: satisfying that call merely defers the failure 8 lines to a bare-global `ReferenceError` (strictly less legible), and that `setPath` is the SOLE registration site for `gina.home` repo-wide (the SQLite connector and session store read it). Failing fast at the first detectable point is correct. Diagnostic instrument for the hang class: `process._getActiveRequests()` reports `TCPConnectWrap` while `_getActiveHandles()` shows only the unref'd Socket. Tests: `test/lib/logger-mq-speaker-resilience.test.js` (§01 deferral-ordering + reconnect-shape pins; §02 a child that blocks 3s right after the dial and must still deliver — red-first: pre-fix it lost the frame while the connection was ESTABLISHED, the defect's own signature; §03 a listener destroyed and rebound must be heard again; §04 the no-listener child still exits promptly) + `logger-mq-speaker-unref.test.js` (comment-stripped source pin — subtracting the fix left a bare existence pin GREEN against the commented-out line — + a behavioural arm with its own must-be-killed control).
|
|
905
905
|
|
|
@@ -1029,5 +1029,7 @@ Dev-mode query instrumentation captures every database query tied to the current
|
|
|
1029
1029
|
|
|
1030
1030
|
314. **General-purpose KV primitive (#KV1) — `gina.kv('<namespace>')`, strict-declared namespaces over pluggable per-namespace backends (`settings.json > kv`).** Declare namespaces under `kv.namespaces` (`{ "kv": { "default": "cache", "namespaces": { "cache": { "failMode": "open" }, "tokens": {} } } }`); `gina.kv(name)` THROWS on an unconfigured block or unknown name (the throw is what surfaces a typo — the `gina.storage()` stance), and the whole feature is dormant when the block is absent. Namespaces are process-global, read once at boot from the STARTING bundle's settings. The handle is promise-native with 13 ops: `get` / `set(k, v, {ttl})` / `del` / `has` / `ttl(k)` (null=miss, -1=no expiry, else remaining ms) / `expire(k, ttlMs)` / `setnx` (set-if-absent) / `consume` (atomic read-AND-delete — the one-shot-token op; the second reader gets null) / `incr` / `decr` (integer counters; `{ttl}` applies on CREATE only; non-integer values and safe-range overflow reject) / `delIfEquals` (compare-and-delete — the safe lock-release primitive; compose with `setnx` for lock recipes) / `clear()` / `getOrSet(k, {ttl}, loader)` (fetch-or-compute with per-process single-flight; no negative caching). Value model: JSON-serialized; `undefined` AND `null` are REFUSED on every write (a stored null would be indistinguishable from a miss — store a wrapper or use `del()`); TTLs are MILLISECONDS, positive integers only — `ttl: 0` refuses rather than meaning no-expiry. Per-namespace `failMode`: `closed` (default) rejects on backend errors; `open` degrades every op to its miss-shaped result with a warning (cache-like namespaces) — consulted on backend errors only, so it becomes operative with connector-backed stores. THREE backends ship, differing by SHARING SCOPE. **In-memory** per namespace (zero config; lazy expiry plus an unref'd sweep, `sweepInterval` ms, default 30000) — SINGLE-PROCESS, lost on restart. **SQLite** (`core/connectors/sqlite/lib/kv-store.js`, #KV1 slice 2) — zero npm deps via the `lib/sqlite-driver` seam (node:sqlite, `bun:sqlite` adapter under Bun), DURABLE across restarts and shared across PROCESSES on ONE HOST; namespaces sharing a file are isolated by a composite `(ns, key)` primary key rather than a prefix, `clear()` is one namespace-scoped DELETE, and every read-modify-write verb runs inside `BEGIN IMMEDIATE`/`COMMIT` — the synchronous driver makes a verb indivisible only WITHIN a process, which is precisely the guarantee that fails once a second process opens the file, i.e. the reason to pick this backend at all. **Redis** — shared across HOSTS. **Redis** (`core/connectors/redis/lib/kv-store.js`, #KV1 slice 1): point a namespace's `store` at a `connectors.json` entry and the `lib/kv-store` dispatcher resolves `core/connectors/<connector>/lib/kv-store.js`; naming a connector that has none still REFUSES the boot rather than degrading silently to memory (the others arrive demand-first, the audit-store shipping order). The redis backend keeps every guarantee server-side: `SET .. PX .. NX` for setnx, `GETDEL` for consume when the server has it (>= 6.2) with a **Lua fallback below that — never a GET+DEL pair**, which would let two concurrent readers both redeem; Lua also for compare-and-delete and for TTL-on-CREATE-only counters (`n === by` is a racy proxy for "was created", so existence is checked inside the script). Keys are prefixed `kv:<namespace>:` by default, so two namespaces may share one entry; cluster prefixes are deliberately NOT hash-tagged (every KV op is single-key, and tagging would pin a namespace to one slot — the opposite of the job store, whose MULTI/SUNION/MGET require the tag), so `clear()` scans each master node and deletes per key rather than assuming one slot. Connection policy (`commandTimeout`/`maxRetriesPerRequest`/`enableOfflineQueue`) is passed through from the entry and otherwise left at ioredis's defaults — deliberately NOT the render-cache's fail-fast tuning, because the right policy depends on the namespace's `failMode`. Deliberate boundary — NOT a redis client: no data structures (lists/hashes/sets/streams/pub-sub), no batch `mget`/`mset`, no key scanning (use `clear()` or track your own keys), no binary values (the storage layer owns bytes), no first-class distributed locks (compose `setnx` + `delIfEquals`; a lock API done right needs fencing/renewal policy no half-primitive should imply). `lib/kv` is framework-independent (config, connector stores and the warn sink are injected by gna.js at boot; `test/lib/kv-import-boundary.test.js` enforces the boundary) and plain-require'd (its namespace registry and sweep timers survive dev-mode refreshCore). Server-side only — NOT browser-bundled, so pickup is a bundle RESTART, no re-bake.
|
|
1031
1031
|
315. **Rate limiting (#MS6) — identified-caller quotas at the router band.** Opt-in via settings.json `server.rateLimit` (`enabled` strictly true, `namespace`, `keyField`, `limit`, `window`; folded into the runtime server block with env.json keys winning, resolved once at engine start onto the instance, so changes need a bundle restart; structurally invalid values on an enabled block refuse the boot). QUOTA semantics, not flood control: the gate runs AFTER route authorization (`authorizeRequest` is the principal resolver — the only writer of `req.machineCaller`, and session identity is its predicate) and BEFORE DTO validation, so the order is 401 -> 429 -> 422 and a throttled caller never receives a 422 field map. A request with NO resolvable principal is SKIPPED, never bucketed — anonymous flood control belongs to the edge proxy and the #H9 HTTP/2 rapid-reset guard, and IP-keying is dishonest behind a proxy (every socket.remoteAddress is the proxy's). Keys: `u:<session.user[keyField]>` for signed-in callers (the field is operator-named because session.user is app-owned shape; an absent/empty value means unidentified => skipped) and `m:<machineCaller.name>` for #MS3 machine callers; session wins, mirroring the authz gate; values over 180 chars are sha256-hexed under the kv 512-char key cap. Counters are FIXED WINDOWS over the #KV1 primitive — `incr(key, 1, {ttl: windowMs})`, TTL-on-create-only, race-free in all three backends — so choosing the namespace's backend chooses the quota's SCOPE: in-memory counts PER PROCESS (each replica grants the quota again; the resolver warns), redis/sqlite share across replicas/processes. For a redis-backed namespace set `enableOfflineQueue: false` + `commandTimeout` on the connectors entry (the render-cache L2 fail-fast trio; with ioredis defaults an outage QUEUES every gated request so neither failMode ever fires — the resolver warns about this too). Outage policy IS the namespace's `failMode`: `open` => incr degrades to null and the gate ALLOWS without headers (the explicit `count === null` check is load-bearing — `null <= limit` is true by coercion); `closed` => the rejection is owned and answers 503 + Retry-After (the caller is NOT over quota). Denials answer 429 via `controller.throwError` with Retry-After (from the store's own ttl — one extra read on the throttled path only) plus the draft-ietf-httpapi-ratelimit-headers-11 structured fields (`RateLimit: "<policy>";r=<remaining>[;t=<sec>]`, `RateLimit-Policy: "<policy>";q=<limit>;w=<windowSec>`); allowed limited requests carry both without `t` (reporting it honestly would cost a second read per request and the parameter is optional). Per-route overrides are TOP-LEVEL routing.json keys (never under `param` — the #CSRF2 trap), propagated by BOTH route builders (cold typeof-guarded — `false` is meaningful — and the warm cached-route opt-in copy): `"rateLimit": false` exempts the route; `{limit, window}` REPLACES the default for that route in its OWN bucket (`<principal>:<rule>`), partial objects inherit the missing key; shapes are linted at boot (a malformed value refuses the boot — the DTO registrar doctrine). Dormant => the dispatch band runs today's exact synchronous path with ZERO promises minted; armed, every terminal of the verdict promise is owned (allow -> band, deny/outage -> answered inside the gate) so a store failure can never strand a request. Implementation: `lib/rate-limit` (plain-required, router-bound), wired in `core/router.js` at both dispatch sites; server-side only, but the route-flag propagation lives in browser-bundled `lib/routing`, so the release carries a dist rebuild (dead code client-side). Tests: `test/core/rate-limit.test.js` (45 — source pins on every seam, resolver refusal/warn matrix, gate behaviour over a scripted namespace, 5 mutation controls fired red-first).
|
|
1032
|
-
316. **Reserved-action controller hooks (`onReady`, `setup`) — dispatch semantics + the #B399 ownership guard** — Before the main action, `core/router.js` dispatches the two reserved hooks at BOTH dispatch sites (with- and without-middleware), in order, synchronously, without awaiting: an app defines `this.onReady = function(req, res, next){...}` on the controller, and `setup` resolves through a framework-synthesized wrapper that requires the bundle's `controllers/setup.js` once per controller instance (the wrapper returns the app call's own result). Since #B399, both hook loops run INSIDE the dispatch try and each hook's return value is captured — a thenable gets a `.catch` routing to `throwError(response, 500, ...)` naming the failing hook, the same guard shape the main action dispatch carries. Consequences: a SYNC hook throw answers 500 (previously it escaped to process level — on the built-in engine that was uncaughtException → emerg + SIGTERM, killing the whole bundle; Express's layer-catch answered its own 500 — engine-asymmetric); an ASYNC hook rejection answers 500 when it lands before the response is released, and degrades to the #ERRREF pairing log line when the action already responded (`throwError` is `!headersSent`-guarded). Ordering caveat: hooks are dispatched in the same synchronous frame as the action (never awaited), so a rejecting async hook does NOT prevent the action from running — its side effects still execute even when the wire answers 500; hooks are not a gating mechanism (use route middleware, route authorization, or DTO validation to refuse a request before the action). Sync or absent hooks mint zero promises — dispatch timing is unchanged for every controller that does not return a thenable from a hook.
|
|
1032
|
+
316. **Reserved-action controller hooks (`onReady`, `setup`) — dispatch semantics + the #B399 ownership guard** — Before the main action, `core/router.js` dispatches the two reserved hooks at BOTH dispatch sites (with- and without-middleware), in order, synchronously, without awaiting: an app defines `this.onReady = function(req, res, next){...}` on the controller, and `setup` resolves through a framework-synthesized wrapper that requires the bundle's `controllers/setup.js` once per controller instance (the wrapper returns the app call's own result). Since #B399, both hook loops run INSIDE the dispatch try and each hook's return value is captured — a thenable gets a `.catch` routing to `throwError(response, 500, ...)` naming the failing hook, the same guard shape the main action dispatch carries. Consequences: a SYNC hook throw answers 500 (previously it escaped to process level — on the built-in engine that was uncaughtException → emerg + SIGTERM, killing the whole bundle; Express's layer-catch answered its own 500 — engine-asymmetric); an ASYNC hook rejection answers 500 when it lands before the response is released, and degrades to the #ERRREF pairing log line when the action already responded (`throwError` is `!headersSent`-guarded). Ordering caveat: hooks are dispatched in the same synchronous frame as the action (never awaited), so a rejecting async hook does NOT prevent the action from running — its side effects still execute even when the wire answers 500; hooks are not a gating mechanism (use route middleware, route authorization, or DTO validation to refuse a request before the action). Sync or absent hooks mint zero promises — dispatch timing is unchanged for every controller that does not return a thenable from a hook. **The QUERY-DELIVERY half of the same #B399 arc (shipped separately): every app-callback delivery in `Controller::query()`** — the Node-style callback form and the `{onComplete}` facade, both transports, success/status AND transport-error deliveries — routes an `async` callback's rejected promise through one constructor-scope `_ownAsyncCbRejection` helper to the same throwError(500) shape the sync-delivery catches build (marker: `Controller Query Exception on async callback rejection.`). Pre-fix the rejection floated to the process handler and the request hung; the two success-delivery sync catches are byte-unchanged, plain callbacks mint zero promises, and a callback that responded before rejecting is absorbed by the #B31 released-response guard. The SYNC half of the same error-path surface is owned too (#B402): all 14 formerly-bare transport-error deliveries (host-missing, circuit refusal, the query-scope outer catch, both ca-read catches, h1 ALPN/request-error, the h2 typed terminals and both pre-flight PING failures) wrap the callback in try/catch routing to a shared `_ownSyncCbThrow` guard (marker: `Controller Query Exception on transport-error callback throw.`, flat 500) — previously an event/timer-frame throw escaped to the process handler (lib/proc.js: emerg + SIGTERM, a whole-bundle kill on BOTH engines for consumer bundles, since an app bug's error code matches none of the transport-lifecycle survive branches), and the caller-frame sites re-invoked the app callback a second time through the query-scope catch, handing it its own exception as the query error. The h2 session-error handler no longer answers the request itself (#B403): request notification on a failing session is owned solely by the stream-level typed deliveries (every live stream self-delivers), retiring the session-level throwError that raced the app callback's own response — and that, on a REUSED session, could answer with the CREATING request's context (session listeners bind once at fresh connect; the cache-hit path never re-binds them). Its by-catch #B404 is FIXED in the same pending release: every error-path `query#complete` emit is SINGLE-argument (mirroring the callback form — `callback(err)` on failure), while both `onComplete` facades' `(err, data)` dispatch dereferenced `data.status` unconditionally — so in handle/emitter mode a transport error or non-2xx outcome never reached the app's listener and the request answered 500 through the facade's sync catch with a marker misattributing it to an app callback exception. A `typeof data == 'undefined'` guard opening each facade's sync-guard try now delivers the payload as the error argument (on failure `cb(err)` with `data` undefined — a plain `{status, error}` object for a non-2xx status or a transport failure, a native `Error` for a pre-transport failure (missing host, unreadable certificate, open circuit) — and on success `cb(false, data)`); listener sync throws and async rejections are owned like every other delivery — and the host-missing emitter branch gained the `return` its callback twin always had (the only fall-through of the 20 emit sites). The documented `util.promisify(self.query)` idiom always used the callback form and was never affected by #B404 — but WAS broken by the last divergent cell, retired as #B405 (measured with controls in both directions): the h2 CALLBACK-mode dispatch delivered only 5xx to the callback and answered every other deliverable non-2xx itself via throwError, so h2 graceful degradation was impossible for 4xx and a promisified h2 query NEVER SETTLED on any non-5xx status (the await's continuation silently abandoned). Every body-announced non-2xx with a known status code now invokes `callback(data)` on both transports (h1 has done this since its #Q1 change, whose inline comment states the intent; the 3xx-with-headers replay is untouched; the retired split is preserved as comments at the site), so the promisify rejection contract now holds transport-uniformly.
|
|
1033
|
+
317. **Boot-frame failure ownership — a crash during bundle bootstrap aborts loudly instead of hanging (#B406, 2026-08-23).** `core/gna.js`'s boot callback (`onGettingProjectConfig`) is declared `async`, so a synchronous throw anywhere in its ~1470-line direct frame became a promise REJECTION: the caller's try/catch (written for a plain callback) could never see it, and the process-level `unhandledRejection` net only logs at error level — no `[ emerg` marker, no exit. The daemon's startup watchdog (`lib/cmd/bundle/start.js`) reads the child's STDOUT only, during startup only, and matches `/(\[|\[\s+)emerg/` — on a match it SIGKILLs the child, prints the cause, and clears the startup timer; a child that dies WITHOUT the marker gets the immediate exit-code report; a child that neither exits nor emits the marker got the 60-second timeout with no cause. Pre-fix, a boot crash in that frame was the third shape. The whole frame is now owned by one try/catch routing every failure — sync throws today, any awaited rejection added later — into `abort()`, the existing boot terminal (context-aware `console.emerg` + synchronous `fs.writeSync(2)` flush + `process.exit(1)`), with non-Error rejections (`throw null`) coerced to an Error FIRST because `abort()` reads `.stack` through branches that would themselves throw on null — and a throw inside the catch would escape back to the silent net. Measured: the levelled emerg line SURVIVES an immediate `process.exit(1)` on a POSIX pipe (short writes; the 64KB truncation class is large payloads), so the single marker line reaches the watchdog — deliberately ONE marker-bearing line, since the watchdog's emerg branch has no re-entry guard and two lines risk double-firing its bulk-start accounting. Behaviour change: a throw LATE in the frame that previously left a half-initialised process limping now refuses the boot. Extends entry 178's severity-axis rule ("lethality follows the nearest async boundary") with the boot-time tier: at boot there is no request to degrade to a logged rejection — an unowned rejection means the process neither starts nor exits, which is strictly worse than either crash-and-respawn or a 500. Tests: `test/core/gna-boot-frame-ownership.test.js` (source pins red-first vs the pre-fix blob; replica arms incl. an awaited-rejection arm proving the seam is ready for a boot-time `await`, a child-process SUBTRACT reproducing the pre-fix escape at a real process-level net, and a plain-callback CONTROL locking the caller contract). Server-side only: pickup is a bundle restart, no re-bake.
|
|
1034
|
+
|
|
1033
1035
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gina",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.14",
|
|
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.14/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.14/core/gna.js"
|
|
85
85
|
},
|
|
86
86
|
"./gna": {
|
|
87
87
|
"types": "./types/gna.d.ts",
|